Check direct payment callback currency, provider, and paid amount before settling Alipay or WxPay top-ups.
380 lines
13 KiB
Go
380 lines
13 KiB
Go
package model
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/shopspring/decimal"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func insertUserForPaymentGuardTest(t *testing.T, id int, quota int) {
|
|
t.Helper()
|
|
user := &User{
|
|
Id: id,
|
|
Username: "payment_guard_user",
|
|
Status: common.UserStatusEnabled,
|
|
Quota: quota,
|
|
}
|
|
require.NoError(t, DB.Create(user).Error)
|
|
}
|
|
|
|
func insertSubscriptionPlanForPaymentGuardTest(t *testing.T, id int) *SubscriptionPlan {
|
|
t.Helper()
|
|
plan := &SubscriptionPlan{
|
|
Id: id,
|
|
Title: "Guard Plan",
|
|
PriceAmount: 9.99,
|
|
Currency: "USD",
|
|
DurationUnit: SubscriptionDurationMonth,
|
|
DurationValue: 1,
|
|
Enabled: true,
|
|
TotalAmount: 1000,
|
|
}
|
|
require.NoError(t, DB.Create(plan).Error)
|
|
return plan
|
|
}
|
|
|
|
func insertSubscriptionOrderForPaymentGuardTest(t *testing.T, tradeNo string, userID int, planID int, paymentProvider string) {
|
|
t.Helper()
|
|
order := &SubscriptionOrder{
|
|
UserId: userID,
|
|
PlanId: planID,
|
|
Money: 9.99,
|
|
TradeNo: tradeNo,
|
|
PaymentMethod: paymentProvider,
|
|
PaymentProvider: paymentProvider,
|
|
Status: common.TopUpStatusPending,
|
|
CreateTime: time.Now().Unix(),
|
|
}
|
|
require.NoError(t, order.Insert())
|
|
}
|
|
|
|
func insertTopUpForPaymentGuardTest(t *testing.T, tradeNo string, userID int, paymentProvider string) {
|
|
t.Helper()
|
|
topUp := &TopUp{
|
|
UserId: userID,
|
|
Amount: 2,
|
|
Money: 9.99,
|
|
TradeNo: tradeNo,
|
|
PaymentMethod: paymentProvider,
|
|
PaymentProvider: paymentProvider,
|
|
Status: common.TopUpStatusPending,
|
|
CreateTime: time.Now().Unix(),
|
|
}
|
|
require.NoError(t, topUp.Insert())
|
|
}
|
|
|
|
func getTopUpStatusForPaymentGuardTest(t *testing.T, tradeNo string) string {
|
|
t.Helper()
|
|
topUp := GetTopUpByTradeNo(tradeNo)
|
|
require.NotNil(t, topUp)
|
|
return topUp.Status
|
|
}
|
|
|
|
func countUserSubscriptionsForPaymentGuardTest(t *testing.T, userID int) int64 {
|
|
t.Helper()
|
|
var count int64
|
|
require.NoError(t, DB.Model(&UserSubscription{}).Where("user_id = ?", userID).Count(&count).Error)
|
|
return count
|
|
}
|
|
|
|
func getUserQuotaForPaymentGuardTest(t *testing.T, userID int) int {
|
|
t.Helper()
|
|
var user User
|
|
require.NoError(t, DB.Select("quota").Where("id = ?", userID).First(&user).Error)
|
|
return user.Quota
|
|
}
|
|
|
|
func insertPromoCodeForPaymentGuardTest(t *testing.T, code string, bonusAmount int, minRechargeAmount int, maxUses int) *PromoCode {
|
|
t.Helper()
|
|
promoCode := &PromoCode{
|
|
Code: code,
|
|
BonusAmount: bonusAmount,
|
|
MaxUses: maxUses,
|
|
Status: 1,
|
|
MinRechargeAmount: minRechargeAmount,
|
|
}
|
|
require.NoError(t, DB.Create(promoCode).Error)
|
|
return promoCode
|
|
}
|
|
|
|
func getPromoCodeUsedCountForPaymentGuardTest(t *testing.T, promoCodeID int) int {
|
|
t.Helper()
|
|
var promoCode PromoCode
|
|
require.NoError(t, DB.Select("used_count").Where("id = ?", promoCodeID).First(&promoCode).Error)
|
|
return promoCode.UsedCount
|
|
}
|
|
|
|
func countPromoCodeUsagesForPaymentGuardTest(t *testing.T, promoCodeID int, userID int) int64 {
|
|
t.Helper()
|
|
var count int64
|
|
require.NoError(t, DB.Model(&PromoCodeUsage{}).Where("promo_code_id = ? AND user_id = ?", promoCodeID, userID).Count(&count).Error)
|
|
return count
|
|
}
|
|
|
|
func TestRechargeWaffoPancake_RejectsMismatchedPaymentMethod(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 101, 0)
|
|
insertTopUpForPaymentGuardTest(t, "waffo-pancake-guard", 101, PaymentProviderStripe)
|
|
|
|
err := RechargeWaffoPancake("waffo-pancake-guard")
|
|
require.Error(t, err)
|
|
|
|
topUp := GetTopUpByTradeNo("waffo-pancake-guard")
|
|
require.NotNil(t, topUp)
|
|
assert.Equal(t, common.TopUpStatusPending, topUp.Status)
|
|
assert.Equal(t, 0, getUserQuotaForPaymentGuardTest(t, 101))
|
|
}
|
|
|
|
func TestUpdatePendingTopUpStatus_RejectsMismatchedPaymentProvider(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
tradeNo string
|
|
storedPaymentProvider string
|
|
expectedPaymentProvider string
|
|
targetStatus string
|
|
}{
|
|
{
|
|
name: "stripe expire",
|
|
tradeNo: "stripe-expire-guard",
|
|
storedPaymentProvider: PaymentProviderCreem,
|
|
expectedPaymentProvider: PaymentProviderStripe,
|
|
targetStatus: common.TopUpStatusExpired,
|
|
},
|
|
{
|
|
name: "waffo failed",
|
|
tradeNo: "waffo-failed-guard",
|
|
storedPaymentProvider: PaymentProviderStripe,
|
|
expectedPaymentProvider: PaymentProviderWaffo,
|
|
targetStatus: common.TopUpStatusFailed,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
truncateTables(t)
|
|
insertUserForPaymentGuardTest(t, 150, 0)
|
|
insertTopUpForPaymentGuardTest(t, tc.tradeNo, 150, tc.storedPaymentProvider)
|
|
|
|
err := UpdatePendingTopUpStatus(tc.tradeNo, tc.expectedPaymentProvider, tc.targetStatus)
|
|
require.ErrorIs(t, err, ErrPaymentMethodMismatch)
|
|
assert.Equal(t, common.TopUpStatusPending, getTopUpStatusForPaymentGuardTest(t, tc.tradeNo))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateTopUpPromoCode_RejectsBelowMinimumAndUsedCode(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 180, 0)
|
|
promoCode := insertPromoCodeForPaymentGuardTest(t, "PROMO_MIN", 123, 10, 0)
|
|
|
|
err := ValidateTopUpPromoCode(180, promoCode.Id, 9)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "最低要求")
|
|
|
|
require.NoError(t, ValidateTopUpPromoCode(180, promoCode.Id, 10))
|
|
require.NoError(t, DB.Create(&PromoCodeUsage{
|
|
PromoCodeId: promoCode.Id,
|
|
UserId: 180,
|
|
UsedAt: time.Now(),
|
|
}).Error)
|
|
|
|
err = ValidateTopUpPromoCode(180, promoCode.Id, 10)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "已使用")
|
|
}
|
|
|
|
func TestRechargeWaffo_AppliesPromoCodeBonusIdempotently(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 181, 10)
|
|
promoCode := insertPromoCodeForPaymentGuardTest(t, "WAFFO_BONUS", 123, 2, 1)
|
|
topUp := &TopUp{
|
|
UserId: 181,
|
|
Amount: 2,
|
|
Money: 2,
|
|
TradeNo: "waffo-promo-guard",
|
|
PaymentMethod: PaymentMethodWaffo,
|
|
PaymentProvider: PaymentProviderWaffo,
|
|
PromoCodeId: promoCode.Id,
|
|
Status: common.TopUpStatusPending,
|
|
CreateTime: time.Now().Unix(),
|
|
}
|
|
require.NoError(t, topUp.Insert())
|
|
|
|
require.NoError(t, RechargeWaffo("waffo-promo-guard", "127.0.0.1"))
|
|
|
|
baseQuota := int(decimal.NewFromInt(2).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart())
|
|
assert.Equal(t, 10+baseQuota+123, getUserQuotaForPaymentGuardTest(t, 181))
|
|
assert.Equal(t, common.TopUpStatusSuccess, getTopUpStatusForPaymentGuardTest(t, "waffo-promo-guard"))
|
|
assert.Equal(t, 1, getPromoCodeUsedCountForPaymentGuardTest(t, promoCode.Id))
|
|
assert.Equal(t, int64(1), countPromoCodeUsagesForPaymentGuardTest(t, promoCode.Id, 181))
|
|
|
|
require.NoError(t, RechargeWaffo("waffo-promo-guard", "127.0.0.1"))
|
|
assert.Equal(t, 10+baseQuota+123, getUserQuotaForPaymentGuardTest(t, 181))
|
|
assert.Equal(t, 1, getPromoCodeUsedCountForPaymentGuardTest(t, promoCode.Id))
|
|
assert.Equal(t, int64(1), countPromoCodeUsagesForPaymentGuardTest(t, promoCode.Id, 181))
|
|
}
|
|
|
|
func TestRechargeWxpay_AppliesPromoCodeBonusIdempotently(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 182, 10)
|
|
promoCode := insertPromoCodeForPaymentGuardTest(t, "WXPAY_BONUS", 123, 2, 1)
|
|
topUp := &TopUp{
|
|
UserId: 182,
|
|
Amount: 2,
|
|
Money: 2,
|
|
TradeNo: "wxpay-promo-guard",
|
|
PaymentMethod: PaymentMethodWxpay,
|
|
PaymentProvider: PaymentProviderWxpay,
|
|
PromoCodeId: promoCode.Id,
|
|
Status: common.TopUpStatusPending,
|
|
CreateTime: time.Now().Unix(),
|
|
}
|
|
require.NoError(t, topUp.Insert())
|
|
|
|
require.NoError(t, RechargeWxpay("wxpay-promo-guard", "127.0.0.1"))
|
|
|
|
baseQuota := int(decimal.NewFromInt(2).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart())
|
|
assert.Equal(t, 10+baseQuota+123, getUserQuotaForPaymentGuardTest(t, 182))
|
|
assert.Equal(t, common.TopUpStatusSuccess, getTopUpStatusForPaymentGuardTest(t, "wxpay-promo-guard"))
|
|
assert.Equal(t, 1, getPromoCodeUsedCountForPaymentGuardTest(t, promoCode.Id))
|
|
assert.Equal(t, int64(1), countPromoCodeUsagesForPaymentGuardTest(t, promoCode.Id, 182))
|
|
|
|
require.NoError(t, RechargeWxpay("wxpay-promo-guard", "127.0.0.1"))
|
|
assert.Equal(t, 10+baseQuota+123, getUserQuotaForPaymentGuardTest(t, 182))
|
|
assert.Equal(t, 1, getPromoCodeUsedCountForPaymentGuardTest(t, promoCode.Id))
|
|
assert.Equal(t, int64(1), countPromoCodeUsagesForPaymentGuardTest(t, promoCode.Id, 182))
|
|
}
|
|
|
|
func TestRechargeWxpay_RejectsMismatchedPaymentProvider(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 183, 0)
|
|
insertTopUpForPaymentGuardTest(t, "wxpay-provider-guard", 183, PaymentProviderAlipay)
|
|
|
|
err := RechargeWxpay("wxpay-provider-guard", "127.0.0.1")
|
|
require.Error(t, err)
|
|
assert.Equal(t, common.TopUpStatusPending, getTopUpStatusForPaymentGuardTest(t, "wxpay-provider-guard"))
|
|
assert.Equal(t, 0, getUserQuotaForPaymentGuardTest(t, 183))
|
|
}
|
|
|
|
func TestRechargeWaffo_ProcessesAffiliateRebateIdempotently(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
common.OptionMapRWMutex.RLock()
|
|
originalOptionMap := common.OptionMap
|
|
common.OptionMapRWMutex.RUnlock()
|
|
common.OptionMapRWMutex.Lock()
|
|
common.OptionMap = map[string]string{
|
|
AffRebateEnabledKey: "true",
|
|
AffRebateRatePercentKey: "10",
|
|
AffRebateFrozenDaysKey: "0",
|
|
}
|
|
common.OptionMapRWMutex.Unlock()
|
|
t.Cleanup(func() {
|
|
common.OptionMapRWMutex.Lock()
|
|
common.OptionMap = originalOptionMap
|
|
common.OptionMapRWMutex.Unlock()
|
|
})
|
|
|
|
inviter := &User{Id: 184, Username: "rebate_inviter", AffCode: "rebate_inviter_code", Status: common.UserStatusEnabled}
|
|
invitee := &User{Id: 185, Username: "rebate_invitee", AffCode: "rebate_invitee_code", Status: common.UserStatusEnabled, InviterId: inviter.Id}
|
|
require.NoError(t, DB.Create(inviter).Error)
|
|
require.NoError(t, DB.Create(invitee).Error)
|
|
topUp := &TopUp{
|
|
UserId: invitee.Id,
|
|
Amount: 2,
|
|
Money: 2,
|
|
TradeNo: "waffo-rebate-guard",
|
|
PaymentMethod: PaymentMethodWaffo,
|
|
PaymentProvider: PaymentProviderWaffo,
|
|
Status: common.TopUpStatusPending,
|
|
CreateTime: time.Now().Unix(),
|
|
}
|
|
require.NoError(t, topUp.Insert())
|
|
|
|
require.NoError(t, RechargeWaffo("waffo-rebate-guard", "127.0.0.1"))
|
|
require.NoError(t, RechargeWaffo("waffo-rebate-guard", "127.0.0.1"))
|
|
|
|
baseQuota := int(decimal.NewFromInt(2).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart())
|
|
var record RebateRecord
|
|
require.NoError(t, DB.First(&record).Error)
|
|
assert.Equal(t, topUp.Id, record.OrderId)
|
|
assert.Equal(t, RebateOrderTypeTopUp, record.OrderType)
|
|
assert.Equal(t, baseQuota, record.OrderAmount)
|
|
assert.Equal(t, baseQuota/10, record.RebateAmount)
|
|
assert.Equal(t, "released", record.Status)
|
|
|
|
var reloadedInviter User
|
|
require.NoError(t, DB.First(&reloadedInviter, inviter.Id).Error)
|
|
assert.Equal(t, baseQuota/10, reloadedInviter.AffQuota)
|
|
assert.Equal(t, baseQuota/10, reloadedInviter.AffHistoryQuota)
|
|
|
|
var count int64
|
|
require.NoError(t, DB.Model(&RebateRecord{}).Count(&count).Error)
|
|
assert.Equal(t, int64(1), count)
|
|
}
|
|
|
|
func TestValidateTopUpPaymentAmount(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 186, 0)
|
|
topUp := &TopUp{
|
|
UserId: 186,
|
|
Amount: 2,
|
|
Money: 12.34,
|
|
TradeNo: "direct-pay-amount-guard",
|
|
PaymentMethod: PaymentMethodAlipay,
|
|
PaymentProvider: PaymentProviderAlipay,
|
|
Status: common.TopUpStatusPending,
|
|
CreateTime: time.Now().Unix(),
|
|
}
|
|
require.NoError(t, topUp.Insert())
|
|
|
|
require.NoError(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderAlipay, 1234, "CNY"))
|
|
require.ErrorIs(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderAlipay, 1233, "CNY"), ErrTopUpAmountMismatch)
|
|
require.ErrorIs(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderAlipay, 1234, "USD"), ErrTopUpCurrencyMismatch)
|
|
require.ErrorIs(t, ValidateTopUpPaymentAmount("direct-pay-amount-guard", PaymentProviderWxpay, 1234, "CNY"), ErrPaymentMethodMismatch)
|
|
}
|
|
|
|
func TestCompleteSubscriptionOrder_RejectsMismatchedPaymentProvider(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 202, 0)
|
|
plan := insertSubscriptionPlanForPaymentGuardTest(t, 301)
|
|
insertSubscriptionOrderForPaymentGuardTest(t, "sub-guard-order", 202, plan.Id, PaymentProviderStripe)
|
|
|
|
err := CompleteSubscriptionOrder("sub-guard-order", `{"provider":"epay"}`, PaymentProviderEpay, "alipay")
|
|
require.ErrorIs(t, err, ErrPaymentMethodMismatch)
|
|
|
|
order := GetSubscriptionOrderByTradeNo("sub-guard-order")
|
|
require.NotNil(t, order)
|
|
assert.Equal(t, common.TopUpStatusPending, order.Status)
|
|
assert.Zero(t, countUserSubscriptionsForPaymentGuardTest(t, 202))
|
|
|
|
topUp := GetTopUpByTradeNo("sub-guard-order")
|
|
assert.Nil(t, topUp)
|
|
}
|
|
|
|
func TestExpireSubscriptionOrder_RejectsMismatchedPaymentProvider(t *testing.T) {
|
|
truncateTables(t)
|
|
|
|
insertUserForPaymentGuardTest(t, 303, 0)
|
|
plan := insertSubscriptionPlanForPaymentGuardTest(t, 401)
|
|
insertSubscriptionOrderForPaymentGuardTest(t, "sub-expire-guard", 303, plan.Id, PaymentProviderStripe)
|
|
|
|
err := ExpireSubscriptionOrder("sub-expire-guard", PaymentProviderCreem)
|
|
require.ErrorIs(t, err, ErrPaymentMethodMismatch)
|
|
|
|
order := GetSubscriptionOrderByTradeNo("sub-expire-guard")
|
|
require.NotNil(t, order)
|
|
assert.Equal(t, common.TopUpStatusPending, order.Status)
|
|
}
|