---
metadata:
  - name: generator
    content: Diplodoc Platform v5.52.0
alternate:
  - https://yandex.com/dev/games/doc/en/sdk/sdk-purchases.md
  - https://yandex.com/dev/games/doc/hi/sdk/sdk-purchases.md
  - https://yandex.com/dev/games/doc/ko/sdk/sdk-purchases.md
  - https://yandex.com/dev/games/doc/ru/sdk/sdk-purchases.md
  - https://yandex.com/dev/games/doc/tr/sdk/sdk-purchases.md
  - https://yandex.com/dev/games/doc/vi/sdk/sdk-purchases.md
  - https://yandex.com/dev/games/doc/zh/sdk/sdk-purchases.md
  - href: en/sdk/sdk-purchases.md
    type: text/markdown
    title: Markdown version
  - href: ../llms.txt
    type: text/markdown
    title: llms.txt
---
> **Documentation Index:** Fetch the complete configuration index at https://yandex.com/dev/games/doc/en/llms.txt

# In-app purchases

<!-- source: en/_includes/script-common.md -->
<!-- source: en/_includes/script/index-js.md -->

<!-- endsource: en/_includes/script/index-js.md -->

<!-- source: en/_includes/script/requirements-js.md -->

<!-- endsource: en/_includes/script/requirements-js.md -->

<!-- source: en/_includes/script/image-modal-js.md -->

<!-- endsource: en/_includes/script/image-modal-js.md -->
<!-- endsource: en/_includes/script-common.md -->

You can generate income by offering users the option to make in-game purchases. For example, extra time for completing a level or accessories for their character. To do this:

- [Enable in-app purchases](https://yandex.com/dev/games/doc/en/console/purchases.md#connect) in the Developer Console.
- Configure the SDK to work with purchases.
- Add a [check for unprocessed purchases](#check-purchases).
- [Test your purchases](https://yandex.com/dev/games/doc/en/console/purchases.md#test).

{% note alert %}

You can only test purchases after enabling their [consumption](#check-purchases). Otherwise, you might end up with unprocessed payments, making it [impossible](https://yandex.com/dev/games/doc/en/concepts/requirements.md#1-13-1) to pass moderation.

{% endnote %}

## Conditions {#conditions}

Before working with the SDK, check the cooperation scheme. To do this, go to the [Developer Console](https://games.yandex.com/console){.external}, navigate to the **Account** section, and check the value of the **Unified licensing model** field:

{% list tabs %}

- Not connected

  Enable monetization and purchases:

  1. Enable [ad monetization](https://yandex.com/dev/games/doc/en/console/adv-monetization.md#enable-int-monetization). In the [YAN partner interface](https://partner.yandex.com/){.external}, specify payment details for ads and purchases. After the data is verified, the contract status in the YAN interface in the **Extras → Documents** section will change to **Offer accepted**.
  2. Send an email requesting activation to [games-partners@yandex-team.com](mailto:games-partners@yandex-team.com){.external}. In the email, specify:

      - game name
      - game ID

      {% note tip %}

      Send the request as early as possible; you can do this before uploading the game archive or adding purchases.

      {% endnote %}

      You will receive a reply from [games-partners@yandex-team.com](mailto:games-partners@yandex-team.com){.external} confirming that purchases are allowed.

  For further steps, see [Enable purchases](https://yandex.com/dev/games/doc/en/console/purchases.md#connect).

- Connected

  Purchases are enabled automatically in all your games. Proceed to [initialization](#install).

{% endlist %}

## Initialization {#install}

To enable players to make in-app purchases, use the `payments` object. You can:

- Access `ysdk.payments` directly. Purchases are initialized upon the first call to any of the object's methods, which may cause the first call to be slightly slower.

- Initialize the object using the `ysdk.getPayments()` method. This preloads the data required for `payments` methods, eliminating the delay during their first call.

{% note alert %}

Both `YaGames.init()` and `ysdk.getPayments()` accept an optional `signed: boolean` parameter for [fraud protection](#signature). The value depends on where payments are processed:

- For client-side processing — call the methods without the parameter or pass `signed: false`. Purchase methods will return data in plain text.

- For server-side processing — pass `signed: true`. In this case, responses from [payments.getPurchases()](#getpurchases) and [payments.purchase()](#payments-purchase) will return all data exclusively in encrypted form within the `signature` parameter.

{% endnote %}

{% list tabs group=purchases %}

- Client-side processing

  Initialization with default parameter (`signed: false`).

  **Option 1: Simplified**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init();

  const payments = ysdk.payments;
  ```

  **Method 2: Preloading via getPayments()**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init();

  try {
      const payments = await ysdk.getPayments();
  } catch (err) {
      // [Purchases unavailable](*key_explanation).
  }
  ```

- Server-side processing

  Initialization with `signed: true`.

  **Option 1: During SDK initialization**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init({ signed: true });

  const payments = ysdk.payments;
  ```

  **Option 2: Granular configuration via getPayments()**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init();

  try {
      const payments = await ysdk.getPayments({ signed: true });
  } catch (err) {
      // [Purchases unavailable](*key_explanation).
  }
  ```

{% endlist %}

&nbsp; {.empty}

## Activating the purchase process {#payments-purchase}

To activate an in-app purchase, use the `payments.purchase()` method. It opens a frame with a payment gateway.

**Method signature**

```typescript showLineNumbers
function purchase(data: {
    id: string;
    developerPayload?: string;
}) => Promise<IPurchase | ISign> {}
```

Accepts parameters:

<div class="full-width-table table-25 table-2c25">

#|
|| **Parameter** | **Type** | **Description** ||
|| `id` | `string` | Product ID that is [set in the Developer Console](https://yandex.com/dev/games/doc/en/console/purchases.md#add-purchases). ||
|| `developerPayload` | `string` | Optional parameter. Contains additional information about the purchase that you want to pass to your server (will be passed in the [signature](*key_sign) parameter). ||
|#

</div>

{% list tabs group=purchases %}

- Client-side processing

  [Initialize](#install) with the default parameter (`signed: false`).

  Returns `Promise<IPurchase>` with purchase information.

  ```typescript showLineNumbers
  interface IPurchase {
      productID: string;
      purchaseToken: string;
      developerPayload: string;
  }
  ```

  Parameters:

  <div class="table-25 table-2c25">

  #|
  || **Parameter** | **Type** | **Description** ||
  || `productID` | `string` | Product ID. ||
  || `purchaseToken` | `string` | Token for [consuming the purchase](#consumepurchase). ||
  || `developerPayload` | `string` | Additional information about the purchase. ||
  |#

  </div>

- Server-side processing

  [Initialize](#install) with the parameter `signed: true`.

  Returns `Promise<ISign>`.

  ```typescript showLineNumbers
  interface ISign {
      signature: string;
  }
  ```

  Parameter:

  <div class="full-width-table table-25 table-2c25">

  #|
  || **Parameter** | **Type** | **Description** ||
  || `signature` | `string` | Encrypted purchase data and signature for [verifying player authenticity](#purchase-data-example). ||
  |#

  </div>

{% endlist %}

After the player successfully makes a purchase, `Promise` resolves with `fulfilled` status. If the player didn't make a purchase and closed the window, `Promise` rejects with `rejected` status.

{% note alert %}

Unstable internet connection may lead to a situation where a player made a purchase, but it was not processed in the game. To avoid this, use the methods described in sections [Checking for unprocessed purchases](#check-purchases) and [payments.consumePurchase()](#consumepurchase) to process purchases.

Failure to follow these instructions may result in the disabling of in-app purchases in the app or in the app's depublishing.

{% endnote %}

A user can make a purchase without authorization, but we recommend offering them to [log in](https://yandex.com/dev/games/doc/en/sdk/sdk-player.md#open-auth-dialog) in advance or during the purchase.

#### Example {#purchase-example}

General:

```javascript showLineNumbers
const ysdk = await YaGames.init();

try {
    const purchase = await ysdk.payments.purchase({ id: 'gold500' });
} catch (err) {
    // Purchase failed: no product with this id exists in the Developer Console,
    // the user didn't log in, changed their mind and closed the payment window,
    // the purchase timed out, there were insufficient funds, etc.
}
```

Using the optional `developerPayload` parameter:

```javascript showLineNumbers
const ysdk = await YaGames.init();

try {
    const purchase = await ysdk.payments.purchase({ id: 'gold500', developerPayload: '{serverId:42}' });
} catch (err) {
    // Handle purchase error.
}
```

## Getting a list of purchased items {#getpurchases}

Use the `payments.getPurchases()` method to:

- Find out which purchases the player has already made.

- Check for [unprocessed purchases](#check-purchases).

- Handle non-consumable purchases.

**Method signature**

```typescript
function getPurchases(): Promise<IPurchase[] | ISign> {}
```

{% list tabs group=purchases %}

- Client-side processing

  [Initialize](#install) with the default parameter (`signed: false`).

  Returns `Promise<IPurchase[]>` with an array of purchases. Each array element has the same format as the purchase returned by the [payments.purchase()](#payments-purchase) method.

  **Example**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init();

  let SHOW_ADS = true;

  try {
      const purchases = await ysdk.payments.getPurchases();

      if (purchases.some(purchase => purchase.productID === 'disable_ads')) {
          SHOW_ADS = false;
      }
  } catch (err) {
      // Error retrieving the list of purchases. Throws the exception PAYMENT_FAILURE.
  }
  ```

- Server-side processing

  [Initialize](#install) with the parameter `signed: true`.

  Returns `Promise<ISign>`.

  Parameter:

  <div class="full-width-table table-25 table-2c25">

  #|
  || **Parameter** | **Type** | **Description** ||
  || `signature` | `string` | Encrypted purchase data and signature for [player authenticity verification](#purchase-data-example). ||
  |#

  </div>

  **Example**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init({ signed: true });

  try {
      const purchases = await ysdk.payments.getPurchases();
      // Send the list of purchases to the server.
      const response = await fetch('https://your.game.server/handlePurchases', {
          method: 'POST',
          headers: { 'Content-Type': 'text/plain' },
          body: purchases.signature
      });
  } catch (err) {
      // Error retrieving or processing the list of purchases.
  }
  ```

{% endlist %}

&nbsp; {.empty}

## Getting the catalog of all products {#getcatalog}

To get a list of available purchases and their cost, use the `payments.getCatalog()` method.

**Method signature**

```typescript showLineNumbers
interface IProduct {
    id: string;
    title: string;
    description: string;
    imageURI: string;
    price: string;
    priceValue: string;
    priceCurrencyCode: string;
    getPriceCurrencyImage(size: 'small' | 'medium' | 'svg'): string;
}

function getCatalog(): Promise<IProduct[]> {}
```

The method returns a list of products available to the user. Generated from the table in the **In-app purchases** tab of the [Developer Console](https://games.yandex.com/console){.external}. Each `IProduct` contains properties: {#product-characteristics}

<div class="table-25 table-2c25">

#|
|| **Property** | **Type** | **Description** ||
|| `id` | `string` | Product ID. ||
|| `title` | `string` | Product name. ||
|| `description` | `string` | Product description. ||
|| `imageURI` | `string` | Product image URL. ||
|| `price` | `string` | Product price in the format `<price> <currency code>`. ||
|| `priceValue` | `string` | Product price in the format `<price>`. ||
|| `priceCurrencyCode` | `string` | Currency code. ||
|| `getPriceCurrencyImage(size)` | `string` | Method for getting the currency icon address depending on the icon size parameter. Possible values:

- `small` (default) — getting a small icon.

- `medium` — getting a medium-sized icon.

- `svg` — getting the icon in vector format. ||
|#

</div>

{% note warning %}

Portal currency must be determined automatically ([item 1.13.2](https://yandex.com/dev/games/doc/en/concepts/requirements.md#1-13-2)). To do this, take its name and icon from `IProduct` properties. For more details, see [Automatic detection of portal currency](https://yandex.com/dev/games/doc/en/requirements/1/13.md#currency-detection).

{% endnote %}

#### Example {#getcatalog-example}

```javascript showLineNumbers
const ysdk = await YaGames.init();

let gameShop = [];

try {
    const purchases = await ysdk.payments.getPurchases();

    gameShop = purchases;
} catch (err) {
    // Error retrieving the list of purchases.
}
```

## Processing purchases and crediting in-game currency {#processing-crediting}

There are two types of purchases:

- Non-consumable (e.g., disabling ads). Use the [payments.getPurchases()](#getpurchases) method to process them.
- Consumable (e.g., in-game currency). Use the `payments.consumePurchase()` method to process them.

#### payments.consumePurchase() {#consumepurchase}

{% note alert %}

After calling the `payments.consumePurchase()` method, the processed purchase is permanently deleted. Therefore, first modify the player data using [player.setData()](https://yandex.com/dev/games/doc/en/sdk/sdk-player.md#ingame-data), [player.setStats()](https://yandex.com/dev/games/doc/en/sdk/sdk-player.md#ingame-data) or [player.incrementStats()](https://yandex.com/dev/games/doc/en/sdk/sdk-player.md#ingame-data) methods, and then process the purchase.

{% endnote %}

**Method signature**

```typescript
function consumePurchase(purchaseToken: string): Promise<void> {}
```

Accepts `purchaseToken` returned by the [payments.purchase()](#payments-purchase) and [payments.getPurchases()](#getpurchases) methods. If processing is successful, `Promise` resolves with `fulfilled` status; if an error occurs, it rejects with `rejected` status.

#### Example {#consumepurchase-example}

```javascript showLineNumbers
const ysdk = await YaGames.init();

function addGold(value) {
    return ysdk.player.incrementStats({ gold: value });
}

try {
    const purchase = await ysdk.payments.purchase({ id: 'gold500' });

    await addGold(500);

    await ysdk.payments.consumePurchase(purchase.purchaseToken);
} catch (err) {
    // Handle consumable purchase processing error.
}
```

## Checking for unprocessed purchases {#check-purchases}

{% note alert %}

This check is mandatory for passing moderation ([item 1.13.1](https://yandex.com/dev/games/doc/en/concepts/requirements.md#1-13-1)), so it's crucial to set it up even for test purchases. If you add purchases to the game and test them before configuring consumption, unprocessed payments could remain after the tests, making passing moderation impossible.

{% endnote %}

If the user loses internet connection when making an in-app purchase, or your server becomes unavailable, the purchase might remain unprocessed. To avoid this, check for unprocessed purchases using the [payments.getPurchases()](#getpurchases) method, for example, each time the game is launched.

{% list tabs group=purchases %}

- Client-side processing

  [Initialize](#install) with the default parameter (`signed: false`).

  **Example**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init();

  async function handlePurchase(purchase) {
      if (purchase.productID === 'gold500') {
          await ysdk.player.incrementStats({ gold: 500 });

          await ysdk.payments.consumePurchase(purchase.purchaseToken);
      }
  }

  const purchases = await ysdk.payments.getPurchases().then(purchases => purchases.forEach(consumePurchase));

  for (let purchase of purchases) {
      await handlePurchase(purchase);
  }
  ```

- Server-side processing

  [Initialize](#install) with the parameter `signed: true`.

  **Example**

  ```javascript showLineNumbers
  const ysdk = await YaGames.init({ signed: true });

  try {
      const purchases = await ysdk.payments.getPurchases();
      // Send the list of purchases to the server.
      const response = await fetch('https://your.game.server/handlePurchases', {
          method: 'POST',
          headers: { 'Content-Type': 'text/plain' },
          body: purchases.signature
      });
  } catch (err) {
      // Error retrieving or processing the list of purchases.
  }
  ```

{% endlist %}

&nbsp; {.empty}

## Fraud prevention {#signature}

To protect yourself from potential in-game stat inflation, process purchases on the server side:

1. Initialize `YaGames.init()` or `ysdk.getPayments()` with the `{ signed: true }` parameter.
2. Pass the signature received in the responses of [payments.purchase()](#payments-purchase) and [payments.getPurchases()](#getpurchases) to your server and decrypt it using the [secret key](#key-example).
3. On your server, credit the player with the items earned in the game.

```javascript showLineNumbers
function serverPurchase(signature) {
    return fetch('https://your.game.server/handlePurchase', {
        method: 'POST',
        headers: { 'Content-Type': 'text/plain' },
        body: signature
    });
}

// Make sure that purchases are initialized with the { signed: true } parameter.
const ysdk = await YaGames.init({ signed: true });

try {
    const purchase = await ysdk.payments.purchase({ id: 'gold500' });

    // Credit 500 gold on the server...
    await serverPurchase(purchase.signature);
} catch (err) {
    // Purchase error.
}
```

The `signature` parameter of the request sent to the server contains purchase data and the signature. It's two strings in `base64` encoding: `<signature>.<JSON with the purchase data>`.

#### Signature example {#signature-example}

```text
hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0=
```

#### Example of transmitted purchase data (in `JSON` format) {#purchase-data-example}

{% note warning %}

The data format of the `signature` parameter in the `serverPurchase(signature)` function differs from that used in the [payments.getPurchases()](#getpurchases) method.

In the `payments.getPurchases()` method, the `signature` parameter contains an array of purchase objects in the `data` field. In the `serverPurchase(signature)` function, it's a purchase object.

{% endnote %}

```json showLineNumbers
{
  "algorithm": "HMAC-SHA256",
  "issuedAt": 1571233371,
  "requestPayload": "qwe",
  "data": {
    "token": "d85ae0b1-9166-4fbb-bb38-6d2a4ca4416d",
    "status": "waiting",
    "errorCode": "",
    "errorDescription": "",
    "url": "https://yandex.ru/games/sdk/payments/trust-fake.html",
    "product": {
      "id": "noads",
      "title": "No ads",
      "description": "Disable ads in the game",
      "price": {
        "code": "YAN",
        "value": "49"
      },
      "imagePrefix": "https://avatars.mds.yandex.net/get-games/1892995/2a0000016d1c1717bd7a0149ccadc86078a1/"
    },
    "developerPayload": "TEST DEVELOPER PAYLOAD"
  }
}
```

#### Secret key example {#key-example}

`t0p$ecret`

The secret key for signature verification is unique for the game. It is generated automatically when creating purchases in the [Developer Console](https://games.yandex.com/console){.external}. The key is displayed on the **In-app purchases** → **Settings** tab.

#### Example of signature verification on the server {#server-check-example}

{% list tabs %}

* Python 3

    ```python showLineNumbers
    import hashlib
    import hmac
    import base64
    import json

    usedTokens = {}

    key = 't0p$ecret' # Keep the key secret.
    secret = bytes(key, 'utf-8')
    signature = 'hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0='

    sign, data = signature.split('.')
    message = base64.b64decode(data)

    purchaseData = json.loads(message)
    result = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest())
    if result.decode('utf-8') == sign:
      print('Signature check ok!')

      if not purchaseData['data']['token'] in usedTokens:
        usedTokens[purchaseData['data']['token']] = True # Use database.
        print('Double spend check ok!')

        print('Apply purchase:', purchaseData['data']['product'])
        # You can safely credit the purchase here.
    ```

* Node.js

    ```javascript showLineNumbers
    const crypto = require('crypto');

    const usedTokens = {};

    const key = 't0p$ecret'; // Keep the key secret.
    const signature = 'hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0=';

    const [sign, data] = signature.split('.');
    const purchaseDataString = Buffer.from(data, 'base64').toString('utf8');
    const hmac = crypto.createHmac('sha256', key);

    hmac.update(purchaseDataString);

    const purchaseData = JSON.parse(purchaseDataString);

    if (sign === hmac.digest('base64')) {
      console.log('Signature check ok!');

      if (!usedTokens[purchaseData.data.token]) {
        usedTokens[purchaseData.data.token] = true; // Use database.
        console.log('Double spend check ok!');

        console.log('Apply purchase:', purchaseData.data.product);
        // You can safely credit the purchase here.
      }
    }
    ```

{% endlist %}

---

<!-- source: en/_includes/sdk-support.md -->
{% note info %}

Our support team can help publish finished games on Yandex Games. If you have any questions about development or testing, ask them in the [Discord channel](https://discord.com/invite/wU4p3whr4T){.external}.

{% endnote %}

If you are facing an issue or have a question regarding the use of Yandex Games SDK, please contact support:

<!-- source: en/_includes/button-chat.md -->
<a href="https://yandex.com/chat/#/user/a4fa5c06-75db-9b38-6eea-b1673785f7d5">
  <span class="button">Write to chat</span>
</a>
<!-- endsource: en/_includes/button-chat.md -->
<!-- endsource: en/_includes/sdk-support.md -->

[*key_id]: `id: string` — product identifier that is [set in the Developer Console](https://yandex.com/dev/games/doc/en/console/purchases.md#connect).

[*key_developerPayload]: `developerPayload: string` — optional parameter. Additional information about the purchase that you want to pass to your server (will be passed in the `signature` parameter).

[*key_sign]: The `signature` parameter of the request sent to the server contains purchase data and a signature. It consists of two strings in `base64` encoding: `<signature>.<JSON with purchase data>`.

[*key_purchaseToken]: `purchaseToken: string` — token returned by the [payments.purchase()](#payments-purchase) and [payments.getPurchases()](#getpurchases) methods.

[*key_payments]: Access `ysdk.payments` directly if you have not initialized purchases using `ysdk.getPayments()`.

[*key_explanation]: - [Enable monetization](https://yandex.com/dev/games/doc/en/console/purchases.md#connect).
- In the [Developer Console](https://games.yandex.com/console){.external}, go to the **In-app purchases** tab and make sure that:
    - There is a table with at least one in-game item.
    - The **Purchases are enabled** message is displayed.