内购是移动端一值在讨论的话题,每次上架内购功能要做好被拒的准备,小萌在2年前也做过内购,是OC版本,回想那段往事一把血泪呀,被拒了无数次才把内购功能送上AppStore
小萌之前的OC内购,中间也经历了不少的困难,详情请看苹果内购审核那些被拒的原因,不过之前是用OC封装的,现在小萌的主要语言是Swift,闲暇之余做了Swift5.X内购StoreKit原生的封装,特别齐全,可以直接使用哦Swift版StoreKit的内购Demo
语言: Swift5
Xcode: 11.4.1
环境: Mac CataLina 10.15.4
注意️: 不建议大家升级,不能传送大文件,小萌升级之后上传AppStore就没有成功过
一、内购的流程
网上有很多,也是各种各样,不过小萌对下面的这张图很感兴趣,好好的研究了一下这张图,看完之后茅塞顿开呀
总结内购流程:
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形式返回。
二、审核流程
网上特别多,小萌在这里就不分享了,小萌主要分享一下,上架的时候注意事项
三、创建产品流程
小萌在创建产品的之后提交AppStore上,因为创建的产品不符合要求,也被拒绝过一次,所以在这里分享一下内购产品创建需要注意的地方
第一步:填写参考名称和内购产品的ID
第二步:选择定价,保持一致
第三步:填写说明和描述,说明可以随意填,但是一定要有意义,描述一定是消耗多少人民币获得多少自己定义的币
第四步:App Store推广 无特殊需要不用传图片
第五步:截屏一定包含内购信息,否则会审核不通过,审核备注里面自己的测试账号和密码写进去,方便测试人员测试哦
四、注意事项
注意️:
1、别提示是否登录,如果没有登录,点击直接跳到登录界面即可,一定不要询问哦
2、别提示是否购买,直接购买即可,美国就是这么直接呀,不买不要点击嘛,增加审核通过率
3、充值说明一定要有,并且声明仅限iOS系统使用,其他系统无权使用
4、创建项目的时候App Store 推广那里的图片是可选的,不必要选哦,如下图⬇️所示
5、还有其他需要注意的地方,详情看苹果内购审核那些被拒的原因,一次又一次被拒的血泪!
五、苹果的二次验证
第一次:交易完成后在移动端验证,返回正确的数据,验证完毕
第二次:小萌交给了后台去做验证,传了Base64位编码数据给后台
六、DemoSwift版StoreKit的内购Demo
小萌已经做了Swift的StoreKit原生的封装,下载直接可以使用
主要代码:
//交易完成在这里完成苹果验证
func completeTransaction(transaction:SKPaymentTransaction){
// let environmentStr = String(data: transaction, encoding: .utf8)
// 验证凭据,获取到苹果返回的交易凭据
// appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
let receiptURL = Bundle.main.appStoreReceiptURL
// 从沙盒中获取到购买凭据
let receiptData = NSData(contentsOf: receiptURL!)
// 发送网络POST请求,对购买凭据进行验证
var urls : URL?
let receiptURLString = receiptURL?.absoluteString
if receiptURLString?.contains("sandbox") == true { //判断是沙盒路径还是正式环境
urls = URL(string: ITMS_SANDBOX_VERIFY_RECEIPT_URL)
}else{
urls = URL(string: VERIFY_RECEIPT_URL)
}
// 国内访问苹果服务器比较慢,timeoutInterval需要长一点
var requset = URLRequest(url: urls!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
requset.httpMethod = "POST"
// 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据
// 传输的是BASE64编码的字符串
let encodeStr = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithLineFeed)
let payload = NSString(string: "{\"receipt-data\" : \"" + encodeStr! + "\"}")
// print(payload)
let payloadData = payload.data(using: String.Encoding.utf8.rawValue)
requset.httpBody = payloadData;
weak var weakSelf = self //避免循环引用
let result = URLSession.shared.dataTask(with: requset) { (data, response, error) in
if data == nil{
print("验证失败")
return
}
if response == nil {
print("验证失败")
return
}
let dict = try? JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
if dict != nil {
// print(dict!)
}
NotificationCenter.default.post(name: NSNotification.Name(rawValue: ProductPurchasedNotification), object: nil)
weakSelf?.finishTransaction(transaction: transaction)
}
result.resume()
}
这是验证流程,具体封装代码可以下载Swift版StoreKit的内购Demo,直接使用项目中的StoreObserver.swift文件即可,单例模式设计,很简单哦