feat: 为订单表添加 app_account_token 字段并增强 Apple IAP 对账逻辑,支持通过交易历史记录查找。
Build docker and publish / build (20.15.1) (push) Successful in 7m25s

This commit is contained in:
2026-03-10 20:47:24 -07:00
parent 26f6400e74
commit 7c2eddf9c3
5 changed files with 537 additions and 32 deletions
+75
View File
@@ -129,3 +129,78 @@ func GetTransactionInfo(cfg ServerAPIConfig, transactionId string) (string, erro
}
return "", fmt.Errorf("apple api error, primary[%d:%s], secondary[%d:%s]", code, body, code2, body2)
}
// GetTransactionHistory 获取指定交易 ID 的完整交易历史(该用户的所有交易)
// Apple API: GET /inApps/v2/history/{transactionId}
// 返回所有交易的 JWS 列表
func GetTransactionHistory(cfg ServerAPIConfig, transactionId string) ([]string, error) {
token, err := buildAPIToken(cfg)
if err != nil {
return nil, err
}
fetchPage := func(host, revision string) ([]string, string, bool, int, error) {
url := fmt.Sprintf("%s/inApps/v2/history/%s?sort=DESCENDING", host, transactionId)
if revision != "" {
url += "&revision=" + revision
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, "", false, 0, err
}
defer resp.Body.Close()
buf := new(bytes.Buffer)
_, _ = buf.ReadFrom(resp.Body)
if resp.StatusCode != 200 {
return nil, "", false, resp.StatusCode, fmt.Errorf("apple api error: %d %s", resp.StatusCode, buf.String())
}
var result struct {
SignedTransactions []string `json:"signedTransactions"`
Revision string `json:"revision"`
HasMore bool `json:"hasMore"`
}
if err := json.Unmarshal(buf.Bytes(), &result); err != nil {
return nil, "", false, resp.StatusCode, err
}
return result.SignedTransactions, result.Revision, result.HasMore, resp.StatusCode, nil
}
primary := "https://api.storekit.itunes.apple.com"
secondary := "https://api.storekit-sandbox.itunes.apple.com"
if cfg.Sandbox {
primary, secondary = secondary, primary
}
// 尝试 primary 环境
var allTx []string
revision := ""
for {
txs, rev, hasMore, _, err := fetchPage(primary, revision)
if err != nil {
break // primary 失败,尝试 secondary
}
allTx = append(allTx, txs...)
if !hasMore || rev == "" {
return allTx, nil
}
revision = rev
}
// Fallback: secondary 环境
allTx = nil
revision = ""
for {
txs, rev, hasMore, code, err := fetchPage(secondary, revision)
if err != nil {
return nil, fmt.Errorf("both environments failed, secondary[%d]: %v", code, err)
}
allTx = append(allTx, txs...)
if !hasMore || rev == "" {
return allTx, nil
}
revision = rev
}
}