应用程序内购买验证
# 货币化模式和工具非常广泛,包括广告支持、微交易、免费增值、一次性购买以及其间的一切。许多这类解决方案中的一个关键工具是应用程序内购买 (IAP)。借助 IAP,可进行单笔购买,获得解锁、游戏内消费品、会员访问权限订阅等等。
对于最常见的应用程序内购买实现,存在许多现成攻击。其关注点通常包括:
向客户馈送虚假的购买响应,以示成功 多次重播某个有效的购买响应 与另外一个客户端共享购买响应,让多个玩家能够得到单笔购买的回报 对于应用程序内购买,需要有可信的真相来源。Nakama 检查和跟踪购买和购买历史,解决了很多可能的漏洞和难点:
无论在哪个平台上,均可以对 Apple 、Google 和华为 购买使用应用程序内购买验证。单个产品和订阅购买均受支持。
通过 Unity IAP 进行的 Apple 和 Google 购买也受支持。
Apple
# Nakama 支持在 iOS 中进行的产品和订阅购买的验证。
Apple 购买收据将被发送到 Apple 进行验证。按照 Apple 的建议,同时使用生产服务器和沙盒服务器来验证收据。
设置
# 为对照 App Store 验证收据,Nakama 需要您应用程序的共享密钥。
在您应用程序的应用程序内购买管理部分下的 App Store Connect 中设置共享密钥:Apple App Store Connect
记录共享密钥,以便在 Nakama 配置中使用:Apple App Store Connect 共享密钥
将 Nakama 的iap.apple.shared_password
配置标志 的值设置为先前创建的共享密钥的值。
经过验证的购买
# Nakama 仅支持验证 iOS 7+ 收据。
Apple 收据可包含多笔购买,Nakama 将进行全部验证并将其作为单独的购买记录存储。
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/apple \
--user 'defaultkey:' \
--data '{" receipt":" base64_encoded_receipt_data"}'
Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local function validate_receipt ( receipt )
local result = client.validate_purchase_apple ( receipt )
if result.error then
print ( result.message )
return
end
pprint ( result )
end
-- Use https://defold.com/extension-iap/
iap.set_listener ( function ( self , transaction , error )
if not error then
validate_receipt ( transaction.receipt )
end
end )
iap.buy ( "com.defold.nakama.goldbars-10" )
Client
1
2
3
4
5
6
string appleReceipt = "<receipt>" ;
var response = await client . ValidatePurchaseAppleAsync ( session , appleReceipt );
foreach ( var validatedPurchase in response . ValidatedPurchases )
{
System . Console . WriteLine ( "Validated purchase: " + validatedPurchase );
}
Client
1
2
3
4
5
const appleReceipt = "<receipt>" ;
const result = await client . validatePurchaseApple ( session , appleReceipt );
result . validatedPurchases . forEach ( validatedPurchase => {
console . info ( "Validated purchase:" , validatedPurchase )
});
Client
1
2
3
4
5
6
7
var apple_receipt = "<receipt>"
var result : NakamaAPI . ApiValidatePurchaseResponse = yield ( client . validate_purchase_apple_async ( session , apple_receipt ), "completed" )
if result . is_exception ():
print ( "An error occurred: %s " % result )
return
for p in result . validated_purchase :
print ( "Validated purchase: %s " % p . validated_purchase )
Client
1
2
3
4
5
6
7
8
POST /v2/iap/purchase/apple
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"receipt" : "base64_encoded_Apple_receipt_payload"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
请参考提供的运行时购买验证功能 的功能参考页面。
Google
# Nakama 支持在 Android 中进行的产品和订阅购买的验证。
设置
# 为对照 Play Store 验证收据,Nakama 需要您的 Google 服务帐户 ClientEmail
和 PrivateKey
。获取这些值:
在 Google API Console 中设置服务帐户 :创建服务帐户
创建服务帐户后,需要创建密钥:创建密钥
下载 JSON 文件形式的密钥:创建 JSON 密钥
打开 JSON 文件,提取 ClientEmail
和 PrivateKey
的值。
将这些值分别设置为 iap.google.client_email
和 iap.google.private_key
的 Nakama 配置 值。
最后,确保向购买验证 API 授予 Nakama 访问权限。从 Google Play Developer Console ,依次进入设置 > API 访问权限 :创建 API 访问权限
您在先前的步骤中创建的服务帐户应在上方列出。授予服务帐户访问 API 的访问权限,确保授予服务帐户对可见性 、查看财务数据 和管理订单 的访问权限。Nakama 对照 Google Play 验证收据需要这些权限。授予访问权限
进入用户和权限 ,检查正确设定了服务帐户:列出有访问权限的用户
验证购买
# 期望的 Google 收据是
Purchase.getOriginalJson()
返回的字符串。请参阅
Google 开发者文档 ,了解详情。
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/google \
--user 'defaultkey:' \
--data '{" purchase":" json_encoded_purchase_data"}'
Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local function validate_receipt ( receipt )
local result = client.validate_purchase_google ( receipt )
if result.error then
print ( result.message )
return
end
pprint ( result )
end
-- Use https://defold.com/extension-iap/
iap.set_listener ( function ( self , transaction , error )
if not error then
validate_receipt ( transaction.receipt )
end
end )
iap.buy ( "com.defold.nakama.goldbars-10" )
Client
1
2
3
4
5
6
string googleReceipt = "<receipt>" ;
var response = await client . ValidatePurchaseGoogleAsync ( session , googleReceipt );
foreach ( var validatedPurchase in response . ValidatedPurchases )
{
System . Console . WriteLine ( "Validated purchase: " + validatedPurchase );
}
Client
1
2
3
4
5
const googleReceipt = "<receipt>" ;
const result = await client . validatePurchaseGoogle ( session , googleReceipt );
result . validatedPurchases . forEach ( validatedPurchase => {
console . info ( "Validated purchase:" , validatedPurchase )
});
Client
1
2
3
4
5
6
7
var google_receipt = "<receipt>"
var result : NakamaAPI . ApiValidatePurchaseResponse = yield ( client . validate_purchase_google_async ( session , google_receipt ), "completed" )
if result . is_exception ():
print ( "An error occurred: %s " % result )
return
for p in result . validated_purchase :
print ( "Validated purchase: %s " % p . validated_purchase )
Client
1
2
3
4
5
6
7
8
POST /v2/iap/purchase/google
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"purchase" :"json_encoded_purchase_data"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
请参考提供的运行时购买验证功能 的功能参考页面。
华为
# Nakama 对照其 IAP 验证服务,验证华为购买。按照华为的建议,对照提供的签名检查购买数据的有效性后,才与华为服务联系。如数据因任何原因而无效,将在通过华为验证服务进行验证之前拒绝购买。
验证购买
# Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/huawei \
--user 'defaultkey:' \
--data '{" purchase":" json_encoded_purchase_data"," signature":" purchase_data_signature"}'
Client
1
-- Huawei purchases are not yet supported by https://defold.com/extension-iap/
Client
1
2
3
4
5
6
7
string huaweiReceipt = "<receipt>" ;
string huaweiSignature = "<signature>" ;
var response = await client . ValidatePurchaseHuaweiAsync ( session , huaweiReceipt , huaweiSignature );
foreach ( var validatedPurchase in response . ValidatedPurchases )
{
System . Console . WriteLine ( "Validated purchase: " + validatedPurchase );
}
Client
1
2
3
4
5
6
const huaweiReceipt = "<receipt>" ;
const huaweiSignature = "<signature>" ;
const result = await client . validatePurchaseHuawei ( session , huaweiReceipt , huaweiSignature );
result . validatedPurchases . forEach ( validatedPurchase => {
console . info ( "Validated purchase:" , validatedPurchase )
});
Client
1
2
3
4
5
6
7
8
var huawei_receipt = "<receipt>"
var huawei_signature = "<signature>"
var result : NakamaAPI . ApiValidatePurchaseResponse = yield ( client . validate_purchase_huawei_async ( session , huawei_receipt , huawei_signature ), "completed" )
if result . is_exception ():
print ( "An error occurred: %s " % result )
return
for p in result . validated_purchase :
print ( "Validated purchase: %s " % p . validated_purchase )
Client
1
2
3
4
5
6
7
8
9
POST /v2/iap/purchase/huawei
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"purchase" :"json_encoded_purchase_data" ,
"signature" :"purchase_data_signature"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
请参考提供的运行时购买验证功能 的功能参考页面。
Unity IAP
# Unity IAP 受到支持,可与大多数流行的应用商店集成,包括 Apple 和 Google。
Unity IAP 购买收据包含以下信息:
验证购买
#
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type unityIAP struct {
Payload string `json:"Payload"`
Store string `json:"Store"`
TransactionID string `json:"TransactionID"`
}
switch unityIAP . Store {
case "GooglePlay" :
validatedReceipt , err = nk . PurchaseValidateGoogle ( ctx , userID , wrapper . Payload )
case "AppleAppStore" :
validatedReceipt , err = nk . PurchaseValidateApple ( ctx , userID , wrapper . Payload )
default :
logger . Warn ( "Unrecognised store type in Unity IAP." )
return ErrBadInput
}
if err != nil {
if err == runtime . ErrPurchaseReceiptAlreadySeen {
logger . WithField ( "err" , err ). Warn ( "Receipt replay attack." )
} else {
logger . WithField ( "err" , err ). Error ( "Receipt validation error." )
}
return ErrBadInput
}
解释验证结果
# 验证结果中会列出经过验证的购买。
因为 Apple 可能会将多笔购买加入单一收据中,因此结果列表中每次经过验证的购买将仅包含经过验证的购买,以及一个表明其是否已经出现过的 boolean
值。如果某笔购买先前已获得 Nakama 验证,“是否出现过”值将是 true
,这样开发者即可区分新的购买,防止重播攻击的发生。
对于 Google 和华为,每次验证与单笔购买对应。单笔购买包含在验证响应列表中,也有一个上述“是否出现过”值。
每次经过验证的购买都包含提供商验证响应的有效负载,以备开发者出于任何原因需要它。
如果购买/收据无效,验证因为任何原因失败,或者无法与提供商联系,将返回错误。