使用 App Store Connect API 批量建立內購商品
一、前言
我們去年開源 AppleParty(蘋果派) 用於批量應用內購商品的建立和更新的方案,具體的技術方案是使用 XML Feed 格式來處理。而今年蘋果在 WWDC22 宣佈,2022 年 11 月開始,不再允許使用 XML 方式上傳元資料和內購商品。
蘋果在 7 月公告 即將從 XML Feed 過渡到 App Store Connect API,並且一直郵件通知開發者,截止 11月 9 日之前:
``` We noticed you recently used the XML feed to manage and deliver content to App Store Connect. As we wrote to you previously, as of November 9, 2022, you’ll need to use the App Store Connect REST API to manage in-app purchases, subscriptions, metadata, and app pricing. The XML feed no longer supports this content, but continues to support existing Game Center management functionality.
If you have any questions, contact us.
Apple Developer Relations ```
如果現在還使用 XML feed 上傳,會收到以下告警:
``` ERROR ITMS-6036: "XML schemas software5.12 and earlier have been deprecated and uploads of app metadata, in-app purchases, and subscriptions are no longer supported through the XML feed. You can use the App Store Connect API instead.
Game Center will continue to be supported with XML schema software6.0." at Software/SoftwareMetadata ```
所以,XML feed 禁止上傳的內容:
- app metadata(app元資料,如截圖、預覽、描述等)
- in-app purchases, and subscriptions(內購商品,包括訂閱型別)
- app pricing(app定價)
而 Game Center
和上傳 ipa
檔案等方式,目前還能上傳,目前來看,是因為 App Store Connect API 還不支援!所以,希望明天 WWDC23 蘋果能支援上傳 ipa 檔案,這樣就更加方便~
二、App Store Connect API
App Store Connect API 需要生成金鑰才能呼叫使用,所以,我們先來介紹一下金鑰的生成,然後在以應用內購商品的建立和更新為例,展示 API 使用示例。
2.1 App Store Connect API 金鑰生成
生成金鑰 ID(kid)和 Issuer ID(iss)
要生成金鑰,您必須在 App Store Connect 中具有管理員角色或帳戶持有人角色。登入 App Store Connect 並完成以下步驟:
- 選擇 “使用者和訪問”,然後選擇 “金鑰” 子標籤頁。
- 在 “金鑰型別” 下選擇 “App Store Connect API”。
- 單擊 “生成 API 金鑰”(如果之前建立過,則點選 “新增(+)” 按鈕新增。)。
- 輸入金鑰的名稱。該名稱僅供您參考,名字不作為金鑰的一部分。
- 單擊 “生成”。
“使用者和訪問” -> “金鑰” -> “App Store Connect API” -> “生成 API 金鑰”
注:訪問許可權: 根據金鑰使用場景,訪問的許可權也不一樣。要建立和管理 App 內購買專案,請確保您擁有以下使用者角色之一: * 帳戶持有人 * 管理 * App 管理(這個要求角色許可權最低)
詳細許可權,可參考文件 職能許可權。
1、Issuer ID:拷貝複製內容 2、金鑰 ID: 生成的金鑰,有一列名為 “金鑰 ID” 就是 kid 的值,滑鼠移動到文字就會顯示 拷貝金鑰 ID,點選按鈕就可以複製 kid 值。 3、API 金鑰檔案,下載 API 金鑰 按鈕(僅當您尚未下載私鑰時,才會顯示下載連結。),此私鑰只能一次性下載!。
注意:將您的私鑰存放在安全的地方。不要共享金鑰,不要將金鑰儲存在程式碼倉庫中,不要將金鑰放在客戶端程式碼中。如果您懷疑私鑰被盜,請立即在 App Store Connect 中撤銷金鑰。有關詳細資訊,請參閱 撤銷API金鑰。
最終,生成以下引數和檔案:
| 名字 | 值示例 | 說明 | 欄位值說明 | | --- | --- | --- | --- | | 金鑰ID | GC8HS3SX37 | kid,Key ID,金鑰ID | 您的私鑰ID,值來自 API 金鑰頁面。 | | 金鑰內容檔案 | SubscriptionKey_GC8HS3SX37.p8 | 金鑰檔案(p8) | 用來訪問和使用 App Store Connect API 介面的服務。 | | Issuer ID | 69a6de92-xxx-xxxx-xxxx-5bc37c11a4d1 | iss,Issuer ID,發行人 | 您的髮卡機構ID,值來自 App Store Connect 的 API 金鑰頁面。 |
2.2 App Store Connect API 使用示例
這裡我們使用 python3 建立 API 請求示例,需要依賴 jwt
和 requests
庫,所以需要在終端安裝:
``` pip3 install jwt
pip3 install requests ```
怎麼請求 App Store Connect API ?蘋果給出了一個示例:
bash
curl -v -H 'Authorization: Bearer [signed token]'
"https://api.appstoreconnect.apple.com/v1/apps"
也就是用 JWT 生成的 token,放到 App Store Connect API 請求連結的 header 部分,key 為 Authorization
,value為 Bearer [signed token]
。
接下來,我們通過 Python 的 requests
來請求 App Store Connect API。大家也可以用其它的工具來模擬,比如線上工具或者 Postman 等。
```python import jwt import time import requests
def createASCToken(p8KeyPath, kid, iss): try: header = { "alg": "ES256", "typ": "JWT", "kid": kid } payload = { "iss": iss, "aud": "appstoreconnect-v1", "iat": int(time.time()), "exp": int(round(time.time() + (20.0 * 60.0))) # 20 minutes timestamp } file = open(p8KeyPath) key_data = file.read() file.close() token = jwt.encode(headers=header, payload=payload, key=key_data, algorithm="ES256") return token except Exception as e: print(e) return ""
金鑰路徑
p8 = "/Users/iHTCboy/Downloads/AppStoreConnectAPI/AuthKey_GC8HS3SX37.p8" kid = "GC8HS3SX37" iss = "69a6de92-xxx-xxxx-xxxx-5bc37c11a4d1"
生成請求 token
token = createASCToken(p8, kid, iss) ```
接下來,以獲取 app 列表為例,請求也非常簡單:
```python
獲取全部 app
url = "https://api.appstoreconnect.apple.com/v1/apps" header = { "Authorization": f"Bearer {token}" } rs1 = requests.get(url, headers=header) data = json.loads(rs1.text)
print(data) ```
返回內容示例:
json
{
"data" : [ {
"type" : "apps",
"id" : "123456737",
"attributes" : {
"name" : "AppleParty - 37手遊 iOS 技術團隊",
"bundleId" : "cn.com.37iOS.AppleParty",
"sku" : "2021.04.25",
"primaryLocale" : "zh-Hans",
"isOrEverWasMadeForKids" : false,
"subscriptionStatusUrl" : null,
"subscriptionStatusUrlVersion" : null,
"subscriptionStatusUrlForSandbox" : null,
"subscriptionStatusUrlVersionForSandbox" : null,
"availableInNewTerritories" : true,
"contentRightsDeclaration" : null
},
"relationships" : {
xxxx
}
}],
"links" : {
"self" : "https://api.appstoreconnect.apple.com/v1/apps"
},
"meta" : {
"paging" : {
"total" : 1,
"limit" : 50
}
}
}
2.3 App Store Connect API 使用說明
App Store Connect API 可以根據官方文件就能大概瞭解,但是依然非常難,就是 POST 介面的 body 和上傳檔案的流程。
POST body
以 Create an In-App Purchase 為例,請求的 body:
json
{
'data': {
'attributes': {
'availableInAllTerritories': True,
'familySharable': False,
'inAppPurchaseType': 'NON_CONSUMABLE',
'name': '我是測試商品01',
'productId': 'com.apple.iap01',
'reviewNote': '稽核備註',
},
'relationships': {
'app': {
'data': {
'id': "{app_id}",
'type': 'apps'
}
}
},
'type': 'inAppPurchases'
}
}
其中 inAppPurchaseType
可能為:
- CONSUMABLE
- NON_CONSUMABLE
- NON_RENEWING_SUBSCRIPTION
而訂閱型別的商品,是另一個 API Create an Auto-Renewable Subscription,對應的請求的 body:
json
{
"data": {
"type": "subscriptions",
"attributes": {
"name": "一個月訂閱會員",
"productId": "com.apple.mon01",
"subscriptionPeriod": "ONE_MONTH",
"familySharable": False,
"reviewNote": "稽核備註",
"groupLevel": 1,
"availableInAllTerritories": True
},
"relationships": {
"group": {
"data": {
"type": "subscriptionGroups",
"id": "{app_iap_grop_id}"
}
}
}
}
}
其中 subscriptionPeriod
可以為:
- ONE_WEEK
- ONE_MONTH
- TWO_MONTHS
- THREE_MONTHS
- SIX_MONTHS
- ONE_YEAR
上傳檔案
上傳檔案的流程,剛開始看文件沒有看明白,最後又仔細查文件才找到 Uploading Assets to App Store Connect,以上傳應用內購買的送審圖片為例,Create an In-App Purchase Review Screenshot,需要對應的請求的 body:
json
{
'data': {
'attributes': {
'fileName': 'test.png',
'fileSize': '1000',
},
'relationships': {
'inAppPurchaseV2': {
'data': {
'id': '{app_iap_id}',
'type': 'inAppPurchases'
}
}
},
'type': 'inAppPurchaseAppStoreReviewScreenshots'
}
}
請求成功後,Response Code 為 201 時:
json
{
"data" : {
"type" : "inAppPurchaseAppStoreReviewScreenshots",
"id" : "caeda501-xxxx-xxxx-8fb3-6a3c0f462720",
"attributes" : {
"fileSize" : 1000,
"fileName" : "test.png",
"sourceFileChecksum" : "",
"imageAsset" : {
"templateUrl" : "",
"width" : 0,
"height" : 0
},
"assetToken" : "",
"assetType" : "SCREENSHOT",
"uploadOperations" : [ {
"method" : "PUT",
"url" : "https://store-032.blobstore.apple.com/itmspod11-assets-massilia-032001/PurpleSource112%2Fv4%2F2c%2F3f%2Fe1%2F2c3fe12e-a9ea-xxx-xxx-12a8c02df932%2FieKZRQnL0o2fK4sbeFRXOQ8tVRjPIVyJaGCNLsLg2Dc_U003d-1669087039587?uploadId=2c75a0f0-6a14-11ed-93d1-d8c4978a0739&Signature=OWuT65nZNeMgWMNbaZtEGc9lcDU%3D&AWSAccessKeyId=MKIA474WIEZZVU5QMKHI&partNumber=1&Expires=1669691839",
"length" : 1000,
"offset" : 0,
"requestHeaders" : [ {
"name" : "Content-Type",
"value" : "application/octet-stream"
} ]
} ],
"assetDeliveryState" : {
"errors" : null,
"warnings" : null,
"state" : "AWAITING_UPLOAD"
}
},
"links" : {
"self" : ""
}
},
"links" : {
"self" : "
}
}
返回的響應內容 uploadOperations
中的 url 就是上傳圖片檔案的請求 url,對應的 requestHeaders
也是組裝 request 必備的 headers 屬性,圖片檔案的大小要與 length 長度一致。
2.4 App Store Connect Swift SDK
從上文就可以看出來,如果自己全部的 API 都實現一次,工作時是非常大,所以我們非常感謝 AvdLee/appstoreconnect-swift-sdk,使用 Xcode 的 Swift Package Manager 匯入 https://github.com/AvdLee/appstoreconnect-swift-sdk.git
就可以使用!
以建立內購商品為例:
```swift func createInAppPurchases(appId: String, product: IAPProduct) async -> ASCInAppPurchaseV2? { let body = [ "data": [ "attributes": [ "availableInAllTerritories": product.availableInAllTerritories, "familySharable": product.familySharable, // CONSUMABLE、NON_CONSUMABLE、NON_RENEWING_SUBSCRIPTION "inAppPurchaseType": product.inAppPurchaseType.rawValue, "name": product.name, "productId": product.productId, "reviewNote": product.reviewNote, ], "relationships": [ "app": [ "data": [ "id": appId, "type": "apps" ] ] ], "type": "inAppPurchases" ] ]
do {
guard let provider = provider else {
return nil
}
let json = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
let model = try JSONDecoder().decode(InAppPurchaseV2CreateRequest.self, from: json)
let request = APIEndpoint.v2.inAppPurchases.post(model)
let data = try await provider.request(request).data
return data
} catch APIProvider.Error.requestFailure(let statusCode, let errorResponse, _) {
handleRequestFailure(statusCode, errorResponse)
} catch {
handleError("建立內購商品失敗: \(error.localizedDescription)")
}
return nil
}
```
這裡就不再展開,詳細可以參考我們開源專案程式碼:AppStoreConnectAPI.swift。
3、Apple Party(蘋果派)更新
下載 2.1.0 更新版本:Releases · 37iOS/AppleParty
更新重點內容
- 截圖不再是必需項
- 支援多種本地化語言
表格格式更新,刪除無法欄位,支援多種本地化語言:
支援多種本地化語言,通過在表格最後的列增加,本地化語言標識,每種語言增加2列,分別對應本地化的名字和描述。
內購列表更新支援不同的價格國家地區的價格顯示:
匯入表格後,首次需要設定 API 金鑰:
金鑰獲取,參考本文的第二章內容。
提交後,會自動執行上傳,如果存在的商品會更新內容,成功時:
四、總結
App Store Connect API 功能非常多,包括元資料的管理,構建版本的管理、TextFlight 管理、證書管理等等,Apple Party(蘋果派)從日常使用場景最多的內購商品批量建立入手,未來依然有非常多的生效力效率提升,歡迎大家一起迭代和 PR 提交!
歡迎你一起體驗和參考 37iOS/AppleParty~
歡迎大家評論區一起討論交流~
歡迎關注我們,瞭解更多 iOS 和 Apple 的動態~
參考引用
- 即將從 XML Feed 過渡到 App Store Connect API - 最新動態 - Apple Developer
- 職能許可權 - App Store Connect 幫助
- Revoking API Keys | Apple Developer Documentation
- Create an In-App Purchase | Apple Developer Documentation
- Create an Auto-Renewable Subscription | Apple Developer Documentation
- Uploading Assets to App Store Connect | Apple Developer Documentation
- Create an In-App Purchase Review Screenshot | Apple Developer Documentation
- AvdLee/appstoreconnect-swift-sdk: The Swift SDK to work with the App Store Connect API from Apple.
- Releases · 37iOS/AppleParty
- App Store 新定價機制 - 2023年最全版
- 關於 App Store 蘋果商店價格的那些事(歷上最全版)
- 使用 App Store Connect API 批量建立內購商品
- 用 SwiftUI 實現 AI 聊天對話 app - iChatGPT
- WWDC22 - In App Purchase 更新總結
- WWDC22 - Apple 隱私技術探索
- WWDC22 開發者需要關注的重點內容
- 蘋果 AppStore 財年和賬單那些趣事
- 開源一款蘋果 macOS 工具 - AppleParty(蘋果派)
- 你一定不知道的 AppStore 祕密
- 揭祕蘋果應用稽核團隊(史上最全版)
- WWDC21 - App Store Server API 實踐總結
- 用 SwiftUI 實現一個開源的 App Store
- Xcode 配置多套 App 圖示的方法 --- AppStore 圖示 A/B Test 實踐
- 教你實現一個 iOS 重簽名工具
- 趣談 iOS Universal Link
- iOS15 安全漏洞分析:價值10萬美元的漏洞曝光