iOS 蘋果內購 IPA

最近寫的兩個項目都涉及到了購買虛擬商品,根據蘋果的要求,所以項目中集成了蘋果內購功能。 網上關于蘋果內購的資料很全面,寫在此處只不過對于項目的一些總計而已。廢話不多說,直接開始。

一、首先


image.png

1.登錄 AppStoreConnect 選擇 協議、稅務和銀行業務選項。


image.png

2.協議、稅務、銀行業務、聯系信息這些如實填寫就好。其中需要注意的是,地址和聯系人、銀行看準是讓填漢字還是英文。稅務選擇美國,銀行也是選擇的不過都是英文的,需要將銀行卡的開戶行翻譯成英文然后去列表里找到對應的總行或支行即可。
image.png

二、設置商品

1.回到 APP 選項,找到要添加內購的應用選擇管理。


image.png

image.png

添加新的內購產品,這里要選擇產品類型,主要類型有以下幾種,根據自己需要選擇對應類型即可。


image.png

填寫內購產品的名稱,如“包月會員” “會員充值”等, 后面產品ID為自己設置的一串數字,用來標識該產品的唯一性,如果這個產品下架了或是沒有這個產品了,這個產品ID也會失效,再創建新的產品這個ID值也不能再用了。
image.png

image.png

這里的審核信息是必傳項,截圖需要是你在項目中測試內購支付的截圖。這里測試的話需要用到一個沙河測試賬號。賬號怎么來的,看下面。


image.png

三、 沙盒測試賬號

選擇用戶和訪問->沙盒->添加測試員

選擇用戶和訪問

image.png

新測試員,按圖填寫信息即可,其中電子郵件隨便填即可,不一定非得是正確的郵箱,只要是是郵箱格式正確即可,如(aabbcc@163.com),不過一定要記住郵箱地址和密碼,在添加好測試員之后,項目里點擊支付喚起蘋果內購彈框后需要填寫的就是這個郵箱和密碼。而不是自己Apple ID 的郵箱。填寫過之后,可以在手機(iPhone)上設置里 -> App Store 選項里可以看到沙盒賬戶,可以對該賬號進行退出登錄等操作。

image.png
image.png

四、以上信息都配置好之后,就是代碼了。

  1. 個人建議將蘋果內購寫成工具類,這樣以后再遇到支付項目,可以直接拿來用。

    1.1 先引入 import StoreKit
    1.2 聲明一個代理
     protocol ZIAPManagerDelegate: class {
         /**
         支付成功回調
         */
         func rechargeSuccess(rechargeInfo: [String: AnyObject])
          /**
         支付失敗回調
         */
         func rechargeFailed()
    }
    
    class ZIAPManager: NSObject {
       // MARK: - Public
       public weak var managerDelegate: ZIAPManagerDelegate?
        /**
        添加/移除支付觀察者(直接在支付 VC 里調用即可,千萬記得要移除觀察者)
         */
       public func addPaymentObserver() {
              SKPaymentQueue.default().add(self)
       }
    
       public func removePaymentObserver() {
               SKPaymentQueue.default().remove(self)
       }
    
        /**
            購買商品,傳入商品id 和 商品價格
         */
        public func buyWithProductId(_ pId: String, pPrice: String) {
              self.productId = pId
              self.pPrice = pPrice
              // 判斷程序是否支持蘋果內購
              if SKPaymentQueue.canMakePayments() {
                    SVProgressHUD.show(withStatus: "支付中...")
                     self.getProductData(pId)
              } else {
                     SVProgressHUD.showMessage("不允許程序內付費")
             }
         }
    
       // MARK: - Private Method
       private func getProductData(_ productId: String) {
             let set = NSSet.init(object: productId)
             let request = SKProductsRequest(productIdentifiers: set as! Set<String>)
             request.delegate = self
             request.start()
      }
    
      // MARK: - Property Private
      fileprivate var productId: String = ""
      fileprivate var pPrice: String = ""
      fileprivate var retryCount: Int = 0
      fileprivate var verifySuccess: Bool = false
      /**
      AppStoreUrl
      /// 沙盒測試驗證地址
      public let SANDBOXUrl: String = "https://sandbox.itunes.apple.com/verifyReceipt"
      /// 正式驗證地址
      public let AppStoreUrl: String = "https://buy.itunes.apple.com/verifyReceipt"
      */
      fileprivate var verifyAddress: String = AppStoreUrl
      
      // MARK: - Sigleton
      private static let instance = ZIAPManager()
      class var shareInstance: ZIAPManager {
           return instance
      }
      private override init() {}
      }
    

    1.2 實現 SKProductsRequestDelegate 代理方法

 extension ZIAPManager: SKProductsRequestDelegate {

  func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    let products = response.products
    guard products.count > 0 else { return }
    var requestProduct: SKProduct?
    for pro in products {
        if pro.productIdentifier == self.productId {
            requestProduct = pro
        }
    }
    if let pro = requestProduct {
        let payment = SKPayment(product: pro)
        SKPaymentQueue.default().add(payment)
    }
}

func requestDidFinish(_ request: SKRequest) {
    SVProgressHUD.show(withStatus: "支付中...")
}

func request(_ request: SKRequest, didFailWithError error: Error) {
    SVProgressHUD.dismiss()
}

}

1.3 實現 SKPaymentTransactionObserver 回調方法

extension ZIAPManager: SKPaymentTransactionObserver {

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    SVProgressHUD.dismiss()
    for transaction in transactions {// 當交易隊列里面添加的每一筆交易狀態發生變化的時候調用
        switch transaction.transactionState {
            case .purchased:
                print("支付成功")
                SVProgressHUD.show(withStatus: "支付中...")
                // 本地驗證憑證
                verifyPurchaseWithPaymentTrasaction()
                finishTransaction(queue, transaction: transaction)
            case .failed:
                    print("支付失敗")
                    var errMsg = "支付失敗"
                    if let error = transaction.error {
                        if error.localizedDescription.length > 0 {
                            errMsg = error.localizedDescription
                        }
                    }
                SVProgressHUD.showMessage(errMsg)
                finishTransaction(queue, transaction: transaction)
            case .restored:
                print("已購買過此商品")
                SVProgressHUD.showMessage("已購買過此商品")
                finishTransaction(queue, transaction: transaction)
            case .deferred:
                print("延遲處理")
            case .purchasing:
                print("商品添加進列表")
                SVProgressHUD.show(withStatus: "支付中...")
            default: break
        }
    }
}

private func finishTransaction(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction) {
    SKPaymentQueue.default().finishTransaction(transaction)
}

/// 重復提交驗證
private func retryVerify() {
    if self.retryCount < 3 && self.verifySuccess == false {
        verifyPurchaseWithPaymentTrasaction()
        self.retryCount += 1
    }
}

/// 驗證購買
private func verifyPurchaseWithPaymentTrasaction() {
    if let url = Bundle.main.appStoreReceiptURL {
        do {
            let receiptData = try Data(contentsOf: url)
            // 發送網絡POST請求,對購買憑據進行驗證
            if let sandBoxUrl = URL(string: verifyAddress) {
                var urlRequest = URLRequest(url: sandBoxUrl, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 30.0)
                urlRequest.httpMethod = "POST"
                let encodeStr = receiptData.base64EncodedString(options: .endLineWithLineFeed)
                let payload = "{\"receipt-data\" : \"\(encodeStr)\"}"
                urlRequest.httpBody = payload.data(using: .utf8)
                let task = URLSession.shared.dataTask(with: urlRequest, completionHandler: { [weak self] (data, response, error) in
                    guard let strongSelf = self else { return }
                    if error == nil {
                        do {
                            if let dict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: AnyObject] {
                                print("支付收據驗證結果===\(dict)")
                                if let status = dict["status"] as? NSNumber {
                                    if status.intValue == 0 {
                                        strongSelf.verifySuccess = true
                                        strongSelf.retryCount = 0
                                        print("支付收據驗證成功")
                                        if let delegate = strongSelf.managerDelegate {
                                            // 這里可以將蘋果返回的票據證明傳給自己的服務器,由自己的服務驗證是否支付成功,也可以不走服務器驗證。走服務器驗證是為了更加準確。
                                            delegate.rechargeSuccess(rechargeInfo: ["receipt-data": encodeStr])
                                        }
                                    } else {
                                        // 這個 21007 錯誤碼判斷,是因為默認驗證地址是AppStore線上驗證地址,所以在測試的時候會報這個錯誤,所以要將驗證地址改為測試線。也可以自己在測試的時候寫測試地址,上線時候改為線上地址,就不會報這個錯誤了。
                                        if status.intValue == 21007 {
                                            strongSelf.verifyAddress = SANDBOXUrl
                                            strongSelf.verifyPurchaseWithPaymentTrasaction()
                                        } else {
                                            print("支付收據驗證失敗")
                                            if let delegate = strongSelf.managerDelegate {
                                                delegate.rechargeFailed()
                                            }
                                        }
                                    }
                                }
                            }
                        } catch let error {
                            print(error)
                        }
                    } else {
                        print("支付收據驗證失敗")
                        strongSelf.verifySuccess = false
                        strongSelf.retryVerify()
                    }
                })
                task.resume()
            }
        } catch let error {
            print(error)
        }
    }
 }
}

好了,以上就是關于蘋果內購的全部流程。僅供參考。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容