From 40a45199a53ec70cc41d295cc034f8da8741fd09 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Tue, 16 Dec 2025 18:58:55 -0800 Subject: [PATCH] =?UTF-8?q?feat(apple=E6=94=AF=E4=BB=98):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=8C=89=E5=B9=B3=E5=8F=B0=E6=9F=A5=E8=AF=A2=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=96=B9=E5=BC=8F=E5=92=8C=E6=81=A2=E5=A4=8D=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加FindListByPlatform方法用于按平台查询支付方式 优化apple支付恢复交易逻辑,支持直接使用交易ID查询 添加API配置处理逻辑和错误回退机制 --- .../logic/public/iap/apple/restoreLogic.go | 80 +++++++++++++++++-- internal/model/payment/model.go | 9 +++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/internal/logic/public/iap/apple/restoreLogic.go b/internal/logic/public/iap/apple/restoreLogic.go index 0450681..fa4aaa1 100644 --- a/internal/logic/public/iap/apple/restoreLogic.go +++ b/internal/logic/public/iap/apple/restoreLogic.go @@ -2,9 +2,13 @@ package apple import ( "context" + "encoding/json" + "strings" "time" + "github.com/google/uuid" iapmodel "github.com/perfect-panel/server/internal/model/iap/apple" + "github.com/perfect-panel/server/internal/model/payment" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" @@ -12,7 +16,6 @@ import ( iapapple "github.com/perfect-panel/server/pkg/iap/apple" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/xerr" - "github.com/google/uuid" "github.com/pkg/errors" "gorm.io/gorm" ) @@ -37,19 +40,85 @@ func (l *RestoreLogic) Restore(req *types.RestoreAppleTransactionsRequest) error return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "invalid access") } pm, _ := iapapple.ParseProductMap(l.svcCtx.Config.Site.CustomData) + // Try to load payment config to get API credentials + var apiCfg iapapple.ServerAPIConfig + // We need to find *any* apple payment config to get credentials. + // In most cases, there is only one apple payment method. + // We can try to find by platform "apple" + payMethods, err := l.svcCtx.PaymentModel.FindListByPlatform(l.ctx, "apple") + if err == nil && len(payMethods) > 0 { + // Use the first available config + pay := payMethods[0] + var cfg payment.AppleIAPConfig + if err := cfg.Unmarshal([]byte(pay.Config)); err == nil { + apiCfg = iapapple.ServerAPIConfig{ + KeyID: cfg.KeyID, + IssuerID: cfg.IssuerID, + PrivateKey: cfg.PrivateKey, + Sandbox: cfg.Sandbox, + } + // Fix private key format if needed (same as in attachTransactionByIdLogic) + if !strings.Contains(apiCfg.PrivateKey, "\n") && strings.Contains(apiCfg.PrivateKey, "BEGIN PRIVATE KEY") { + apiCfg.PrivateKey = strings.ReplaceAll(apiCfg.PrivateKey, " ", "\n") + apiCfg.PrivateKey = strings.ReplaceAll(apiCfg.PrivateKey, "-----BEGIN\nPRIVATE\nKEY-----", "-----BEGIN PRIVATE KEY-----") + apiCfg.PrivateKey = strings.ReplaceAll(apiCfg.PrivateKey, "-----END\nPRIVATE\nKEY-----", "-----END PRIVATE KEY-----") + } + } + } + + // Fallback credentials if missing (dev/debug) + if apiCfg.PrivateKey == "" { + apiCfg.PrivateKey = `-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgsVDj0g/D7uNCm8aC +E4TuaiDT4Pgb1IuuZ69YdGNvcAegCgYIKoZIzj0DAQehRANCAARObgGumaESbPMM +SIRDAVLcWemp0fMlnfDE4EHmqcD58arEJWsr3aWEhc4BHocOUIGjko0cVWGchrFa +/T/KG1tr +-----END PRIVATE KEY-----` + apiCfg.KeyID = "2C4X3HVPM8" + } + if apiCfg.IssuerID == "" { + apiCfg.IssuerID = "34f54810-5118-4b7f-8069-c8c1e012b7a9" + } + // Try to get BundleID + if apiCfg.BundleID == "" && l.svcCtx.Config.Site.CustomData != "" { + var customData struct { + IapBundleId string `json:"iapBundleId"` + } + _ = json.Unmarshal([]byte(l.svcCtx.Config.Site.CustomData), &customData) + apiCfg.BundleID = customData.IapBundleId + } + return l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { - for _, j := range req.Transactions { - txp, err := iapapple.ParseTransactionJWS(j) - if err != nil { + for _, txID := range req.Transactions { + // 1. Try to verify as JWS first (if client sends JWS) + var txp *iapapple.TransactionPayload + var err error + + // Try to parse as JWS + if len(txID) > 50 && (strings.Contains(txID, ".") || strings.HasPrefix(txID, "ey")) { + txp, err = iapapple.VerifyTransactionJWS(txID) + } else { + // 2. If not JWS, treat as TransactionID and fetch from Apple + var jws string + jws, err = iapapple.GetTransactionInfo(apiCfg, txID) + if err == nil { + txp, err = iapapple.VerifyTransactionJWS(jws) + } + } + + if err != nil || txp == nil { + l.Errorw("restore: invalid transaction", logger.Field("id", txID), logger.Field("error", err)) continue } + m, ok := pm.Items[txp.ProductId] if !ok { continue } + // Check if already processed _, e := iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txp.OriginalTransactionId) if e == nil { - continue + continue // Already processed, skip } iapTx := &iapmodel.Transaction{ UserId: u.Id, @@ -83,4 +152,3 @@ func (l *RestoreLogic) Restore(req *types.RestoreAppleTransactionsRequest) error return nil }) } - diff --git a/internal/model/payment/model.go b/internal/model/payment/model.go index e273ca3..f6f42a2 100644 --- a/internal/model/payment/model.go +++ b/internal/model/payment/model.go @@ -12,6 +12,7 @@ type customPaymentLogicModel interface { FindAll(ctx context.Context) ([]*Payment, error) FindListByPage(ctx context.Context, page, size int, req *Filter) (int64, []*Payment, error) FindAvailableMethods(ctx context.Context) ([]*Payment, error) + FindListByPlatform(ctx context.Context, platform string) ([]*Payment, error) } // NewModel returns a model for the database table. @@ -21,6 +22,14 @@ func NewModel(conn *gorm.DB, c *redis.Client) Model { } } +func (m *customPaymentModel) FindListByPlatform(ctx context.Context, platform string) ([]*Payment, error) { + var resp []*Payment + err := m.QueryNoCacheCtx(ctx, &resp, func(conn *gorm.DB, v interface{}) error { + return conn.Model(&Payment{}).Where("platform = ?", platform).Find(v).Error + }) + return resp, err +} + func (m *customPaymentModel) FindOneByPaymentToken(ctx context.Context, token string) (*Payment, error) { var resp *Payment key := cachePaymentTokenPrefix + token