Внутриигровые покупки
- Портальная валюта
- Условия подключения
- Инициализация
- Активация процесса покупки
- Получение списка купленных товаров
- Получение каталога всех товаров
- Обработка покупки и начисление внутриигровой валюты
- Проверка необработанных покупок
- Защита от накруток
Вы можете получать доход, предоставив пользователям возможность совершать покупки в игре. Например, дополнительное время на прохождение уровня или аксессуары для игрового персонажа. Для этого:
- Подключите внутриигровые покупки в консоли разработчика Яндекс Игр.
- Настройте в SDK возможность работы с покупками.
Портальная валюта
- в шапке каталога;
- в профиле игрока;
- во время покупки в игре.
Пользователи также могут получать яны бонусом за участие в акциях или покупку фиксированных паков.
Совершать внутриигровые покупки могут как авторизованные на Яндексе пользователи, так и неавторизованные. Авторизоваться пользователь может непосредственно во время игры, в том числе и в момент совершения покупки.
Порядок и условия выплаты лицензионного вознаграждения разработчику в связи с введением портальной валюты не изменятся.
Условия подключения
Требования к подключению покупок зависят от резиденства вашего юридического лица.
Для настройки и тестирования покупок необходимо подписать «Акт приема-передачи неисключительных прав на использование Игры».
После добавления покупок и публикации черновика игры отправьте письмо с запросом формы акта на почту games-partners@yandex-team.ru. В письме обязательно укажите название и идентификатор (ID) игры.
В ответ на ваше письмо будет отправлена форма «Акт приема-передачи неисключительных прав на использование Игры». Эту форму необходимо заполнить, подписать и отправить скан (или фото хорошего качества) в ответ на наше письмо.
Только после получения подписанного акта внутриигровые покупки можно будет настраивать и тестировать.
После добавления покупок и публикации черновика игры отправьте письмо с запросом о подключении покупок на почту games-partners@yandex-team.ru. В письме обязательно укажите название и идентификатор (ID) игры, а также страну резидентства вашего юридического лица.
Инициализация
payments
.var payments = null;
ysdk.getPayments({ signed: true }).then(_payments => {
// Покупки доступны.
payments = _payments;
}).catch(err => {
// Покупки недоступны. Включите монетизацию в консоли разработчика.
// Убедитесь, что на вкладке Покупки консоли разработчика присутствует таблица
// хотя бы с одним внутриигровым товаром и надписью «Покупки разрешены».
})
signed: true
— опциональный параметр. Возвращает параметр signature в методах payments.getPurchases()
и payments.purchase()
. Предназначен для защиты игры от накруток.
Активация процесса покупки
payments.purchase({ id, developerPayload })
id
— string — идентификатор товара, который задан в консоли разработчика.developerPayload
— string — опциональный параметр. Дополнительная информация о покупке, которую вы хотите передавать на свой сервер (будет передана в параметреsignature
).
Метод открывает фрейм с платежным шлюзом. Возвращает Promise<Purchase>
.
Purchase
— object — информация о покупке. Содержит свойства:productID
— string — идентификатор товара.purchaseToken
— string — токен для использования покупки.developerPayload
— string — дополнительные данные о покупке.signature
— string — данные о покупке и подпись для проверки подлинности игрока.
После того как игрок успешно совершил покупку, Promise
переходит в состояние «resolved». Если игрок не совершил покупку и закрыл окно, Promise
переходит в состояние «rejected».
Отказ от следования этим инструкциям может привести к отключению внутриигровых покупок в приложении или снятию его с публикации.
Если игрок в момент покупки не авторизован на Яндексе, откроется окно авторизации. Также вы можете предложить пользователю авторизоваться заранее.
- Пример
-
В общем случае:
payments.purchase({ id: 'gold500' }).then(purchase => { // Покупка успешно совершена! }).catch(err => { // Покупка не удалась: в консоли разработчика не добавлен товар с таким id, // пользователь не авторизовался, передумал и закрыл окно оплаты, // истекло отведенное на покупку время, не хватило денег и т. д. })
С использованием опционального параметра
developerPayload
:payments.purchase({ id: 'gold500', developerPayload: '{serverId:42}' }).then(purchase => { // purchase.developerPayload === '{serverId:42}' })
Получение списка купленных товаров
payments.getPurchases()
Метод возвращает Promise<Purchase[]>
.
Purchase[]
— array — список покупок, совершенных игроком. У списка есть свойство:signature
— string — данные о покупке и подпись для проверки подлинности игрока.
Purchase
содержит свойства:productID
— string — идентификатор товара.purchaseToken
— string — токен для использования покупки.developerPayload
— string — дополнительные данные о покупке.
- Пример
-
var SHOW_ADS = true; payments.getPurchases().then(purchases => { if (purchases.some(purchase => purchase.productID === 'disable_ads')) { SHOW_ADS = false; } }).catch(err => { // Выбрасывает исключение USER_NOT_AUTHORIZED для неавторизованных пользователей. })
Получение каталога всех товаров
Чтобы получить список доступных покупок и их стоимость, используйте метод payments.getCatalog()
.
Метод возвращает Promise<Product[]>
.
Product[]
— object — список доступных для пользователя товаров. Формируется из таблицы на вкладке Покупки консоли разработчика. Каждый Product
содержит свойства:id
— string — идентификатор товара.title
— string — название.description
— string — описание.imageURI
— string — URL изображения.price
— string — стоимость товара в формате<цена> <код валюты>
. Например, «25 YAN».priceValue
— string — стоимость товара в формате<цена>
. Например, «25».priceCurrencyCode
— string — код валюты («YAN»).
Также у Product
есть метод получения адреса иконки валюты:
getPriceCurrencyImage(size: ECurrencyImageSize = ECurrencyImageSize.SMALL)
— string — адрес иконки, например //yastatic.net/s3/games-static/static-data/images/payments/sdk/currency-icon-s@2x.png
. Значением параметра size
можно указать medium
, small
или svg
.
price
;priceValue
и иконка изgetPriceCurrencyImage
.
- Пример
-
var gameShop = [] payments.getCatalog().then(products => { gameShop = products; });
Обработка покупки и начисление внутриигровой валюты
Существуют два типа покупок — постоянные (например, отключение рекламы) и используемые (например, внутриигровая валюта).
Для обработки постоянных покупок применяйте метод payments.getPurchases()
.
Для обработки используемых покупок применяйте метод payments.consumePurchase()
.
- payments.consumePurchase()
-
Метод возвращает
Promise
в состоянии «resolved», если обработка прошла успешна, или «rejected» если возникли ошибки.Внимание. После вызова методаpayments.consumePurchase()
обработанная покупка удаляется без возможности восстановления. Поэтому сначала модифицируйте данные игрока методамиplayer.setStats()
илиplayer.incrementStats()
, а затем обрабатывайте покупку.
- Пример
-
payments.purchase({ id: 'gold500' }).then(purchase => { // Покупка успешно совершена! // Начисляем на баланс 500 золотых и используем покупку. addGold(500).then(() => payments.consumePurchase(purchase.purchaseToken)); }); function addGold(value) { return player.incrementStats({ gold: value }); // Подробнее см. в разделе Данные игрока. }
purchaseToken
— string — токен, возвращаемый методамиpayments.purchase()
иpayments.getPurchases()
.
Проверка необработанных покупок
Если во время совершения внутриигровой покупки у пользователя отключился интернет, или ваш сервер был недоступен, покупка может остаться необработанной. Чтобы избежать такой ситуации, проверяйте наличие необработанных покупок с помощью метода payments.getPurchases()
, например, при каждом запуске игры.
- Пример для игры, данные которой хранятся на сервере Яндекса
-
payments.getPurchases().then(purchases => purchases.forEach(consumePurchase)); function consumePurchase(purchase) { if (purchase.productID === 'gold500') { player.incrementStats({ gold: 500 }).then(() => { payments.consumePurchase(purchase.purchaseToken) }); } }
- Пример для игры, данные которой хранятся на сервере разработчика
-
payments.getPurchases().then(purchases => { fetch('https://your.game.server?purchase', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: purchases.signature }); });
Защита от накруток
Вы можете обезопасить себя от возможных накруток показателей в игре. Для этого вместо методов player.setStats() и player.incrementStats() используйте для обработки покупок функцию serverPurchase(signature)
.
serverPurchase(signature)
требуется настроить сохранение игровых данных на сервере разработчика и инициализировать покупки с параметром signed: true
. // Убедитесь что покупки инициализированы с параметром { signed: true }
payments.purchase({ id: 'gold500' }).then(purchase => {
// Покупка успешно совершена!
// Начисляем на сервере 500 золотых...
serverPurchase(purchase.signature.slice(1)); // fail — проверьте подпись.
serverPurchase(purchase.signature); // ok — покупка подтверждена.
serverPurchase(purchase.signature); // fail — проверьте уникальность покупки.
});
function serverPurchase(signature) {
return fetch('https://your.game.server?purchase', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: signature
});
}
Параметр signature
передаваемого на сервер запроса содержит данные о покупке и подпись. Представляет собой две строки в кодировке base64
: <подпись>.<JSON с данными о покупке>
.
- Пример signature
-
hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0=
- Пример передаваемых данных о покупке (в формате
JSON
) -
Обратите внимание, что формат данных параметра
signature
в функцииserverPurchase(signature)
отличается от используемого в методе payments.getPurchases().В методе
payments.getPurchases()
параметрsignature
содержит массив объектов покупок в полеdata
. В функцииserverPurchase(signature)
— объект покупки.{ "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" } }
- Пример секретного ключа
t0p$ecret
Секретный ключ для проверки подписи является уникальным для игры. Формируется автоматически при создании покупок в консоли разработчика. Размещен под таблицей с покупками.
- Пример проверки подписи на сервере
-
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']) # Здесь можно безопасно начислить купленное.
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); // Здесь можно безопасно начислить купленное. } }
Служба поддержки
Если при использовании SDK Яндекс Игр вы столкнулись с проблемой или у вас появился вопрос, обратитесь в службу поддержки: