跳转到主要内容

安卓收据验证

用于验证安卓应用内购买(IAP)收据的 API。

URL 确认

此 API 使用 service-api.playnanoo.com 域名。

API 信息

  • URL: https://service-api.playnanoo.com/iap/v20221001/unity/android
  • Method: PUT
  • 需要认证: 是
DeviceInfo 继承

此 API 的 Req 类继承自 DeviceInfo。DeviceInfo 的所有属性会自动包含。

请求参数

参数类型必填说明
receiptstring必填收据信息
skuDetailsstring必填安卓商品信息
signaturestring必填安卓 Signature
duplicate_allowstring必填是否允许重复验证收据 (Y/N)

响应数据

Res 类

字段类型说明
UserIDstring用户 ID
PackageNamestring包名
OrderIDstring订单 ID
ProductIDstring商品 ID
Currencystring货币代码
Quantitystring数量
Pricestring价格
PurchaseStatestring验证结果
purchase : 正常支付
wait : 处理中
cancel : 验证失败
wait 状态处理

当状态为 wait 时,请发送重新验证请求。

Unity C# 实现

BaseResponse 类

所有 API 响应的基类。

public class BaseResponse
{
public string ErrorCode;
public string Message;
public string WithdrawalKey;
public string BlockKey;
}

字段说明:

  • ErrorCode:错误代码
  • Message:错误信息
  • WithdrawalKey:账号处于注销等待状态时,用于恢复的密钥(仅注销等待中的账号提供)
  • BlockKey:账号被封禁时提供的密钥(仅被封禁的账号提供)

安卓 IAP 验证类

using System;
using System.Collections;
using UnityEngine.Networking;

public class IAPUnityAndroid
{
static string path = "https://service-api.playnanoo.com/iap/v20221001/unity/android";

[Serializable]
public class Req : DeviceInfo
{
//필수
public string receipt; // 영수증 정보
//옵션
public string skuDetails; // 안드로이드 상품 정보
//옵션
public string signature; // 안드로이드 Signature
//필수
public string duplicate_allow; // 영수증 중복 검증 허용 여부

public IEnumerator Send(string receipt, string skuDetails, string signature, bool isDuplicateAllow, Action<Res> onSuccess, Action<BaseResponse> onError)
{
if (!string.IsNullOrEmpty(receipt)) this.receipt = receipt;
if (!string.IsNullOrEmpty(skuDetails)) this.skuDetails = skuDetails;
if (!string.IsNullOrEmpty(signature)) this.signature = signature;
this.duplicate_allow = isDuplicateAllow ? "Y" : "N";

yield return HttpClient.Send<Req, Res>(
UnityWebRequest.kHttpVerbPUT,
path,
requireToken: true,
body: this,
onSuccess: onSuccess,
onError: onError
);
}
}

[Serializable]
public class Res : BaseResponse
{
public string UserID;
public string PackageName;
public string OrderID;
public string ProductID;
public string Currency;
public string Quantity;
public string Price;
public string PurchaseState;
}
}

使用示例

public void ValidateAndroidIAP()
{
IAPUnityAndroid.Req req = new IAPUnityAndroid.Req();

// Google Play 결제 정보
string receipt = "{\"orderId\":\"GPA.1234-5678-9012-34567\",\"packageName\":\"com.example.game\",\"productId\":\"gold_100\",\"purchaseTime\":1234567890,\"purchaseState\":0,\"purchaseToken\":\"abcdefghijklmnop\"}";
string skuDetails = "{\"productId\":\"gold_100\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"100 Gold\",\"description\":\"Get 100 gold coins\"}";
string signature = "Base64EncodedSignature==";

StartCoroutine(req.Send(
receipt: receipt,
skuDetails: skuDetails,
signature: signature,
isDuplicateAllow: false, // 중복 영수증 허용 안 함
onSuccess: res =>
{
switch (res.PurchaseState)
{
case "purchase":
// 정상 결제 - 아이템 지급
Debug.Log($"결제 완료: {res.ProductID}");
GiveItemToPlayer(res.ProductID, int.Parse(res.Quantity));
break;

case "wait":
// 처리 진행중 - 재검증 필요
Debug.Log("결제 처리 중입니다. 잠시 후 재검증을 요청합니다.");
StartCoroutine(RetryValidation(receipt, skuDetails, signature));
break;

case "cancel":
// 검증 실패
Debug.LogError("영수증 검증에 실패했습니다.");
break;
}
},
onError: (error) =>
{
Debug.LogError($"IAP 검증 실패: [{error.ErrorCode}] [{error.Message}]");
}
));
}

private void GiveItemToPlayer(string productId, int quantity)
{
// 실제 아이템 지급 로직 구현
Debug.Log($"아이템 지급: {productId} x {quantity}");
}

private IEnumerator RetryValidation(string receipt, string skuDetails, string signature)
{
yield return new WaitForSeconds(3f); // 3초 후 재시도
ValidateAndroidIAP(); // 재검증 호출
}
收据重复验证

将 isDuplicateAllow 设置为 false 时,无法使用同一收据进行重复验证。在测试环境中可以设置为 true 以允许重复验证。

Google Play 收据

receipt 是 Google Play Billing Library 提供的包含 purchaseToken 的 JSON 格式收据信息。

Unity IAP 使用示例

使用 Unity IAP 时,可以在 ProcessPurchase 回调中执行收据验证。

using UnityEngine.Purchasing;

public class IAPManager : IStoreListener
{
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
// 안드로이드 플랫폼 체크
if (Application.platform == RuntimePlatform.Android)
{
// 플레이나누 영수증 검증 API 호출
IAPUnityAndroid.Req req = new IAPUnityAndroid.Req();

StartCoroutine(req.Send(
receipt: args.purchasedProduct.receipt, // Unity IAP 영수증 전체
skuDetails: "", // Unity IAP 사용 시 선택사항
signature: "", // Unity IAP 사용 시 선택사항
isDuplicateAllow: false,
onSuccess: res =>
{
switch (res.PurchaseState)
{
case "purchase":
// 정상 결제 - 아이템 지급
Debug.Log($"영수증 검증 성공: {res.ProductID}");
GiveItemToPlayer(res.ProductID, int.Parse(res.Quantity));
break;

case "wait":
// 처리 진행중 - 재검증 필요
Debug.Log("결제 처리 중입니다. 잠시 후 재검증을 요청합니다.");
break;

case "cancel":
// 검증 실패
Debug.LogError("영수증 검증에 실패했습니다.");
break;
}
},
onError: error =>
{
Debug.LogError($"영수증 검증 실패: [{error.ErrorCode}] {error.Message}");
}
));

// 비동기 검증이 완료될 때까지 보류
return PurchaseProcessingResult.Pending;
}

return PurchaseProcessingResult.Complete;
}

private void GiveItemToPlayer(string productId, int quantity)
{
// 실제 아이템 지급 로직 구현
Debug.Log($"아이템 지급: {productId} x {quantity}");
}

// IStoreListener 나머지 메서드들
public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { }
public void OnInitializeFailed(InitializationFailureReason error) { }
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) { }
}
异步处理注意

ProcessPurchase 方法是同步方法,但收据验证是异步处理的。在验证完成之前返回 PurchaseProcessingResult.Pending,验证完成后需调用 IStoreController.ConfirmPendingPurchase() 来完成购买处理。

重试逻辑

收据验证失败时返回 PurchaseProcessingResult.Pending,Unity IAP 会在下次启动应用时自动重试。可以应对网络错误等临时问题。