通過蘋果開發(fā)文檔、搜索與掙扎摸索,我已經(jīng)在App上實現(xiàn)了【消耗型商品】的內(nèi)購。總結(jié)流程如下:
- 完成必須的準備工作
- 用有效的商品id創(chuàng)建SKProductRequest請求蘋果服務器返回商品(SKProduct)。
- 用返回的有效的商品創(chuàng)建payment,并將payment加入蘋果的支付隊列。
- 監(jiān)聽隊列,并根據(jù)監(jiān)聽到的交易狀態(tài)進行適當?shù)奶幚恚绕湫枰诮灰淄戤厱r關閉交易。
這方面的文檔,我覺得蘋果官方的Programming Guide就寫的很好??梢宰屑氉x一下。
我實際遇到的坑
- 必須先簽署協(xié)議才能進行內(nèi)購開發(fā),否則無法返回商品。
- 創(chuàng)建沙箱測試員時,密碼必須是強密碼,即同時包含大寫字母、小寫字母和數(shù)字。否則,會報錯:Unknown Errors while creating Sandbox Tester, Please check Error Log, email=xxx。
- 請求商品的
SKProductRequest實例的delegate必須在離開頁面時viewDidDisappear設置為nil,因為即使你已經(jīng)離開了頁面,蘋果依然會嘗試向SKProductRequest的代理發(fā)送消息,然后App就崩潰了。 - 必須在
AppDelegate中聲明遵守SKPaymentTransactionObserver協(xié)議、實現(xiàn)協(xié)議方法updatedTransactions、并在didFinishLaunching方法中加入如下代碼SKPaymentQueue.default().add(self)。
這一行代碼是將AppDelegate作為內(nèi)購隊列的監(jiān)聽者。這樣,即使你離開了內(nèi)購頁面、即使上次支付未完成等情況,都能夠在這里得到及時的處理。
如果不在這里這么做,可能的報錯有:
This In-App purchase has already been bought. It will be restored for free.
如果需要在內(nèi)購頁面上實現(xiàn)交互,可以也在內(nèi)購界面上添加監(jiān)聽,并進行交互上面適當?shù)奶幚怼5珶o路如何,不能省掉AppDelegate中的那部分。
內(nèi)購接入核心流程
以下是我實際接入中使用的代碼。
請求商品
獲取商品id
獲取商品id,這個可以請求服務器,也可以保存在App上。我的App是用的后者。
首先創(chuàng)建一個名為ProductID的plist文件。文件內(nèi)容是一個Array,里面是保存的商品id字符串。
這里需要說明的是,商品id,即product identifier,就是你在iTunes Connect 內(nèi)購列表頁面上看到的商品id,不需要再拼接包名bundle identifier。
// Get product id from plist.
func predefinedProductIdentifiers() -> [String]? {
guard let url = Bundle.main.url(forResource: "ProductID", withExtension: "plist") else { return nil }
guard let productIdentifiers = NSArray(contentsOf: url) as? [String] else { return nil }
return productIdentifiers
}
驗證商品id
請求蘋果服務器驗證我們提供的商品id是否有效。不過記得要把內(nèi)購Controller設置為遵循SKProductsRequestDelegate協(xié)議,否則無法收到通知。
func validateProductIdentifers(with productIdentifiers: [String]) {
guard let set = NSSet(array: productIdentifiers) as? Set<String> else { return }
let productRequest = SKProductsRequest(productIdentifiers: set)
request = productRequest // 將request強引用,避免在完成前被提前釋放;不過也務必在離開頁面后,將request.delegate設置為nil
productRequest.delegate = self
productRequest.start()
SVProgressHUD.setDefaultStyle(.dark)
}
在協(xié)議中處理商品
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("received product response")
products = response.products // 將獲取到的商品存儲在界面實例變量,再次購買時不需要重復請求商品
// 下面是繼續(xù)進行支付請求
請求支付
在獲取了有效商品后,就可以用商品創(chuàng)建SKPayment,并將payment實例加入蘋果支付隊列中。
func requestPayment(with product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
處理支付
這個步驟核心的點就是創(chuàng)建并添加SKPaymentTransactionObserver。
AppDelegate添加observer
- 在AppDelegate聲明遵循
SKPaymentTransactionObserver協(xié)議。 - 實現(xiàn)協(xié)議方法,實現(xiàn)后實現(xiàn)者就可以作為observer。
*// MARK: - SK Product Request Delegate*
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchasing:
DispatchQueue.main.async {
SVProgressHUD.show(withStatus: NSLocalizedString("訂單處理中", comment: "請求支付提示" ))
}
case .deferred:
SVProgressHUD.show(withStatus: NSLocalizedString("訂單處理中", comment: "請求支付提示" ))
case .purchased:
// 購買成功,此處需要發(fā)送玩家道具、保存憑證等動作。
// 當然交易完畢后,一定要手動關閉
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
// 交易失敗,也要關閉交易
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
// 恢復訂單
SKPaymentQueue.default().finishTransaction(transaction)
default:
print("other situation")
SKPaymentQueue.default().finishTransaction(transaction)
}
}
}
- 在
didFinishLaunchingWithOptions方法中添加觀察者:
SKPaymentQueue.default().add(self)
其他界面添加observer
假如用戶一直呆在內(nèi)購界面等待結(jié)束,我們很可能需要在內(nèi)購界面也要一套前端的展示機制。跟AppDelegate的流程一樣,我們將內(nèi)購界面也作為SKPaymentTransactionObserver添加到隊列中。這樣,內(nèi)購界面也能收到通知了。
當內(nèi)購界面可用的時候,就可以讓內(nèi)購界面去處理一些前端交互,比如彈出窗口,關閉當前界面等。
本人初學,有不當或錯漏之處,感謝斧正!