---
title: "StoreKit 2 API的新特性是什么？苹果如何简化应用内购买的整合"
description: "StoreKit 2回顾：新特性、好处和实现。了解Store Kit 2如何简化应用内购买整合"
url: "https://adapty.io/zh/blog/storekit-2/"
language: "zh"
slug: "storekit-2"
category: "adapty-updates"
date: "2021-07-08T00:00:00.000Z"
date_modified: "2021-07-08T00:00:00.000Z"
author: "Kirill Potekhin"
---

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

在最近举行的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）标准请求认证。

### 密钥生成

首先，[创建一个私钥](https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api)，这个私钥将用于对请求进行授权。打开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](https://adapty.io/wp-content/uploads/2023/03/60e45f2a6b787b58ee716273_ifcbx8vy24m3b4vkjvuqzriaa1rjd_d2vnqx-sx7jktuduuvx83lvvmec1btr_jbwjnh5caa8okqxaqkrggopw5oycrgv5i1qweg97ri08zjya_ah97ak_pmx6191mij_md5mew-2.png)
*创建一个用于App Store Server API的私钥*

### 创建一个令牌

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

```python
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. 交易签名。

```json
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
```

### 交易头部

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

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

### 交易有效负载

```json
{
  "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"
}
```

苹果改变并扩展了交易格式。在我看来，现在用它们工作更方便了。您可以在[文档](https://developer.apple.com/documentation/appstoreserverapi/jwstransactiondecodedpayload)中了解有关新格式的详细信息。我会在下面描述一些最重要的变化。

- 苹果添加了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时间戳格式显示，以毫秒为单位。

## 用户订阅状态

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

```json
{
  "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键包含有关[续订](https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfodecodedpayload)的信息。

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

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

## 用户的交易历史

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

```json
{
	"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](https://adapty.io/wp-content/uploads/2023/03/60e45f2b5e95667e75ed3de3_inu1ytxwiyzwcicaojvveqfrprxa-jgwsrxh5suslcb9s765h6ih0yhhmxkj1ccxy1buvg-occjqmws0qh_v6lb05esbhsplz7nhxvzd9cg-keggzpeznviwovzbrw0b0lv0ntm-2.png)

### 通知类型

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

![Notifications in StoreKit 2 have types](https://adapty.io/wp-content/uploads/2023/03/60e45f2bb1b7fd418d73ce43_-ep2_vgedh1wjnfrpjuxyqsdjr-ifu4z_jcqiw0jjmuzycenjgumctezrk4xaozvgf3wpbh-cw-fdumwqatbpiqktueow5ecivpvp7-_jw7saewzdeqwagcysxkh6hw4ryibqr4-2.png)

通知类型

```json
{
  "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](https://adapty.io/)可以很好地解决这些问题：

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

[了解更多关于这些功能的信息](https://adapty.io/)，以便更快地在您的应用中实施订阅，更快、更好地为您的应用创收。
