修复支付取消与成功统计

This commit is contained in:
yml
2026-06-14 23:58:39 +08:00
parent 3ff8c1de88
commit a275a006eb
11 changed files with 412 additions and 7 deletions
@@ -34,7 +34,14 @@ func (r *Repository) updateChannelStatus(ctx context.Context, paymentID uint64,
if source == channelSourceNotify {
updates["notified_at"] = time.Now()
}
return r.db.WithContext(ctx).Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(updates).Error
return r.db.WithContext(ctx).Model(&model.PaymentOrder{}).
Where("id = ? AND status NOT IN ?", paymentID, []string{"paid", "closed", "failed"}).
Updates(updates).Error
}
// isOrderPaymentTerminalStatus 判断订单支付流水是否已进入终态,终态不再被查询或普通回调刷回支付中。
func isOrderPaymentTerminalStatus(status string) bool {
return status == "paid" || status == "closed" || status == "failed"
}
func (r *Repository) confirmPaid(ctx context.Context, payment *model.PaymentOrder, status string, paidAt time.Time, raw map[string]string, source string) error {
var newConvID uint64
@@ -71,6 +78,11 @@ func (r *Repository) confirmPaid(ctx context.Context, payment *model.PaymentOrde
if newConvID > 0 && r.orderRepo != nil {
r.orderRepo.NotifyNewConversation(newConvID)
}
if latest, err := r.findPaymentByID(ctx, payment.ID); err == nil {
if runtimeConfig, err := r.runtimeConfigForPayment(ctx, latest); err == nil {
r.recordConfigUsage(ctx, runtimeConfig, latest)
}
}
return nil
}
func (r *Repository) markPaymentFailed(ctx context.Context, paymentID uint64, raw map[string]string, message string) error {
+5 -1
View File
@@ -14,11 +14,15 @@ func (r *Repository) Query(ctx context.Context, userID uint64, orderID uint64) (
}
return nil, err
}
if isOrderPaymentTerminalStatus(payment.Status) {
dto := toDTO(payment)
return &dto, nil
}
runtimeConfig, err := r.runtimeConfigForPayment(ctx, &payment)
if err != nil {
return nil, ErrPaymentUnavailable
}
if payment.Status == "paid" || runtimeConfig.isMockMode() {
if runtimeConfig.isMockMode() {
dto := toDTO(payment)
return &dto, nil
}
@@ -150,11 +150,14 @@ func runtimeConfigFromDTO(dto *paymentconfig.ConfigDTO) *runtimePaymentConfig {
}
}
// recordConfigUsage 记录支付配置命中情况,失败只写日志不影响主流程。
// recordConfigUsage 记录成功支付使用的配置,失败只写日志不影响主流程。
func (r *Repository) recordConfigUsage(ctx context.Context, runtimeConfig *runtimePaymentConfig, payment *model.PaymentOrder) {
if r.configRepo == nil || runtimeConfig == nil || payment == nil || runtimeConfig.ID == 0 {
return
}
if payment.BizType != "order_pay" || payment.Status != "paid" {
return
}
if err := r.configRepo.RecordUsage(ctx, runtimeConfig.ID, payment.ID, runtimeConfig.Provider, runtimeConfig.MerchantID, payment.AmountCent, payment.BizType); err != nil {
log.Printf("[payment] record config usage failed config_id=%d payment_id=%d err=%v", runtimeConfig.ID, payment.ID, err)
}
@@ -512,3 +512,63 @@ func TestConfirmPaidRollsBackOrderWhenPaymentUpdateFails(t *testing.T) {
t.Fatalf("asset status = %q/%q, want rollback to published/published", latestListing.Status, latestAccount.Status)
}
}
// TestQueryClosedPaymentReturnsLocalStatus 验证关闭状态不再依赖渠道配置或继续轮询渠道。
func TestQueryClosedPaymentReturnsLocalStatus(t *testing.T) {
db := setupPaymentTestDB(t)
repo := NewRepository(db, nil, nil)
payment := model.PaymentOrder{
PaymentNo: "PAY20260614CLOSED",
OrderID: 9001,
OrderNo: "ORD20260614CLOSED",
UserID: 8001,
Provider: "lakala",
ThirdOrderID: "PAY20260614CLOSED",
AmountCent: 1000,
BizType: "order_pay",
Status: "closed",
}
if err := db.Create(&payment).Error; err != nil {
t.Fatalf("create payment failed: %v", err)
}
dto, err := repo.Query(t.Context(), payment.UserID, payment.OrderID)
if err != nil {
t.Fatalf("Query() error = %v", err)
}
if dto.Status != "closed" {
t.Fatalf("status = %q, want closed", dto.Status)
}
}
// TestApplyChannelStatusKeepsTerminalPayment 验证已关闭支付单不会被渠道普通状态重新刷回支付中。
func TestApplyChannelStatusKeepsTerminalPayment(t *testing.T) {
db := setupPaymentTestDB(t)
repo := NewRepository(db, nil, nil)
payment := model.PaymentOrder{
PaymentNo: "PAY20260614TERMINAL",
OrderID: 9002,
OrderNo: "ORD20260614TERMINAL",
UserID: 8002,
Provider: "lakala",
ThirdOrderID: "PAY20260614TERMINAL",
AmountCent: 1000,
BizType: "order_pay",
Status: "closed",
}
if err := db.Create(&payment).Error; err != nil {
t.Fatalf("create payment failed: %v", err)
}
if err := repo.applyChannelStatus(t.Context(), &payment, "paying", "", map[string]string{"status": "paying"}, channelSourceNotify); err != nil {
t.Fatalf("applyChannelStatus() error = %v", err)
}
var latest model.PaymentOrder
if err := db.First(&latest, payment.ID).Error; err != nil {
t.Fatalf("find payment failed: %v", err)
}
if latest.Status != "closed" {
t.Fatalf("status = %q, want closed", latest.Status)
}
}