一.简介
什么是IAP,即in-app-purchase。(虚拟商品如:课程、视频、音乐等数字产品只能走apple 的内购),苹果要扣除30%的销售额,再扣除一点相关的交易税,用户到手将不到7成。
官方参考文档
内购流程
1.获取内购列表(从App内读取或从自己服务器读取)
2.App Store请求可用的内购列表
3.向用户展示内购列表
4.用户选择了内购列表,再发个购买请求,收到购买完成的回调(购买完成后会把钱打给申请内购的银行卡内)
5.购买流程结束后, 向服务器发起验证凭证以及支付结果的请求
6.自己的服务器将支付结果信息返回给前端并发放虚拟产品
7.服务端的工作比较简单,分4步:
7.1.接收ios端发过来的购买凭证。
7.2.判断凭证是否已经存在或验证过,然后存储该凭证。
7.3.将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
7.4.如果需要,修改用户相应的会员权限。
7.5.考虑到网络异常情况,服务器的验证应该是一个可恢复的队列,如果网络失败了,应该进行重试。
简单来说就是将该购买凭证用Base64编码,然后POST给苹果的验证服务器,苹果将验证结果以JSON形式返回。
二、流程
1.登录苹果开发者网站新建一个App应用:
2.填写协议、税务、银行信息:
3.创建内购商品:
(1).商品名称根据你的消费道具的实际意义来说明,比如“100颗宝石”,“100金币”等。
(2).产品ID是比较重要的,由项目自定义,只要唯一即可,我一般都是用App的bundleID加一个后缀来表示,这样既跟项目关联又具有唯一性。
(3).价格等级的话“查看价格表”中有对应的说明,可以对照着表中每个国家的货币价格与等级来选择。
4.添加沙盒测试者
沙盒测试环境下苹果不会抽成,购买成功后直接返回商品。
appid 需要时从未与apple id 从未关联过的账号
appid 可以是任何有效的邮箱地址
5.内购代码封装IapManager.swift:
//
// IapManager.swift
// tai_chi
//
// Created by vincent on 2019/10/9.
// Copyright © 2019 vincent. All rights reserved.
//import Foundation
import StoreKit//内购协议
@objc protocol IapManagerProtocol {//商品请求结果回调func productsResponse(_ response:SKProductsResponse?,error:Error?)//交易成功回调func completedTransaction(_ transaction:SKPaymentTransaction)//交易失败回调@objc optional func transactionFail(transaction:SKPaymentTransaction)}//内购管理
class IapManager : NSObject,SKProductsRequestDelegate,SKPaymentTransactionObserver {private static var instance:IapManager?var delegate:IapManagerProtocol?var requestResponse:SKProductsResponse?var requestErr:Error?override init() {super.init()SKPaymentQueue.default().add(self)}//静态方法static func shared() -> IapManager{if instance == nil {instance = IapManager()}return instance!}//判断app 是否允许apple payfunc canPayments() -> Bool{return SKPaymentQueue.canMakePayments()}//请求商品//productIds 内购商品id 集合func requestProducts(productIds:Set<String>){let request = SKProductsRequest(productIdentifiers: productIds)request.delegate = selfrequest.start()}//购买商品func addPayment(_ payment:SKPayment){SKPaymentQueue.default().add(payment)}//监听商品返回信息,然后使用返回的商品信息发起购买请求func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {print("--------------收到产品反馈消息---------------------")self.requestResponse = response}func request(_ request: SKRequest, didFailWithError error: Error) {print("--------------收到产品反馈错误消息---------------------")print(error)self.requestErr = error}func requestDidFinish(_ request: SKRequest) {print("--------------反馈消息结束---------------------")self.delegate?.productsResponse(self.requestResponse, error: self.requestErr)}//获取内购成功后apple server 返回给客户端的数据func receiptData() -> NSData? {let url = Bundle.main.appStoreReceiptURLvar data:NSData? = nilif url != nil {data = NSData(contentsOf: url!)}return data}//监听购买结果func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {for transaction in transactions{if transaction.transactionState == .purchasing {print("--------------updatedTransactions---------------------purchasing")}else if transaction.transactionState == .purchased {print("--------------updatedTransactions---------------------purchased")SKPaymentQueue.default().finishTransaction(transaction)self.delegate?.completedTransaction(transaction)}else if transaction.transactionState == .failed {print("--------------updatedTransactions---------------------failed")SKPaymentQueue.default().finishTransaction(transaction)self.delegate?.transactionFail?(transaction: transaction)}else if transaction.transactionState == .restored {print("--------------updatedTransactions---------------------restored")SKPaymentQueue.default().finishTransaction(transaction)}else if transaction.transactionState == .deferred {print("--------------updatedTransactions---------------------deferred")}}}deinit {SKPaymentQueue.default().remove(self)}
}
6.交易相关代码:
ViewController
func completedTransaction(_ transaction: SKPaymentTransaction) {let data = IapManager.shared().receiptData()if data != nil {//获取交易成功凭证let base64Str = (data! as NSData).base64EncodedString()//本地记录充值成功 以防后台请求失败时下次进入app 时再网络请求UserDefaults.standard.set(base64Str, forKey: "iap_transaction_key")//网络通知服务器和苹果服务器验证是否交易成功 并给相关账号充值}}
AppDelegate 检测是否有漏单请求,并把漏单通知服务器
//用户内购成功后需要通知服务器,此过程可能网络请求失败//检测是否有充值还未提交给服务器
if let pams = UserDefaults.standard.dictionary(forKey:"iap_transaction_key" ) {//网络通知服务器}
三.测试:
1.沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。
2.请务必使用真机来测试,一切以真机为准。
3.项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。
4.退成App Store 账号:
测试截屏如下: