kra-new/internal/biz/payment/payment_order.go

233 lines
7.2 KiB
Go

package payment
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
EventType string
EventSource string
EventMessage string
}
type PaymentOrderFilter struct {
Provider string
TradeNo string
ProviderTradeNo string
Keyword string
BusinessType string
BusinessID string
PaymentStatus string
RefundStatus string
CreatedFrom *time.Time
CreatedTo *time.Time
PaidFrom *time.Time
PaidTo *time.Time
IssueOnly bool
}
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)
SummarizePaymentOrders(context.Context, PaymentOrderFilter) (*PaymentOrderSummary, error)
ListPaymentEvents(context.Context, string, string, int) ([]*PaymentEvent, 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
}