package internal import ( "bytes" "encoding/json" "fmt" "github.com/SermoDigital/jose" "github.com/gogf/gf/v2/encoding/gjson" "github.com/golang-jwt/jwt/v5" "io" "log" "net/http" "strings" "time" "tyj_admin/api/v1/game" ) // Apple JWT 认证配置 type AppleAPIConfig struct { IssuerID string `json:"issuer_id"` // 苹果开发者团队ID(格式:57246542-96fe-1a63e053-0824d011072a) BundleID string `json:"bundle_id"` // 应用Bundle ID(如:com.example.game) KeyID string `json:"key_id"` // 苹果API密钥ID(格式:2X9R4HXF34) PrivateKey []byte `json:"private_key"` // 从苹果开发者平台下载的.p8私钥文件内容 } // 订单查询响应结构体 type AppleOrderResponse struct { Status int `json:"status"` // 状态码(0表示成功) SignedTransactions []string `json:"signedTransactions"` } func LoadP8File() []byte { return []byte(`-----BEGIN PRIVATE KEY----- MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg2XqPEHgWj2cUnO2GoPfIeEcAc0tJsTehvNNNBTGf4KigCgYIKoZIzj0DAQehRANCAAT6IdBMPYuNAQYuZsYi3EkflniotI/KJa6ELt1ednywlOpuwgNOn2WXONmDzzVVMJqQjD/6FSJ4jH7fRtP+Eci6 -----END PRIVATE KEY-----`) } func GenerateAppleJWT(config AppleAPIConfig) (string, error) { // 使用ES256算法签名 key, _ := jwt.ParseECPrivateKeyFromPEM(config.PrivateKey) token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{ "iss": config.IssuerID, "iat": time.Now().Unix(), "exp": time.Now().Add(5 * time.Minute).Unix(), // 有效期5分钟 "aud": "appstoreconnect-v1", "bid": config.BundleID, }) token.Header["kid"] = config.KeyID return token.SignedString(key) } func QueryAppleOrder(orderID string) (*game.SignedTransaction, error) { config := AppleAPIConfig{ IssuerID: "b8b82821-922e-4b43-a3cf-d293020a70d1", BundleID: "com.XiamenAvatar.PeachValley", KeyID: "J4RAWQBLHF", PrivateKey: LoadP8File(), } // 构造API地址 baseURL := "https://api.storekit.itunes.apple.com/inApps/v1/lookup/" if isSandboxOrder(orderID) { // 根据订单号自动判断环境 baseURL = strings.Replace(baseURL, "storekit", "storekit-sandbox", 1) } url := baseURL + orderID // 生成JWT令牌 authToken, err := GenerateAppleJWT(config) if err != nil { return nil, fmt.Errorf("JWT生成失败: %v", err) } log.Printf("authToken %v", gjson.MustEncodeString(authToken)) // 创建HTTP请求 req, _ := http.NewRequest("GET", url, nil) req.Header.Set("Authorization", "Bearer "+authToken) req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("API请求失败: %v", err) } defer resp.Body.Close() // 解析响应 if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("苹果接口异常[%d]: %s", resp.StatusCode, string(body)) } var result AppleOrderResponse if err = json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } if result.Status != 0 { return nil, fmt.Errorf("订单查询失败,状态码:%d", result.Status) } data, err := DecodeJWSTransaction([]byte(result.SignedTransactions[0])) log.Printf("result %v", gjson.MustEncodeString(data)) return data, err } func DecodeJWSTransaction(jwsToken []byte) (inappOrder *game.SignedTransaction, err error) { parts := bytes.Split(jwsToken, []byte{'.'}) // todo: parts[0] 为header 用作验证jwt, 暂未验证 dec, err := jose.Base64Decode(parts[1]) log.Println("解码64", string(dec)) err = json.Unmarshal(dec, &inappOrder) return } // 根据订单号特征判断沙盒环境(测试订单通常以特定前缀开头) func isSandboxOrder(orderID string) bool { return strings.HasPrefix(orderID, "SANDBOX_") || strings.HasPrefix(orderID, "TEST_") }