package biz import ( "context" "encoding/json" "errors" "strings" "sync" "time" ) const ( PaymentStatusInitialized = "initialized" PaymentStatusPending = "pending" PaymentStatusPaid = "paid" PaymentStatusFailed = "failed" PaymentStatusClosed = "closed" PaymentStatusPartiallyRefunded = "partially_refunded" PaymentStatusRefunded = "refunded" FulfillmentStatusPending = "pending" FulfillmentStatusProcessing = "processing" FulfillmentStatusSucceeded = "succeeded" FulfillmentStatusFailed = "failed" RefundStatusNone = "none" RefundStatusProcessing = "processing" RefundStatusPending = "pending" RefundStatusPartial = "partial" RefundStatusSucceeded = "succeeded" RefundStatusFailed = "failed" ) var ( ErrPaymentOrderNotFound = errors.New("支付订单不存在") ErrPaymentOrderConflict = errors.New("支付订单参数冲突") ErrPaymentOrderBusy = errors.New("支付订单正在处理中") ErrPaymentOrderState = errors.New("支付订单状态不允许当前操作") ErrPaymentProviderConflict = errors.New("支付平台交易号冲突") ) // PaymentOrder is one payment attempt. Business modules own their domain // orders and link to this record through BusinessType and BusinessID. type PaymentOrder struct { ID uint64 TradeNo string Provider string ProviderTradeNo string QueryID string BusinessType string BusinessID string Subject string PaymentMode string OriginalAmount int64 Amount int64 PaidAmount int64 PayerPaidAmount int64 CashPaidAmount int64 PointPaidAmount int64 DiscountAmount int64 ProviderDiscountAmount int64 MerchantDiscountAmount int64 SettlementAmount int64 Currency string PayerCurrency string AmountBreakdownKnown bool PaymentStatus string ProviderStatus string FulfillmentStatus string RefundStatus string RefundedAmount int64 RefundRequestedAmount int64 RefundNo string ConfirmationID string RequestFingerprint string CreatePayload json.RawMessage Extra json.RawMessage LastEventID string LastPayloadHash string LastError string FulfillmentToken string RefundToken string Version uint64 CreatedAt time.Time UpdatedAt time.Time PaidAt *time.Time FulfilledAt *time.Time RefundedAt *time.Time FulfillmentLeaseUntil *time.Time RefundLeaseUntil *time.Time } type PaymentProviderUpdate struct { Status string ProviderStatus string ProviderTradeNo string QueryID string Amount int64 PayerPaidAmount int64 CashPaidAmount int64 PointPaidAmount int64 DiscountAmount int64 ProviderDiscountAmount int64 MerchantDiscountAmount int64 SettlementAmount int64 Currency string PayerCurrency string AmountBreakdownKnown bool EventID string PayloadHash string CreatePayload json.RawMessage } type PaymentOrderFilter struct { Provider string TradeNo string BusinessType string BusinessID string PaymentStatus string RefundStatus string } type PaymentOrderRepo interface { CreatePaymentOrder(context.Context, *PaymentOrder) (order *PaymentOrder, created bool, err error) FindPaymentOrder(context.Context, string, string) (*PaymentOrder, error) ListPaymentOrders(context.Context, int, int, PaymentOrderFilter) ([]*PaymentOrder, int64, error) RecordPaymentCreate(context.Context, string, string, *PaymentProviderUpdate) (*PaymentOrder, error) ApplyPaymentResult(context.Context, string, string, *PaymentProviderUpdate) (*PaymentOrder, error) BeginPaymentFulfillment(context.Context, string, string, time.Duration) (order *PaymentOrder, token string, duplicate bool, err error) CompletePaymentFulfillment(context.Context, string, string, string, bool, string) (*PaymentOrder, error) BeginPaymentRefund(context.Context, string, string, int64, time.Duration) (order *PaymentOrder, token string, err error) CompletePaymentRefundRequest(context.Context, string, string, string, bool, string) (*PaymentOrder, error) ConfirmPaymentRefund(context.Context, string, string, string, int64, bool, string) (*PaymentOrder, error) } // PaymentOrderSource is implemented by each business module. It returns the // canonical order facts; client-provided amount, currency and subject are not // trusted once the persistent payment-order flow is enabled. type PaymentOrderSource interface { Type() string PreparePayment(context.Context, string, string, string) (*PaymentIntent, error) } type PaymentBusinessModule interface { PaymentOrderSource PaymentFulfillmentHandler PaymentRefundAuthorizer } type PaymentRefundAuthorizer interface { AuthorizeRefund(context.Context, *PaymentOrder, int64) error } type PaymentOrderSourceRegistry struct { mu sync.RWMutex sources map[string]PaymentOrderSource } func NewPaymentOrderSourceRegistry() *PaymentOrderSourceRegistry { return &PaymentOrderSourceRegistry{sources: map[string]PaymentOrderSource{}} } func (r *PaymentOrderSourceRegistry) Register(source PaymentOrderSource) error { if source == nil || strings.TrimSpace(source.Type()) == "" { return errors.New("支付业务订单来源无效") } r.mu.Lock() defer r.mu.Unlock() if _, exists := r.sources[source.Type()]; exists { return errors.New("支付业务订单来源已注册: " + source.Type()) } r.sources[source.Type()] = source return nil } func (r *PaymentOrderSourceRegistry) Source(kind string) PaymentOrderSource { if r == nil { return nil } r.mu.RLock() defer r.mu.RUnlock() return r.sources[kind] } func (r *PaymentOrderSourceRegistry) Unregister(kind string) { if r == nil { return } r.mu.Lock() defer r.mu.Unlock() delete(r.sources, kind) } func (o *PaymentOrder) Intent() *PaymentIntent { if o == nil { return nil } return &PaymentIntent{ Provider: o.Provider, TradeNo: o.TradeNo, BusinessType: o.BusinessType, BusinessID: o.BusinessID, Subject: o.Subject, Amount: o.Amount, Currency: o.Currency, Extra: append(json.RawMessage(nil), o.Extra...), PaymentMode: defaultPaymentMode(o.Provider, o.PaymentMode), OriginalAmount: o.OriginalAmount, } } func defaultPaymentMode(provider, mode string) string { if strings.TrimSpace(mode) != "" { return mode } if provider == PaymentInternal { return PaymentModeInternal } return PaymentModeExternal }