---
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: zh/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/zh/llms.txt

# 应用内购买

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

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

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

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

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

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

您可以通过为用户提供游戏内购买的选项来赚取收入。例如，额外的关卡通关时间或游戏角色配饰。为此：

- 在开发人员控制台中[启用应用内购买](https://yandex.com/dev/games/doc/zh/console/purchases.md#connect)。
- 在 SDK 中配置购买功能。
- 添加[未处理购买的检查](#check-purchases)。
- [测试购买](https://yandex.com/dev/games/doc/zh/console/purchases.md#test)。

{% note alert %}

只有在连接它们的[消费](#check-purchases)之后，才能测试购买。否则，可能会出现未处理的支付，这将使通过审核变得[不可能](https://yandex.com/dev/games/doc/zh/concepts/requirements.md#1-13-1)。

{% endnote %}

## 连接条件 {#conditions}

在使用SDK之前，请检查合作方案。为此，在[开发人员控制台](https://games.yandex.com/console){.external}中转到**Account**部分，并检查**Unified licensing model**字段的值：

{% list tabs %}

- Not connected

  启用变现和购买：

  1. 启用[广告变现](https://yandex.com/dev/games/doc/zh/console/adv-monetization.md#enable-int-monetization)。在[Yandex Advertising Network合作伙伴界面](https://partner.yandex.com/){.external}中指定广告和购买付款的详细信息。数据验证后，Yandex Advertising Network界面中**Extras → Documents**部分的合同状态将更改为**Offer accepted**。
  2. 向[games-partners@yandex-team.com](mailto:games-partners@yandex-team.com){.external}发送启用请求的电子邮件。在邮件中注明：

      - 游戏名称
      - 游戏标识符（ID）

      {% note tip %}

      请尽早发送请求，您可以在上传游戏存档或添加购买项之前执行此操作。

      {% endnote %}

      您将收到来自 [games-partners@yandex-team.com](mailto:games-partners@yandex-team.com){.external} 的确认邮件，表明购买已被允许。

  有关说明的后续步骤，请参阅[连接购买功能](https://yandex.com/dev/games/doc/zh/console/purchases.md#connect)部分。

- Connected

  您所有游戏中的购买功能已自动启用。继续进行[初始化](#install)。

{% endlist %}

## 初始化 {#install}

要让玩家能够进行应用内购买，请使用 `payments` 对象。您可以：

- 直接访问 `ysdk.payments`。第一次调用该对象的任何方法时会初始化购买功能，因此首次调用可能会稍慢一些。

- 通过 `ysdk.getPayments()` 方法初始化对象。这会预加载 `payments` 方法所需的数据，避免首次调用时的延迟。

{% note alert %}

在 `YaGames.init()` 和 `ysdk.getPayments()` 中可传递可选参数 `signed: boolean`，用于[防作弊保护](#signature)。参数值的选择取决于支付处理的位置：

- 如果在客户端处理 —— 调用方法时不传参数或传递 `signed: false`。购买方法将返回未加密的数据。

- 如果在服务端处理 —— 传递 `signed: true`。这种情况下 [payments.getPurchases()](#getpurchases) 和 [payments.purchase()](#payments-purchase) 方法返回的所有数据都会以加密形式存放在 `signature` 参数中。

{% endnote %}

{% list tabs group=purchases %}

- 客户端处理

  使用默认参数初始化（`signed: false`）。

  **方式1：简化版**

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

  const payments = ysdk.payments;
  ```

  **方式2：通过 getPayments() 预加载**

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

  try {
      const payments = await ysdk.getPayments();
  } catch (err) {
      // [无法使用购买功能](*key_explanation)。
  }
  ```

- 服务端处理

  使用 `signed: true` 参数初始化。

  **方式1：初始化 SDK 时设置**

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

  const payments = ysdk.payments;
  ```

  **方式2：通过 getPayments() 精细配置**

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

  try {
      const payments = await ysdk.getPayments({ signed: true });
  } catch (err) {
      // [无法使用购买功能](*key_explanation)。
  }
  ```

{% endlist %}

&nbsp; {.empty}

## 激活购买流程 {#payments-purchase}

要激活应用内购买，请使用方法 `payments.purchase()`。它会打开一个带有支付网关的框架。

方法签名：

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

接受参数：

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

#|
|| **参数** | **类型** | **说明** ||
|| `id` | `string` | 商品标识符，在[开发人员控制台中设置](https://yandex.com/dev/games/doc/zh/console/purchases.md#add-purchases)。 ||
|| `developerPayload` | `string` | 可选参数。包含您希望传递到服务器的有关购买的附加信息（将在 [signature](*key_sign) 参数中传递）。 ||
|#

</div>

{% list tabs group=purchases %}

- 客户端处理

  [初始化](#install)使用默认参数（`signed: false`）。

  返回 `Promise<IPurchase>`，包含购买信息。

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

  包含：

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

  #|
  || **参数** | **类型** | **说明** ||
  || `productID` | `string` | 商品标识符。 ||
  || `purchaseToken` | `string` | 用于[使用购买](#consumepurchase)的令牌。 ||
  || `developerPayload` | `string` | 有关购买的附加信息。 ||
  |#

  </div>

- 服务端处理

  [初始化](#install)使用 `signed: true` 参数。

  返回 `Promise<ISign>`.

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

  包含：

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

  #|
  || **参数** | **类型** | **说明** ||
  || `signature` | `string` | 用于[验证玩家真实性](#purchase-data-example)的加密购买数据和签名。 ||
  |#

  </div>

{% endlist %}

成功完成购买后，`Promise` 以 `fulfilled` 状态解析。如果玩家未完成购买并关闭窗口，`Promise` 以 `rejected` 状态拒绝。

{% note alert %}

不稳定的互联网连接可能导致玩家完成了购买，但游戏中未处理的情况。为避免这种情况，请使用 [检查未处理的购买](#check-purchases) 和 [payments.consumePurchase()](#consumepurchase) 部分描述的方法处理购买。

不遵守这些说明可能导致应用中的应用内购买被禁用或应用被下架。

{% endnote %}

用户可以在未授权的情况下进行购买，但我们建议提前或在购买时建议他们[登录账户](https://yandex.com/dev/games/doc/zh/sdk/sdk-player.md#open-auth-dialog)。

#### 示例 {#purchase-example}

一般情况：

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

try {
    const purchase = await ysdk.payments.purchase({ id: 'gold500' });
} catch (err) {
    // 购买失败：开发人员控制台中不存在该 id 的产品、
    // 用户未授权、改变主意并关闭了支付窗口、
    // 购买超时、资金不足等。
}
```

使用可选参数 `developerPayload`:

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

try {
    const purchase = await ysdk.payments.purchase({ id: 'gold500', developerPayload: '{serverId:42}' });
} catch (err) {
    // 处理购买错误。
}
```

## 获取已购商品列表 {#getpurchases}

使用方法 `payments.getPurchases()` 来：

- 了解玩家已经完成了哪些购买。

- 检查是否存在[未处理的购买](#check-purchases)。

- 处理永久性购买。

方法签名：

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

{% list tabs group=purchases %}

- 客户端处理

  [初始化](#install)使用默认参数（`signed: false`）。

  返回 `Promise<IPurchase[]>`，包含购买数组。数组中的每个元素格式与[payments.purchase()](#payments-purchase)方法返回的购买格式相同。

  **示例**

  ```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) {
      // 获取购买列表时出错。抛出异常 PAYMENT_FAILURE。
  }
  ```

- 服务端处理

  [初始化](#install)使用 `signed: true` 参数。

  返回 `Promise<ISign>`.

  包含：

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

  #|
  || **参数** | **类型** | **说明** ||
  || `signature` | `string` | 用于[验证玩家真实性](#purchase-data-example)的加密购买数据和签名。 ||
  |#

  </div>

  **示例**

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

  try {
      const purchases = await ysdk.payments.getPurchases();
      // 将购买列表发送到服务器。
      const response = await fetch('https://your.game.server/handlePurchases', {
          method: 'POST',
          headers: { 'Content-Type': 'text/plain' },
          body: purchases.signature
      });
  } catch (err) {
      // 获取购买列表或处理购买时出错。
  }
  ```

{% endlist %}

&nbsp; {.empty}

## 获取所有商品目录 {#getcatalog}

要获取可用购买项及其价格的列表，请使用方法 `payments.getCatalog()`。

方法签名：

```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[]> {}
```

该方法返回用户可用的产品列表。根据[开发人员控制台](https://games.yandex.com/console){.external} **In-app purchases** 选项卡中的表格生成。每个 `IProduct` 包含以下属性： {#product-characteristics}

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

#|
|| **属性** | **类型** | **说明** ||
|| `id` | `string` | 商品标识符。 ||
|| `title` | `string` | 商品名称。 ||
|| `description` | `string` | 商品说明。 ||
|| `imageURI` | `string` | 商品图片的URL。 ||
|| `price` | `string` | 商品价格，格式为`<价格> <货币代码>`。 ||
|| `priceValue` | `string` | 商品价格，格式为`<价格>`。 ||
|| `priceCurrencyCode` | `string` | 货币代码。 ||
|| `getPriceCurrencyImage(size)` | `string` | 根据图标大小参数获取货币图标地址的方法。可能的值：

- `small` (默认) —— 获取小图标。

- `medium` —— 获取中等大小的图标。

- `svg` —— 获取矢量图标。 ||
|#

</div>

{% note warning %}

门户货币应自动确定 ([第 1.13.2 项](https://yandex.com/dev/games/doc/zh/concepts/requirements.md#1-13-2)). 为此，从 `IProduct` 属性中获取其名称和图标。详见 [自动检测门户货币](https://yandex.com/dev/games/doc/zh/requirements/1/13.md#currency-detection)部分。

{% endnote %}

#### 示例 {#getcatalog-example}

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

let gameShop = [];

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

    gameShop = purchases;
} catch (err) {
    // 获取购买列表时出错。
}
```

## 处理购买并充值游戏内货币 {#processing-crediting}

存在两种类型的购买：

- 永久性（例如禁用广告）。使用 [payments.getPurchases()](#getpurchases) 方法处理。
- 消耗性（例如游戏内货币）。使用 `payments.consumePurchase()` 方法处理。

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

{% note alert %}

调用 `payments.consumePurchase()` 方法后，已处理的购买将被永久删除。因此，请先使用 [player.setData()](https://yandex.com/dev/games/doc/zh/sdk/sdk-player.md#ingame-data), [player.setStats()](https://yandex.com/dev/games/doc/zh/sdk/sdk-player.md#ingame-data) 或 [player.incrementStats()](https://yandex.com/dev/games/doc/zh/sdk/sdk-player.md#ingame-data) 方法修改玩家数据，然后再处理购买。

{% endnote %}

方法签名：

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

接受 [payments.purchase()](#payments-purchase) 和 [payments.getPurchases()](#getpurchases) 方法返回的 `purchaseToken`。如果处理成功，`Promise` 以 `fulfilled` 状态解析；如果发生错误，以 `rejected` 状态拒绝。

#### 示例 {#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) {
    // 处理消耗性购买错误。
}
```

## 检查未处理的购买 {#check-purchases}

{% note alert %}

此检查是通过审核的必要条件 ([第 1.13.1 项](https://yandex.com/dev/games/doc/zh/concepts/requirements.md#1-13-1))，因此即使对于测试购买，配置它也很重要。如果在配置消费之前在游戏中添加购买并进行测试，测试后可能会留下未处理的支付，这将使通过审核变得不可能。

{% endnote %}

如果在进行应用内购买时用户的互联网连接断开或您的服务器不可用，购买可能会保持未处理状态。为避免这种情况，请使用 [payments.getPurchases()](#getpurchases) 方法检查未处理的购买，例如在每次启动游戏时。

{% list tabs group=purchases %}

- 客户端处理

  [初始化](#install)使用默认参数（`signed: false`）。

  **示例**

  ```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);
  }
  ```

- 服务端处理

  [初始化](#install)使用 `signed: true` 参数。

  **示例**

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

  try {
      const purchases = await ysdk.payments.getPurchases();
      // 将购买列表发送到服务器。
      const response = await fetch('https://your.game.server/handlePurchases', {
          method: 'POST',
          headers: { 'Content-Type': 'text/plain' },
          body: purchases.signature
      });
  } catch (err) {
      // 获取购买列表或处理购买时出错。
  }
  ```

{% endlist %}

&nbsp; {.empty}

## 防作弊保护 {#signature}

为了保护自己免受游戏中可能的刷分操作，应在服务器端处理购买：

1. 使用 `{ signed: true }` 参数初始化 `YaGames.init()` 或 `ysdk.getPayments()`。
2. 将 [payments.purchase()](#payments-purchase) 和 [payments.getPurchases()](#getpurchases) 响应中获得的签名传送到您的服务器，使用[密钥](#key-example)解密。
3. 在您的服务器上为玩家充值游戏中获得的物品。

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

// 确保使用 { signed: true } 参数初始化购买。
const ysdk = await YaGames.init({ signed: true });

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

    // 在服务器上充值 500 金币...
    await serverPurchase(purchase.signature);
} catch (err) {
    // 购买错误。
}
```

发送到服务器的请求中的 `signature` 参数包含购买数据和签名。它由两个 `base64` 编码的字符串组成：`<签名>.<购买数据的 JSON>`。

#### signature 示例 {#signature-example}

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

#### 传输的购买数据示例 (`JSON` 格式) {#purchase-data-example}

{% note warning %}

`serverPurchase(signature)` 函数中 `signature` 参数的数据格式与 [payments.getPurchases()](#getpurchases) 方法中使用的格式不同。

在 `payments.getPurchases()` 方法中，`signature` 参数在 `data` 字段中包含购买对象数组。在 `serverPurchase(signature)` 函数中 —— 购买对象。

{% 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": "无广告",
      "description": "在游戏中禁用广告",
      "price": {
        "code": "YAN",
        "value": "49"
      },
      "imagePrefix": "https://avatars.mds.yandex.net/get-games/1892995/2a0000016d1c1717bd7a0149ccadc86078a1/"
    },
    "developerPayload": "TEST DEVELOPER PAYLOAD"
  }
}
```

#### 密钥示例 {#key-example}

`t0p$ecret`

用于验证签名的密钥对于游戏是唯一的。在[开发人员控制台](https://games.yandex.com/console){.external}中创建购买时自动生成。密钥显示在 **In-app purchases** → **Settings** 选项卡中。

#### 服务器上的签名验证示例 {#server-check-example}

{% list tabs %}

* Python 3

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

    usedTokens = {}

    key = 't0p$ecret' # 保密密钥。
    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 # 使用数据库。
        print('Double spend check ok!')

        print('Apply purchase:', purchaseData['data']['product'])
        # 您可以在此处安全地充值购买的物品。
    ```

* Node.js

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

    const usedTokens = {};

    const key = 't0p$ecret'; // 保密密钥。
    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; // 使用数据库。
        console.log('Double spend check ok!');

        console.log('Apply purchase:', purchaseData.data.product);
        // 您可以在此处安全地充值购买的物品。
      }
    }
    ```

{% endlist %}

---

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

技术支持团队将协助您将已完成的游戏发布到 Yandex 游戏平台。关于开发和测试方面的具体问题，其他开发人员将在[Discord 频道](https://discord.com/invite/wU4p3whr4T){.external}中进行回答。

{% endnote %}

如果您遇到 Yandex Games SDK 方面的问题或有其他问题想要咨询，请联系支持部门：

<!-- source: zh/_includes/button-chat.md -->
<a href="https://yandex.com/chat/#/user/a4fa5c06-75db-9b38-6eea-b1673785f7d5">
  <span class="button">写入聊天信息</span>
</a>
<!-- endsource: zh/_includes/button-chat.md -->
<!-- endsource: zh/_includes/sdk-support.md -->

[*key_id]: `id: string` —— 产品标识符，在[开发人员控制台中设置](https://yandex.com/dev/games/doc/zh/console/purchases.md#connect)。

[*key_developerPayload]: `developerPayload: string` —— 可选参数。您希望传递到服务器的有关购买的附加信息（将在 `signature` 参数中传递）。

[*key_sign]: 传递到服务器的请求的 `signature` 参数包含有关购买的数据和签名。它由两个 `base64` 编码的字符串组成：`<签名>.<有关购买的 JSON 数据>`。

[*key_purchaseToken]: `purchaseToken: string` —— 由 [payments.purchase()](#payments-purchase) 和 [payments.getPurchases()](#getpurchases) 方法返回的令牌。

[*key_payments]: 如果您没有使用 `ysdk.getPayments()` 初始化购买，请直接访问 `ysdk.payments`。

[*key_explanation]: - [启用变现](https://yandex.com/dev/games/doc/zh/console/purchases.md#connect)。
- 在[开发人员控制台](https://games.yandex.com/console){.external}中，转到 **In-app purchases** 选项卡并确保：
    - 存在至少包含一个游戏内产品的表格。
    - 显示 **Purchases are enabled** 文本。