265 lines
8.3 KiB
Go
265 lines
8.3 KiB
Go
package payment
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/database"
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/internal/modules/paymentconfig"
|
|
"hfb_sys/backend/internal/modules/qualityowner"
|
|
)
|
|
|
|
func TestConfirmQualityOwnerPaymentFreezesDepositAndIsIdempotent(t *testing.T) {
|
|
db := database.NewTestDB()
|
|
user := model.User{
|
|
Phone: "13900009401",
|
|
RealnameStatus: "verified",
|
|
Status: "active",
|
|
}
|
|
if err := db.Create(&user).Error; err != nil {
|
|
t.Fatalf("创建用户失败: %v", err)
|
|
}
|
|
if err := db.Create(&model.WalletAccount{
|
|
UserID: user.ID,
|
|
AvailableBalanceCent: 0,
|
|
Status: "active",
|
|
}).Error; err != nil {
|
|
t.Fatalf("创建钱包失败: %v", err)
|
|
}
|
|
payment := model.PaymentOrder{
|
|
PaymentNo: "PAYQUALITYOWNER01",
|
|
OrderID: 0,
|
|
OrderNo: "QOQUALITY",
|
|
BizID: user.ID,
|
|
UserID: user.ID,
|
|
Provider: "mock",
|
|
ThirdOrderID: "PAYQUALITYOWNER01",
|
|
AmountCent: 20_000,
|
|
BizType: qualityOwnerPayBizType,
|
|
Status: "paying",
|
|
}
|
|
if err := db.Create(&payment).Error; err != nil {
|
|
t.Fatalf("创建支付单失败: %v", err)
|
|
}
|
|
repo := NewRepository(db, nil, nil, WithQualityOwnerRepo(qualityowner.NewRepository(db)))
|
|
raw := map[string]string{"status": "2", "third_order_id": payment.ThirdOrderID}
|
|
if err := repo.confirmPaid(t.Context(), &payment, "2", time.Now(), raw, channelSourceMock); err != nil {
|
|
t.Fatalf("确认保证金支付失败: %v", err)
|
|
}
|
|
if err := repo.confirmPaid(t.Context(), &payment, "2", time.Now(), raw, channelSourceMock); err != nil {
|
|
t.Fatalf("重复确认保证金支付失败: %v", err)
|
|
}
|
|
|
|
var gotUser model.User
|
|
if err := db.First(&gotUser, user.ID).Error; err != nil {
|
|
t.Fatalf("读取用户失败: %v", err)
|
|
}
|
|
if gotUser.QualityOwnerStatus != qualityowner.StatusActive || gotUser.QualityOwnerDepositCent != 20_000 {
|
|
t.Fatalf("用户未开通优质号主: %+v", gotUser)
|
|
}
|
|
var account model.WalletAccount
|
|
if err := db.Where("user_id = ? AND distribution_site_id = 0", user.ID).First(&account).Error; err != nil {
|
|
t.Fatalf("读取钱包失败: %v", err)
|
|
}
|
|
if account.AvailableBalanceCent != 0 || account.FrozenBalanceCent != 20_000 {
|
|
t.Fatalf("保证金钱包余额不正确: %+v", account)
|
|
}
|
|
var ledgerCount int64
|
|
if err := db.Model(&model.WalletLedger{}).
|
|
Where("user_id = ? AND biz_type = ?", user.ID, "quality_owner_deposit").
|
|
Count(&ledgerCount).Error; err != nil {
|
|
t.Fatalf("统计保证金流水失败: %v", err)
|
|
}
|
|
if ledgerCount != 1 {
|
|
t.Fatalf("保证金流水条数 = %d, want 1", ledgerCount)
|
|
}
|
|
}
|
|
|
|
func TestNewQualityOwnerPaymentUsesBizIDAndMockCashier(t *testing.T) {
|
|
payment, err := newQualityOwnerPayment(88, 20_000, StartPaymentRequest{PayWay: "WXZF"}, runtimePaymentConfig{
|
|
Provider: "mock",
|
|
MerchantID: "mock-merchant",
|
|
PayWay: "WXZF",
|
|
JSPayFlag: "2",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("newQualityOwnerPayment() error = %v", err)
|
|
}
|
|
if payment.BizType != qualityOwnerPayBizType || payment.BizID != 88 || payment.UserID != 88 {
|
|
t.Fatalf("保证金支付单业务字段不正确: %+v", payment)
|
|
}
|
|
if payment.OrderID != 0 || payment.AmountCent != 20_000 {
|
|
t.Fatalf("保证金支付单订单/金额不正确: %+v", payment)
|
|
}
|
|
if payment.TDCode == "" || payment.ProviderOrderID == "" {
|
|
t.Fatalf("mock 支付单未生成收银台: %+v", payment)
|
|
}
|
|
}
|
|
|
|
func TestCanReuseQualityOwnerPaymentRejectsPaidActivation(t *testing.T) {
|
|
config := runtimePaymentConfig{
|
|
Provider: "leshua",
|
|
MerchantID: "merchant-1",
|
|
PayWay: "ZFBZF",
|
|
}
|
|
paid := model.PaymentOrder{
|
|
Status: "paid",
|
|
Provider: "leshua",
|
|
MerchantID: "merchant-1",
|
|
PayWay: "ZFBZF",
|
|
}
|
|
if canReuseQualityOwnerPayment(paid, config) {
|
|
t.Fatal("已完成的上轮保证金支付单不应被复用")
|
|
}
|
|
|
|
paying := paid
|
|
paying.Status = "paying"
|
|
if !canReuseQualityOwnerPayment(paying, config) {
|
|
t.Fatal("同一配置下尚未完成的支付单应继续复用")
|
|
}
|
|
}
|
|
|
|
func TestQualityOwnerCanReactivateAfterCancelAndOldPaymentCannotReactivate(t *testing.T) {
|
|
db := setupPaymentTestDB(t)
|
|
user := model.User{
|
|
Phone: "13900009402",
|
|
RealnameStatus: "verified",
|
|
Status: "active",
|
|
}
|
|
if err := db.Create(&user).Error; err != nil {
|
|
t.Fatalf("创建用户失败: %v", err)
|
|
}
|
|
if err := db.Create(&model.WalletAccount{
|
|
UserID: user.ID,
|
|
Status: "active",
|
|
}).Error; err != nil {
|
|
t.Fatalf("创建钱包失败: %v", err)
|
|
}
|
|
config := model.PaymentMerchantConfig{
|
|
Name: "优质号主模拟支付",
|
|
Provider: "mock",
|
|
MerchantID: "QUALITY_OWNER_MOCK",
|
|
PayWay: "ZFBZF",
|
|
IsDefault: true,
|
|
Status: "active",
|
|
Environment: "sandbox",
|
|
}
|
|
if err := db.Create(&config).Error; err != nil {
|
|
t.Fatalf("创建支付配置失败: %v", err)
|
|
}
|
|
|
|
qualityOwnerRepo := qualityowner.NewRepository(db)
|
|
paymentRepo := NewRepository(
|
|
db,
|
|
paymentconfig.NewRepository(db, &paymentconfig.MockEncryptor{}),
|
|
nil,
|
|
WithQualityOwnerRepo(qualityOwnerRepo),
|
|
)
|
|
|
|
first, err := paymentRepo.StartQualityOwner(t.Context(), user.ID, StartPaymentRequest{PayWay: "ZFBZF"}, "")
|
|
if err != nil {
|
|
t.Fatalf("首次在线开通失败: %v", err)
|
|
}
|
|
if !first.Paid {
|
|
t.Fatalf("首次支付状态 = %q, want paid", first.Status)
|
|
}
|
|
status, err := qualityOwnerRepo.Status(t.Context(), user.ID)
|
|
if err != nil {
|
|
t.Fatalf("读取首次开通状态失败: %v", err)
|
|
}
|
|
if !status.Active {
|
|
t.Fatal("首次支付后应开通优质号主")
|
|
}
|
|
|
|
status, err = qualityOwnerRepo.Cancel(t.Context(), user.ID, qualityowner.AuditMeta{})
|
|
if err != nil {
|
|
t.Fatalf("取消优质号主失败: %v", err)
|
|
}
|
|
if status.Active {
|
|
t.Fatal("取消后不应保持优质号主身份")
|
|
}
|
|
|
|
var firstPayment model.PaymentOrder
|
|
if err := db.First(&firstPayment, first.ID).Error; err != nil {
|
|
t.Fatalf("读取首次支付单失败: %v", err)
|
|
}
|
|
if err := paymentRepo.confirmPaid(
|
|
t.Context(),
|
|
&firstPayment,
|
|
"2",
|
|
time.Now(),
|
|
map[string]string{"status": "2", "third_order_id": firstPayment.ThirdOrderID},
|
|
channelSourceMock,
|
|
); err != nil {
|
|
t.Fatalf("重放旧支付回调失败: %v", err)
|
|
}
|
|
status, err = qualityOwnerRepo.Status(t.Context(), user.ID)
|
|
if err != nil {
|
|
t.Fatalf("读取旧回调后状态失败: %v", err)
|
|
}
|
|
if status.Active {
|
|
t.Fatal("取消后重放旧支付回调不应重新开通优质号主")
|
|
}
|
|
|
|
second, err := paymentRepo.StartQualityOwner(t.Context(), user.ID, StartPaymentRequest{PayWay: "ZFBZF"}, "")
|
|
if err != nil {
|
|
t.Fatalf("取消后再次在线开通失败: %v", err)
|
|
}
|
|
if !second.Paid {
|
|
t.Fatalf("第二次支付状态 = %q, want paid", second.Status)
|
|
}
|
|
if second.ID == first.ID || second.PaymentNo == first.PaymentNo {
|
|
t.Fatalf("第二次开通复用了旧支付单: first=%+v second=%+v", first, second)
|
|
}
|
|
status, err = qualityOwnerRepo.Status(t.Context(), user.ID)
|
|
if err != nil {
|
|
t.Fatalf("读取第二次开通状态失败: %v", err)
|
|
}
|
|
if !status.Active || status.DepositSource != qualityowner.DepositSourcePaid {
|
|
t.Fatalf("第二次在线支付后状态不正确: %+v", status)
|
|
}
|
|
|
|
var paymentCount int64
|
|
if err := db.Model(&model.PaymentOrder{}).
|
|
Where("user_id = ? AND biz_type = ?", user.ID, qualityOwnerPayBizType).
|
|
Count(&paymentCount).Error; err != nil {
|
|
t.Fatalf("统计保证金支付单失败: %v", err)
|
|
}
|
|
if paymentCount != 2 {
|
|
t.Fatalf("保证金支付单数量 = %d, want 2", paymentCount)
|
|
}
|
|
|
|
var duplicateFrozenCount int64
|
|
if err := db.Model(&model.WalletLedger{}).
|
|
Where(
|
|
"user_id = ? AND biz_type = ? AND biz_no = ? AND direction = ? AND balance_type = ?",
|
|
user.ID,
|
|
"quality_owner_deposit",
|
|
first.PaymentNo,
|
|
"in",
|
|
"frozen",
|
|
).
|
|
Count(&duplicateFrozenCount).Error; err != nil {
|
|
t.Fatalf("统计首次保证金冻结流水失败: %v", err)
|
|
}
|
|
if duplicateFrozenCount != 1 {
|
|
t.Fatalf("首次支付冻结流水数量 = %d, want 1", duplicateFrozenCount)
|
|
}
|
|
}
|
|
|
|
func TestConfirmQualityOwnerPaymentRejectsEmptyPaymentNo(t *testing.T) {
|
|
db := setupPaymentTestDB(t)
|
|
repo := NewRepository(db, nil, nil, WithQualityOwnerRepo(qualityowner.NewRepository(db)))
|
|
payment := model.PaymentOrder{
|
|
BizType: qualityOwnerPayBizType,
|
|
UserID: 1,
|
|
AmountCent: 20_000,
|
|
}
|
|
err := repo.confirmPaid(t.Context(), &payment, "2", time.Now(), nil, channelSourceMock)
|
|
if !errors.Is(err, qualityowner.ErrInvalidUser) {
|
|
t.Fatalf("空支付单号 error = %v, want quality owner invalid user", err)
|
|
}
|
|
}
|