# Virtual Store

**URL:** https://heroiclabs.com/docs/hiro/concepts/economy/virtual-store/
**Keywords:** virtual store, hiro
**Categories:** hiro, virtual-store, economy

---


# Virtual Store

A virtual store is an in-game marketplace where players can acquire items, consumables, cosmetics, and other resources using various forms of currency. It supports purchases made with game currencies (such as gold or gems) as well as in-app purchases (IAPs) validated through Apple App Store and Google Play.

Virtual stores are defined in your [Economy configuration JSON](../../economy#configuration-overview) file under the `store_items` collection.

![Offers are an essential part of Virtual Stores - Example from Highway Racer Pro by Spektra Games]({{< fingerprint_image "/images/pages/hiro/concepts/economy/offer-highwayracerpro.png" >}})

{{< note "outline" "" hide_icon>}}
Ready to try it yourself? Explore the [Virtual Store sample project](../../../../sample-projects/unity/hiro-store/).
{{</ note>}}

## Store items

A store is essentially a collection of items. Each item specifies its cost (currencies and/or SKU), the rewards granted on purchase, and optional metadata. Here's an example of a `PlasmaCorePack` item:

{{< code type="server" filename="economy-config.json" hideable="false">}}

```json
"PlasmaCorePack": {
    "category": "powerups",
    "cost": {
        "currencies": {
            "currencies": {},
            "sku": "com.example.item1" // in-app purchase
        }
    },
    "description": "Power your gear with concentrated plasma energy.",
    "name": "Plasma Core Pack",
    "reward": {
        "guaranteed": {
            "currencies": {
                "PlasmaCores": {
                    "min": 1 // the player receives just 1 of this item
                }
            }
        }
    },
    "additional_properties": {
        "limited": false // additional metadata
    }
}
```

{{< / code >}}

The reward is what the player receives for purchasing the item. In this example, the player is guaranteed to receive `PlasmaCores`, which is defined separately in the [Inventory](../../inventory/) system.

Think of rewards as the result of players taking a desirable action in your game, including store purchases. See [Rewards](../rewards/) to learn more about the different ways you can configure and deliver rewards to your players.

## Purchases

By default, store items can be purchased repeatedly as long as the player has sufficient currency or a valid receipt. The virtual store handles both game currency purchases and in-app purchases.

### Game currency purchase vs in-app purchase

The `sku` field in your store item configuration determines how the server processes the purchase:

| Configuration | Server behavior                                                                          |
| ------------- | ---------------------------------------------------------------------------------------- |
| **No `sku`**  | Purchase using game currencies. Server deducts currency and grants reward.               |
| **Has `sku`** | In-app purchase. Server validates receipt with platform provider before granting reward. |

{{< note "important" >}}
If a store item has a `sku` but you haven't configured IAP validation on your server, purchases will fail with an error like "Apple IAP is not configured". Either remove the `sku` for game currency-only items, or configure your IAP provider to handle it. See [In-app purchase validation](../../../../nakama/concepts/iap-validation) for setup instructions.
{{< /note >}}

<figure class="float-right" style="max-width: 35%; border: 20px solid transparent; text-align: center;">
  <img 
    src="{{< fingerprint_image "/images/pages/hiro/concepts/economy/store-fruitfall.png" >}}" 
    style="width: 100%;"
  >
  <figcaption> Virtual Store in Fruit Fall by Gram Games </figcaption>
</figure>

### Purchase flow

The steps depend on whether the item is an in-app purchase or not.

**In-app purchase:**

1. Player selects an item from your store UI.
2. Client calls `PurchaseIntent` to record the selected item (optional, see below).
3. Client initiates platform purchase (Apple/Google/etc) and receives a receipt.
4. Client calls `PurchaseItem` with the item ID and receipt.
5. Server validates the receipt with the platform provider.
6. Server grants rewards to player.

**Game currency:**

1. Player selects an item from your store UI.
2. Client calls `PurchaseItem` with the item ID.
3. Server checks the player has sufficient balance and deducts the cost.
4. Server grants rewards to player.

{{< note "important" "Subscriptions" >}}
To handle subscriptions, use Nakama (`ValidateSubscriptionApple`, `ValidateSubscriptionGoogle`) to do purchase validation. Register `AfterValidateSubscription` hooks to grant rewards. Subscription lifecycle events (renewals, cancellations) are handled through Server notifications. See [IAP validation](../../../../nakama/concepts/iap-validation/) for more details.
{{< /note >}}

#### Purchase intents

Purchase intents disambiguate which store item to grant when multiple items share the same SKU. This is common with LiveOps or A/B testing, where you want the same price point in the app store but different rewards for different player segments.

For example, two items might both cost $0.99 and use SKU `com.game.premium_100`, but one gives 150 gems to new players while the other gives 100 gems to everyone else. When the app store returns a receipt for `com.game.premium_100`, the server needs to know which item to grant.

Call `PurchaseIntent` before initiating the purchase to record which item the player selected. The server uses this to resolve the correct item when processing the receipt. Intents expire after 10 minutes.

If each store item has a unique SKU, you don't need to call `PurchaseIntent`.

### Testing in-app purchases in Unity

The `allow_fake_receipts` option in your economy configuration only applies when you're using the Unity Editor. When enabled, receipts containing the string "fake receipt" or "FakeReceipt" bypass validation entirely.

This setting does **not** affect:

- Google Play test purchases or license testing
- Apple TestFlight or sandbox purchases

For real device testing, configure your IAP credentials and use the platform's testing tools.

### Customizing store behavior

The basic store configuration supports simple, repeatable purchases. For more advanced scenarios, use the [Personalizer](/hiro/concepts/personalizers/) to dynamically modify store data based on player state:

- **One-time offers**: For items purchasable only once per player (like a "Starter Pack"), use the Personalizer to hide items after purchase. See [One-time store offers](/hiro/guides/personalizer/one-time-store-offers/) for a complete implementation guide.
- **Player-specific offers**: Show different prices, rewards, or items to different player segments (new players, VIPs, players at certain levels). Combined with [Satori audiences](/satori/concepts/audiences/), you can run A/B tests or target specific groups with tailored offers.

## Store types

When making in-app purchases, the server needs to know which platform's receipt validation to use. You specify this when initializing the Economy system on the client. Refer to each client's SDK guide for more details (for example: [Unity Economy SDK](../../../unity/economy/), [Unreal Economy SDK](../../../unreal/economy/)).

### EconomyStoreType values

| Value           | Description                 |
| --------------- | --------------------------- |
| `Unspecified`   | Defaults to Apple App Store |
| `AppleAppstore` | Apple App Store             |
| `GooglePlay`    | Google Play Store           |
| `Fbinstant`     | Facebook Instant Games      |

{{< note "important" >}}
`Unspecified` defaults to Apple validation. If a player is on Android and you didn't explicitly set `GooglePlay`, your receipts will be sent to Apple for validation and fail with status code `21002`.
{{< /note >}}

## Virtual Store configuration

{{< code type="server" filename="base-economy.json" url="https://github.com/heroiclabs/hiro/blob/main/definitions/09-Hiro-Economy.json" hideable="false">}}

```json
{
  "store_items": {
    "<itemId>": {
      "name": "Item Name",
      "description": "Item description",
      "category": "optional_category",
      "cost": {
        "currencies": {},
        "sku": "optional_sku"
      },
      "reward": {},
      "additional_properties": {},
      "disabled": false
    }
  }
}
```

{{< / code >}}

{{< table name="gdk.concepts.economy.store-item" >}}

### Store item cost

{{< table name="gdk.concepts.economy.store-item-cost" >}}

## Related resources

- [Personalizer](/hiro/concepts/personalizers/)
- [In-app purchase validation](/nakama/concepts/iap-validation/)
- [Implement one-time store offers](/hiro/guides/personalizer/one-time-store-offers/)
- [Build an in-game store](/hiro/guides/gameplay-mechanics/in-game-store/)
- [Server framework function reference](../../../server-framework/economy/)
