年前由于需求的原因做了一次苹果的内购:实现了普通购买 和 订阅功能; 订阅功能有个首月特惠的需求,当时并不知道是通过商品的 推介促销优惠 去设置首月特惠;使用了促销优惠,然后让服务器配合做了很多没必要的工作,做出来的效果也不是首月特惠,最后才发现只需要在 推介促销优惠 里面设置第一个月的价格就可以实现首月特惠;为了避免以后再走弯路,在这里简单记录一下。
一、内购准备
在iTunes Connect 填写银行账号信息;申请沙盒环境的测试账号,用于测试使用。
二、配置商品
1、配置普通商品没有什么要注意的,按流程走就是。
2、订阅商品的配置需注意:如果验证、续费都是在服务器完成,需要在iTunes Connect App信息中配置用于接收App store 通知的URL,沙盒环境和生成环境各一个
三 、执行购买
1、向苹果服务器请求商品
/// 请求对应的商品
/// - Parameter ProductId: 商品id
func reuqestProduct(ProductId:String){
if SKPaymentQueue.canMakePayments() {
self.ProductId = ProductId
reuqestProductData(type: ProductId)
}
}
private func reuqestProductData(type: String) {
let product = [type]
let set = NSSet(array: product as [AnyObject])
let request = SKProductsRequest(productIdentifiers: set as! Set<String>)
request.delegate = self
request.start()
}
2、在SKProductsRequestDelegate代理中拿到商品
///SKProductsRequestDelegate商品请求回调
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if response.products.count == 0 {
if let dlg = delegate {
dlg.buyFailed(type: .notProduct)
}
}
var prod: SKProduct?
for pro in response.products {
if pro.productIdentifier == self.ProductId {
prod = pro
}
}
if let produ = prod {
///发起下单请求
creteOrder(product: produ)
}
}
3、向服务器下单,生成orderID并存储到本地,向苹果发起内购
/// 向服务器下单 (网络请求的代码就不拿出来了)
func creteOrder(product : SKProduct){
orderId = ""
// ApiProvider.requestData(SSApi.mood_buySkin(),model:orderModel.self,alert: false) { [self] (type, msg, res) in
// if type == .success {
///发起内购之前存储商品id 用于丢单情况下的二次验证
let orderid = "请求得到的orderId"
UserDefaults.setValue(orderid, forKey: orderIdMark)
orderId = orderid
///向苹果服务器发起内购
let payment = SKMutablePayment(product: product)
SKPaymentQueue.default().add(payment)
// } else {
// }
// }
}
4、内购成功,向自己服务器验证支付是否成功
///SKPaymentTransactionObserver购买回调
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for tran in transactions {
switch tran.transactionState {
case .purchased:
///购买成功
prPayverifyPurchase(transaction: tran)
case .purchasing:
/////商品添加进列表
break
case .restored:
///已经购买了
payFailed(transaction: tran)
case .failed:
payFailed(transaction: tran)
default:
payFailed(transaction: tran)
break
}
}
}
/// 支付服务器验证1
/// - Parameters:
/// - transactionId: 凭证
func prPayverifyPurchase(transaction:SKPaymentTransaction){
if orderId.count <= 1{
orderId = (UserDefaults.value(forKey: orderIdMark)) as! String
}
if orderId.count <= 1 {
payFailed(transaction: transaction)
return
}
/// 获取收据
let receiptUrl = Bundle.main.appStoreReceiptURL
let data = NSData(contentsOf: receiptUrl!)
if let _ = transaction.transactionIdentifier,let str = data?.base64EncodedString(options: .endLineWithLineFeed){
validationOrder(orderId: orderId, receipt: str, transaction: transaction)
} else {
payFailed(transaction:transaction,finishTransaction: true)
}
}
/// 支付完成向自己服务器验证2
/// - Parameters:
/// - orderId: 自己服务器生成的订单
/// - receipt : 收据
/// - transaction:SKPaymentTransaction
func validationOrder(orderId:String,receipt:String,transaction:SKPaymentTransaction){
///发起验证网络请求 transactionId 为普通商品购买的凭证id; originalTransactionId 为订阅商品购买的凭证id
// ApiProvider.requestNotDolls(SSApi.pay_vildSerives(orderCode:orderId, receiptData: receipt, transactionId:transaction.transactionIdentifier,originalTransactionId: transaction.original?.transactionIdentifier),alert: false) {[self](type, msg, res) in
if true {
if let dlg = delegate {
dlg.buySuccess()
}
///验证成功 移除凭证 和临时存储的商品
SKPaymentQueue.default().finishTransaction(transaction)
UserDefaults.setValue(nil, forKey: orderId)
} else {
payFailed(transaction:nil,finishTransaction: false)
}
// }
}
5、内购成功并且验证成功 需要关闭支付事务finishTransaction;内购失败也需要finishTransaction;内购成功并且验证失败不要关闭等下次验证成功再关闭
SKPaymentQueue.default().finishTransaction(tran)
四、 漏单处理
漏单主要处理 钱扣了 ,服务器没有发货的情况,也就是内购成功,向自己服务器验证失败的情况。
1、 存储交易订单的transactionIdentifier和自己服务器生成的orderId.
2、app启动会重新执行func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]),在这个方法里面检测orderid 是否与 transactions的transactionIdentifier 对应,对应说明有漏单的情况 ,需要向自己的服务器发起验证.
3、漏单并未重启app,在进入支付页面的时候发起检测,检测SKPaymentQueue.default().transactions是否有订单与存储的orderId匹配,有的话就发起验证。
注意:为什么需要orderid与transactionIdentifier 对应?
订阅商品会在续期的时候触发客服端的func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]的方法,如果不做绑定处理,很有可能将订阅续期通知当做漏单处理,又从新购买一次了。
对于订阅这种通知的处理我是直接finishTransaction了,如果大家有更好的处理可以留言讨论一下。
五、 测试
在测试环境下用最开始申请的沙盒账号购买即可。
1、沙盒环境下 自动续费时间缩短了,1周对应3分钟,1月对于5分钟;续费时间到了客服端会执行func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]方法
2、沙盒环境购买订阅商品后,可以在手机的 设置 -> APPStore -> 沙盒账号 -> 管理 -> 取消订阅 进行退订,退订的同时 服务器会收到退订通知。
六、 内购审核
1、普通商品的审核
补全商品的审核信息, 上传一张商品的截屏,和app一起提交给苹果审核
2、订阅商品的审核
额外需要在 APP描述里面添加一些连续包月会员说明;按照苹果给出的规则需要指明商品名、价格、取消地方等;可以参考其它APP怎么写的。
这里贴出一个模板
【连续包月会员说明】
1、服务名称:连续包月会员
2、价格:6元/月
3、购买连续包月会员的账号,会在每个月到期前24小时,自动在iTunes账号扣费并延长1个月的会员有效期。
4、如需取消订阅,请手动打开苹果手机”设置“ --> 进入"iTunes Store 与 App Store" --> 点击"Apple ID",进入”账户设置“页面,点击”订阅“,选择连续包月会员取消订阅即可。如未在订阅期结束的至少24小时前关闭订阅,此订阅将会自动续订。
5、自动续费服务协议:vctone.com/automaticRe…
6、隐私协议:vctone.com/userAgree.h…
末尾:
每一步没有详细的写出来,但是需要注意的地方都写下来了,希望让看到的人少走一点弯路。当然订阅不止这么简单,比如 自动订阅的归属问题;自动订阅更改时长问题;还有优惠卷、促销商品的使用都等着我们一步步去摸索。
贴一个简单的dome:github.com/CodePerryH/…