Инап-покупки

Вы можете получать доход, предоставив пользователям возможность совершать покупки в игре. Например, дополнительное время на прохождение уровня или аксессуары для игрового персонажа. Для этого:

Внимание

Тестировать покупки можно только после подключения их консумирования. Иначе могут появиться необработанные платежи, которые сделают прохождение модерации невозможным.

Условия подключения

Перед работой с SDK проверьте схему сотрудничества. Для этого в Консоли разработчика перейдите в раздел Профиль и проверьте значение поля Единая лицензионная схема:

Подключите монетизацию и покупки:

  1. Подключите рекламную монетизацию. В партнерском интерфейсе РСЯ укажите реквизиты для выплат по рекламе и покупкам. После проверки данных статус договора в интерфейсе РСЯ в разделе Дополнительно → Документы изменится на Оферта акцептована.

  2. Отправьте письмо с запросом о подключении на почту games-partners@yandex-team.ru. В письме укажите:

    • название игры;
    • идентификатор (ID) игры.

    Совет

    Отправьте запрос как можно раньше, вы можете сделать это до загрузки архива игры или добавления покупок.

  3. Дождитесь ответного письма от games-partners@yandex-team.ru с подтверждением, что покупки разрешены.

Дальнейшие шаги инструкции см. в разделе Подключить покупки.

Покупки во всех ваших играх включены автоматически. Перейдите к инициализации.

Инициализация

Чтобы игроки могли совершать инап-покупки, используйте объект payments. Вы можете:

  • Обратиться напрямую к ysdk.payments. Покупки инициализируются при первом вызове любого из методов объекта, из-за чего первый вызов может быть чуть медленнее.

  • Инициализировать объект с помощью метода ysdk.getPayments(). Он предзагружает данные, необходимые для методов payments. Так их первый вызов не будет замедлен.

Внимание

В YaGames.init() и ysdk.getPayments() можно передать опциональный параметр signed: boolean, который предназначен для защиты от накруток. Выбор значения зависит от того, где обрабатываются платежи:

  • Если на стороне клиента — вызовите методы без параметра или передайте signed: false. Методы покупок будут возвращать данные в открытом виде.

  • Если на стороне сервера — передайте signed: true. В таком случае в ответах методов payments.getPurchases() и payments.purchase() все данные возвращаются только в зашифрованном виде в параметре signature.

Инициализация с параметром по умолчанию (signed: false).

Способ 1: Упрощенный

1const ysdk = await YaGames.init();
2
3const payments = ysdk.payments;

Способ 2: Предзагрузка через getPayments()

1const ysdk = await YaGames.init();
2
3try {
4    const payments = await ysdk.getPayments();
5} catch (err) {
6    // Покупки недоступны.
7}

Инициализация с параметром signed: true.

Способ 1: При инициализации SDK

1const ysdk = await YaGames.init({ signed: true });
2
3const payments = ysdk.payments;

Способ 2: Более тонкая настройка через getPayments()

1const ysdk = await YaGames.init();
2
3try {
4    const payments = await ysdk.getPayments({ signed: true });
5} catch (err) {
6    // Покупки недоступны.
7}

 

Активация процесса покупки

Чтобы активировать инап-покупку, используйте метод payments.purchase(). Он открывает фрейм с платежным шлюзом.

Сигнатура метода:

1function purchase(data: {
2    id: string;
3    developerPayload?: string;
4}) => Promise<IPurchase | ISign> {}

Принимает параметры:

Параметр

Тип

Описание

id

string

Идентификатор товара, который задан в Консоли разработчика.

developerPayload

string

Опциональный параметр. Содержит дополнительную информацию о покупке, которую вы хотите передавать на свой сервер (будет передана в параметре signature).

Инициализация с параметром по умолчанию (signed: false).

Возвращает Promise<IPurchase> с информацией о покупке.

1interface IPurchase {
2    productID: string;
3    purchaseToken: string;
4    developerPayload: string;
5}

Содержит:

Параметр

Тип

Описание

productID

string

Идентификатор товара.

purchaseToken

string

Токен для использования покупки.

developerPayload

string

Дополнительная информация о покупке.

Инициализация с параметром signed: true.

Возвращает Promise<ISign>.

1interface ISign {
2    signature: string;
3}

Содержит:

Параметр

Тип

Описание

signature

string

Зашифрованные данные о покупке и подпись для проверки подлинности игрока.

После успешного совершения покупки Promise разрешается со статусом fulfilled. Если игрок не совершил покупку и закрыл окно, Promise отклоняется со статусом rejected.

Внимание

Нестабильная работа интернета может привести к ситуации, когда игрок совершил покупку, но она не была обработана в игре. Чтобы избежать этого, для обработки покупок применяйте методы, описанные в разделах Проверка необработанных покупок и payments.consumePurchase().

Отказ от следования этим инструкциям может привести к отключению инап-покупок в приложении или снятию его с публикации.

Пользователь может совершить покупку без авторизации, но мы рекомендуем предлагать ему войти в аккаунт заранее или при совершении покупки.

Пример

В общем случае:

1const ysdk = await YaGames.init();
2
3try {
4    const purchase = await ysdk.payments.purchase({ id: 'gold500' });
5} catch (err) {
6    // Покупка не удалась: в Консоли разработчика не добавлен товар с таким id,
7    // пользователь не авторизовался, передумал и закрыл окно оплаты,
8    // истекло отведенное на покупку время, не хватило денег и т. д.
9}

С использованием опционального параметра developerPayload:

1const ysdk = await YaGames.init();
2
3try {
4    const purchase = await ysdk.payments.purchase({ id: 'gold500', developerPayload: '{serverId:42}' });
5} catch (err) {
6    // Обработка ошибки покупки.
7}

Получение списка купленных товаров

Используйте метод payments.getPurchases(), чтобы:

Сигнатура метода:

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

Инициализация с параметром по умолчанию (signed: false).

Возвращает Promise<IPurchase[]> с массивом покупок. Каждый элемент массива имеет тот же формат, что и покупка, возвращаемая методом payments.purchase().

Пример

 1const ysdk = await YaGames.init();
 2
 3let SHOW_ADS = true;
 4
 5try {
 6    const purchases = await ysdk.payments.getPurchases();
 7
 8    if (purchases.some(purchase => purchase.productID === 'disable_ads')) {
 9        SHOW_ADS = false;
10    }
11} catch (err) {
12    // Ошибка получения списка покупок. Выбрасывает исключение PAYMENT_FAILURE.
13}

Инициализация с параметром signed: true.

Возвращает Promise<ISign>.

Содержит:

Параметр

Тип

Описание

signature

string

Зашифрованные данные о покупке и подпись для проверки подлинности игрока.

Пример

 1const ysdk = await YaGames.init({ signed: true });
 2
 3try {
 4    const purchases = await ysdk.payments.getPurchases();
 5    // Отправляем список покупок на сервер.
 6    const response = await fetch('https://your.game.server/handlePurchases', {
 7        method: 'POST',
 8        headers: { 'Content-Type': 'text/plain' },
 9        body: purchases.signature
10    });
11} catch (err) {
12    // Ошибка получения списка покупок или их обработки.
13}

 

Получение каталога всех товаров

Чтобы получить список доступных покупок и их стоимость, используйте метод payments.getCatalog().

Сигнатура метода:

 1interface IProduct {
 2    id: string;
 3    title: string;
 4    description: string;
 5    imageURI: string;
 6    price: string;
 7    priceValue: string;
 8    priceCurrencyCode: string;
 9    getPriceCurrencyImage(size: 'small' | 'medium' | 'svg'): string;
10}
11
12function getCatalog(): Promise<IProduct[]> {}

Метод возвращает список доступных для пользователя товаров. Формируется из таблицы на вкладке Инап-покупки Консоли разработчика. Каждый IProduct содержит свойства:

Свойство

Тип

Описание

id

string

Идентификатор товара.

title

string

Название товара.

description

string

Описание товара.

imageURI

string

URL изображения товара.

price

string

Стоимость товара в формате <цена> <код валюты>.

priceValue

string

Стоимость товара в формате <цена>.

priceCurrencyCode

string

Код валюты.

getPriceCurrencyImage(size)

string

Метод получения адреса иконки валюты в зависимости от параметра размера иконки. Возможные значения:

  • small (по умолчанию) — получение маленькой иконки.

  • medium — получение иконки среднего размера.

  • svg — получение иконки в векторе.

Важно

Портальная валюта должна определяться автоматически (пункт 3.8). Для этого берите ее название и иконку из свойств IProduct. Подробнее см. в разделе Автоматическое определение портальной валюты.

Пример

 1const ysdk = await YaGames.init();
 2
 3let gameShop = [];
 4
 5try {
 6    const purchases = await ysdk.payments.getPurchases();
 7
 8    gameShop = purchases;
 9} catch (err) {
10    // Ошибка получения списка покупок.
11}

Обработка покупки и начисление внутриигровой валюты

Существуют два типа покупок:

  • Постоянные (например, отключение рекламы). Для их обработки применяйте метод payments.getPurchases().
  • Используемые (например, внутриигровая валюта). Для их обработки применяйте метод payments.consumePurchase().

payments.consumePurchase()

Внимание

После вызова метода payments.consumePurchase() обработанная покупка удаляется без возможности восстановления. Поэтому сначала модифицируйте данные игрока методами player.setData(), player.setStats() или player.incrementStats(), а затем обрабатывайте покупку.

Сигнатура метода:

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

Принимает purchaseToken, возвращаемый методами payments.purchase() и payments.getPurchases(). Если обработка прошла успешно, Promise разрешается со статусом fulfilled, если возникла ошибка — отклоняется со статусом rejected.

Пример

 1const ysdk = await YaGames.init();
 2
 3function addGold(value) {
 4    return ysdk.player.incrementStats({ gold: value });
 5}
 6
 7try {
 8    const purchase = await ysdk.payments.purchase({ id: 'gold500' });
 9
10    await addGold(500);
11
12    await ysdk.payments.consumePurchase(purchase.purchaseToken);
13} catch (err) {
14    // Обработка ошибки обработки используемой покупки.
15}

Проверка необработанных покупок

Внимание

Эта проверка обязательна для прохождения модерации (пункт 1.13.1), поэтому важно настроить ее даже для тестовых покупок. Если добавить в игру покупки и протестировать их до настройки консумирования, то после тестов могут остаться необработанные платежи, которые сделают прохождение модерации невозможным.

Если во время совершения инап-покупки у пользователя отключился интернет или ваш сервер был недоступен, покупка может остаться необработанной. Чтобы этого избежать, проверяйте наличие необработанных покупок с помощью метода payments.getPurchases(), например, при каждом запуске игры.

Инициализация с параметром по умолчанию (signed: false).

Пример

 1const ysdk = await YaGames.init();
 2
 3async function handlePurchase(purchase) {
 4    if (purchase.productID === 'gold500') {
 5        await ysdk.player.incrementStats({ gold: 500 });
 6
 7        await ysdk.payments.consumePurchase(purchase.purchaseToken);
 8    }
 9}
10
11const purchases = await ysdk.payments.getPurchases().then(purchases => purchases.forEach(consumePurchase));
12
13for (let purchase of purchases) {
14    await handlePurchase(purchase);
15}

Инициализация с параметром signed: true.

Пример

 1const ysdk = await YaGames.init({ signed: true });
 2
 3try {
 4    const purchases = await ysdk.payments.getPurchases();
 5    // Отправляем список покупок на сервер.
 6    const response = await fetch('https://your.game.server/handlePurchases', {
 7        method: 'POST',
 8        headers: { 'Content-Type': 'text/plain' },
 9        body: purchases.signature
10    });
11} catch (err) {
12    // Ошибка получения списка покупок или их обработки.
13}

 

Защита от накруток

Чтобы обезопасить себя от возможных накруток показателей в игре, обрабатывайте покупки на стороне сервера:

  1. Инициализируйте YaGames.init() или ysdk.getPayments() с параметром { signed: true }.
  2. Полученную подпись в ответах payments.purchase() и payments.getPurchases() передайте на свой сервер, расшифруйте ее с помощью секретного ключа.
  3. На своем сервере начислите игроку полученные в игре предметы.
 1function serverPurchase(signature) {
 2    return fetch('https://your.game.server/handlePurchase', {
 3        method: 'POST',
 4        headers: { 'Content-Type': 'text/plain' },
 5        body: signature
 6    });
 7}
 8
 9// Убедитесь что покупки инициализированы с параметром { signed: true }.
10const ysdk = await YaGames.init({ signed: true });
11
12try {
13    const purchase = await ysdk.payments.purchase({ id: 'gold500' });
14
15    // Начисляем на сервере 500 золотых...
16    await serverPurchase(purchase.signature);
17} catch (err) {
18    // Ошибка покупки.
19}

Параметр signature передаваемого на сервер запроса содержит данные о покупке и подпись. Представляет собой две строки в кодировке base64: <подпись>.<JSON с данными о покупке>.

Пример signature

hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0=

Пример передаваемых данных о покупке (в формате JSON)

Важно

Формат данных параметра signature в функции serverPurchase(signature) отличается от используемого в методе payments.getPurchases().

В методе payments.getPurchases() параметр signature содержит массив объектов покупок в поле data. В функции serverPurchase(signature) — объект покупки.

 1{
 2  "algorithm": "HMAC-SHA256",
 3  "issuedAt": 1571233371,
 4  "requestPayload": "qwe",
 5  "data": {
 6    "token": "d85ae0b1-9166-4fbb-bb38-6d2a4ca4416d",
 7    "status": "waiting",
 8    "errorCode": "",
 9    "errorDescription": "",
10    "url": "https://yandex.ru/games/sdk/payments/trust-fake.html",
11    "product": {
12      "id": "noads",
13      "title": "Без рекламы",
14      "description": "Отключить рекламу в игре",
15      "price": {
16        "code": "YAN",
17        "value": "49"
18      },
19      "imagePrefix": "https://avatars.mds.yandex.net/get-games/1892995/2a0000016d1c1717bd7a0149ccadc86078a1/"
20    },
21    "developerPayload": "TEST DEVELOPER PAYLOAD"
22  }
23}

Пример секретного ключа

t0p$ecret

Секретный ключ для проверки подписи является уникальным для игры. Формируется автоматически при создании покупок в Консоли разработчика. Ключ отображается на вкладке Инап-покупкиНастройки.

Пример проверки подписи на сервере

 1import hashlib
 2import hmac
 3import base64
 4import json
 5
 6usedTokens = {}
 7
 8key = 't0p$ecret' # Держите ключ в секрете.
 9secret = bytes(key, 'utf-8')
10signature = 'hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0='
11
12sign, data = signature.split('.')
13message = base64.b64decode(data)
14
15purchaseData = json.loads(message)
16result = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest())
17if result.decode('utf-8') == sign:
18  print('Signature check ok!')
19
20  if not purchaseData['data']['token'] in usedTokens:
21    usedTokens[purchaseData['data']['token']] = True # Используйте базу данных.
22    print('Double spend check ok!')
23
24    print('Apply purchase:', purchaseData['data']['product'])
25    # Здесь можно безопасно начислить купленное.
 1const crypto = require('crypto');
 2
 3const usedTokens = {};
 4
 5const key = 't0p$ecret'; // Держите ключ в секрете.
 6const signature = 'hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0=';
 7
 8const [sign, data] = signature.split('.');
 9const purchaseDataString = Buffer.from(data, 'base64').toString('utf8');
10const hmac = crypto.createHmac('sha256', key);
11
12hmac.update(purchaseDataString);
13
14const purchaseData = JSON.parse(purchaseDataString);
15
16if (sign === hmac.digest('base64')) {
17  console.log('Signature check ok!');
18
19  if (!usedTokens[purchaseData.data.token]) {
20    usedTokens[purchaseData.data.token] = true; // Используйте базу данных.
21    console.log('Double spend check ok!');
22
23    console.log('Apply purchase:', purchaseData.data.product);
24    // Здесь можно безопасно начислить купленное.
25  }
26}

Примечание

Сотрудники службы поддержки помогают разместить готовую игру на платформе Яндекс Игр. На прикладные вопросы о разработке и тестировании предметно ответят другие разработчики в Сообществе в Телеграме.

Если при использовании SDK Яндекс Игр вы столкнулись с проблемой или у вас появился вопрос, обратитесь в службу поддержки:

Написать в чат

Параметр signature передаваемого на сервер запроса содержит данные о покупке и подпись. Представляет собой две строки в кодировке base64: <подпись>.<JSON с данными о покупке>.

Предыдущая
Следующая