一、布景
2021 年 WWDC,在 iOS 15 体系上推出了一个新的 StoreKit 2 库,该库选用了彻底新的 API 来处理运用内购买问题。
- Meet StoreKit 2 – WWDC21 – Videos – Apple Developer:要点内容:Storekit 2 API 介绍和代码演示,以及 appAccountToken
- Manage in-app purchases on your server – WWDC21 – Videos – Apple Developer:要点内容:JWS 签名买卖的服务器验证,新服务器 API,新服务器告诉,沙盒测验的新功用
- Support customers and handle refunds – WWDC21 – Videos – Apple Developer:要点内容:用来支撑用户和处理退款的 Storekit 2 API 以及服务器 API
二、物料
名词 | 解释 | |
---|---|---|
IAP | In-App Purchase:运用内购买 | |
StoreKit 1 | 原始的运用内购买 API | Choosing a StoreKit API for In-App Purchase |
StoreKit 2 | 运用内购买 API | Choosing a StoreKit API for In-App Purchase |
三、StoreKit 1 存在的问题
- 苹果后台能否查看到退款的订单详情?
不能。只能苹果处理退款后发告诉给咱们的服务器,奉告产生了一笔退款
- 耗费性、非耗费性、非续期订阅、主动续订能不能在沙盒环境测验退款?
不能。体系没供给这种测验方法。
- 能够将用户反馈的苹果收据里的 orderID 与详细的买卖进行相关吗?
不能。
- 服务器端 Receipt 收据解析后,没有包括 orderID 信息,所以无法直接相关他们之间的联络。
- 不支撑运用苹果收据里的 orderID 去苹果服务器查询买卖信息,没有供给这个 API(StoreKit 2 出来后支撑去查询 StoreKit1 的买卖了,developer.apple.com/documentati… )。
- 在开发过程中,无法直接相关 transaction 与 orderID 之间联络,尽管有一个 applicationUserName 字段,能够存储一个信息。可是这个字段是不是 100%靠谱,在某些情况下会丢掉存储的数据。
- 无法主动的去苹果服务器获取买卖前史记载,退款信息。无法依据用户供给的苹果收据里的 orderID 主动相关上咱们当时已知的订单。
- 现在 sk1 的 skproduct 无法区别耗费品,非耗费品,订阅产品,非接连订阅产品。
- sk1 存在行列监听,每次购买需求经过行列监听对应的购买状况的变更,一切的 transaction 的回调都在监听傍边,欠好区别哪些是补单的 transaction 和正常购买的 transaction。
四、StoreKit v2 新特性
StoreKit 2 新特性首要包括三部分:
- StoreKit 2:关于在 App 里 API 的更新和改动,包括运用内更改订阅、退款等;
- Server to Server:苹果服务器与开发者服务器之间的通讯,包括苹果告诉、开发者主动恳求苹果服务器、新的验证收据流程等;
- Sandbox Test:关于沙盒测验环境相关的更新,还有一些注意事件等。
五、StoreKit 2 API
StoreKit 2 首要的更新有这几个:
- 运用 swift 新特性开发
- 更新收据和买卖(数据格局和字段变更)
- 更多订阅类型的接口
- 相同的 StoreKit 框架
5.1 只支撑 Swift 开发
StoreKit 2 运用了 Swift 5.5 的新特性进行开发,彻底修改了获取产品、建议买卖、办理买卖信息等接口 API 的完成方法。swift.org/blog/
例如获取产品方法语法不同:
原始获取产品方法
// 1. 恳求产品
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
request.delegate = pipoRequest;
[request start];
// 2. 完成 SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
// success
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error API_AVAILABLE(ios(3.0), macos(10.7))
{
// failed
}
新获取产品方法
//获取产品
letproducts=tryawaitProduct.products(for:productIDs)
//购买产品
funcpurchase(_product:Product)asyncthrows->Transaction?{
//Beginapurchase.
letresult=tryawaitproduct.purchase()
switchresult{
case.success(letverification):
lettransaction=trycheckVerified(verification)
//Delivercontenttotheuser.
awaitupdatePurchasedIdentifiers(transaction)
//Alwaysfinishatransaction.
awaittransaction.finish()
returntransaction
case.userCancelled,.pending:
returnnil
default:
returnnil
}
}
5.2 新 API
- 产品
- 购买
- 买卖信息
- 买卖前史
- 订阅状况
5.2.1 Product
- 新增了一些产品类型,订阅信息,这些字段信息在 StoreKit 1 里是没有的。
便利咱们运用的字段:
- 经过新增的 product type 咱们能够轻易的知道当时的产品是耗费品仍是订阅产品
- 针对于主动接连订阅的第一次购买优惠,咱们能够直接感知到当时的产品是不是用户的 Apple ID 下的第一次购买
举个例子:
某些 APP 会有会员订阅服务,那些服务会有 1 个月,3 个月,12 个月等的主动续期,一起还会有一些第一次购买的优惠,这个第一次购买的优惠便是首购优惠,而且这个优惠跟Apple ID
挂钩,跟 APP 内自己的账号体系无关,例如小马哥旗下产品,自有的账号体系是QQ 号 + 微信号
,那么咱们在之前是无法简略得判别你这个Apple ID
是否享受过首购优惠
了,究竟用户能够有多个QQ 号
,或许多个微信号
,在弹出苹果的购买页面前,咱们是不知道这个Apple ID
有没有享受过首购优惠的,会对用户产生误解,我在上一个页面还告诉我首个月只需 18 块钱,实际付出的时分为什么要 25 元了 ? 这个对用户的购买意愿肉眼可见是有下降的。
现在咱们就能够经过isEligibleForIntroOffer
这个特点,轻松又便利得提早拿到这些信息,对现已享受过的Apple ID
账号不展示这个优惠。
- 供给了新的获取产品接口
publicstaticfuncproducts<Identifiers>(foridentifiers:Identifiers)asyncthrows->[Product]whereIdentifiers:Collection,Identifiers.Element==String
- 供给了新的购买产品接口。其间购买产品时增加了一些可选参数
PurchaseOption
结构体,该结构体里有新增的特别重要的字段appAccountToken, 类似 SKPayment.applicationUsername 字段,可是 appAccountToken 信息会永久保存在 Transaction 信息内。
appAccountToken 字段是由开发者创建的;相关到 App 里的用户账号;运用 UUID 格局;永久存储在 Transaction 信息里。
PS:这儿的 appAccountToken 字段苹果的意思是用来存储用户账号信息的,可是应该也能够用来存储 orderID 相关的信息,需求将 orderID 转成 UUID 格局塞到 Transaction 信息内,便利处理补单、退款等操作。
publicfuncpurchase(options:Set<Product.PurchaseOption>=[])asyncthrows->Product.PurchaseResult
letuuid=Product.PurchaseOption.appAccountToken(UUID.init(uuidString:"uid")!)
//建议一笔购买之后,直接等候苹果的回来成果,无需在paymenqueue中等候transaction状况的更新。
//运用sk2建议的购买的订单的信息,在sk1一切的回调接口都不会得到相应的transaction的更新状况
letresult=tryawaitproduct.purchase(options:[uuid])
//demo
funcpurchase(_product:Product)asyncthrows->Transaction?{
//Beginapurchase.
letresult=tryawaitproduct.purchase()
switchresult{
case.success(letverification):
lettransaction=trycheckVerified(verification)
//Delivercontenttotheuser.
awaitupdatePurchasedIdentifiers(transaction)
//Alwaysfinishatransaction.
awaittransaction.finish()
returntransaction
case.userCancelled,.pending:
returnnil
default:
returnnil
}
}
- 处理验证 Transaction。体系会验证是否是一个合法的 Transaction,此时体系不再供给 base64 的 receip string 信息,只需求上传 transaction.id 和 transaction.originalID,服务器端依据需求挑选合适的 ID 进行验证。
funccheckVerified<T>(_result:VerificationResult<T>)throws->T{
//CheckifthetransactionpassesStoreKitverification.
switchresult{
case.unverified:
//StoreKithasparsedtheJWSbutfailedverification.Don'tdelivercontenttotheuser.
throwStoreError.failedVerification
case.verified(letsafe):
//Ifthetransactionisverified,unwrapandreturnit.
returnsafe
}
}
- 监听 Transaction 更新
funclistenForTransactions()->Task<Void,Error>{
returnTask.detached{
//Iteratethroughanytransactionswhichdidn'tcomefromadirectcallto`purchase()`.
forawaitresultinTransaction.updates{
do{
lettransaction=tryself.checkVerified(result)
//Delivercontenttotheuser.
awaitself.updatePurchasedIdentifiers(transaction)
//Alwaysfinishatransaction.
awaittransaction.finish()
}catch{
//StoreKithasareceiptitcanreadbutitfailedverification.Don'tdelivercontenttotheuser.
print("Transactionfailedverification")
}
}
}
}
针对 transaction 的更新,这个监听是让咱们监听:
- 这笔订单用户敞开了一笔购买,这笔订单在苹果那儿还没有得到成果,用户杀死 app 或许用户卸载了 app 并从头安装 app,这个时分咱们能够经过这个监听收到对应的 transaction 更新
- 用户建议一笔购买,这笔购买或许因为网络状况欠好的要素,在端上收到了失利的 transaction 回调,可是后续苹果发现这种 case,从头下发 transaction 到端上进行对应的验证。
5.2.2 Transaction History
供给了三个新的买卖(Transcation)相关的 API:
- All transactions:悉数的购买买卖订单,在 transaction 里面获取
- Latest transactions:最新的购买买卖订单。
- Current entitlements:一切当时订阅的买卖,以及一切购买(且未交还)的非耗费品。
依据能够购买的订阅产品、非耗费品能够过滤呈现已购买过的产品。
extensionTransaction{
publicstaticvarall:Transaction.Transactions{get}
publicstaticvarcurrentEntitlements:Transaction.Transactions{get}
publicstaticfunccurrentEntitlement(forproductID:String)async->VerificationResult<Transaction>?
publicstaticfunclatest(forproductID:String)async->VerificationResult<Transaction>?
publicstaticvarunfinished:Transaction.Transactions{get}
}
- 同步不同设备的购买记载。这个 API 能够替换 StoreKit 1 里面的恢复购买 API,调用该方法后,体系会弹出提示框要求输入 AppleID 帐号密码信息。
extensionAppStore{
publicstaticfuncsync()asyncthrows
}
5.2.3 Subscription status
订阅类型项目的状况,比方主动获取最新的买卖、获取更新订阅的状况,获取更新订阅的信息等。其间获取更新订阅的信息,能够获取更新的状况、品项 id、假如过期的话,能够知道过期的原因。(比方用户取消、扣费失利、订阅正常过期等。)获取的一切数据都是 JWS 格局验证。
5.2.4 show manager subscriptions
能够直接引发 App Store 里的办理订阅页面。
extensionAppStore{
@available(iOS15.0,*)
@available(macOS,unavailable)
@available(watchOS,unavailable)
@available(tvOS,unavailable)
publicstaticfuncshowManageSubscriptions(inscene:UIWindowScene)asyncthrows
}
5.2.5 request refund API
供给了新的建议退款 API,答应用户在开发者的 App 中直接进行退款恳求。用户进行恳求退款后,App 能够收到告诉、别的苹果服务器也会告诉开发者服务器。(沙盒环境也可进行退款测验了,可是 App Store 里还没敞开这个功用。)
extensionTransaction{
publicstaticfuncbeginRefundRequest(fortransactionID:UInt64,inscene:UIWindowScene)asyncthrows->Transaction.RefundRequestStatus
}
5.2.6 总结:
- StoreKit 2 库选用 Swift 5.5 版别最新特性重写,只支撑 Swift、iOS 15+,供给了一些新的 API 接口,导致新的付出流程会产生一些改动。
- 供给了获取买卖前史记载、可购买的产品列表(主动续期订阅以及非耗费品)信息
- 供给了获取订阅状况、办理订阅状况接口
- 支撑在 App 内建议退款
六、Server to Server
构建开发者服务器能够完成以下几个功用:
- 接收内购状况改动告诉
- 经过接口跟踪内购状况改动(获取订阅状况、获取一切的买卖前史记载)
- 随时验证用户的拜访权限(是否已购买,是否已退款等信息)
- 办理订阅状况
- 跟踪退款信息
6.1 Validate status with receipts
服务器端在经过/verifyReceipt
接口验证收据时,新 API 的数据结构也产生了改动。例如一致了购买时刻、过期时刻、原始购买时刻格局,新增了appAcountToken字段、内购类型字段、退款时刻、退款原因、促销优惠类型等。详细的能够参阅 Manage in-app purchases on your server – WWDC21 – Videos – Apple Developer 视频,或许 Validating Receipts with the App Store | Apple Developer Documentation
6.2 Check status with APIs
新增了一些 API,能够主动去获取订阅状况、买卖前史记载等等。详细能够参阅这个文档:App Store Server API | Apple Developer Documentation
- 获取订阅状况:get_all_subscription_statuses,只需求一个
originalTransactionId
参数,就能够获取用户订阅的各种状况 - 获取买卖前史记载:get_transaction_history,只需求用户任意一个买卖里的
originalTransactionId
即可获取一切的前史记载 - 在您的服务器收到消费恳求告诉后,将有关运用程序内消费品购买的消费信息发送到运用程序商店。(首要用于用户恳求退款后,奉告 App Store 购买的产品有没有被消费,用于评价是否需求退款)。send_consumption_information
- App Store Server API standards:JSON Web Signature (JWS)
6.3 Track status with notifications
当订阅状况产生改动时,Apple server 会主动告诉咱们的服务器,奉告产生了哪些改动。功用跟之前的版别相同,可是删除了一些状况,也新增了一些状况。
为了便利测验沙盒环境的退款告诉,App Store 能够为沙盒环境单独设置一个 server URL 配置。
6.4 购买流程改动
例如第一次购买订阅类型产品时,购买成功后,Apple server 会主动告诉 咱们的 server,奉告状况。此时咱们的 server 能够不必再去 Apple server 那儿验证了。及时以后想验证,也可经过/inApps/v1/subscriptions
接口随时去验证。
续订、账单宽限期、用户退款等操作时除了能够接受苹果的告诉外,也能够主动去恳求苹果服务器,获取最新的状况。例如在自己服务器宕机或许因为某种原因导致没有接收到苹果的告诉时,此时主动去恳求苹果服务器获取买卖前史记载,买卖状况信息,就发挥出了巨大的作用。
6.5 服务器搬迁升级到 JWS 格局
对于 StoreKit 2,苹果现已抛弃了用 receipt 收据验证逻辑,只需求供给买卖的 originalTransactionId 即可获取到完好的买卖信息。那么怎么从 StoreKit 1 升级到 StoreKit 2 呢?
- 上传 receipt 到咱们的服务器
- 拿着 receipt 去苹果服务器验证,并获取 originalTransactionId 信息
- 依据 originalTransactionId 去苹果服务器获取前史买卖记载,找到特定 originalTransactionId,Transaction
- 假如是订阅类型的产品,能够持续去获取订阅状况
6.6 Manager family sharing
办理家庭同享。现在苹果对 非耗费型 和 主动订阅 类型品项是支撑 家庭同享(family sharing),别的,苹果会回来一个字段 inAppOwnershipType 表明当时用户是否为购买品项的主用户。更便利的追踪用户的状况
6.7 Sandbox test
- 清楚沙盒账号购买记载
- 新增沙盒环境的回调 URL 配置
- 改动沙盒账号国家/地区
- 调整沙盒续订频率
- 安全性改良:testFlight 版别验证收据将失利
6.8 总结
- 以前只支撑 Apple server 单向发送状况改动的告诉给咱们自己的服务器,现在支撑主动去恳求 Apple server 获取前史买卖记载,订阅状况信息
- StoreKit 2 服务器端接口支撑了新的格局(JWS 格局)。
- 沙盒环境测验优化
七、Customer Support and Handle refunds(客服支撑和退款处理)
Support customers and handle refunds – WWDC21 – Videos – Apple Developer
7.1 How do I identify the in-app purchase made by this customer?
怎么识别用户的购买项目。当用户扣款了,可是没有收到产品时,用户会过来回来问题并供给了苹果邮箱里的扣款信息截图。
那么咱们的服务器端能够运用截图里的invoice order ID去恳求/inApps/v1/lookup/{customer_order_id}
这个接口查找到对应的 Transaction 信息。然后咱们再去验证是否购买成功、是否已恳求退款、是否需求补发产品等等。
https://developer.apple.com/documentation/appstoreserverapi/look_up_order_id
/inApps/v1/lookup/{customer_order_id}
7.2 How do I lookup this customer’s past refunds?
怎么查询该用户曩昔的退款信息?
现在的情况是,假如咱们服务器宕机了或许没有收到退款告诉,那么咱们是不知道用户是有没有进行退款的。尽管 StoreKit 2 供给了一个获取买卖记载的 API,可是假如经过该 API 来自己过滤退款的买卖,不是一个最好的完成方法。所以 Apple 新供给了一个 API 能够查到这个用户的一切退款记载订单,只需求任意的一个 original_transaction_id。
https://developer.apple.com/documentation/appstoreserverapi/get_refund_history
/inApps/v1/refund/lookup/{original_transaction_id}
7.3 How do I compensate subscribers for a service issue?
怎么补偿订阅者的服务问题?
比方说当服务器出问题了,为了款留用户/吸引更多用户,计划怎么给用户发补偿。开发者能够供给一个内购对兑码(一切的内购类型都能够),在苹果后台那里生成。然后让用户在 App Store 进行兑换,也能够在 App 里经过presentCodeRedemptionSheet()
接口调用,弹出体系的兑换界面:
7.4 How do I appease customers for outages or canceled events?
怎么安抚客户中断或取消的活动?
首要仍是想给用户一些福利,安抚用户。类似其他 App 里的签到一个月,能够赠送用户 1 个月会员等活动。但这种方法的过期时刻是由自己的服务器后端决议的。
这儿 Apple 也供给了一个接口,答应开发者一年有 2 次机会给订阅内购用户每次加 90 天免费补偿。也便是有主动订阅类型的 App,能够开发者主动在服务器给用户补偿(免费延伸)用户的订单时刻,每次最多是 90 天。
https://developer.apple.com/documentation/appstoreserverapi/extend_a_subscription_renewal_date
/inApps/v1/subscription/extend/{original_transaction_id}
7.5 App 内怎么办理订阅
同上面 5.2.4,供给了一个 showManageSubscriptions 接口,能够直接引发办理订阅页面。
extensionAppStore{
@available(iOS15.0,*)
@available(macOS,unavailable)
@available(watchOS,unavailable)
@available(tvOS,unavailable)
publicstaticfuncshowManageSubscriptions(inscene:UIWindowScene)asyncthrows
}
7.6 APP 内 怎么恳求退款
同上面 5.2.5 request refund API
八、新 API 购买流程
原始 API 购买流程
- 恳求 Product
- 建议购买
- 接收回调,成功后上传 receipt
- 服务器验证 receipt 并发货
新 API 购买流程
整个付出购买流程与原始 API 购买流程相同,区别是 3.1 步上传买卖信息时,不再上传 receipt/token 信息,上传 transaction_id 就能够了。服务器端能够经过 transaction_id 去苹果服务器获取买卖成果,不再需求运用 receipt/token 验证收据。
九、QA
9.1 怎么挑选新 API(StoreKit 2) 仍是原始 API(StoreKit 1)
Choosing a StoreKit API for In-App Purchase | Apple Developer Documentation
假如您的运用程序依赖于以下任何功用,您或许需求运用原始的运用程序内购买 API:
- 为批量采购计划(VPP)供给支撑。有关更多信息,请参阅设备办理。
- 供给运用程序预购。有关更多信息,请参阅为预购供给运用程序。
- 您的运用程序从高级版更改为免费版,反之亦然。
对现有和旧运用程序运用原始 API。
- 假如是一个全新的 App,只支撑 iOS 15+体系且支撑 Swift5 开发本钱不大,推荐运用 StoreKit 2,可是运用 StoreKit 1 也没有问题;
老 App:
- 从 SDK 视点来看,能够单独新增一个子仓支撑 StoreKit 2。即使现在不支撑,以后早晚也会支撑。
- 从宿主视点来看,要不要激进的引进新功用。引进新功用后会不会带来 OC 与 Swift 混编的问题,以及引进新功用后需求做好相应的 backup 计划。
9.2 客户端运用 StoreKit 1,服务器端升级到 StoreKit 2 的 API,能否这样运用?
能够。
对于后端来说,Apple Server API V1 和 Apple Server API V2 都能够运用,与客户端是否升级到 StoreKit 2 无关。
9.3 Native SDK 运用 StoreKit 2 后,交互流程会不会有什么改动?以及与服务器通讯流程会不会产生什么改动?
能够参阅上面新 API 购买流程图。
9.4 StoreKit 2 会不会呈现丢单的情况,以及怎么处理丢单问题?
仍是有或许呈现丢单的情况,例如购买成功了,Apple 回来成果时因为网络的原因导致失利了,可是此时会更容易处理。
处理办法:
- 冷启动时,可监听 Transaction 改动,收到成功的 Transaction 后从头上传,与 StoreKit 1 类似。
- 在购买时向 product 内 appAccountToken 字段里塞入事务方 orderID 相关的信息,当用户反馈扣款了可是没发货时,能够让用户供给 Apple 的 orderID,经过它能够直接去苹果服务器获取对应的 Transaction 信息,找到 Transaction.appAccountToken ,再给用户发货。(这儿能够做成一个主动化处理工具,只需求用户供给苹果的 orderID,就能够去查找对应的事务方 orderID 进行发货。)
9.5 购买成功可是未 finishTransaction,下次冷启动后还会从头下发 Transaction 吗?
会,与 StoreKit 1 功用相同,只是调用的接口不同。
9.6 从 StoreKit 1 升级到 StoreKit 2 后,能否看到之前运用 StoreKit 1 购买的产品?
能看到,相互兼容了。
9.7 针对运用 StoreKit1 的 app,是否能够放弃读取本地 receipt 的方法传给服务端来验证,直接选用 StoreKit2 的 transaction_id 传递给苹果服务端进行验证收据?
能够。
9.8 针对于苹果回来的 transaction 信息,咱们是否能判别这个 transaction 的状况信息对应的产品类型是哪种?
能够,storekit2 针对于 transaction 的回来信息傍边,清晰的告诉了咱们当时的产品类型是什么。针对于服务端对于耗费品和订阅产品的两套不同逻辑,咱们经过这个字段,就能够轻易的区别是否是订阅产品再恳求对应的接口
WWDC 视频
- Meet StoreKit 2 – WWDC21 – Videos – Apple Developer:要点内容:Storekit 2 API 介绍和代码演示,以及 appAccountToken
- Manage in-app purchases on your server – WWDC21 – Videos – Apple Developer:要点内容:JWS 签名买卖的服务器验证,新服务器 API,新服务器告诉,沙盒测验的新功用
- Support customers and handle refunds – WWDC21 – Videos – Apple Developer:要点内容:用来支撑用户和处理退款的 Storekit 2 API 以及服务器 API
- What’s new with in-app purchase – WWDC20 – Videos – Apple Developer:要点内容:退款告诉,家人同享和 SKAdNetwork
- In-App Purchases and Using Server-to-Server Notifications – WWDC19 – Videos – Apple Developer:要点内容:App Store 服务器告诉详解
- What’s New in StoreKit – WWDC17 – Videos – Apple Developer:要点内容:规范 IAP 付出流程,applicationUserName,promoted IAP
其他资料
- 主动续期订阅
- App Store Server API | Apple Developer Documentation
- 【WWDC21 10114】 初见 StoreKit 2 – 小专栏