拆分订单仓储模块
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/internal/modules/chat"
|
||||
"hfb_sys/backend/internal/modules/notification"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, error) {
|
||||
var createdID uint64
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var listing model.RentalListing
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, req.ListingID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if listing.Status != "published" || listing.ReviewStatus != "approved" || listing.InTransaction {
|
||||
return ErrListingUnavailable
|
||||
}
|
||||
if listing.OwnerID == renterID {
|
||||
return ErrCannotRentOwnListing
|
||||
}
|
||||
|
||||
var account model.GameAccount
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, listing.AccountID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
snapshot, err := makeAccountSnapshot(account, listing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderNo, err := newOrderNo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rentHours := internalOrderHours
|
||||
pricing := buildOrderPricing(listing, account)
|
||||
depositOriginalAmountCent := listing.DepositAmountCent
|
||||
paidDepositAmountCent, waivedDepositAmountCent, err := r.depositAmountsForOrder(tx, renterID, depositOriginalAmountCent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
order := model.RentalOrder{
|
||||
OrderNo: orderNo,
|
||||
ListingID: listing.ID,
|
||||
AccountID: listing.AccountID,
|
||||
OwnerID: listing.OwnerID,
|
||||
RenterID: renterID,
|
||||
EstimatedDurationHours: rentHours,
|
||||
RentAmountCent: pricing.RentAmountCent,
|
||||
OwnerRentAmountCent: pricing.OwnerRentAmountCent,
|
||||
DepositAmountCent: paidDepositAmountCent,
|
||||
DepositOriginalAmountCent: depositOriginalAmountCent,
|
||||
DepositWaivedAmountCent: waivedDepositAmountCent,
|
||||
PlatformFeeCent: pricing.PlatformFeeCent,
|
||||
AccountSnapshot: snapshot,
|
||||
Status: "pending_payment",
|
||||
HandoffStatus: "none",
|
||||
SettlementStatus: "unsettled",
|
||||
}
|
||||
if err := tx.Create(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
orderID := order.ID
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
UserID: order.RenterID,
|
||||
Type: "order",
|
||||
Title: "订单已创建",
|
||||
Content: "订单已创建,请在有效时间内完成支付。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
listing.InTransaction = true
|
||||
if err := tx.Save(&listing).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
createdID = order.ID
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindForUser(renterID, createdID)
|
||||
}
|
||||
|
||||
func (r *Repository) depositAmountsForOrder(tx *gorm.DB, renterID uint64, originalDepositCent int64) (int64, int64, error) {
|
||||
if originalDepositCent <= 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
var user model.User
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, renterID).Error; err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
used, err := activeDepositFreeUsed(tx, renterID)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
paidDeposit, waivedDeposit := calculateDepositWaiver(originalDepositCent, user.DepositFreeQuotaCent, used)
|
||||
return paidDeposit, waivedDeposit, nil
|
||||
}
|
||||
|
||||
func activeDepositFreeUsed(tx *gorm.DB, renterID uint64) (int64, error) {
|
||||
var used int64
|
||||
err := tx.Model(&model.RentalOrder{}).
|
||||
Where("renter_id = ? AND status NOT IN ?",
|
||||
renterID,
|
||||
[]string{"completed", "cancelled", "closed"},
|
||||
).
|
||||
Select("COALESCE(SUM(deposit_waived_amount_cent), 0)").
|
||||
Scan(&used).Error
|
||||
return used, err
|
||||
}
|
||||
|
||||
func calculateDepositWaiver(originalDepositCent int64, quotaCent int64, usedCent int64) (int64, int64) {
|
||||
remaining := maxCent(quotaCent-usedCent, 0)
|
||||
waived := minCent(originalDepositCent, remaining)
|
||||
paid := maxCent(originalDepositCent-waived, 0)
|
||||
return paid, waived
|
||||
}
|
||||
|
||||
// Pay 保留旧接口兼容,但真实付款必须走 payment 模块的渠道支付入口。
|
||||
func (r *Repository) Pay(userID uint64, orderID uint64) error {
|
||||
return ErrChannelPaymentRequired
|
||||
}
|
||||
|
||||
// ConfirmPaidFromChannel 在乐刷确认支付后推进订单状态;租客资金不进入站内钱包。
|
||||
func (r *Repository) ConfirmPaidFromChannel(orderID uint64, providerBizNo string) error {
|
||||
var newConvID uint64
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var order model.RentalOrder
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Status == "pending_handoff" || order.Status == "renting" {
|
||||
return nil
|
||||
}
|
||||
if order.Status != "pending_payment" {
|
||||
return ErrOrderCannotPay
|
||||
}
|
||||
|
||||
var listing model.RentalListing
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if listing.Status != "published" || listing.ReviewStatus != "approved" || !listing.InTransaction {
|
||||
return ErrListingUnavailable
|
||||
}
|
||||
var account model.GameAccount
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 租客已通过外部渠道付款,这里不写租客钱包流水。
|
||||
orderID := order.ID
|
||||
order.Status = "pending_handoff"
|
||||
order.HandoffStatus = "pending_owner"
|
||||
listing.Status = "rented"
|
||||
account.Status = "rented"
|
||||
conv, err := chat.EnsureOrderConversation(tx, order)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newConvID = conv.ID
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
UserID: order.OwnerID,
|
||||
Type: "order",
|
||||
Title: "收到新的租号订单",
|
||||
Content: "租客已完成支付,请尽快提交交接说明。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
notification.Entry{
|
||||
UserID: order.RenterID,
|
||||
Type: "order",
|
||||
Title: "订单支付成功",
|
||||
Content: "支付已完成,等待号主提交交接说明。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Save(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Save(&listing).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Save(&account).Error
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newConvID > 0 && r.chatRepo != nil {
|
||||
r.chatRepo.NotifyNewConversation(newConvID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) Cancel(userID uint64, orderID uint64) error {
|
||||
var refund *refundAction
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var order model.RentalOrder
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("id = ? AND renter_id = ?", orderID, userID).
|
||||
First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Status != "pending_payment" && order.Status != "pending_handoff" {
|
||||
return ErrOrderCannotCancel
|
||||
}
|
||||
var listing model.RentalListing
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var account model.GameAccount
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
beforeStatus := order.Status
|
||||
order.Status = "cancelled"
|
||||
order.HandoffStatus = "cancelled"
|
||||
orderID := order.ID
|
||||
if beforeStatus == "pending_handoff" {
|
||||
totalCent := order.RentAmountCent + order.DepositAmountCent
|
||||
action, err := r.prepareRefund(&order, totalCent, "cancel_refund", "取消订单原路退款")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refund = action
|
||||
}
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
UserID: order.OwnerID,
|
||||
Type: "order",
|
||||
Title: "订单已取消",
|
||||
Content: "租客已取消订单,账号已重新释放。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
notification.Entry{
|
||||
UserID: order.RenterID,
|
||||
Type: "order",
|
||||
Title: "订单取消成功",
|
||||
Content: "订单已取消,退款将原路退回您的支付账户。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
listing.Status = "published"
|
||||
listing.InTransaction = false
|
||||
account.Status = "published"
|
||||
if err := tx.Save(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Save(&listing).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Save(&account).Error
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.startRefundBestEffort(refund)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var order model.RentalOrder
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.RenterID != userID {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
if order.Status != "pending_handoff" || order.HandoffStatus != "pending_renter_confirm" {
|
||||
return ErrOrderCannotReceive
|
||||
}
|
||||
now := time.Now()
|
||||
if err := tx.Model(&model.HandoffRecord{}).
|
||||
Where("order_id = ? AND type = ?", order.ID, "owner_handoff").
|
||||
Update("confirmed_by_renter_at", now).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
order.Status = "renting"
|
||||
order.HandoffStatus = "received"
|
||||
durationHours := orderDurationHours(order)
|
||||
order.EstimatedDurationHours = durationHours
|
||||
order.RentedAt = &now
|
||||
orderID := order.ID
|
||||
if err := notification.Append(tx, notification.Entry{
|
||||
UserID: order.OwnerID,
|
||||
Type: "handoff",
|
||||
Title: "租客已确认收号",
|
||||
Content: "订单已进入使用中。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Save(&order).Error
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user