Report: State of in-app subscriptions in the US 2023 Get a report

StoreKit 2 API的新特性是什么?苹果如何简化应用内购买的整合

Kirill Potekhin

Updated: October 10, 2023

5 min read

Content

62fdf0da8b35ff3ba074dbce jp android tutorial 1 configuration 5 6

在最近举行的2021年全球开发者大会上,苹果推出了新版的StoreKit 2。这是一个负责在iOS中进行购买的框架。带有应用内购买(in-app purchase)功能和订阅功能的应用份额稳步增长,苹果通过发布StoreKit 2显著简化了应用内购买功能在应用(APP)中的整合。今天,我们将考虑在服务器端使用StoreKit 2,换句话说,就是在App Store Server API的帮助下进行。

请求认证

在当前的API版本中,您需要共享密钥来发送请求。这是一个您可以在App Store Connect中获得的秘密固定字符串。新版本的API使用JSON Web Token(JWT)标准请求认证。   

密钥生成 

首先,创建一个私钥,这个私钥将用于对请求进行授权。打开App Store Connect,前往“Users and Access”部分,然后到“Keys”选项卡。选择“In-App Purchase”密钥类型。下载一个新密钥。您还需要它的ID——您可以将其复制到App Store Connect API标签页中的发行者ID所在页面。

Creating a private key to work with StoreKit 2
创建一个用于App Store Server API的私钥

创建一个令牌

下一步是创建一个将用于对请求进行授权的令牌。这个过程在文档中有详细描述,所以没有理由对它进行过多的关注。下面是一个现成的Python实现示例。值得注意的是,为每个新请求生成新令牌是没有意义的。在创建令牌时,您将其生存期设置为至多60分钟,并在此期间使用相同的令牌。 

import time, uuid
from authlib.jose import jwt

BUNDLE_ID = 'com.adapty.sample_app'
ISSUER_ID = '4336a124-f214-4d40-883b-6db275b5e4aa'
KEY_ID = 'J65UYBDA74'
PRIVATE_KEY = '''
-----BEGIN PRIVATE KEY-----
MIGTAgMGByqGSMBHkAQQgR/fR+3Lkg4...
-----END PRIVATE KEY-----
'''

issue_time = round(time.time())
expiration_time = issue_time + 60 * 60 # 1 hour expiration


header = {
 'alg': 'ES256',
 'kid': KEY_ID,
 'typ': 'JWT'
}

payload = {
 'iss': ISSUER_ID,
 'iat': issue_time,
 'exp': expiration_time,
 'aud': 'appstoreconnect-v1',
 'nonce': str(uuid.uuid4()),
 'bid': BUNDLE_ID
}

token_encoded = jwt.encode(header, payload, PRIVATE_KEY)
token_decoded = token_encoded.decode()

authorization_header = {
 'Authorization': f'Bearer {token_decoded}'
}

签署的交易

在API的新版本中,所有交易都以JSON Web签名(JWS)标准返回。这是一个由三部分组成的字符串,由点分割。  

  1. Base64头部。
  2. Base64交易有效负载。
  3. 交易签名。
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))

交易头部

需要一个头部来确保交易是真实的。Alg密钥包含加密算法,x5c密钥包含证书链。

{
  "kid": "AMP/DEV",
  "alg": "ES256",
  "x5c": [
    "MIIEO...",
    "MIIDK..."
  ]
}

交易有效负载

{
  "transactionId": "1000000831360853",
  "originalTransactionId": "1000000806937552",
  "webOrderLineItemId": "1000000063561721",
  "bundleId": "com.adapty.sample_app",
  "productId": "basic_subscription_1_month",
  "subscriptionGroupIdentifier": "27636320",
  "purchaseDate": 1624446341000,
  "originalPurchaseDate": 1619686337000,
  "expiresDate": 1624446641000,
  "quantity": 1,
  "type": "Auto-Renewable Subscription",
  "appAccountToken": "fd12746f-2d3a-46c8-bff8-55b75ed06aca",
  "inAppOwnershipType": "PURCHASED",
  "signedDate": 1624446484882,
  "offerType": 2,
  "offerIdentifier": "basic_subscription_1_month.pay_as_you_go.3_months"
}

苹果改变并扩展了交易格式。在我看来,现在用它们工作更方便了。您可以在文档中了解有关新格式的详细信息。我会在下面描述一些最重要的变化。    

  • 苹果添加了appAccountToken字段,它包含系统的用户ID。这个ID必须是通用唯一标识符(UUID)格式,它是在初始化购买时在移动应用程序中设置的。如果已经设置,它将在这个链中的所有交易中返回(续订、账单问题等),您将很容易理解哪个用户进行了购买。    
  • 苹果还添加了offerType和offerIdentifier字段,用于包含使用过的价格信息(如果有的话)。下面是offerType字段的值:
  • 1 ——试销优惠价(intro offer,仅适用于没有活跃订阅或过期订阅的用户)
  • 2 ——优惠推广(promo offer,仅对当前和过期订阅有效)
  • 3 ——优惠代码

如果使用了优惠推广或优惠代码,offerIdentifier键将包含所使用优惠的ID。在过去,不可能在服务器端(server-side)跟踪优惠的使用情况,这就让分析更难了。现在,您可以使用优惠代码进行分析。   

  • 苹果增加了inAppOwnershipType字段,这有助于了解用户是购买了产品还是通过家庭订阅访问了产品。可能的值:
  • PURCHASED
  • FAMILY_SHARED
  • 另一个新字段——类型——包括交易类型。可能的值:
  • 自动续订
  • 非消耗性
  • 消耗性
  • 非续订
  • Cancellation_date和cancellation_reason字段现在有了新的名称:revocationDate和revocationReason。在此提醒一下,它们包含由于退款而撤销订阅(subscription revocation)的日期和原因,因此,新名称看起来更符合逻辑。
  • 所有的键都以camelCase格式返回(就像App Store Server API里所有的请求一样)。
  • 所有日期都以Unix时间戳格式显示,以毫秒为单位。   

Subscribe to Adapty newsletter

Get fresh paywall ideas, subscription insights, and mobile app news every month!

用户订阅状态

如需检查当前用户的订阅状态,请向https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}发送一个GET请求,其中{originalTransactionId}是该用户的任何交易链的ID。然后,您将为每一组订阅获得带有状态的交易。

{
  "environment": "Sandbox",
  "bundleId": "com.adapty.sample_app",
  "data": [
    {
      "subscriptionGroupIdentifier": "39636320",
      "lastTransactions": [
        {
          "originalTransactionId": "1000000819078552",
          "status": 2,
          "signedTransactionInfo": "eyJraWQiOi...",
          "signedRenewalInfo": "eyJraWQiOi..."
        }
      ]
    }
  ]
}

状态键显示当前的订阅状态,基于此,您可以决定是否应该为用户提供应用程序的付费功能。可能的值:

  • 1 —— 订阅是活跃的,用户必须能够访问付费功能。
  • 2 —— 订阅已过期,用户必须无法访问付费功能。  
  • 3 —— 订阅是计费重试状态,这意味着用户没有取消订阅,但遇到了付费问题。苹果公司将尝试在60天内向该卡收费。用户必须无法访问付费功能。  
  • 4 —— 订阅处于宽限期,这意味着用户没有取消订阅,但遇到了支付问题。宽限期在App Store Connect中,所以用户必须能够访问付费功能。    
  • 5 —— 订阅因退款被取消,用户必须无法访问付费功能。 

SignedTransactionInfo键包含链中最后一次交易的信息。您可以在上面找到关于其格式的详细信息。

续订(subscription renewal)信息  

SignedRenewalInfo键包含有关续订的信息。

{
  "expirationIntent": 1,
  "originalTransactionId": "1000000819078552",
  "autoRenewProductId": "basic_subscription_1_month",
  "productId": "basic_subscription_1_month",
  "autoRenewStatus": 0,
  "isInBillingRetryPeriod": false,
  "signedDate": 1624520884048
}

这些信息使我们能够了解在下一次付费期间订阅将会发生什么。例如,如果您看到用户取消了自动续订,您可以让他们切换到其他订阅计划或向他们提供优惠。在服务器通知的帮助下跟踪这类事件(event)很方便,我会很快告诉您具体的。  

用户的交易历史

如需获取用户的交易历史记录,请将GET请求发送到https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId},其中{originalTransactionId}是该用户的任何交易链的ID。然后,您将得到一个按时间排序的交易数组。     

{
	"revision": "1625872984000_1000000212854038",
	"bundleId": "com.adapty.sample_app",
	"environment": "Sandbox",
	"hasMore": true,
	"signedTransactions": [
		"eyJraWQiOiJ...",
		"joiRVMyNeyX...",
		"5MnkvOTlOZl...",
		...
	]
}

一个请求不能包含超过20个交易。如果用户拥有更多,则hasMore标志的值将为true。如果需要下一个交易页,则再次发送请求,其中包含修订GET参数,它将包含来自同一个键的值。  

服务器交易通知

服务器通知(Server notification)可以帮助您获取关于新购买、续订、账单问题等的信息。这有助于建立更准确的分析,以及简化管理订阅者的状态。   

现有的服务器通知(V1)可以解决大部分问题,但有时会带来不便。通常情况下,这是关于用户的一个操作获得多个通知的情况。例如,现在,当用户升级订阅时,苹果会发送两条通知:DID_CHANGE_RENEWAL_STATUS和INTERACTIVE_RENEWAL。当前为了处理这种情况,您需要以某种方式保存状态,并检查是否发送了第二个通知。在新版本的服务器通知(V2)中,对于用户的一个操作只有一个通知,这样就方便多了。   

服务器通知的第二个版本提供了新的事件——OFFER_REDEEMED,EXPIRED和GRACE_PERIOD_EXPIRED。它们使管理订阅者状态变得更加容易。SUBSCRIBED和PRICE_INCREASE事件是第一版的改进事件。

Transaction notifications in StoreKit 2 feature new events

通知类型

通知现在有了类型,因此,对于用户的任何操作,一个通知就足以理解发生了什么。 

Notifications in StoreKit 2 have types

通知类型

{
  "notificationType": "SUBSCRIBED",
  "subtype": "INITIAL_BUY",
  "version": 2,
  "data": {
    "environment": "Sandbox",
    "bundleId": "com.adapty.sample_app",
    "appAppleId": 739104078,
    "bundleVersion": 1,
    "signedTransactionInfo": "eyJraWQiOi...",
    "signedRenewalInfo": "eyJraWQiOi..."
  }
}

服务器通知包含我前面描述的JWS格式的交易和更新信息。  

使用沙盒(Sandbox)环境

您需要使用沙盒环境的链接来测试购买情况:https://api.storekit-sandbox.itunes.apple.com。

新版本的服务器通知还不能进行测试。一旦它可用,就可以为生产和沙盒通知指定不同链接。您可以为沙盒选择V2,为生产选择V1进行测试。 

此外,App Store Connect现在允许:

  • 清除沙盒用户的购买历史,这意味着您不必再创建一个新账户来做这件事了。   
  • 更改沙盒用户的存储国家。
  • 更改沙盒订阅续订期,例如,您可以每月购买1小时而不是5分钟。     

结论

苹果在服务器端显著改善了应用内购买和订阅功能。在我看来,以下是最有用的新功能:

  • 完善的优惠推广和优惠代码支持;  
  • 更简单、信息更丰富的服务器通知;   
  • 有机会通过一个简单的API调用了解当前的订阅状态;
  • 清除用户的沙盒购买历史。   

切换到一个新的API并不困难,为每个收据获得originalTransactionId就足够了,它可能已经包含在了您的数据库中。    

总之,将订阅整合到移动应用中最困难的部分是构建分析系统和优化经济。Adapty可以很好地解决这些问题:

  • 内置分析让我们能够快速理解主要应用的参数。
  • 群组分析(Cohort analysis)有助于理解经济是否存在任何问题。归因
  • A/B测试(A/B testing)能够提高应用的盈利能力。   
  • 与外部系统的集成允许将交易发送到归因和产品分析服务。    
  • 促销活动可以减少受众的流失。   
  • 开源软件开发工具包(SDK)让您可以在几个小时内将订阅整合到应用程序中。  

了解更多关于这些功能的信息,以便更快地在您的应用中实施订阅,更快、更好地为您的应用创收。