feat(v2): order 补 biz_code + webhook payload 带 product_biz_code(收口 P2 延后项)
This commit is contained in:
@@ -72,7 +72,7 @@ type OrderResult struct {
|
||||
// account, persists a pending Order + Attempt (P1 OrderStore), and returns the
|
||||
// payment session {render_type, payload}. 加渠道不改 client(设计 §4.2)。
|
||||
func (g *Gateway) CreateOrder(ctx context.Context, in CreateOrderInput) (*OrderResult, error) {
|
||||
amountMinor, currency, subject, _, err := g.products.Resolve(in.SKU)
|
||||
amountMinor, currency, subject, bizCode, err := g.products.Resolve(in.SKU)
|
||||
if err != nil {
|
||||
return nil, err // ErrProductNotFound
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (g *Gateway) CreateOrder(ctx context.Context, in CreateOrderInput) (*OrderR
|
||||
|
||||
if err := g.orders.CreateOrder(&model.OrderV2{
|
||||
OutTradeNo: outNo, BizSystem: in.BizSystem, BizRef: in.BizRef,
|
||||
Subject: subject, AmountMinor: amountMinor, Currency: currency,
|
||||
BizCode: bizCode, Subject: subject, AmountMinor: amountMinor, Currency: currency,
|
||||
Status: model.OrderPendingV2,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -81,6 +81,23 @@ func TestCreateOrderPipeline(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOrderPersistsBizCode(t *testing.T) {
|
||||
g, _, _, orders := newGateway(t)
|
||||
res, err := g.CreateOrder(context.Background(), gateway.CreateOrderInput{
|
||||
SKU: "pro_year", Method: "fake", BizSystem: "pangolin", BizRef: "u-1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
o, err := orders.GetOrder(res.OrderNo)
|
||||
if err != nil {
|
||||
t.Fatalf("get order: %v", err)
|
||||
}
|
||||
if o.BizCode != "pro_year" { // stubResolver 的 bizCode
|
||||
t.Fatalf("order.BizCode = %q, want pro_year", o.BizCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOrderErrors(t *testing.T) {
|
||||
g, _, _, _ := newGateway(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -82,14 +82,15 @@ func (g *Gateway) enqueuePaymentSucceeded(att *model.Attempt, paidAt time.Time)
|
||||
return nil // 独立收款无业务方回调;或已翻转/已取消
|
||||
}
|
||||
data := map[string]any{
|
||||
"event_type": "payment.succeeded",
|
||||
"out_trade_no": o.OutTradeNo,
|
||||
"biz_system": o.BizSystem,
|
||||
"biz_ref": o.BizRef,
|
||||
"amount_minor": o.AmountMinor,
|
||||
"currency": o.Currency,
|
||||
"channel": att.Channel,
|
||||
"paid_at": paidAt.Format(time.RFC3339),
|
||||
"event_type": "payment.succeeded",
|
||||
"out_trade_no": o.OutTradeNo,
|
||||
"biz_system": o.BizSystem,
|
||||
"biz_ref": o.BizRef,
|
||||
"product_biz_code": o.BizCode, // 设计 §5:业务方按套餐码映射权益(时长/档位),不硬编码 product_id
|
||||
"amount_minor": o.AmountMinor,
|
||||
"currency": o.Currency,
|
||||
"channel": att.Channel,
|
||||
"paid_at": paidAt.Format(time.RFC3339),
|
||||
}
|
||||
return g.webhook.Enqueue(o.OutTradeNo, o.BizSystem, "payment.succeeded", data)
|
||||
}
|
||||
|
||||
@@ -53,6 +53,29 @@ func TestSettleHappyIdempotentAndWebhook(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettleWebhookCarriesProductBizCode(t *testing.T) {
|
||||
g, fp, spy, orders := newGateway(t)
|
||||
ctx := context.Background()
|
||||
res, _ := g.CreateOrder(ctx, gateway.CreateOrderInput{
|
||||
SKU: "pro_year", Method: "fake", BizSystem: "pangolin", BizRef: "u-9",
|
||||
})
|
||||
ref := attemptRef(t, orders)
|
||||
fp.SetQueryResult(ref, provider.PaidEvent{
|
||||
ProviderRef: ref, Status: provider.PaidSucceeded,
|
||||
PaidAmountMinor: 29990000, PaidCurrency: "USDT",
|
||||
})
|
||||
if _, err := g.SyncPendingAttempts(ctx, 10); err != nil {
|
||||
t.Fatalf("sync: %v", err)
|
||||
}
|
||||
if len(spy.calls) != 1 {
|
||||
t.Fatalf("want 1 webhook, got %d", len(spy.calls))
|
||||
}
|
||||
if spy.calls[0]["product_biz_code"] != "pro_year" {
|
||||
t.Fatalf("payload product_biz_code = %v, want pro_year", spy.calls[0]["product_biz_code"])
|
||||
}
|
||||
_ = res
|
||||
}
|
||||
|
||||
func TestSettleGuards(t *testing.T) {
|
||||
g, _, spy, orders := newGateway(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -47,6 +47,7 @@ type OrderV2 struct {
|
||||
BizSystem string `gorm:"index;size:32"` // 接入方(pangolin/jiu),空=独立收款
|
||||
BizRef string `gorm:"size:128"` // 接入方业务单号,回调原样带回
|
||||
ProductID uint64 `gorm:"index"`
|
||||
BizCode string `gorm:"index;size:64"` // 套餐码副本(下单时从 product 复制),回调带回业务方按码映射权益(设计 §5)
|
||||
Subject string `gorm:"size:128"`
|
||||
// 金额:int64 最小单位 + 币种(presentment/charge/settlement 三态在 charge 落地此处)
|
||||
AmountMinor int64 `gorm:"not null"`
|
||||
@@ -74,7 +75,7 @@ type Attempt struct {
|
||||
AmountMinor int64 `gorm:"not null"`
|
||||
Currency string `gorm:"size:16;not null"`
|
||||
Status AttemptStatus `gorm:"index;size:16;not null"`
|
||||
ExpiresAt *time.Time // 本次尝试超时
|
||||
ExpiresAt *time.Time // 本次尝试超时
|
||||
PaidAt *time.Time
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user