Внутриигровые покупки
Вы можете получать доход, предоставив пользователям возможность совершать покупки в игре. Например, дополнительное время на прохождение уровня или аксессуары для игрового персонажа. Для этого:
- Подключите внутриигровые покупки в консоли разработчика Яндекс Игр.
- Настройте в SDK возможность работы с покупками.
- Добавьте проверку необработанных покупок.
- Протестируйте покупки.
Внимание
Тестировать покупки можно только после подключения их консумирования. Иначе могут появиться необработанные платежи, которые сделают прохождение модерации невозможным.
Портальная валюта
Портальная валюта платформы Яндекс Игры используется для оплаты внутриигровых покупок. Она хранится на едином для всех игр балансе игрока, который можно пополнить с помощью банковских карт. Курс портальной валюты к рублю динамический.
Примечание
Для международных платежей коэффициент портальной валюты к национальной будет зависеть от страны игрока.
Пополнить баланс можно:
- в шапке каталога;
- в профиле игрока;
- во время покупки в игре.
Пользователи также могут получать портальную валюту бонусом за участие в акциях или покупку фиксированных паков.
Совершать внутриигровые покупки могут как авторизованные на Яндексе пользователи, так и неавторизованные. Авторизоваться пользователь может непосредственно во время игры, в том числе и в момент совершения покупки.
Порядок и условия выплаты лицензионного вознаграждения разработчику в связи с введением портальной валюты не изменятся.
Условия подключения
После добавления покупок и публикации черновика игры отправьте письмо с запросом о подключении покупок на почту games-partners@yandex-team.ru. В письме обязательно укажите название и идентификатор (ID) игры.
После получения ответного письма от games-partners@yandex-team.ru с подтверждением, что покупки разрешены, их можно будет настраивать и тестировать.
Инициализация
Чтобы предоставить пользователям возможность совершать внутриигровые покупки, используйте объект 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 — стоимость товара в формате<цена> <код валюты>
.priceValue
— string — стоимость товара в формате<цена>
.priceCurrencyCode
— string — код валюты.
Product.getPriceCurrencyImage(size)
— метод получения адреса иконки валюты.
-
size
— string — опциональный параметр. Возможные значения:-
small
(по умолчанию) — получение маленькой иконки. -
medium
— получение иконки среднего размера. -
svg
— получение иконки в векторе.
-
Метод возвращает string — адрес иконки, например //yastatic.net/s3/games-static/static-data/images/payments/sdk/currency-icon-s@2x.png
.
Пример
var gameShop = []
payments.getCatalog().then(products => {
gameShop = products;
});
Обозначение портальной валюты
Чтобы отобразить название и иконку портальной валюты в игре, используйте возможности SDK:
Product.price
— string — цена с кодом валюты.Product.priceCurrencyCode
— string — код валюты.Product.getPriceCurrencyImage()
— string — адрес иконки валюты.
Подробнее об объекте Product
см. в разделе Получение каталога всех товаров.
Обработка покупки и начисление внутриигровой валюты
Существуют два типа покупок — постоянные (например, отключение рекламы) и используемые (например, внутриигровая валюта).
Для обработки постоянных покупок применяйте метод 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);
// Здесь можно безопасно начислить купленное.
}
}
Примечание
Сотрудники службы поддержки помогают разместить готовую игру или WebApp на платформе Яндекс Игр. На прикладные вопросы о разработке и тестировании предметно ответят другие разработчики в Сообществе в Telegram.
Если при использовании SDK Яндекс Игр вы столкнулись с проблемой или у вас появился вопрос, обратитесь в службу поддержки:
signed: true
— опциональный параметр. Возвращает параметр signature
в методах payments.getPurchases()
и payments.purchase()
. Предназначен для защиты игры от накруток.
signature
— string — данные о покупке и подпись для проверки подлинности игрока.
id
— string — идентификатор товара, который задан в консоли разработчика.
developerPayload
— string — опциональный параметр. Дополнительная информация о покупке, которую вы хотите передавать на свой сервер (будет передана в параметре signature
).
Параметр signature
передаваемого на сервер запроса содержит данные о покупке и подпись. Представляет собой две строки в кодировке base64
: <подпись>.<JSON с данными о покупке>
.
purchaseToken
— string — токен, возвращаемый методами payments.purchase()
и payments.getPurchases()
.