fix(invitation): 邀请返利改用实际结算利润并修正提号基准
This commit is contained in:
@@ -62,7 +62,13 @@ func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, che
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_ = invitation.ProcessOrderInvitationRebateTx(tx, order.ID)
|
||||
// 返利必须按本次实际结算的平台利润计算,不能用订单创建时的预收毛利快照。
|
||||
actualPlatformProfitCent := settlement.PlatformFeeCent
|
||||
_ = invitation.ProcessOrderInvitationRebateWithOptionsTx(tx, order.ID, invitation.OrderRebateOptions{
|
||||
GrossProfitCent: &actualPlatformProfitCent,
|
||||
RateReferenceAt: now,
|
||||
SettledAt: now,
|
||||
})
|
||||
|
||||
return refund, nil
|
||||
}
|
||||
|
||||
@@ -2754,3 +2754,149 @@ func TestHideForOwnerRemovesSellerViewOnly(t *testing.T) {
|
||||
t.Fatalf("隐藏只写时间,订单状态应保持完成,got hidden=%v status=%s", saved.OwnerHiddenAt, saved.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// 邀请返利必须以实际结账落定的平台利润为基数:
|
||||
// 租客申报 0 哈夫币损耗、0 额外物品时,实际平台利润为 0,不得再按订单预收毛利返利。
|
||||
func TestCheckoutInvitationRebateUsesActualPlatformProfit(t *testing.T) {
|
||||
db := setupOrderTestDB(t)
|
||||
if err := database.AutoMigrateIfMissing(db,
|
||||
&model.UserInvitation{},
|
||||
&model.InvitationRebateConfig{},
|
||||
&model.InvitationRebateLedger{},
|
||||
); err != nil {
|
||||
t.Fatalf("迁移邀请返利表失败: %v", err)
|
||||
}
|
||||
repo := NewRepository(db, Dependencies{
|
||||
RefundStarter: RefundStarterFunc(func(context.Context, uint64, int64, string, string) (string, error) {
|
||||
return "refunding", nil
|
||||
}),
|
||||
})
|
||||
snapshot := `{"haf_coin_amount":1000000,"asset_summary":{"price_breakdown":{"buyer_ratio":1,"seller_ratio":1.5,"buyer_coin_base_price":100,"seller_coin_base_price":80}}}`
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
coinConsumedM float64
|
||||
wantPlatformCent int64
|
||||
wantBasisCent int64
|
||||
wantSingleCent int64
|
||||
wantRebateCent int64
|
||||
}{
|
||||
{name: "零消耗不产生返利", coinConsumedM: 0, wantPlatformCent: 0},
|
||||
{name: "部分消耗按实际利润", coinConsumedM: 0.1, wantPlatformCent: 333, wantBasisCent: 333, wantSingleCent: 166, wantRebateCent: 100},
|
||||
}
|
||||
|
||||
for index, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
inviter := model.User{
|
||||
Phone: fmt.Sprintf("1391000%04d", index),
|
||||
Nickname: "结算返利邀请人",
|
||||
RealnameStatus: "verified",
|
||||
Status: "active",
|
||||
}
|
||||
owner := model.User{Phone: fmt.Sprintf("1392000%04d", index), Nickname: "结算返利号主", Status: "active"}
|
||||
renter := model.User{Phone: fmt.Sprintf("1393000%04d", index), Nickname: "结算返利租客", Status: "active"}
|
||||
if err := db.Create(&inviter).Error; err != nil {
|
||||
t.Fatalf("创建邀请人失败: %v", err)
|
||||
}
|
||||
if err := db.Create(&owner).Error; err != nil {
|
||||
t.Fatalf("创建号主失败: %v", err)
|
||||
}
|
||||
if err := db.Create(&renter).Error; err != nil {
|
||||
t.Fatalf("创建租客失败: %v", err)
|
||||
}
|
||||
|
||||
verifiedAt := time.Now().AddDate(0, 0, -2).Truncate(time.Second)
|
||||
if err := db.Create(&model.UserInvitation{
|
||||
InviterID: inviter.ID,
|
||||
InviteeID: owner.ID,
|
||||
InviteCode: fmt.Sprintf("actual%02d", index),
|
||||
Status: "active",
|
||||
VerifiedAt: &verifiedAt,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("创建邀请关系失败: %v", err)
|
||||
}
|
||||
|
||||
account := model.GameAccount{
|
||||
OwnerID: owner.ID,
|
||||
ServerRegion: "国服",
|
||||
LoginPlatform: "steam",
|
||||
Title: "结算返利测试账号",
|
||||
Status: "rented",
|
||||
}
|
||||
if err := db.Create(&account).Error; err != nil {
|
||||
t.Fatalf("创建账号失败: %v", err)
|
||||
}
|
||||
listing := model.RentalListing{
|
||||
ListingNo: fmt.Sprintf("LST-REBATE-ACTUAL-%02d", index),
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
PriceCent: 10000,
|
||||
Status: "rented",
|
||||
ReviewStatus: "approved",
|
||||
InTransaction: true,
|
||||
}
|
||||
if err := db.Create(&listing).Error; err != nil {
|
||||
t.Fatalf("创建上架记录失败: %v", err)
|
||||
}
|
||||
order := model.RentalOrder{
|
||||
OrderNo: fmt.Sprintf("ORD-REBATE-ACTUAL-%d-%02d", time.Now().UnixNano(), index),
|
||||
ListingID: listing.ID,
|
||||
AccountID: account.ID,
|
||||
OwnerID: owner.ID,
|
||||
RenterID: renter.ID,
|
||||
Status: orderStatusRenting,
|
||||
HandoffStatus: handoffStatusReceived,
|
||||
EstimatedDurationHours: 24,
|
||||
RentAmountCent: 10000,
|
||||
RentOriginalAmountCent: 10000,
|
||||
OwnerRentAmountCent: 8000,
|
||||
PlatformFeeCent: 2000,
|
||||
PureCoinOriginalAmountCent: 10000,
|
||||
AccountSnapshot: datatypes.JSON([]byte(snapshot)),
|
||||
}
|
||||
if err := db.Create(&order).Error; err != nil {
|
||||
t.Fatalf("创建订单失败: %v", err)
|
||||
}
|
||||
|
||||
if _, err := repo.SubmitCheckout(t.Context(), renter.ID, order.ID, SubmitCheckoutRequest{
|
||||
Content: "按实际消耗结账",
|
||||
CoinConsumedM: tc.coinConsumedM,
|
||||
}); err != nil {
|
||||
t.Fatalf("提交结账失败: %v", err)
|
||||
}
|
||||
if err := repo.ConfirmCheckout(t.Context(), owner.ID, order.ID); err != nil {
|
||||
t.Fatalf("确认结账失败: %v", err)
|
||||
}
|
||||
|
||||
var checkout model.OrderCheckout
|
||||
if err := db.Where("order_id = ?", order.ID).Order("id DESC").First(&checkout).Error; err != nil {
|
||||
t.Fatalf("读取结账单失败: %v", err)
|
||||
}
|
||||
if checkout.PlatformFeeCent != tc.wantPlatformCent {
|
||||
t.Fatalf("实际平台利润 = %d, want %d", checkout.PlatformFeeCent, tc.wantPlatformCent)
|
||||
}
|
||||
|
||||
var ledgers []model.InvitationRebateLedger
|
||||
if err := db.Where("source = ? AND order_id = ?", "rental_order", order.ID).Find(&ledgers).Error; err != nil {
|
||||
t.Fatalf("读取返利流水失败: %v", err)
|
||||
}
|
||||
if tc.wantRebateCent == 0 {
|
||||
if len(ledgers) != 0 {
|
||||
t.Fatalf("零消耗结账不应发返利,实际 %d 条", len(ledgers))
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(ledgers) != 1 {
|
||||
t.Fatalf("返利流水条数 = %d, want 1", len(ledgers))
|
||||
}
|
||||
ledger := ledgers[0]
|
||||
if ledger.OrderProfitBasisCent != tc.wantBasisCent ||
|
||||
ledger.SingleSideBasisCent != tc.wantSingleCent ||
|
||||
ledger.RebateAmountCent != tc.wantRebateCent {
|
||||
t.Fatalf("返利基数/单边/金额 = %d/%d/%d, want %d/%d/%d",
|
||||
ledger.OrderProfitBasisCent, ledger.SingleSideBasisCent, ledger.RebateAmountCent,
|
||||
tc.wantBasisCent, tc.wantSingleCent, tc.wantRebateCent)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user