合并 internal → main: 退款/提现/激活/CI 全面优化 #4
@@ -58,3 +58,26 @@ func calculatePurchasePrice(
|
||||
result.DiscountAmount = originalPrice - result.PayableBase
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func calculateRenewalPrice(
|
||||
ctx context.Context,
|
||||
svcCtx *svc.ServiceContext,
|
||||
userID int64,
|
||||
subscribeID int64,
|
||||
unitPrice int64,
|
||||
quantity int64,
|
||||
discounts []types.SubscribeDiscount,
|
||||
eligibleForDiscount bool,
|
||||
) (*orderPriceResult, error) {
|
||||
return calculatePurchasePrice(
|
||||
ctx,
|
||||
svcCtx,
|
||||
userID,
|
||||
subscribeID,
|
||||
unitPrice,
|
||||
quantity,
|
||||
discounts,
|
||||
eligibleForDiscount,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -297,3 +297,78 @@ func TestCalculatePurchasePriceNewUserGatedByFirstPurchase(t *testing.T) {
|
||||
t.Fatalf("PayableBase = %d, want 900 (regular 90%% discount)", result.PayableBase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateRenewalPriceCampaignAppliesToReturningUsers(t *testing.T) {
|
||||
model := &fakePromoModel{rules: []*promo.RuleWithPrice{
|
||||
{
|
||||
Rule: promo.Rule{
|
||||
Id: 21,
|
||||
Name: "renewal campaign",
|
||||
Type: promo.RuleTypeCampaign,
|
||||
Enabled: true,
|
||||
},
|
||||
PromoPrice: 700,
|
||||
},
|
||||
}}
|
||||
svcCtx := &svc.ServiceContext{DB: &gorm.DB{}, PromoModel: model}
|
||||
|
||||
result, err := calculateRenewalPrice(
|
||||
context.Background(),
|
||||
svcCtx,
|
||||
42,
|
||||
2,
|
||||
500,
|
||||
3,
|
||||
[]types.SubscribeDiscount{{Quantity: 3, Discount: 80}},
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("calculateRenewalPrice returned error: %v", err)
|
||||
}
|
||||
if result.OriginalPrice != 1500 {
|
||||
t.Fatalf("OriginalPrice = %d, want 1500", result.OriginalPrice)
|
||||
}
|
||||
if result.PayableBase != 700 {
|
||||
t.Fatalf("PayableBase = %d, want 700", result.PayableBase)
|
||||
}
|
||||
if result.DiscountAmount != 0 {
|
||||
t.Fatalf("DiscountAmount = %d, want 0 when promo applies", result.DiscountAmount)
|
||||
}
|
||||
if result.PromoRuleId != 21 {
|
||||
t.Fatalf("PromoRuleId = %d, want 21", result.PromoRuleId)
|
||||
}
|
||||
if result.PromoDiscount != 800 {
|
||||
t.Fatalf("PromoDiscount = %d, want 800", result.PromoDiscount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateRenewalPriceFallsBackToRegularDiscountWhenPromoMisses(t *testing.T) {
|
||||
model := &fakePromoModel{rules: nil}
|
||||
svcCtx := &svc.ServiceContext{DB: &gorm.DB{}, PromoModel: model}
|
||||
|
||||
result, err := calculateRenewalPrice(
|
||||
context.Background(),
|
||||
svcCtx,
|
||||
42,
|
||||
2,
|
||||
500,
|
||||
3,
|
||||
[]types.SubscribeDiscount{{Quantity: 3, Discount: 80}},
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("calculateRenewalPrice returned error: %v", err)
|
||||
}
|
||||
if result.OriginalPrice != 1500 {
|
||||
t.Fatalf("OriginalPrice = %d, want 1500", result.OriginalPrice)
|
||||
}
|
||||
if result.PayableBase != 1200 {
|
||||
t.Fatalf("PayableBase = %d, want 1200", result.PayableBase)
|
||||
}
|
||||
if result.DiscountAmount != 300 {
|
||||
t.Fatalf("DiscountAmount = %d, want 300", result.DiscountAmount)
|
||||
}
|
||||
if result.PromoRuleId != 0 || result.PromoDiscount != 0 {
|
||||
t.Fatalf("promo fields = (%d, %d), want (0, 0)", result.PromoRuleId, result.PromoDiscount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package order
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -165,13 +164,28 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
var discount float64 = 1
|
||||
if len(newUserDiscount.Discounts) > 0 {
|
||||
discount = getDiscount(newUserDiscount.Discounts, req.Quantity, newUserDiscount.EligibleForDiscount)
|
||||
priceResult, err := calculateRenewalPrice(
|
||||
l.ctx,
|
||||
l.svcCtx,
|
||||
entitlement.EffectiveUserID,
|
||||
userSubscribe.SubscribeId,
|
||||
sub.UnitPrice,
|
||||
req.Quantity,
|
||||
newUserDiscount.Discounts,
|
||||
newUserDiscount.EligibleForDiscount,
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorw("[Renewal] Promo price calculation error",
|
||||
logger.Field("error", err.Error()),
|
||||
logger.Field("user_id", u.Id),
|
||||
logger.Field("effective_user_id", entitlement.EffectiveUserID),
|
||||
logger.Field("subscribe_id", userSubscribe.SubscribeId),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
price := sub.UnitPrice * req.Quantity
|
||||
amount := int64(math.Round(float64(price) * discount))
|
||||
discountAmount := price - amount
|
||||
price := priceResult.OriginalPrice
|
||||
amount := priceResult.PayableBase
|
||||
discountAmount := priceResult.DiscountAmount
|
||||
|
||||
// Validate amount to prevent overflow
|
||||
if amount > MaxOrderAmount {
|
||||
@@ -269,6 +283,8 @@ func (l *RenewalLogic) Renewal(req *types.RenewalOrderRequest) (resp *types.Rene
|
||||
Amount: amount,
|
||||
GiftAmount: deductionAmount,
|
||||
Discount: discountAmount,
|
||||
PromoRuleId: priceResult.PromoRuleId,
|
||||
PromoDiscount: priceResult.PromoDiscount,
|
||||
Coupon: req.Coupon,
|
||||
CouponDiscount: coupon,
|
||||
PaymentId: payment.Id,
|
||||
|
||||
Reference in New Issue
Block a user