shanshanzhong 5bc453b09f
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 6m40s
fix(iap/apple): 添加缺失的IssuerID默认值并更新测试配置
当IssuerID缺失或为默认值时,使用硬编码值作为回退方案
更新测试文件中的IssuerID和BundleID为实际值
2025-12-16 01:53:36 -08:00

172 lines
4.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"log"
"net/http"
"time"
)
// 配置区域 - 请在此处填入您的真实信息进行测试
const (
// 必填:您的 Key ID (从 App Store Connect 获取)
KeyID = "2C4X3HVPM8"
// 必填:您的 Issuer ID (从 App Store Connect 获取,通常是一个 UUID)
IssuerID = "34f54810-5118-4b7f-8069-c8c1e012b7a9" // 请替换为您真实的 Issuer ID
// 必填:您的 Bundle ID (App 的包名)
BundleID = "com.taw.hifastvpn" // 请替换为您真实的 Bundle ID
// 必填:用于测试的 Transaction ID (任意一个真实的交易 ID)
TestTransactionID = "2000001083318819"
// 必填:是否为沙盒环境
IsSandbox = true
)
// P8 私钥内容 (硬编码用于测试)
const PrivateKeyPEM = `-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgsVDj0g/D7uNCm8aC
E4TuaiDT4Pgb1IuuZ69YdGNvcAegCgYIKoZIzj0DAQehRANCAARObgGumaESbPMM
SIRDAVLcWemp0fMlnfDE4EHmqcD58arEJWsr3aWEhc4BHocOUIGjko0cVWGchrFa
/T/KG1tr
-----END PRIVATE KEY-----`
func main() {
log.Println("开始测试 Apple IAP API 连接...")
log.Printf("环境: %v (Sandbox=%v)\n", func() string {
if IsSandbox {
return "沙盒 (Sandbox)"
}
return "生产 (Production)"
}(), IsSandbox)
log.Printf("KeyID: %s\n", KeyID)
log.Printf("IssuerID: %s\n", IssuerID)
log.Printf("BundleID: %s\n", BundleID)
log.Printf("TransactionID: %s\n", TestTransactionID)
token, err := buildAPIToken()
if err != nil {
log.Fatalf("生成 JWT Token 失败: %v", err)
}
log.Println("JWT Token 生成成功")
// 发起请求
host := "https://api.storekit.itunes.apple.com"
if IsSandbox {
host = "https://api.storekit-sandbox.itunes.apple.com"
}
url := fmt.Sprintf("%s/inApps/v1/transactions/%s", host, TestTransactionID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+token)
log.Printf("正在请求: %s", url)
start := time.Now()
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("请求失败: %v", err)
}
defer resp.Body.Close()
duration := time.Since(start)
body, _ := io.ReadAll(resp.Body)
log.Printf("请求耗时: %v", duration)
log.Printf("状态码: %d", resp.StatusCode)
if resp.StatusCode == 200 {
log.Println("✅ 测试成功API 调用正常。")
log.Printf("响应内容: %s", string(body))
} else {
log.Println("❌ 测试失败!")
log.Printf("错误响应: %s", string(body))
if resp.StatusCode == 401 {
log.Println("原因分析: 401 Unauthorized 通常表示:")
log.Println("1. Key ID 或 Issuer ID 错误")
log.Println("2. Bundle ID 不匹配")
log.Println("3. 私钥错误")
log.Println("4. Token 格式错误 (如算法或 Claims)")
} else if resp.StatusCode == 404 {
log.Println("原因分析: 404 Not Found 通常表示 Transaction ID 不存在或环境(沙盒/生产)选错了")
}
}
}
// 下面是复制过来的工具函数
func buildAPIToken() (string, error) {
header := map[string]interface{}{
"alg": "ES256",
"kid": KeyID,
"typ": "JWT",
}
now := time.Now().Unix()
payload := map[string]interface{}{
"iss": IssuerID,
"iat": now,
"exp": now + 60, // 测试 Token 有效期短一点即可
"aud": "appstoreconnect-v1",
}
if BundleID != "" {
payload["bid"] = BundleID
}
hb, _ := json.Marshal(header)
pb, _ := json.Marshal(payload)
enc := func(b []byte) string {
return base64.RawURLEncoding.EncodeToString(b)
}
unsigned := fmt.Sprintf("%s.%s", enc(hb), enc(pb))
block, _ := pem.Decode([]byte(PrivateKeyPEM))
if block == nil {
return "", fmt.Errorf("invalid private key")
}
keyAny, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", err
}
priv, ok := keyAny.(*ecdsa.PrivateKey)
if !ok {
return "", fmt.Errorf("private key is not ECDSA")
}
digest := sha256Sum([]byte(unsigned))
r, s, err := ecdsa.Sign(rand.Reader, priv, digest)
if err != nil {
return "", err
}
curveBits := priv.Curve.Params().BitSize
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes += 1
}
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
sig := append(rBytesPadded, sBytesPadded...)
return unsigned + "." + base64.RawURLEncoding.EncodeToString(sig), nil
}
func sha256Sum(b []byte) []byte {
h := sha256.New()
h.Write(b)
return h.Sum(nil)
}