package order import ( "context" "time" "hfb_sys/backend/internal/model" "hfb_sys/backend/internal/modules/notification" "gorm.io/gorm" "gorm.io/gorm/clause" ) func (r *Repository) SubmitCheckout(ctx context.Context, userID uint64, orderID uint64, req SubmitCheckoutRequest) (*HandoffRecordDTO, error) { var recordID uint64 err := r.db.WithContext(ctx).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 != orderStatusRenting && order.Status != orderStatusOverdue) || (order.HandoffStatus != handoffStatusReceived && order.HandoffStatus != handoffStatusReturnOverdue) { return ErrCheckoutCannotSubmit } hasOpen, err := hasOpenCheckout(tx, order.ID) if err != nil { return err } if hasOpen { return ErrCheckoutCannotSubmit } record := model.HandoffRecord{ OrderID: order.ID, FromUserID: order.RenterID, ToUserID: order.OwnerID, Type: "renter_checkout", Content: req.Content, } if err := tx.Create(&record).Error; err != nil { return err } checkout, err := buildCheckout(order, order.RenterID, checkoutStatusSubmitted, req.Content, req.EvidenceURLS, req.ConsumableAmountCent, req.CoinConsumedM, req.OtherAmountCent, 0, false) if err != nil { return err } if err := tx.Create(&checkout).Error; err != nil { return err } order.Status = orderStatusPendingCheckoutConfirm order.HandoffStatus = handoffStatusPendingOwnerCheckout order.SettlementStatus = settlementStatusPending orderID := order.ID if err := notification.Append(tx, notification.Entry{ UserID: order.OwnerID, Type: "checkout", Title: "租客已发起结账", Content: "请检查账号状态和消耗明细,确认无误后完成结算。", BizType: "order", BizID: &orderID, }); err != nil { return err } if err := tx.Save(&order).Error; err != nil { return err } recordID = record.ID return nil }) if err != nil { return nil, err } return r.findHandoffRecord(ctx, recordID) } func (r *Repository) ConfirmReturn(ctx context.Context, userID uint64, orderID uint64) error { return r.ConfirmCheckout(ctx, userID, orderID) } func (r *Repository) ConfirmCheckout(ctx context.Context, userID uint64, orderID uint64) error { var refund *refundAction err := r.db.WithContext(ctx).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.OwnerID != userID { return ErrPermissionDenied } if order.Status != orderStatusPendingCheckoutConfirm || order.HandoffStatus != handoffStatusPendingOwnerCheckout { return ErrCheckoutCannotConfirm } var checkout model.OrderCheckout if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("order_id = ? AND status = ?", order.ID, checkoutStatusSubmitted). Order("id DESC"). First(&checkout).Error; err != nil { return err } now := time.Now() if err := tx.Model(&model.HandoffRecord{}). Where("order_id = ? AND type = ?", order.ID, "renter_checkout"). Update("confirmed_by_owner_at", now).Error; err != nil { return err } checkout.Status = checkoutStatusAccepted checkout.OwnerAdjustedAt = &now action, err := r.finalizeCheckout(tx, &order, &checkout, "号主已确认结账,订单完成。") refund = action return err }) if err != nil { return err } r.startRefundBestEffort(ctx, refund) return nil } func (r *Repository) CounterCheckout(ctx context.Context, userID uint64, orderID uint64, req CounterCheckoutRequest) (*CheckoutDTO, error) { var checkoutID uint64 err := r.db.WithContext(ctx).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.OwnerID != userID { return ErrPermissionDenied } if order.Status != orderStatusPendingCheckoutConfirm || order.HandoffStatus != handoffStatusPendingOwnerCheckout { return ErrCheckoutCannotCounter } var checkout model.OrderCheckout if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("order_id = ? AND status = ?", order.ID, checkoutStatusSubmitted). Order("id DESC"). First(&checkout).Error; err != nil { return err } next, err := buildCheckout(order, checkout.InitiatedBy, checkoutStatusCountered, checkout.Content, req.EvidenceURLS, req.ConsumableAmountCent, req.CoinConsumedM, req.OtherAmountCent, req.DepositDeductAmountCent, true) if err != nil { return err } now := time.Now() checkout.Status = checkoutStatusCountered checkout.RentAmountCent = next.RentAmountCent checkout.OwnerRentAmountCent = next.OwnerRentAmountCent checkout.PlatformFeeCent = next.PlatformFeeCent checkout.DepositAmountCent = next.DepositAmountCent checkout.ConsumableAmountCent = next.ConsumableAmountCent checkout.CoinConsumedM = next.CoinConsumedM checkout.OtherAmountCent = next.OtherAmountCent checkout.DepositDeductAmountCent = next.DepositDeductAmountCent checkout.RenterRefundAmountCent = next.RenterRefundAmountCent checkout.OwnerIncomeAmountCent = next.OwnerIncomeAmountCent checkout.OwnerAdjustmentReason = req.Reason checkout.OwnerAdjustedAt = &now checkout.EvidenceURLS = next.EvidenceURLS order.Status = orderStatusPendingCheckoutAccept order.HandoffStatus = handoffStatusPendingRenterCheckout order.SettlementStatus = settlementStatusPending orderID := order.ID if err := notification.Append(tx, notification.Entry{ UserID: order.RenterID, Type: "checkout", Title: "号主已修改结账金额", Content: "请核对号主修正的消耗和结算金额。同意后订单完成;不同意可发起争议。", BizType: "order", BizID: &orderID, }); err != nil { return err } if err := tx.Save(&order).Error; err != nil { return err } if err := tx.Save(&checkout).Error; err != nil { return err } checkoutID = checkout.ID return nil }) if err != nil { return nil, err } checkout, err := r.findCheckout(ctx, checkoutID) if err != nil { return nil, err } dto := toCheckoutDTOForUser(*checkout, userID, model.RentalOrder{ OwnerID: userID, RentAmountCent: checkout.RentAmountCent, OwnerRentAmountCent: checkout.OwnerRentAmountCent, DepositAmountCent: checkout.DepositAmountCent, }) return &dto, nil } func (r *Repository) AcceptCheckout(ctx context.Context, userID uint64, orderID uint64) error { var refund *refundAction err := r.db.WithContext(ctx).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 != orderStatusPendingCheckoutAccept || order.HandoffStatus != handoffStatusPendingRenterCheckout { return ErrCheckoutCannotConfirm } var checkout model.OrderCheckout if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("order_id = ? AND status = ?", order.ID, checkoutStatusCountered). Order("id DESC"). First(&checkout).Error; err != nil { return err } now := time.Now() checkout.Status = checkoutStatusAccepted checkout.RenterConfirmedAt = &now action, err := r.finalizeCheckout(tx, &order, &checkout, "租客已确认修正结账,订单完成。") refund = action return err }) if err != nil { return err } r.startRefundBestEffort(ctx, refund) return nil }