You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.7 KiB

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_")
}