shanshanzhong 72400ae054
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 6m36s
feat(appleIAP): 实现苹果应用内购买通知处理逻辑
添加苹果IAP通知处理功能,包括解析和验证JWS签名、处理交易状态变更
新增订单号字段用于关联订单处理
实现交易记录的创建和更新逻辑
处理订阅状态的变更和过期时间计算
2025-12-15 17:49:16 -08:00

111 lines
2.4 KiB
Go

package apple
import (
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"strings"
"time"
)
func ParseTransactionJWS(jws string) (*TransactionPayload, error) {
parts := strings.Split(jws, ".")
if len(parts) != 3 {
return nil, ErrInvalidJWS
}
payloadB64 := parts[1]
// add padding if required
switch len(payloadB64) % 4 {
case 2:
payloadB64 += "=="
case 3:
payloadB64 += "="
}
data, err := base64.RawURLEncoding.DecodeString(payloadB64)
if err != nil {
return nil, err
}
var raw map[string]interface{}
if err = json.Unmarshal(data, &raw); err != nil {
return nil, err
}
var resp TransactionPayload
if v, ok := raw["bundleId"].(string); ok {
resp.BundleId = v
}
if v, ok := raw["productId"].(string); ok {
resp.ProductId = v
}
if v, ok := raw["transactionId"].(string); ok {
resp.TransactionId = v
}
if v, ok := raw["originalTransactionId"].(string); ok {
resp.OriginalTransactionId = v
}
if v, ok := raw["purchaseDate"].(float64); ok {
resp.PurchaseDate = time.UnixMilli(int64(v))
} else if v, ok := raw["purchaseDate"].(int64); ok {
resp.PurchaseDate = time.UnixMilli(v)
}
if v, ok := raw["revocationDate"].(float64); ok {
t := time.UnixMilli(int64(v))
resp.RevocationDate = &t
}
return &resp, nil
}
type jwsHeader struct {
Alg string `json:"alg"`
X5c []string `json:"x5c"`
Kid string `json:"kid"`
}
func VerifyTransactionJWS(jws string) (*TransactionPayload, error) {
parts := strings.Split(jws, ".")
if len(parts) != 3 {
return nil, ErrInvalidJWS
}
hdrB64 := parts[0]
switch len(hdrB64) % 4 {
case 2:
hdrB64 += "=="
case 3:
hdrB64 += "="
}
var hdr jwsHeader
hdrBytes, err := base64.RawURLEncoding.DecodeString(hdrB64)
if err != nil {
return nil, err
}
if err = json.Unmarshal(hdrBytes, &hdr); err != nil {
return nil, err
}
if len(hdr.X5c) == 0 {
return nil, ErrInvalidJWS
}
certDer, err := base64.StdEncoding.DecodeString(hdr.X5c[0])
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certDer)
if err != nil {
return nil, err
}
pub, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return nil, ErrInvalidJWS
}
signingInput := parts[0] + "." + parts[1]
sig, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, err
}
d := sha256.Sum256([]byte(signingInput))
if !ecdsa.VerifyASN1(pub, d[:], sig) {
return nil, ErrInvalidJWS
}
return ParseTransactionJWS(jws)
}