应用程序内购买验证 货币化模式和工具非常广泛,包括广告支持、微交易、免费增值、一次性购买以及其间的一切。许多这类解决方案中的一个关键工具是应用程序内购买 (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 和华为,每次验证与单笔购买对应。单笔购买包含在验证响应列表中,也有一个上述“是否出现过”值。
每次经过验证的购买都包含提供商验证响应的有效负载,以备开发者出于任何原因需要它。
如果购买/收据无效,验证因为任何原因失败,或者无法与提供商联系,将返回错误。