新增订单待支付与支付确认后端流程
This commit is contained in:
@@ -21,6 +21,8 @@ type Repository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
const defaultPendingPaymentTimeoutMinutes = 15
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
@@ -32,7 +34,7 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, req.ListingID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if listing.Status != "published" || listing.ReviewStatus != "approved" {
|
||||
if listing.Status != "published" || listing.ReviewStatus != "approved" || listing.InTransaction {
|
||||
return ErrListingUnavailable
|
||||
}
|
||||
if listing.OwnerID == renterID {
|
||||
@@ -51,72 +53,42 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
rentHours := internalOrderHours
|
||||
rentEnd := now.Add(time.Duration(rentHours) * time.Hour)
|
||||
order := model.RentalOrder{
|
||||
OrderNo: orderNo,
|
||||
ListingID: listing.ID,
|
||||
AccountID: listing.AccountID,
|
||||
OwnerID: listing.OwnerID,
|
||||
RenterID: renterID,
|
||||
RentStartAt: &now,
|
||||
RentEndAt: &rentEnd,
|
||||
RentHours: rentHours,
|
||||
RentAmount: listing.PriceHourly * float64(rentHours),
|
||||
DepositAmount: listing.DepositAmount,
|
||||
PlatformFee: 0,
|
||||
AccountSnapshot: snapshot,
|
||||
Status: "pending_handoff",
|
||||
HandoffStatus: "pending_owner",
|
||||
Status: "pending_payment",
|
||||
HandoffStatus: "none",
|
||||
SettlementStatus: "unsettled",
|
||||
}
|
||||
if err := tx.Create(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
orderID := order.ID
|
||||
if err := wallet.AppendEntries(tx,
|
||||
wallet.Entry{
|
||||
UserID: renterID,
|
||||
OrderID: &orderID,
|
||||
Direction: "in",
|
||||
Amount: order.RentAmount + order.DepositAmount,
|
||||
BalanceType: "frozen",
|
||||
BizType: "order_lock",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "开发态模拟冻结订单金额和押金",
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
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: "账号已锁定,等待号主提交交接说明。",
|
||||
Content: "订单已创建,请在有效时间内完成支付。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
listing.Status = "rented"
|
||||
account.Status = "rented"
|
||||
listing.InTransaction = true
|
||||
if err := tx.Save(&listing).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Save(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
createdID = order.ID
|
||||
return nil
|
||||
})
|
||||
@@ -126,6 +98,98 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
return r.FindForUser(renterID, createdID)
|
||||
}
|
||||
|
||||
func (r *Repository) Pay(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"}).
|
||||
Where("id = ? AND renter_id = ?", orderID, userID).
|
||||
First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Status != "pending_payment" {
|
||||
return ErrOrderCannotPay
|
||||
}
|
||||
timeoutMinutes := pendingPaymentTimeoutMinutes(tx)
|
||||
if order.CreatedAt.Before(time.Now().Add(-time.Duration(timeoutMinutes) * time.Minute)) {
|
||||
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
|
||||
}
|
||||
|
||||
total := order.RentAmount + order.DepositAmount
|
||||
if err := wallet.AppendEntries(tx,
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &order.ID,
|
||||
Direction: "out",
|
||||
Amount: total,
|
||||
BalanceType: "available",
|
||||
BizType: "order_pay",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "订单支付扣减可用余额",
|
||||
},
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &order.ID,
|
||||
Direction: "in",
|
||||
Amount: total,
|
||||
BalanceType: "frozen",
|
||||
BizType: "order_lock",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "订单支付冻结租金和押金",
|
||||
},
|
||||
); err != nil {
|
||||
if errors.Is(err, wallet.ErrInsufficientBalance) {
|
||||
return ErrInsufficientBalance
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
order.Status = "pending_handoff"
|
||||
order.HandoffStatus = "pending_owner"
|
||||
listing.Status = "rented"
|
||||
account.Status = "rented"
|
||||
orderID := order.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
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) Cancel(userID uint64, orderID uint64) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var order model.RentalOrder
|
||||
@@ -134,7 +198,7 @@ func (r *Repository) Cancel(userID uint64, orderID uint64) error {
|
||||
First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.Status != "pending_handoff" {
|
||||
if order.Status != "pending_payment" && order.Status != "pending_handoff" {
|
||||
return ErrOrderCannotCancel
|
||||
}
|
||||
var listing model.RentalListing
|
||||
@@ -146,29 +210,43 @@ func (r *Repository) Cancel(userID uint64, orderID uint64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
beforeStatus := order.Status
|
||||
order.Status = "cancelled"
|
||||
order.HandoffStatus = "cancelled"
|
||||
orderID := order.ID
|
||||
if err := wallet.AppendEntries(tx,
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &orderID,
|
||||
Direction: "out",
|
||||
Amount: order.RentAmount + order.DepositAmount,
|
||||
BalanceType: "frozen",
|
||||
BizType: "order_cancel",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "取消订单释放模拟冻结金额",
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
if beforeStatus == "pending_handoff" {
|
||||
total := order.RentAmount + order.DepositAmount
|
||||
if err := wallet.AppendEntries(tx,
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &orderID,
|
||||
Direction: "out",
|
||||
Amount: total,
|
||||
BalanceType: "frozen",
|
||||
BizType: "order_cancel",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "取消订单释放冻结金额",
|
||||
},
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &orderID,
|
||||
Direction: "in",
|
||||
Amount: total,
|
||||
BalanceType: "available",
|
||||
BizType: "order_cancel_refund",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "取消订单退回可用余额",
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
UserID: order.OwnerID,
|
||||
Type: "order",
|
||||
Title: "订单已取消",
|
||||
Content: "租客已取消待交接订单,账号已重新释放。",
|
||||
Content: "租客已取消订单,账号已重新释放。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
@@ -176,7 +254,7 @@ func (r *Repository) Cancel(userID uint64, orderID uint64) error {
|
||||
UserID: order.RenterID,
|
||||
Type: "order",
|
||||
Title: "订单取消成功",
|
||||
Content: "待交接订单已取消,模拟冻结金额已释放。",
|
||||
Content: "订单已取消,相关金额已释放。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
@@ -184,6 +262,7 @@ func (r *Repository) Cancel(userID uint64, orderID uint64) error {
|
||||
return err
|
||||
}
|
||||
listing.Status = "published"
|
||||
listing.InTransaction = false
|
||||
account.Status = "published"
|
||||
if err := tx.Save(&order).Error; err != nil {
|
||||
return err
|
||||
@@ -433,6 +512,7 @@ func (r *Repository) ConfirmReturn(userID uint64, orderID uint64) error {
|
||||
return err
|
||||
}
|
||||
listing.Status = "published"
|
||||
listing.InTransaction = false
|
||||
account.Status = "published"
|
||||
if err := tx.Save(&order).Error; err != nil {
|
||||
return err
|
||||
@@ -521,18 +601,34 @@ func (r *Repository) AdminClose(adminID uint64, orderID uint64, req AdminActionR
|
||||
order.SettlementStatus = "closed"
|
||||
order.SettledAt = &now
|
||||
listing.Status = "offline"
|
||||
listing.InTransaction = false
|
||||
account.Status = "offline"
|
||||
if err := wallet.AppendEntries(tx, wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &order.ID,
|
||||
Direction: "out",
|
||||
Amount: order.RentAmount + order.DepositAmount,
|
||||
BalanceType: "frozen",
|
||||
BizType: "admin_order_close",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "后台关闭订单释放模拟冻结金额",
|
||||
}); err != nil {
|
||||
return err
|
||||
if beforeOrderStatus != "pending_payment" {
|
||||
total := order.RentAmount + order.DepositAmount
|
||||
if err := wallet.AppendEntries(tx,
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &order.ID,
|
||||
Direction: "out",
|
||||
Amount: total,
|
||||
BalanceType: "frozen",
|
||||
BizType: "admin_order_close",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "后台关闭订单释放冻结金额",
|
||||
},
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
OrderID: &order.ID,
|
||||
Direction: "in",
|
||||
Amount: total,
|
||||
BalanceType: "available",
|
||||
BizType: "admin_order_close_refund",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "后台关闭订单退回可用余额",
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
@@ -599,6 +695,7 @@ func (r *Repository) AdminMarkAbnormal(adminID uint64, orderID uint64, req Admin
|
||||
order.Status = "abnormal"
|
||||
order.HandoffStatus = "admin_abnormal"
|
||||
listing.Status = "abnormal"
|
||||
listing.InTransaction = false
|
||||
account.Status = "abnormal"
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
@@ -687,6 +784,19 @@ func (r *Repository) findHandoffRecord(id uint64) (*HandoffRecordDTO, error) {
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func pendingPaymentTimeoutMinutes(tx *gorm.DB) int {
|
||||
var row model.SystemConfig
|
||||
err := tx.Where("`key` = ?", "order.pending_payment_timeout_minutes").First(&row).Error
|
||||
if err != nil {
|
||||
return defaultPendingPaymentTimeoutMinutes
|
||||
}
|
||||
value, err := strconv.Atoi(row.Value)
|
||||
if err != nil || value <= 0 {
|
||||
return defaultPendingPaymentTimeoutMinutes
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (r *Repository) baseQuery() *gorm.DB {
|
||||
return r.db.Table("rental_orders AS o").
|
||||
Select("o.*, a.title, a.server_region, a.login_platform").
|
||||
|
||||
Reference in New Issue
Block a user