package payment
import ( "context" "errors" "fmt" "sync" "time"
"github.com/alibaba/sentinel-golang/api" "github.com/alibaba/sentinel-golang/core/base" )
type BasePayTemplate struct { meta *PayMeta redisClient *redis.Client asyncPool *sync.WaitGroup }
func NewBasePayTemplate(meta *PayMeta, redisClient *redis.Client) *BasePayTemplate { return &BasePayTemplate{ meta: meta, redisClient: redisClient, asyncPool: &sync.WaitGroup{}, } }
func (t *BasePayTemplate) Pay(ctx context.Context, req *PayRequest) (*PayResponse, error) { if err := t.BeforePay(ctx, req); err != nil { return nil, fmt.Errorf("前置处理失败:%w", err) }
if err := t.CheckIdempotent(ctx, req); err != nil { return nil, fmt.Errorf("幂等校验失败:%w", err) }
if err := t.ValidateCommonParam(ctx, req); err != nil { return nil, fmt.Errorf("通用参数校验失败:%w", err) }
if err := t.ValidateCustomParam(ctx, req); err != nil { return nil, fmt.Errorf("自定义参数校验失败:%w", err) }
resp, err := t.CallPayAPIWithSentinel(ctx, req) if err != nil { return nil, fmt.Errorf("支付接口调用失败:%w", err) }
if t.NeedNotifyResult(ctx, req) { if err := t.NotifyResult(ctx, req, resp); err != nil { t.asyncPool.Add(1) go func() { defer t.asyncPool.Done() t.RetryNotify(ctx, req, resp) }() } }
t.RecordLogAsync(ctx, req, resp)
if err := t.AfterPay(ctx, req, resp); err != nil { return resp, fmt.Errorf("后置处理失败:%w", err) }
return resp, nil }
func (t *BasePayTemplate) ValidateCommonParam(ctx context.Context, req *PayRequest) error { if req.OrderID == "" { return errors.New("订单号不能为空") } if req.Amount <= 0 { return errors.New("支付金额必须大于0") } if req.Amount > t.meta.MaxAmount { return fmt.Errorf("超出%s单笔限额%.2f", t.meta.Name, t.meta.MaxAmount) } sceneValid := false for _, s := range t.meta.SupportScenes { if s == req.Scene { sceneValid = true break } } if !sceneValid { return fmt.Errorf("%s不支持%s场景", t.meta.Name, req.Scene) } return nil }
func (t *BasePayTemplate) CheckIdempotent(ctx context.Context, req *PayRequest) error { key := fmt.Sprintf("pay:idempotent:%s:%s", req.OrderID, req.PayCode) ok, err := t.redisClient.SetNX(ctx, key, "1", 5*time.Minute).Result() if err != nil { return fmt.Errorf("Redis操作失败:%w", err) } if !ok { return errors.New("请勿重复提交支付请求") } return nil }
func (t *BasePayTemplate) CallPayAPIWithSentinel(ctx context.Context, req *PayRequest) (*PayResponse, error) { resourceName := fmt.Sprintf("%s_pay_api", t.meta.PayCode) entry, err := api.Entry(resourceName, api.WithTrafficType(base.Inbound)) if err != nil { return nil, errors.New("当前支付方式暂时不可用,请更换支付方式") } defer entry.Exit()
return t.CallPayAPI(ctx, req) }
func (t *BasePayTemplate) RecordLogAsync(ctx context.Context, req *PayRequest, resp *PayResponse) { t.asyncPool.Add(1) go func() { defer t.asyncPool.Done() fmt.Printf("[异步日志] 订单:%s,支付方式:%s,金额:%.2f,结果:%t\n", req.OrderID, req.PayCode, req.Amount, resp.Success) }() }
func (t *BasePayTemplate) RetryNotify(ctx context.Context, req *PayRequest, resp *PayResponse) { for i := 0; i < 3; i++ { err := t.NotifyResult(ctx, req, resp) if err == nil { fmt.Printf("[重试通知成功] 订单:%s,次数:%d\n", req.OrderID, i+1) return } time.Sleep(1 * time.Second) } fmt.Printf("[重试通知失败] 订单:%s\n", req.OrderID) }
func (t *BasePayTemplate) BeforePay(ctx context.Context, req *PayRequest) error { return nil }
func (t *BasePayTemplate) AfterPay(ctx context.Context, req *PayRequest, resp *PayResponse) error { return nil }
func (t *BasePayTemplate) NeedNotifyResult(ctx context.Context, req *PayRequest) bool { return true }
func (t *BasePayTemplate) ValidateCustomParam(ctx context.Context, req *PayRequest) error { panic("必须由子类实现") }
func (t *BasePayTemplate) CallPayAPI(ctx context.Context, req *PayRequest) (*PayResponse, error) { panic("必须由子类实现") }
func (t *BasePayTemplate) NotifyResult(ctx context.Context, req *PayRequest, resp *PayResponse) error { panic("必须由子类实现") }
func (t *BasePayTemplate) GetMeta() *PayMeta { return t.meta }
|