feat(pay-v2): P8 Task6 拒付 chargeback 记录 + chargeback.received
This commit is contained in:
@@ -102,6 +102,14 @@ func (p *Provider) Create(_ context.Context, req provider.CreateRequest) (*provi
|
||||
},
|
||||
},
|
||||
}},
|
||||
// 一次性(mode=payment)Checkout 生成的 PaymentIntent 打 out_trade_no metadata(P8
|
||||
// Task6):dispute webhook(charge.dispute.created)只带 payment_intent id,须靠此
|
||||
// metadata 才能反查回原订单——订阅首期/续费的 charge 由 invoice 生成,Stripe 不透传
|
||||
// SubscriptionData.Metadata 到 PI,那类拒付的 out_trade_no 天然解析为空(honest scope,
|
||||
// 见 VerifyCallback 的 charge.dispute.created 分支注释)。
|
||||
PaymentIntentData: &gostripe.CheckoutSessionPaymentIntentDataParams{
|
||||
Metadata: map[string]string{"out_trade_no": req.OutTradeNo},
|
||||
},
|
||||
}
|
||||
sess, err := p.sc.CheckoutSessions.New(params)
|
||||
if err != nil {
|
||||
@@ -259,6 +267,28 @@ func (p *Provider) VerifyCallback(_ context.Context, in provider.CallbackInput)
|
||||
ev.SubscriptionRef = inv.Subscription.ID
|
||||
}
|
||||
return ev, nil
|
||||
case "charge.dispute.created":
|
||||
// 拒付(设计 §6 决策记录):dispute payload 里 payment_intent 常只是 id 未展开,须
|
||||
// 反查 PaymentIntents.Get 取其 metadata["out_trade_no"](Create 时已 stamp,见上方
|
||||
// Create 的 PaymentIntentData 注释)。订阅首期/续费 charge 的 PI 不带该 metadata
|
||||
// (Stripe 不透传 SubscriptionData.Metadata 到 PI)→ OutTradeNo 天然为空,交
|
||||
// gateway.recordChargeback 记录但不转发(honest scope,精确定位留后续轮次)。
|
||||
var d gostripe.Dispute
|
||||
if err := json.Unmarshal(event.Data.Raw, &d); err != nil {
|
||||
return nil, fmt.Errorf("stripe: 解析 dispute 失败: %w", err)
|
||||
}
|
||||
ev := &provider.PaidEvent{
|
||||
Kind: provider.EventChargeback, DisputeRef: d.ID, Status: provider.PaidFailed,
|
||||
PaidAmountMinor: d.Amount, PaidCurrency: strings.ToUpper(string(d.Currency)),
|
||||
Reason: string(d.Reason), Raw: string(in.Raw),
|
||||
}
|
||||
if d.PaymentIntent != nil && d.PaymentIntent.ID != "" {
|
||||
ev.ProviderPaymentRef = d.PaymentIntent.ID
|
||||
if pi, err := p.sc.PaymentIntents.Get(d.PaymentIntent.ID, nil); err == nil && pi.Metadata != nil {
|
||||
ev.OutTradeNo = pi.Metadata["out_trade_no"] // 一次性单带;订阅 charge 为空
|
||||
}
|
||||
}
|
||||
return ev, nil
|
||||
default:
|
||||
// 其它事件此阶段不处理:归一化 pending(管线 Settle 视为 ignored)。
|
||||
return &provider.PaidEvent{Status: provider.PaidPending, Raw: string(in.Raw)}, nil
|
||||
|
||||
Reference in New Issue
Block a user