蛤!到了要查 User 購買訂單的時候了… (AppStore)

開開心心接完 Unity 內購付費(幾年前!?),在來就是面對迷之掉單的過程。掉單總是伴隨著客訴,不管怎麼發生的,User 大都無助的反應補償或是退費,那我們當下能處理也只能根據 User 提供的訂單編號(orderID)反向查詢交易紀錄(transaction)。

訂單編號(orderID)

先來說說怎麼找到 User 需要提供(orderID)。

上圖!設定 > Apple ID 帳號 > 媒體與購買項目 > 購買紀錄 > 尋找購買訂單 > 1~N 個交易紀錄
訂單截圖訂單編號 (大寫英數組合)提供給客訴對象。

App Store API

拿到訂單編號(orderID)的我們能使用 App Store 提供給開發商的 API 查詢交易資料,這次需要用到的是 Look Up Order ID API。

URLGET https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId}
Sandbox URLGET https://api.storekit-sandbox.itunes.apple.com/inApps/v1/lookup/{orderId}
說明:取得 User 內購付費交易收據紀錄 by User’s order ID.

JWT組成

要使用這個 API 需要同時附上 JWT 的授權,由三個部分組成:中間以 “." 符號串聯

  • base64(header)
  • base64(payload)
  • sign(base64(header) + “." + base64(payload))

而 Apple 官方指定的 JWT 內容 header、payload、sign 方法:

alg – Encryption AlgorithmES256
All JWTs for App Store Server API must be signed with ES256 encryption
kid – Key IDYour private key ID from App Store Connect (Ex: 2X9R4HXF34)
typ – Token TypeJWT
說明:創建 JWT header

{
    "alg": "ES256",
    "kid": "2X9R4HXF34",
    "typ": "JWT"
}
iss – IssuerYour issuer ID from the Keys page in App Store Connect (Ex: “57246542-96fe-1a63-e053-0824d011072a")
iat – Issued AtThe time at which you issue the token, in UNIX time, in seconds (Ex: 1623085200)
exp – Expiration TimeThe 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 – Audienceappstoreconnect-v1
bid – Bundle IDYour app’s bundle ID (Ex: “com.example.testbundleid2021”)
說明:創建 JWT payload

{
  "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 後台供客服人員使用(還再許願池裡面!?) 。

參考

  1. WWDC21 – App Store Server API 实践总结
  2. Document-Look Up Order ID
  3. Document-Generating tokens for API requests
  4. Document-Decode Transaction Payload

發表留言