iOS内购的开发步骤
- 苹果官网配置
- 工程项目配置
- 代码开发集成
苹果官网配置
- 关于苹果官网配置可以直接查看苹果的App 内购买项目配置流程,里面详细说明了用户协议和税务信息、添加商品信息、创建沙盒测试账号等相关步骤。
- 也可以查看网上的博文(iOS 苹果内购详细步骤、iOS开发内购全套图文教程、iOS开发支付篇-内购(IAP)),个人建议要过一遍苹果官网的权威文章,毕竟里面的说明是最新最完整的,网上的博文有些可能已经过时或者缺漏。
工程项目配置
1. 使用xcode打开项目工程,并将In-App Purchase选上,
2. 添加StoreKit.framework
也可以利用PBXProject进行代码自动化添加,
PBXProject proj = new PBXProject();
string targetName = PBXProject.GetUnityTargetName();
string target = proj.TargetGuidByName(targetName);
......
proj.AddCapability(target, PBXCapabilityType.InAppPurchase);
proj.AddFrameworkToProject(target, "StoreKit.framework", false);
代码开发集成
1. 内购支付流程
- 客户端向服务器请求商品信息(注意此时的商品信息要跟苹果官网配置的商品信息一致)。
- 玩家点击购买按钮,客户端向苹果服务器发送消息请求购买产品,苹果服务器验证产品成功后,从用户的Apple账户余额中扣费。
- 苹果服务器向客户端返回一个JSON格式的消息,里面有产品名称、Receipt(string类型)等信息。
- 客户端取得Receipt,发送给服务器验证。
- 服务器对Receipt进行一次base64编码并发往苹果服务器进行二次验证。
- 苹果服务器返回JSON格式的验证结果给服务器。
- 服务器向客户端发放相应的道具与推送数据更新通知。
2. 关于服务器向苹果服务器二次验证
说明:客户端跟苹果服务器的交互集成在插件里,服务器跟苹果服务器的交互需要自己开发
目的:防止玩家外挂恶意刷单
官网说明:Validating Receipts With the App Store
正式验证接口:https://buy.itunes.apple.com/verifyReceipt
沙盒测试接口:https://sandbox.itunes.apple.com/verifyReceipt
请求方式:POST
参数:receipt-data,如
{
'receipt-data':'Bdkd7D97DBVLhjldj6LLFD.....'
}
苹果服务器返回验证结果:JSON格式,如
{
"status": 0,
"environment": "Sandbox",
"receipt": {
"receipt_type": "ProductionSandbox",
"adam_id": 0,
"app_item_id": 0,
......
}
}
苹果服务器返回验证状态:status
O:验证成功。
21000: App Store无法读取你提供的JSON数据。
21002: receipt-data域的数据有问题。
21003: receipt无法通过验证。
21004: 你提供的共享密钥和账户的共享密钥不一致。
21005: receipt服务器当前不可用。
21006: receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送。
21007: receipt是沙盒环境的收据,但却发送至生产环境验证。
21008: receipt是生产环境的收据,但却发送至沙盒环境验证。
3. 代码开发集成
引擎:Unity3D
插件:OpenIAB(https://github.com/onepf/OpenIAB-Unity-Plugin)
1. 首先在github上拉取插件工程,并使用Unity打开OpenIAB-Unity-Plugin\unity_plugin\unity_src,如下
2. 由于我们只做iOS开发,所以下面标颜色的文件可以删除
3. 将Assets/Plugins/Prefabs/OpenIABEventManager.prefab拖到Scene下
4. 新建C#文件,命名为OpenIAPMananger.cs
添加代码如下,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using OnePF;
public enum PurchaseResult
{
Product_Info_Error = 1,
Product_Null_Error,
Purchase_Fail,
Purchase_Success
}
public class OpenIAPMananger
{
private static List<SkuDetails> skuList = null;
void OnEnable()
{
#if UNITY_IOS
//添加回调响应函数
OpenIABEventManager.billingSupportedEvent += OnBillingSupported;
OpenIABEventManager.billingNotSupportedEvent += OnBillingNotSupported;
OpenIABEventManager.queryInventorySucceededEvent += OnQueryInventorySucceeded;
OpenIABEventManager.queryInventoryFailedEvent += OnQueryInventoryFailed;
OpenIABEventManager.purchaseSucceededEvent += OnPurchaseSucceded;
OpenIABEventManager.purchaseFailedEvent += OnPurchaseFailed;
OpenIABEventManager.consumePurchaseSucceededEvent += OnConsumePurchaseSucceeded;
OpenIABEventManager.consumePurchaseFailedEvent += OnConsumePurchaseFailed;
OpenIABEventManager.transactionRestoredEvent += OnTransactionRestored;
OpenIABEventManager.restoreSucceededEvent += OnRestoreSucceeded;
OpenIABEventManager.restoreFailedEvent += OnRestoreFailed;
#endif
}
void OnDisable()
{
#if UNITY_IOS
//移除回调响应函数
OpenIABEventManager.billingSupportedEvent -= OnBillingSupported;
OpenIABEventManager.billingNotSupportedEvent -= OnBillingNotSupported;
OpenIABEventManager.queryInventorySucceededEvent -= OnQueryInventorySucceeded;
OpenIABEventManager.queryInventoryFailedEvent -= OnQueryInventoryFailed;
OpenIABEventManager.purchaseSucceededEvent -= OnPurchaseSucceded;
OpenIABEventManager.purchaseFailedEvent -= OnPurchaseFailed;
OpenIABEventManager.consumePurchaseSucceededEvent -= OnConsumePurchaseSucceeded;
OpenIABEventManager.consumePurchaseFailedEvent -= OnConsumePurchaseFailed;
OpenIABEventManager.transactionRestoredEvent -= OnTransactionRestored;
OpenIABEventManager.restoreSucceededEvent -= OnRestoreSucceeded;
OpenIABEventManager.restoreFailedEvent -= OnRestoreFailed;
#endif
}
void Start()
{
#if UNITY_IOS
-- 映射sku, 将自定义sku与苹果官网配置的商品的sku映射
for(int i=0, shopListFormServer.Count, i++)
{
string strSku = GetServerSku(i); //获取苹果官网配置的商品的sku
OpenIAB.mapSku("sku" + i,toString(), OpenIAB_iOS.STORE, strSku);
}
//初始化options,这一步可忽略
Options options = new Options();
options.storeSearchStrategy = SearchStrategy.INSTALLER_THEN_BEST_FIT;
options.prefferedStoreNames = new string[] { OpenIAB_Android.STORE_GOOGLE, OpenIAB_Android.STORE_YANDEX };
options.storeKeys = new Dictionary<string, string> { { OpenIAB_Android.STORE_GOOGLE, "publicKey" } };
options.verifyMode = OptionsVerifyMode.VERIFY_SKIP;
OpenIAB.init(options);
#endif
}
private void OnBillingSupported()
{
//Debug.Log("Billing supported");
OpenIAB.queryInventory();
}
private void OnBillingNotSupported(string error)
{
//Debug.Log("Billing not supported: " + error);
}
private void OnQueryInventorySucceeded(Inventory inventory)
{
//苹果服务器返回商品信息
//Debug.Log("Query inventory succeeded: " + inventory.ToString());
skuList = inventory.GetAllAvailableSkus();
}
private void OnQueryInventoryFailed(string error)
{
//Debug.Log("Query inventory failed: " + error);
}
private void OnPurchaseSucceded(Purchase purchase)
{
//苹果服务器返回购买成功
OnPurchaseResult(PurchaseResult.Purchase_Success, purchase.Receipt, purchase.Sku, purchase.OrderId);
}
private void OnPurchaseFailed(int errorCode, string error)
{
//苹果服务器返回购买失败
OnPurchaseResult(PurchaseResult.Purchase_Fail);
}
private void OnConsumePurchaseSucceeded(Purchase purchase)
{
//Debug.Log("Consume purchase succeded: " + purchase.ToString());
}
private void OnConsumePurchaseFailed(string error)
{
//Debug.Log("Consume purchase failed: " + error);
}
private void OnTransactionRestored(Purchase purchase)
{
//Debug.Log("Transaction restored: " + purchase.Sku);
}
private void OnRestoreSucceeded()
{
//Debug.Log("Transactions restored successfully");
}
private void OnRestoreFailed(string error)
{
//Debug.Log("Transaction restore failed: " + error);
}
//购买结果回调
private void OnPurchaseResult(PurchaseResult result, string receipt = "", string sku = "", string orderId = "")
{
if (result == PurchaseResult.Purchase_Success)
{
Debug.Log("购买成功,receipt:" + receipt);
}
else if (result == PurchaseResult.Purchase_Fail)
{
Debug.Log("购买失败,请重新购买");
}
else if (result == PurchaseResult.Product_Null_Error)
{
Debug.Log("购买失败,无此商品");
}
else if (result == PurchaseResult.Product_Info_Error)
{
Debug.Log("购买失败,商品信息有误");
}
}
//购买接口,传入的是自定义sku
public void Buy(string sku)
{
int index = 0;
//没有商品信息
if (skuList.Count == 0)
{
OnPurchaseResult(PurchaseResult.Product_Info_Error);
return;
}
//验证要购买的商品是否存在
for (index = 0; index < skuList.Count; index++)
{
if (sku == skuList[index].Sku)
{
break;
}
}
if (index >= skuList.Count)
{
OnPurchaseResult(PurchaseResult.Product_Null_Error);
return;
}
//向苹果服务器发送购买信息
OpenIAB.purchaseProduct(sku, "");
}
}
5. 创建空物体,将OpenIAPMananger.cs挂上去,就可以调用购买接口进行商品购买
常见问题
1. 漏单
说明:由于网络原因,服务器与苹果服务器的交互可能存在延迟,当玩家在客户端购买某件商品后,服务器一直没返回验证结构给客户端,因此导致玩家扣费却收不到商品。
解决:客户端将苹果服务器返回的Receipt进行存储,并定期或者适当的时机向服务器多次请求,等收到服务器返回结果后在将Receipt删除。
2. 苹果服务器向游戏服务器返回status:21002
说明:POST的参数格式有问题。
解决:要注意"receipt-data"中间的横杠必需而且要包装成JSON进行请求。
3. 苹果服务器向游戏服务器返回status:21007
说明:receipt是沙盒环境的收据,但却发送至生产环境验证,其实主要是沙盒验证接口与正式验证接口搞混了。
解决:先生产后沙盒,即先调用正式验证接口进行验证,等苹果服务器返回21007之后再调用沙盒验证接口验证。
参考链接
https://www.jianshu.com/p/d0ac8cec794b
https://blog.csdn.net/wjsshhx/article/details/73088094
https://blog.csdn.net/weixin_33796177/article/details/91376840
https://www.jianshu.com/p/307a3b7cfd6c
https://www.jianshu.com/p/17e0d11149f3