游戏内购
您可以通过为用户提供游戏内购买的选项来赚取收入。例如,他们可通过购买额外的时间来完成关卡或获得人物装饰。为此,您需要:
条件
在添加购买项并发布游戏草案后,发送邮件至 games-partners@yandex-team.com,请求开通购买功能。邮件中必须注明游戏的名称和标识符(ID)。
收到games-partners@yandex-team.com的回复邮件,确认购买功能已获授权后,即可开始设置和测试购买项。
初始化
要让玩家能够进行应用内购买,请使用 payments
对象。您可以选择以下方式:
-
直接访问
ysdk.payments
。第一次调用该对象的任何方法时会初始化购买功能,因此首次调用可能会稍慢一些。 -
通过
ysdk.getPayments()
方法预先初始化对象。这会预加载payments
方法所需的数据,避免首次调用时的延迟。
注意
在 YaGames.init()
和 ysdk.getPayments()
中可传递可选参数 signed: boolean
,用于防作弊保护。参数值的选择取决于支付处理的位置:
-
如果在客户端处理 — 调用方法时不传参数或传递
signed: false
。购买方法将返回未加密的数据。 -
如果在服务端处理 — 传递
signed: true
。这种情况下 payments.getPurchases() 和 payments.purchase() 方法返回的所有数据都会以加密形式存放在 signature 参数中。
使用默认参数初始化(signed: false
)。
方式1:简化版
YaGames.init() // 或 YaGames.init({ signed: false })
.then(ysdk => {
// 购买方法可通过 ysdk.payments 访问
console.log(ysdk.payments);
}).catch(err => {
// 无法使用购买功能
});
方式2:通过 getPayments() 预加载
var payments = null;
YaGames.init()
.then(ysdk => {
ysdk.getPayments()
.then(_payments => {
// 购买方法可通过 payments 访问
payments = _payments;
console.log(payments);
}).catch(err => {
// 无法使用购买功能
});
});
使用 signed: true
参数初始化。
方式1:初始化 SDK 时设置
YaGames.init({ signed: true }) // 启用购买的签名功能
.then(ysdk => {
// 购买方法可通过 ysdk.payments 访问
console.log(ysdk.payments);
}).catch(err => {
// 无法使用购买功能
});
方式2:通过 getPayments() 精细配置
var payments = null;
YaGames.init()
.then(ysdk => {
ysdk.getPayments({ signed: true })
.then(_payments => {
// 购买方法可通过 payments 访问
payments = _payments;
console.log(payments);
}).catch(err => {
// 无法使用购买功能
});
});
激活购买流程
要激活游戏内购买,请使用方法 payments.purchase({ id, developerPayload })
。
id: string
— 开发者仪表板中设置的产品 ID。developerPayload: string
— 可选参数。您想发送到服务器的其他购买数据(在 signature 参数中传输)。
此方法会打开一个带有支付网关的框架。
玩家成功进行购买后,Promise
会切换到“已完成”状态。如果玩家没有进行购买并关闭了窗口,Promise
状态将变为“已拒绝”。
注意
由于互联网连接不稳定,当玩家在进行购买时,可能会出现购买未在游戏中注册的情况。为避免出现这种情况,请使用检查未处理的购买和处理购买并扣除游戏货币中描述的方法处理购买。
如果不遵守这些说明,可能导致应用的游戏内购买被禁用或被取消资格。
用户可以在未授权的情况下进行购买,但我们建议提前或在购买时建议他们进行授权。
示例
一般:
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()
来:
-
了解玩家已经完成了哪些购买;
-
检查未处理的购买是否存在;
-
处理持续性购买。
初始化 使用默认参数 (signed: false
)。
返回 Promise<Purchase[]>
.
Purchase[]: array
— 玩家完成的购买列表。每一笔购买 Purchase
包含属性:
productID: string
— 产品 ID。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[]: array
— 提供给用户的产品列表。它根据开发者仪表板的 In-app purchases 选项卡中的表格生成。每个 Product
都有以下属性:
id: string
— 产品 ID。title: string
— 产品名称。description: string
— 产品描述。imageURI: string
— 产品图片的 URL。price: string
—<price> <currency code>
格式的产品价格。例如。priceValue: string
—<price>
格式的产品价格。例如。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
。
注意
在调用 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() 检查未处理的购买(例如在每次启动游戏时)。
注意
在添加购买之前,请配置未处理支付检查。
这项检查对于通过审核是必需的,因此即使是对于测试购买,配置它也非常重要。如果在设置消费之前就在游戏中添加购买并对其进行测试,那么测试后可能会留下未处理的支付,这将使通过审核变得不可能。
初始化 使用默认参数 (signed: false
)。
示例
payments.getPurchases().then(purchases => purchases.forEach(consumePurchase));
function consumePurchase(purchase) {
if (purchase.productID === 'gold500') {
player.incrementStats({ gold: 500 })
.then(() => {
payments.consumePurchase(purchase.purchaseToken)
});
}
}
初始化 参数为 signed: true
。
示例
payments.getPurchases()
.then(purchases => {
fetch('https://your.game.server?purchase', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: purchases.signature
});
});
欺诈防范
为了保护自己免受游戏中可能的刷分操作,应在服务器端处理购买:
- 初始化
YaGames.init()
或ysdk.getPayments()
时传入{ signed: true }
参数。 - 将 payments.purchase() 和 payments.getPurchases() 响应中获得的签名传送到您的服务器,并使用秘钥进行解密。
- 在您的服务器上为玩家充值游戏中获得的物品。
// 确保使用 { signed: true } 参数初始化购买
payments.purchase({ id: 'gold500' })
.then(purchase => {
// 购买成功!
// 在服务器上添加 500 个金币...
serverPurchase(purchase.signature); // 检查签名并为玩家充值数据。
});
function serverPurchase(signature) {
return fetch('https://your.game.server?purchase', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: signature
});
}
发送到服务器的请求中的 signature
参数包含购买数据和签名。两个字符串采用 base64
编码方式:<signature>.<JSON with the purchase data>
。
签名示例
hQ8adIRJWD29Nep+0P36Z6edI5uzj6F3tddz6Dqgclk=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZEF0IjoxNTcxMjMzMzcxLCJyZXF1ZXN0UGF5bG9hZCI6InF3ZSIsImRhdGEiOnsidG9rZW4iOiJkODVhZTBiMS05MTY2LTRmYmItYmIzOC02ZDJhNGNhNDQxNmQiLCJzdGF0dXMiOiJ3YWl0aW5nIiwiZXJyb3JDb2RlIjoiIiwiZXJyb3JEZXNjcmlwdGlvbiI6IiIsInVybCI6Imh0dHBzOi8veWFuZGV4LnJ1L2dhbWVzL3Nkay9wYXltZW50cy90cnVzdC1mYWtlLmh0bWwiLCJwcm9kdWN0Ijp7ImlkIjoibm9hZHMiLCJ0aXRsZSI6ItCR0LXQtyDRgNC10LrQu9Cw0LzRiyIsImRlc2NyaXB0aW9uIjoi0J7RgtC60LvRjtGH0LjRgtGMINGA0LXQutC70LDQvNGDINCyINC40LPRgNC1IiwicHJpY2UiOnsiY29kZSI6IlJVUiIsInZhbHVlIjoiNDkifSwiaW1hZ2VQcmVmaXgiOiJodHRwczovL2F2YXRhcnMubWRzLnlhbmRleC5uZXQvZ2V0LWdhbWVzLzE4OTI5OTUvMmEwMDAwMDE2ZDFjMTcxN2JkN2EwMTQ5Y2NhZGM4NjA3OGExLyJ9fX0=
JSON
格式)
传输的购买数据示例(请注意,serverPurchase(signature)
函数使用的 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": "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"
}
}
密钥示例
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);
// 您可以在此处安全地应用购买。
}
}
备注
技术支持团队将协助您将已完成的游戏发布到 Yandex 游戏平台。关于开发和测试方面的具体问题,其他开发人员将在Discord 频道中进行回答。
如果您遇到 Yandex Games SDK 方面的问题或有其他问题想要咨询,请联系支持部门:
id: string
— 开发者仪表板中设置的产品 ID。
developerPayload: string
— 可选参数。您想发送到服务器的其他购买数据(在 signature
参数中传输)。
发送到服务器的请求中的 signature
参数包含购买数据和签名。两个字符串采用 base64
编码方式:<signature>.<JSON with the purchase data>
。
purchaseToken: string
— 由 payments.purchase() 和 payments.getPurchases() 方法返回的令牌。
如果未通过 ysdk.getPayments()
初始化支付功能,请直接使用 ysdk.payments
- 启用盈利功能。
- 在 Yandex Games 控制台 中进入 In-app purchases 选项卡并确保:
- 存在至少包含一个应用内商品的表格。
- 显示 Purchases are enabled 字样。