package service import ( "context" "crypto/ecdsa" "crypto/x509" "encoding/json" "encoding/pem" "fmt" "io/ioutil" "net/http" "strconv" "time" "tyc-server/app/main/api/internal/config" "github.com/golang-jwt/jwt/v4" ) // ApplePayService 是 Apple IAP 支付服务的结构体 type ApplePayService struct { config config.ApplepayConfig // 配置项 } // NewApplePayService 是一个构造函数,用于初始化 ApplePayService func NewApplePayService(c config.Config) *ApplePayService { return &ApplePayService{ config: c.Applepay, } } func (a *ApplePayService) GetIappayAppID(productName string) string { return fmt.Sprintf("%s.%s", a.config.BundleID, productName) } // VerifyReceipt 验证苹果支付凭证 func (a *ApplePayService) VerifyReceipt(ctx context.Context, receipt string) (*AppleVerifyResponse, error) { var reqUrl string if a.config.Sandbox { reqUrl = a.config.SandboxVerifyURL } else { reqUrl = a.config.ProductionVerifyURL } // 读取私钥 privateKey, err := loadPrivateKey(a.config.LoadPrivateKeyPath) if err != nil { return nil, fmt.Errorf("加载私钥失败:%v", err) } // 生成 JWT token, err := generateJWT(privateKey, a.config.KeyID, a.config.IssuerID) if err != nil { return nil, fmt.Errorf("生成JWT失败:%v", err) } // 构造查询参数 queryParams := fmt.Sprintf("?receipt-data=%s", receipt) fullUrl := reqUrl + queryParams // 构建 HTTP GET 请求 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullUrl, nil) if err != nil { return nil, fmt.Errorf("创建 HTTP 请求失败:%v", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) // 发送请求 client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("请求苹果验证接口失败:%v", err) } defer resp.Body.Close() // 解析响应 body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应体失败:%v", err) } var verifyResponse AppleVerifyResponse err = json.Unmarshal(body, &verifyResponse) if err != nil { return nil, fmt.Errorf("解析响应体失败:%v", err) } // 根据实际响应处理逻辑 if verifyResponse.Status != 0 { return nil, fmt.Errorf("验证失败,状态码:%d", verifyResponse.Status) } return &verifyResponse, nil } func loadPrivateKey(path string) (*ecdsa.PrivateKey, error) { data, err := ioutil.ReadFile(path) if err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil || block.Type != "PRIVATE KEY" { return nil, fmt.Errorf("无效的私钥数据") } key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return nil, err } ecdsaKey, ok := key.(*ecdsa.PrivateKey) if !ok { return nil, fmt.Errorf("私钥类型错误") } return ecdsaKey, nil } func generateJWT(privateKey *ecdsa.PrivateKey, keyID, issuerID string) (string, error) { now := time.Now() claims := jwt.RegisteredClaims{ Issuer: issuerID, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Hour)), Audience: jwt.ClaimStrings{"appstoreconnect-v1"}, } token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) token.Header["kid"] = keyID tokenString, err := token.SignedString(privateKey) if err != nil { return "", err } return tokenString, nil } // GenerateOutTradeNo 生成唯一订单号 func (a *ApplePayService) GenerateOutTradeNo() string { length := 16 timestamp := time.Now().UnixNano() timeStr := strconv.FormatInt(timestamp, 10) randomPart := strconv.Itoa(int(timestamp % 1e6)) combined := timeStr + randomPart if len(combined) >= length { return combined[:length] } for len(combined) < length { combined += strconv.Itoa(int(timestamp % 10)) } return combined } // AppleVerifyResponse 定义苹果验证接口的响应结构 type AppleVerifyResponse struct { Status int `json:"status"` // 验证状态码:0 表示收据有效 Receipt *Receipt `json:"receipt"` // 收据信息 } // Receipt 定义收据的精简结构 type Receipt struct { BundleID string `json:"bundle_id"` // 应用的 Bundle ID InApp []InAppItem `json:"in_app"` // 应用内购买记录 } // InAppItem 定义单条交易记录 type InAppItem struct { ProductID string `json:"product_id"` // 商品 ID TransactionID string `json:"transaction_id"` // 交易 ID PurchaseDate string `json:"purchase_date"` // 购买日期 (ISO 8601) OriginalTransID string `json:"original_transaction_id"` // 原始交易 ID }