package payment import ( "fmt" bizpayment "kra/internal/biz/payment" "time" "github.com/google/uuid" "gorm.io/gorm" ) const DemoTradePrefix = "DEMO-PAY-" type DemoSeedResult struct { Orders int Events int } // SeedDemoOrders replaces only DEMO-PAY-* records with a representative set // of payment operations for administration-page review. func SeedDemoOrders(db *gorm.DB, now time.Time) (*DemoSeedResult, error) { if db == nil { return nil, fmt.Errorf("demo payment database is nil") } if !db.Migrator().HasTable(&paymentOrderPO{}) || !db.Migrator().HasTable(&paymentEventPO{}) { return nil, fmt.Errorf("payment tables are not migrated") } if now.IsZero() { now = time.Now().UTC().Truncate(time.Second) } else { now = now.UTC().Truncate(time.Second) } type demoOrder struct { order paymentOrderPO events []paymentEventPO } makeOrder := func(index int, provider, subject, status, fulfillment, refund string, amount int64, currency string, created time.Time) paymentOrderPO { tradeNo := fmt.Sprintf("%s%02d", DemoTradePrefix, index) return paymentOrderPO{ TradeNo: tradeNo, Provider: provider, BusinessType: bizpayment.DemoBusinessType, BusinessID: fmt.Sprintf("DEMO-LICENSE-%02d", index), Subject: subject, PaymentMode: bizpayment.PaymentModeExternal, OriginalAmount: amount, Amount: amount, Currency: currency, PaymentStatus: status, FulfillmentStatus: fulfillment, RefundStatus: refund, ConfirmationID: uuid.NewString(), RequestFingerprint: fmt.Sprintf("demo-fingerprint-%02d", index), CreatePayload: "{}", Extra: "{}", Version: 1, CreatedAt: created, UpdatedAt: created, } } makeEvent := func(order paymentOrderPO, eventType, source, status, providerStatus, message string, amount int64, at time.Time) paymentEventPO { event := paymentEventPO{ Provider: order.Provider, TradeNo: order.TradeNo, Type: eventType, Source: source, Status: status, ProviderStatus: providerStatus, Message: message, Amount: amount, Currency: order.Currency, CreatedAt: at, } if source == "client" { event.ClientIP, event.UserAgent, event.DeviceID = "111.29.161.79", "Kra-Demo/1.2 Android/16", fmt.Sprintf("DEMO-DEVICE-%s", order.TradeNo) } if source == "admin" { event.OperatorID, event.OperatorName = 1, "demo-admin" } return event } providerDate := now.Format("20060102") created1, paid1, fulfilled1 := now.Add(-35*time.Minute), now.Add(-34*time.Minute), now.Add(-33*time.Minute) paid := makeOrder(1, bizpayment.PaymentAlipay, "专业版 7 天授权", bizpayment.PaymentStatusPaid, bizpayment.FulfillmentStatusSucceeded, bizpayment.RefundStatusNone, 940, "CNY", created1) providerTrade1 := fmt.Sprintf("DEMO-ALIPAY-%s%04d", providerDate, 1) paid.ProviderTradeNo = &providerTrade1 paid.ProviderStatus, paid.PaidAmount, paid.PayerPaidAmount, paid.CashPaidAmount, paid.SettlementAmount = "TRADE_SUCCESS", 940, 940, 940, 934 paid.AmountBreakdownKnown, paid.PaidAt, paid.FulfilledAt, paid.UpdatedAt = true, &paid1, &fulfilled1, fulfilled1 pendingCreated := now.Add(-28 * time.Minute) pending := makeOrder(2, bizpayment.PaymentWechatV3, "专业版 30 天授权", bizpayment.PaymentStatusPending, bizpayment.FulfillmentStatusPending, bizpayment.RefundStatusNone, 2990, "CNY", pendingCreated) pending.ProviderStatus = "NOTPAY" pending.UpdatedAt = now.Add(-27 * time.Minute) failedCreated := now.Add(-24 * time.Minute) failed := makeOrder(3, bizpayment.PaymentPayPal, "海外数字商品", bizpayment.PaymentStatusFailed, bizpayment.FulfillmentStatusPending, bizpayment.RefundStatusNone, 1299, "USD", failedCreated) failed.ProviderStatus, failed.LastError = "DECLINED", "付款方式被渠道拒绝" failed.UpdatedAt = now.Add(-23 * time.Minute) fulfillCreated, fulfillPaid := now.Add(-20*time.Minute), now.Add(-19*time.Minute) fulfillFailed := makeOrder(4, bizpayment.PaymentAlipayV3, "企业版 90 天授权", bizpayment.PaymentStatusPaid, bizpayment.FulfillmentStatusFailed, bizpayment.RefundStatusNone, 8990, "CNY", fulfillCreated) providerTrade4 := fmt.Sprintf("DEMO-ALIPAY-%s%04d", providerDate, 4) fulfillFailed.ProviderTradeNo, fulfillFailed.ProviderStatus = &providerTrade4, "TRADE_SUCCESS" fulfillFailed.PaidAmount, fulfillFailed.SettlementAmount, fulfillFailed.PaidAt = 8990, 8936, &fulfillPaid fulfillFailed.LastError, fulfillFailed.UpdatedAt = "业务授权服务暂时不可用,可人工重试发货", now.Add(-18*time.Minute) partialCreated, partialPaid, partialRefunded := now.Add(-16*time.Minute), now.Add(-15*time.Minute), now.Add(-12*time.Minute) partial := makeOrder(5, bizpayment.PaymentWechatV3, "团队版 30 天授权", bizpayment.PaymentStatusPartiallyRefunded, bizpayment.FulfillmentStatusSucceeded, bizpayment.RefundStatusPartial, 5000, "CNY", partialCreated) providerTrade5 := fmt.Sprintf("DEMO-WECHAT-%s%04d", providerDate, 5) partial.ProviderTradeNo, partial.ProviderStatus = &providerTrade5, "SUCCESS" partial.PaidAmount, partial.SettlementAmount, partial.RefundedAmount = 5000, 4970, 1200 partial.PaidAt, partial.FulfilledAt, partial.RefundedAt, partial.UpdatedAt = &partialPaid, &partialPaid, &partialRefunded, partialRefunded refundCreated, refundPaid := now.Add(-11*time.Minute), now.Add(-10*time.Minute) refundPending := makeOrder(6, bizpayment.PaymentAlipay, "增值服务包", bizpayment.PaymentStatusPaid, bizpayment.FulfillmentStatusSucceeded, bizpayment.RefundStatusPending, 1880, "CNY", refundCreated) providerTrade6 := fmt.Sprintf("DEMO-ALIPAY-%s%04d", providerDate, 6) refundPending.ProviderTradeNo, refundPending.ProviderStatus = &providerTrade6, "TRADE_SUCCESS" refundPending.PaidAmount, refundPending.SettlementAmount, refundPending.RefundRequestedAmount = 1880, 1869, 600 refundPending.RefundNo, refundPending.PaidAt, refundPending.FulfilledAt, refundPending.UpdatedAt = "DEMO-REFUND-06", &refundPaid, &refundPaid, now.Add(-8*time.Minute) refundedCreated, refundedPaid, refundedAt := now.Add(-9*time.Minute), now.Add(-8*time.Minute), now.Add(-5*time.Minute) refunded := makeOrder(7, bizpayment.PaymentWechatV3, "体验卡 7 天", bizpayment.PaymentStatusRefunded, bizpayment.FulfillmentStatusSucceeded, bizpayment.RefundStatusSucceeded, 500, "CNY", refundedCreated) providerTrade7 := fmt.Sprintf("DEMO-WECHAT-%s%04d", providerDate, 7) refunded.ProviderTradeNo, refunded.ProviderStatus = &providerTrade7, "REFUND_SUCCESS" refunded.PaidAmount, refunded.SettlementAmount, refunded.RefundedAmount = 500, 497, 500 refunded.PaidAt, refunded.FulfilledAt, refunded.RefundedAt, refunded.UpdatedAt = &refundedPaid, &refundedPaid, &refundedAt, refundedAt internalCreated, internalPaid := now.Add(-4*time.Minute), now.Add(-3*time.Minute) internal := makeOrder(8, bizpayment.PaymentInternal, "积分兑换月卡", bizpayment.PaymentStatusPaid, bizpayment.FulfillmentStatusSucceeded, bizpayment.RefundStatusNone, 1200, "POINT", internalCreated) internal.PaymentMode, internal.ProviderStatus = bizpayment.PaymentModeInternal, "AUTHORIZED" internal.PaidAmount, internal.PointPaidAmount, internal.SettlementAmount, internal.AmountBreakdownKnown = 1200, 1200, 1200, true internal.PaidAt, internal.FulfilledAt, internal.UpdatedAt = &internalPaid, &internalPaid, internalPaid demos := []demoOrder{ {paid, []paymentEventPO{makeEvent(paid, bizpayment.PaymentEventOrderCreated, "client", bizpayment.PaymentStatusInitialized, "", "客户端创建订单", paid.Amount, created1), makeEvent(paid, bizpayment.PaymentEventPaymentCallback, bizpayment.PaymentAlipay, bizpayment.PaymentStatusPaid, paid.ProviderStatus, "支付宝异步通知验签并主动查单成功", paid.PaidAmount, paid1), makeEvent(paid, bizpayment.PaymentEventFulfillmentFinished, "system", bizpayment.FulfillmentStatusSucceeded, "", "授权自动激活成功,有效期 7 天", 0, fulfilled1)}}, {pending, []paymentEventPO{makeEvent(pending, bizpayment.PaymentEventOrderCreated, "client", bizpayment.PaymentStatusInitialized, "", "客户端创建订单", pending.Amount, pendingCreated), makeEvent(pending, bizpayment.PaymentEventProviderCreated, bizpayment.PaymentWechatV3, bizpayment.PaymentStatusPending, pending.ProviderStatus, "预支付订单已创建,等待买家付款", pending.Amount, pending.UpdatedAt)}}, {failed, []paymentEventPO{makeEvent(failed, bizpayment.PaymentEventOrderCreated, "client", bizpayment.PaymentStatusInitialized, "", "客户端创建订单", failed.Amount, failedCreated), makeEvent(failed, bizpayment.PaymentEventPaymentSynchronized, "admin", bizpayment.PaymentStatusFailed, failed.ProviderStatus, failed.LastError, 0, failed.UpdatedAt)}}, {fulfillFailed, []paymentEventPO{makeEvent(fulfillFailed, bizpayment.PaymentEventPaymentCallback, bizpayment.PaymentAlipayV3, bizpayment.PaymentStatusPaid, fulfillFailed.ProviderStatus, "支付成功", fulfillFailed.PaidAmount, fulfillPaid), makeEvent(fulfillFailed, bizpayment.PaymentEventFulfillmentFinished, "system", bizpayment.FulfillmentStatusFailed, "", fulfillFailed.LastError, 0, fulfillFailed.UpdatedAt)}}, {partial, []paymentEventPO{makeEvent(partial, bizpayment.PaymentEventPaymentCallback, bizpayment.PaymentWechatV3, bizpayment.PaymentStatusPaid, partial.ProviderStatus, "支付成功并完成发货", partial.PaidAmount, partialPaid), makeEvent(partial, bizpayment.PaymentEventRefundConfirmed, bizpayment.PaymentWechatV3, bizpayment.RefundStatusPartial, "SUCCESS", "售后部分退款 12.00 CNY", partial.RefundedAmount, partialRefunded)}}, {refundPending, []paymentEventPO{makeEvent(refundPending, bizpayment.PaymentEventPaymentCallback, bizpayment.PaymentAlipay, bizpayment.PaymentStatusPaid, refundPending.ProviderStatus, "支付成功并完成发货", refundPending.PaidAmount, refundPaid), makeEvent(refundPending, bizpayment.PaymentEventRefundSubmitted, "admin", bizpayment.RefundStatusPending, "", "客户重复购买,退款已提交渠道", refundPending.RefundRequestedAmount, refundPending.UpdatedAt)}}, {refunded, []paymentEventPO{makeEvent(refunded, bizpayment.PaymentEventPaymentCallback, bizpayment.PaymentWechatV3, bizpayment.PaymentStatusPaid, "SUCCESS", "支付成功并完成发货", refunded.PaidAmount, refundedPaid), makeEvent(refunded, bizpayment.PaymentEventRefundConfirmed, bizpayment.PaymentWechatV3, bizpayment.RefundStatusSucceeded, refunded.ProviderStatus, "原路全额退款成功", refunded.RefundedAmount, refundedAt)}}, {internal, []paymentEventPO{makeEvent(internal, bizpayment.PaymentEventOrderCreated, "client", bizpayment.PaymentStatusInitialized, "", "创建积分支付订单", internal.Amount, internalCreated), makeEvent(internal, bizpayment.PaymentEventPaymentSynchronized, bizpayment.PaymentInternal, bizpayment.PaymentStatusPaid, internal.ProviderStatus, "积分扣减与授权发放成功", internal.PaidAmount, internalPaid)}}, } result := &DemoSeedResult{} err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Where("trade_no LIKE ?", DemoTradePrefix+"%").Delete(&paymentEventPO{}).Error; err != nil { return err } if err := tx.Where("trade_no LIKE ?", DemoTradePrefix+"%").Delete(&paymentOrderPO{}).Error; err != nil { return err } for i := range demos { if err := tx.Create(&demos[i].order).Error; err != nil { return err } result.Orders++ if len(demos[i].events) > 0 { if err := tx.Create(&demos[i].events).Error; err != nil { return err } result.Events += len(demos[i].events) } } return nil }) return result, err }