- 数据库与数据模型:新增 migration 000081,支持用户专属邀请码、被邀请人绑定与实名时间戳;新增邀请返利配置表与结算明细表 - 业务与结算逻辑:实现首月 60%、次月 30%、往后 15% 阶梯返利算法;买卖双向独立寻源并根据订单平台利润 50% 单边基准核算;与终态订单结算和钱包可用余额强幂等联动 - 分销站点支持:支持主站与加盟分站独立配置返利比例;跨站交易按照收号端与售卖端独立出资,资金互不侵蚀 - 前端交互与入口: - 新增专属短链注册落地页 /i/:code,自动带入并锁定邀请码,引导实名认证 - 个人中心与移动端新增邀请返利看板(专属码、推广链接、统计面板、好友记录与返利流水明细) - 管理后台新增邀请返利管理(比例配置、邀请关系与返利流水审计) - 验证方式: - 通过 repository_test.go 单元测试(阶梯区间、实名激活、跨站多方分流验证) - 通过 npm run format、npm run check 与 scripts/check.sh 全量门禁校验
204 lines
7.0 KiB
Go
204 lines
7.0 KiB
Go
package order
|
|
|
|
import (
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/internal/modules/distribution"
|
|
"hfb_sys/backend/internal/modules/invitation"
|
|
"hfb_sys/backend/internal/modules/notification"
|
|
"hfb_sys/backend/internal/modules/rentergrowth"
|
|
"hfb_sys/backend/internal/modules/wallet"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, checkout *model.OrderCheckout, renterContent string) (*refundAction, error) {
|
|
listing, account, err := r.lockListingAccountForOrder(tx, order)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
now := time.Now()
|
|
order.Status = orderStatusCompleted
|
|
order.HandoffStatus = handoffStatusReturned
|
|
order.SettlementStatus = settlementStatusSettled
|
|
order.SettledAt = &now
|
|
if !isPlatformSettlementOrder(*order) {
|
|
order.OwnerSettledAt = &now
|
|
}
|
|
if err := completeAssets(tx, listing, account); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
settlement := buildCheckoutSettlement(*order, checkout)
|
|
if settlement.ShortfallCent > 0 {
|
|
return nil, ErrCheckoutDepositShortfall
|
|
}
|
|
if err := appendCheckoutOwnerIncome(tx, order, listing, settlement); err != nil {
|
|
return nil, err
|
|
}
|
|
refund, err := r.prepareCheckoutRefund(order, settlement)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
applyCheckoutSettlement(checkout, settlement)
|
|
order.ActualCoinConsumedM = settlement.CoinConsumedM
|
|
order.ActualPureCoinAmountCent = settlement.PureCoinAmountCent
|
|
order.ActualPureCoinDiscountCent = settlement.PureCoinDiscountCent
|
|
order.ActualExtraItemAmountCent = settlement.ExtraItemAmountCent
|
|
if err := appendCheckoutCompletedNotifications(tx, order, renterContent); err != nil {
|
|
return nil, err
|
|
}
|
|
if order.DistributionSiteID == nil || *order.DistributionSiteID == 0 {
|
|
if err := rentergrowth.AwardOrderCompleted(tx, order, settlement.PureCoinAmountCent, rentergrowth.SourceOrderCompleted); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := saveFinalizedCheckout(tx, order, checkout, listing, account); err != nil {
|
|
return nil, err
|
|
}
|
|
if order.DistributionSiteID != nil && *order.DistributionSiteID > 0 {
|
|
if err := distribution.ApplyRefundTx(tx, order.ID, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
_ = invitation.ProcessOrderInvitationRebateTx(tx, order.ID)
|
|
|
|
return refund, nil
|
|
}
|
|
|
|
func ownerWalletSiteID(listing *model.RentalListing) uint64 {
|
|
if listing != nil && listing.DistributionSiteID != nil {
|
|
return *listing.DistributionSiteID
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func appendCheckoutOwnerIncome(tx *gorm.DB, order *model.RentalOrder, listing *model.RentalListing, settlement checkoutSettlement) error {
|
|
if isPlatformSettlementOrder(*order) {
|
|
amountCent := settlement.OwnerRentIncomeCent + settlement.DepositCompensationCent
|
|
order.OfflineSettlementAmountCent = amountCent
|
|
if amountCent > 0 {
|
|
order.OfflineSettlementStatus = offlineSettlementStatusPending
|
|
} else {
|
|
order.OfflineSettlementStatus = offlineSettlementStatusNone
|
|
}
|
|
return nil
|
|
}
|
|
orderID := order.ID
|
|
siteID := ownerWalletSiteID(listing)
|
|
var ownerEntries []wallet.Entry
|
|
if settlement.OwnerRentIncomeCent > 0 {
|
|
ownerEntries = append(ownerEntries, wallet.Entry{
|
|
UserID: order.OwnerID,
|
|
DistributionSiteID: siteID,
|
|
OrderID: &orderID,
|
|
Direction: "in",
|
|
AmountCent: settlement.OwnerRentIncomeCent,
|
|
BalanceType: "available",
|
|
BizType: walletBizOwnerIncome,
|
|
BizNo: order.OrderNo,
|
|
Remark: "订单结账租金收入",
|
|
})
|
|
}
|
|
if settlement.DepositCompensationCent > 0 {
|
|
ownerEntries = append(ownerEntries, wallet.Entry{
|
|
UserID: order.OwnerID,
|
|
DistributionSiteID: siteID,
|
|
OrderID: &orderID,
|
|
Direction: "in",
|
|
AmountCent: settlement.DepositCompensationCent,
|
|
BalanceType: "available",
|
|
BizType: walletBizDepositCompensation,
|
|
BizNo: order.OrderNo,
|
|
Remark: "订单结账押金赔付",
|
|
})
|
|
}
|
|
if len(ownerEntries) == 0 {
|
|
return nil
|
|
}
|
|
return wallet.AppendEntries(tx, ownerEntries...)
|
|
}
|
|
|
|
func (r *Repository) prepareCheckoutRefund(order *model.RentalOrder, settlement checkoutSettlement) (*refundAction, error) {
|
|
renterRefundTotalCent := settlement.RentRefundCent + settlement.DepositRefundCent
|
|
// 押金暂扣:若订单已暂扣,拦截本次结账中应退还租客的押金部分挂起不退,其余(租金退款)照常原路退。
|
|
actualRefundCent := order.ApplyDepositHold(renterRefundTotalCent, settlement.DepositRefundCent)
|
|
if actualRefundCent <= 0 {
|
|
return nil, nil
|
|
}
|
|
return r.prepareRefund(order, actualRefundCent, refundBizCheckout, "结账退款原路退还")
|
|
}
|
|
|
|
func applyCheckoutSettlement(checkout *model.OrderCheckout, settlement checkoutSettlement) {
|
|
checkout.RentAmountCent = settlement.ActualRentAmountCent
|
|
checkout.OwnerRentAmountCent = settlement.OwnerRentIncomeCent
|
|
checkout.PlatformFeeCent = settlement.PlatformFeeCent
|
|
checkout.PureCoinAmountCent = settlement.PureCoinAmountCent
|
|
checkout.PureCoinDiscountCent = settlement.PureCoinDiscountCent
|
|
checkout.PureCoinPayableCent = settlement.PureCoinPayableCent
|
|
checkout.RenterRefundAmountCent = settlement.RenterRefundCent
|
|
checkout.OwnerIncomeAmountCent = settlement.OwnerIncomeCent
|
|
checkout.ShortfallCent = settlement.ShortfallCent
|
|
checkout.OvershootAmountCent = settlement.OvershootAmountCent
|
|
}
|
|
|
|
func appendCheckoutCompletedNotifications(tx *gorm.DB, order *model.RentalOrder, renterContent string) error {
|
|
orderID := order.ID
|
|
if isPlatformSettlementOrder(*order) {
|
|
if err := notification.Append(tx, notification.Entry{
|
|
UserID: order.RenterID,
|
|
Type: "settlement",
|
|
Title: "订单已完成",
|
|
Content: renterContent,
|
|
BizType: "order",
|
|
BizID: &orderID,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
if order.OfflineSettlementAmountCent > 0 {
|
|
return appendManagedAdminNotification(tx, *order, "settlement", "代管订单待线下结算", "订单已完成,请按线下流程向卖家转出结算金额并回填状态。")
|
|
}
|
|
return nil
|
|
}
|
|
return notification.Append(tx,
|
|
notification.Entry{
|
|
UserID: order.RenterID,
|
|
Type: "settlement",
|
|
Title: "订单已完成",
|
|
Content: renterContent,
|
|
BizType: "order",
|
|
BizID: &orderID,
|
|
},
|
|
notification.Entry{
|
|
UserID: order.OwnerID,
|
|
Type: "settlement",
|
|
Title: "订单已完成",
|
|
Content: "订单已完成,结账金额已入账。",
|
|
BizType: "order",
|
|
BizID: &orderID,
|
|
},
|
|
)
|
|
}
|
|
|
|
func saveFinalizedCheckout(tx *gorm.DB, order *model.RentalOrder, checkout *model.OrderCheckout, listing *model.RentalListing, account *model.GameAccount) error {
|
|
if err := tx.Save(order).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Save(checkout).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Save(listing).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Save(account).Error
|
|
}
|
|
|
|
func hasOpenCheckout(tx *gorm.DB, orderID uint64) (bool, error) {
|
|
var count int64
|
|
err := tx.Model(&model.OrderCheckout{}).
|
|
Where("order_id = ? AND status IN ?", orderID, []string{checkoutStatusSubmitted, checkoutStatusCountered, checkoutStatusAccepted, checkoutStatusDisputed}).
|
|
Count(&count).Error
|
|
return count > 0, err
|
|
}
|