轉載自elangduan的博客
蘋果IAP接入
相比微信支付和支付寶支付要麻煩一些,麻煩的地方主要體現在對測試支付環境的要求以及蘋果審覈方面的要求上面。本文是自己在接入iOS的IAP模塊的經驗及總結,發出來分享下。建議需要接入的話還是瀏覽一遍蘋果官方文檔
注意事項
- 測試支付的ipa必須使用[App-Store]證書
- 越獄機器無法測試IAP
- 用SandBox賬號測試支付的時候,必須把在系統[設置]裏面把[Itunes Store 與 App Store]登錄的非SandBox賬號註銷掉,否則向蘋果服務器請求不到訂單信息
- Sandbox賬號不要在正式支付環境登陸支付,登陸過的正式支付環境的SandBox賬號會失效
- 所有在itunes上配置的商品都必須可購買,不能有某些商品根據商戶自己的服務器的數據在某個時期出現免費的情況
- 商品列表不能按照某些特定條件進行排序(比如說下載量)
- 非消耗型商品必須的有恢復商品功能
- 非消耗類型的商品不要和商戶自己的服務器關聯
關於證書
證書名稱 | 證書類型 | 適用場景 |
---|---|---|
Ad-hoc | 內測版本 | 需要把設備Identifier添加到證書纔可安裝 |
In-house | 企業版本 | 任何iOS設備都可以安裝 |
App-Store | App-Store版本 | 上傳到App-Store審覈過了,在App-Store中安裝 |
說明: 如果需要接入IAP則需要用App-Store版本證書,在開發調試的時候我們使用App-Store Developer證書和SandBox賬號即可調試。平時我們提交到fir.im的包可以用In-house證書,也可用Ad-hoc證書。關於證書的更多介紹可以參考這裏。
商品配置
首先在 [iTunes Connect] 構建一個App版本,然後我們在[Xcode]裏面或者[Application Loader]上傳一個App到App-Store。上傳好之後我們在[iTunes Connect]中的[我的App]中看到我們剛上傳的App。(配置IAP商品之前必須先上傳版本,蘋果要求的)
在 [iTunes Connect] 打開我的App中的[功能]可以看到配置App內購項目的選項[App內購項目]。然後添加對應的商品就可以了。這裏有個地方需要注意的是商品的類型,具體的商品類型說明可以看這裏。切記要選對商品類型,在此說下如果是遊戲中的虛擬金幣之類的商品的話就選擇 Consumable 類型即可。
在 [iTunes Connect] 添加Sandbox測試賬號。
購買流程
訂單收據驗證
兩種驗證方式
客戶端本地驗證
發送receipt數據給遊戲服務器,讓遊戲服務器向AppStore服務器驗證(我現在用的這種,安全)
如果在購買的時候沒有進行receipt驗證退出app了,那麼在下次購買的時候會storekit會首先查詢receipt數據把未驗證訂單先驗證了纔會進行接下來的購買。
收據(receipt)
對於消費類型的商品(Consumable)收據會在驗證之後自動刪除掉,對於非消費類型的商品(Non-Consumable)每次購買的訂單都保存在了收據裏面,並且收據文件不會刪除。如果用戶手動把receipt文件刪除了的話,那麼在下次app啓動的時候會自動生成一個和之前一樣的receipt數據文件並且在下次購買的時候會自動驗證。
丟單的考慮
整個支付過程從表面上看有可能丟單的地方是在客戶端支付完成的時候準備向遊戲服務器發送receipt數據進行驗證的時候,如果這個時候客戶端退出遊戲了(比如用戶按Home鍵退出遊戲殺掉進程),那麼此時用戶已經付費了但是收不到商品。經過驗證在這個過程中如果我們在購買完成的時候沒有調用storekit的完成訂單API的話也會導致每次app重新啓動的時候storekit會回調訂單完成的接口直到你調用finishTransaction纔會停止。注意storekit回調成功的前提是你的購買監聽API已經實例化,在這裏提供的對應的是IAPHelper類,必須在app啓動的時候就實例化。所以我們的做法是先向遊戲服務器進行訂單驗證,驗證成功之後在調用storekit的finishTrasaction,那麼整個過程基本上就不會有丟單的情況了。
支付過程
具體代碼見這裏(密碼是q5j3),下面只列出部分要講解的代碼
//IAPHelper.mm
- (void)getProductInfoById:(NSString*)productID
{
NSLog(@"--productId: %@", productID);
NSArray *product = nil;
product = [[NSArray alloc] initWithObjects:productID, nil];
NSSet *set = [NSSet setWithArray:product];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
//設置並啓動監聽
request.delegate = self;
[request start];
}
//這個函數是getProductInfoById查詢訂單的回調函數
//一般查詢不到商品信息有三種情況:
//1.沒有在iTunes中沒有配置對應商品
//2.沒有使用Appstore Developer證書
//3.使用了越獄的機器
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProduct = response.products;
if (myProduct.count == 0)
{
UnitySendMessage(self.mCallBackObjectName.UTF8String, "PreBuyProductFailed", "ProductNotExist");
return;
}
//如果iOS已經登錄了[iTunes Store與App Store]賬號,則這一步會失敗
SKPayment * payment = [SKPayment paymentWithProduct:myProduct[0]];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
...
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
// Call the appropriate custom method for the transaction state.
case SKPaymentTransactionStatePurchasing:
[self showTransactionAsInProgress:transaction deferred:NO];
break;
case SKPaymentTransactionStateDeferred:
[self showTransactionAsInProgress:transaction deferred:YES];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:
//這個函數是支付成功之後App Store服務器會回調的函數,裏面包含了Receipt數據
//我這邊的處理是先把這個數據發送給Mono層,通過Mono層的網路接口把數據發給自己的商戶服務器
//商戶服務器會把Receipt數據發往App Store服務器進行驗證,再把驗證結果返回給客戶端,如果驗證成功
//這次支付纔算完成
[self verifyPurchaseWithPaymentTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
break;
default:
// For debugging
NSLog(@"Unexpected transaction state %@", @(transaction.transactionState));
break;
}
}
}
//這個函數是服務器驗證成功之後必須調用的函數,
//作用就是將購買完成的訂單從peymentQueue中移除,否則這個訂單會在你的機器上一直
//保留,算作一個未完成的訂單。就算你的App刪除重裝也沒有用。
//transactionIdentifier是每個訂單的唯一表示ID
- (void) completeTransactionByIdentifier:(NSString*)transactionIdentifier
{
NSArray<SKPaymentTransaction *> * transactions = [[SKPaymentQueue defaultQueue] transactions];
for (SKPaymentTransaction *transaction in transactions)
{
NSLog(@"completeTransactionByIdentifier %@ --- %@", transaction.transactionIdentifier, transactionIdentifier);
BOOL result = [transaction.transactionIdentifier compare:transactionIdentifier];
if (NULL != transaction && !result)
{
[self completeTransaction:transaction];
return;
}
}
}
- (void) completeTransaction:(SKPaymentTransaction*)transaction
{
NSLog(@"completeTransaction: %@", transaction.transactionIdentifier);
//NSLog(@"completeTransaction: %@", transaction.transactionIdentifier);
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
// Your application should implement these two methods.
NSString * productIdentifier = transaction.payment.productIdentifier;
if([productIdentifier length] > 0)
{
NSLog(@"productIdentifier : %@", productIdentifier);
}
UnitySendMessage(self.mCallBackObjectName.UTF8String, "BuyProductSuccess", productIdentifier.UTF8String);
}
微信支付
微信支付的流程及操作步驟官方文檔已經描述的非常清楚了,這裏就不介紹了。這裏主要講下遇到的問題及在Unity中接入的問題
遇到的問題
支付完成之後不回調onResp函數
- 由於沒有在AndroidManifest中註冊回調監聽
<receiver android:name=".AppRegister"> <intent-filter> <action android:name="com.tencent.mm.plugin.openapi.Intent.ACTION_REFRESH_WXAPP" /> </intent-filter> </receiver>
Unity生成APK的時候Bundle Identifier填寫的和微信開放平臺註冊的Bundle Identifier不一致
應用包簽名錯誤,確保在Unity生成APK的時候選擇一個固定的Keystore文件,這個文件的簽名要和微信開放平臺註冊的應用簽名保持一致
支付第一次成功之後,後面就再也支付一直返回錯誤碼-1
解決:網上有答案說把微信的緩存清除掉就可以再支付,但是每支付一次就清楚一次。這其實沒有解決根本問題。其實返回錯誤碼-1官方明確說了一種可能是APPID或者簽名錯了。我這邊還遇到了另外一種情況就是服務器傳遞給客戶端的sign值錯了,這個得服務器確認好。
在遊戲中啓動不了微信支付回調界面
解決: 把WXPayEntryActivity這個Activity添加到Unity的Plugins下面的AndroidManifest文件中