feat(apple支付): 添加按平台查询支付方式和恢复交易逻辑优化
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 6m52s
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 6m52s
添加FindListByPlatform方法用于按平台查询支付方式 优化apple支付恢复交易逻辑,支持直接使用交易ID查询 添加API配置处理逻辑和错误回退机制
This commit is contained in:
parent
5bc453b09f
commit
40a45199a5
@ -2,9 +2,13 @@ package apple
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
iapmodel "github.com/perfect-panel/server/internal/model/iap/apple"
|
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/model/user"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
@ -12,7 +16,6 @@ import (
|
|||||||
iapapple "github.com/perfect-panel/server/pkg/iap/apple"
|
iapapple "github.com/perfect-panel/server/pkg/iap/apple"
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/perfect-panel/server/pkg/xerr"
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -37,19 +40,85 @@ func (l *RestoreLogic) Restore(req *types.RestoreAppleTransactionsRequest) error
|
|||||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "invalid access")
|
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "invalid access")
|
||||||
}
|
}
|
||||||
pm, _ := iapapple.ParseProductMap(l.svcCtx.Config.Site.CustomData)
|
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 {
|
return l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
for _, j := range req.Transactions {
|
for _, txID := range req.Transactions {
|
||||||
txp, err := iapapple.ParseTransactionJWS(j)
|
// 1. Try to verify as JWS first (if client sends JWS)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
m, ok := pm.Items[txp.ProductId]
|
m, ok := pm.Items[txp.ProductId]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Check if already processed
|
||||||
_, e := iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txp.OriginalTransactionId)
|
_, e := iapmodel.NewModel(l.svcCtx.DB, l.svcCtx.Redis).FindByOriginalId(l.ctx, txp.OriginalTransactionId)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
continue
|
continue // Already processed, skip
|
||||||
}
|
}
|
||||||
iapTx := &iapmodel.Transaction{
|
iapTx := &iapmodel.Transaction{
|
||||||
UserId: u.Id,
|
UserId: u.Id,
|
||||||
@ -83,4 +152,3 @@ func (l *RestoreLogic) Restore(req *types.RestoreAppleTransactionsRequest) error
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ type customPaymentLogicModel interface {
|
|||||||
FindAll(ctx context.Context) ([]*Payment, error)
|
FindAll(ctx context.Context) ([]*Payment, error)
|
||||||
FindListByPage(ctx context.Context, page, size int, req *Filter) (int64, []*Payment, error)
|
FindListByPage(ctx context.Context, page, size int, req *Filter) (int64, []*Payment, error)
|
||||||
FindAvailableMethods(ctx context.Context) ([]*Payment, error)
|
FindAvailableMethods(ctx context.Context) ([]*Payment, error)
|
||||||
|
FindListByPlatform(ctx context.Context, platform string) ([]*Payment, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewModel returns a model for the database table.
|
// 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) {
|
func (m *customPaymentModel) FindOneByPaymentToken(ctx context.Context, token string) (*Payment, error) {
|
||||||
var resp *Payment
|
var resp *Payment
|
||||||
key := cachePaymentTokenPrefix + token
|
key := cachePaymentTokenPrefix + token
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user