feat(appleIAP): 实现苹果应用内购买通知处理逻辑
Build docker and publish / build (20.15.1) (push) Successful in 6m36s
Build docker and publish / build (20.15.1) (push) Successful in 6m36s
添加苹果IAP通知处理功能,包括解析和验证JWS签名、处理交易状态变更 新增订单号字段用于关联订单处理 实现交易记录的创建和更新逻辑 处理订阅状态的变更和过期时间计算
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
@@ -53,3 +56,55 @@ func ParseTransactionJWS(jws string) (*TransactionPayload, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NotificationEnvelope struct {
|
||||
NotificationType string
|
||||
TransactionJWS string
|
||||
}
|
||||
|
||||
func ParseNotificationSignedPayload(jws string) (*NotificationEnvelope, error) {
|
||||
parts := strings.Split(jws, ".")
|
||||
if len(parts) != 3 {
|
||||
return nil, ErrInvalidJWS
|
||||
}
|
||||
payloadB64 := parts[1]
|
||||
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
|
||||
}
|
||||
env := &NotificationEnvelope{}
|
||||
if v, ok := raw["notificationType"].(string); ok {
|
||||
env.NotificationType = v
|
||||
}
|
||||
if d, ok := raw["data"].(map[string]interface{}); ok {
|
||||
if sjws, ok := d["signedTransactionInfo"].(string); ok {
|
||||
env.TransactionJWS = sjws
|
||||
}
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func VerifyNotificationSignedPayload(jws string) (*TransactionPayload, string, error) {
|
||||
env, err := ParseNotificationSignedPayload(jws)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if env.TransactionJWS == "" {
|
||||
return nil, env.NotificationType, ErrInvalidJWS
|
||||
}
|
||||
tx, err := VerifyTransactionJWS(env.TransactionJWS)
|
||||
if err != nil {
|
||||
return nil, env.NotificationType, err
|
||||
}
|
||||
return tx, env.NotificationType, nil
|
||||
}
|
||||
Reference in New Issue
Block a user