開開心心接完 Unity 內購付費(幾年前!?),在來就是面對迷之掉單的過程。掉單總是伴隨著客訴,不管怎麼發生的,User 大都無助的反應補償或是退費,那我們當下能處理也只能根據 User 提供的訂單編號(orderID)反向查詢交易紀錄(transaction)。
訂單編號(orderID)
先來說說怎麼找到 User 需要提供(orderID)。
上圖!設定 > Apple ID 帳號 > 媒體與購買項目 > 購買紀錄 > 尋找購買訂單 > 1~N 個交易紀錄
將訂單截圖或訂單編號 (大寫英數組合)提供給客訴對象。
App Store API
拿到訂單編號(orderID)的我們能使用 App Store 提供給開發商的 API 查詢交易資料,這次需要用到的是 Look Up Order ID API。
| URL | GET https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId} |
| Sandbox URL | GET https://api.storekit-sandbox.itunes.apple.com/inApps/v1/lookup/{orderId} |
JWT組成
要使用這個 API 需要同時附上 JWT 的授權,由三個部分組成:中間以 “." 符號串聯
- base64(header)
- base64(payload)
- sign(base64(header) + “." + base64(payload))
而 Apple 官方指定的 JWT 內容 header、payload、sign 方法:
| alg – Encryption Algorithm | ES256 All JWTs for App Store Server API must be signed with ES256 encryption |
| kid – Key ID | Your private key ID from App Store Connect (Ex: 2X9R4HXF34) |
| typ – Token Type | JWT |
{
"alg": "ES256",
"kid": "2X9R4HXF34",
"typ": "JWT"
}
| iss – Issuer | Your issuer ID from the Keys page in App Store Connect (Ex: “57246542-96fe-1a63-e053-0824d011072a") |
| iat – Issued At | The time at which you issue the token, in UNIX time, in seconds (Ex: 1623085200) |
| exp – Expiration Time | The token’s expiration time, in UNIX time, in seconds. Tokens that expire more than 60 minutes after the time in iat are not valid (Ex: 1623086400) |
| aud – Audience | appstoreconnect-v1 |
| bid – Bundle ID | Your app’s bundle ID (Ex: “com.example.testbundleid2021”) |
{
"iss": "57246542-96fe-1a63e053-0824d011072a",
"iat": 1623085200,
"exp": 1623086400,
"aud": "appstoreconnect-v1",
"bid": "com.example.testbundleid2021"
}
而 kid、iss的值要從 App Store Connect 後台創建取得。
取得 Key ID(kid)、Issuer ID (iss)
首先,必須登入 App Store Connect 具有管理員或帳戶持有人的角色,可以在人員設定查詢。

接著前往:金鑰 > App 內購買項目,新增金鑰

注意:剛產生完金鑰,點選列表右邊的 “下載 App 內購買項目金鑰" 按鈕(下載過後按鈕消失)。
請將金鑰保存在安全的地方。
生成 JWT
在還不清楚如何實作生成 JWT,可以使用線上開源的網頁生成或解析 JWT。點選 JWT.io:
右邊欄位填上 header、payload、PKCS#8(來自下載的金鑰)。
而左邊便是生成出的 JWT,作為一次性使用。

API 請求 Look Up Order ID
千辛萬苦取得 JWT 後,終於可以發送 API 查詢交易紀錄。
不要猶豫,找個 linux 或 Mac 直接在 Terminal 呼叫 curl 送出去吧~
curl -v -H 'Authorization: Bearer [signed token]' https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId}"
這個 API 回應的格式包含:status、signedTransactions (array)
{
'status': 0,
'signedTransactions': [
'eyJhbGciOiJFUz.............',
'eyJ0eXAiOiJKV1.............',
'eyJ0eXAiJhbGci.............'
]
}
- status: 0 表示有效的訂單編號 (orderID)
- signedTransactions:包含多個交易收據,原因是 App Store 並非每一次購買就結帳,而是一段時間內結帳多個購買項目並產生發票,所以交易項目就被合併到一個訂單。
交易收據這看起來熟悉的格式,484想到了剛才使用的 JWT 生成網頁工具,沒錯!App Store 回應交易收據也是經過 JWT 授權生成的。
反向操作 JWT.io:左邊貼上 signedTransaction(注意不要貼到整個 array),右邊 JWT decode 後 payload 部分就是我們要的交易收據。
有關 payload 有的欄位,參考 decode transaction payload 說明。
消耗品項目 payload
以參考文章的範例來看:
{
"transactionId": "20000964758895",
"originalTransactionId": "20000964758895",
"bundleId": "com.apple.test",
"productId": "com.apple.iap.60",
"purchaseDate": 1640409900000,
"originalPurchaseDate": 1640409900000,
"quantity": 1,
"type": "Consumable",
"inAppOwnershipType": "PURCHASED",
"signedDate": 1642995907240
}
我們需要額外關注 transactionId、productId、bundleId,比對收據內容與我們在交易發生當下紀錄的是否一致。
另外,如果是已經被退款的交易收據,payload 會多兩個欄位:
- revocationReason:退款的原因
0:App Store 因其他原因(例如意外購買)代表客戶退款。
1:由於您的應用程序中存在實際或感知的問題,App Store 代表客戶對交易進行退款。 - revocationDate:退款的 UNIX 時間
還有一些 murmur
這篇 WWDC21 – App Store Server API 实践总结 文章講得很詳細,也包含實際操作結果的解說,非常感謝有這樣的資源能參考,而且還是中文的(簡體!?)。我也是看了之後才開始用簡單的方式做看看,先用了 JWT.io 開源工具驗證正確的流程、結果,再來就可以著手寫自己的工具,再發布到對應的 Server 後台供客服人員使用(還再許願池裡面!?) 。
參考
- WWDC21 – App Store Server API 实践总结
- Document-Look Up Order ID
- Document-Generating tokens for API requests
- Document-Decode Transaction Payload




