Files
hfb_sys/backend/internal/modules/payment/quality_owner_payment.go
T
yml2213 98174c0e33 feat(quality-owner): 增加优质号主、极品专区与无忧租号保险
- 支持保证金余额支付、在线支付开通及后台手动设置审计
- 新增极品专区发布权限、赔付方案与保险结算费率快照
- 优化首页、商品卡和个人中心的优质号主身份展示
2026-11-04 15:04:35 +08:00

159 lines
5.0 KiB
Go

package payment
import (
"context"
"fmt"
"hfb_sys/backend/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
const qualityOwnerPayBizType = "quality_owner_pay"
// StartQualityOwner 创建优质号主保证金在线支付单,支付成功后自动开通资格并冻结保证金。
func (r *Repository) StartQualityOwner(ctx context.Context, userID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) {
if r.qualityOwnerRepo == nil {
return nil, ErrDependencyUnavailable
}
req.PayWay = normalizePayWay(req.PayWay)
payment, err := r.prepareQualityOwnerPayment(ctx, userID, req)
if err != nil {
return nil, err
}
return r.startPreparedPayment(ctx, payment, "优质号主保证金", payment.OrderNo, clientIP)
}
func (r *Repository) prepareQualityOwnerPayment(ctx context.Context, userID uint64, req StartPaymentRequest) (*model.PaymentOrder, error) {
amountCent, active, err := r.qualityOwnerRepo.PaymentAmount(ctx, userID)
if err != nil {
return nil, err
}
if active || amountCent <= 0 {
return nil, ErrPaymentCannotStart
}
runtimeConfig, err := r.defaultRuntimeConfig(ctx, req.PayWay)
if err != nil {
return nil, err
}
var paymentID uint64
err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var user model.User
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Select("id").
First(&user, userID).Error; err != nil {
return err
}
var existing model.PaymentOrder
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
Where("user_id = ? AND biz_type = ? AND biz_id = ?", userID, qualityOwnerPayBizType, userID).
Order("id DESC").
First(&existing).Error
if err == nil {
if canReuseOrderPayment(existing, *runtimeConfig) {
existing.PayWay = firstNonEmpty(req.PayWay, existing.PayWay, runtimeConfig.PayWay, "ZFBZF")
existing.JSPayFlag = firstNonEmpty(req.JSPayFlag, existing.JSPayFlag, runtimeConfig.JSPayFlag, "2")
existing.AmountCent = amountCent
existing.PaymentConfigID = firstNonZero(existing.PaymentConfigID, runtimeConfig.ID)
existing.Provider = firstNonEmpty(existing.Provider, runtimeConfig.Provider)
existing.MerchantID = firstNonEmpty(existing.MerchantID, runtimeConfig.MerchantID)
if existing.Provider == "mock" && existing.ProviderOrderID == "" {
existing.ProviderOrderID = "MOCK" + existing.ThirdOrderID
}
if err := tx.Save(&existing).Error; err != nil {
return err
}
paymentID = existing.ID
return nil
}
} else if err != gorm.ErrRecordNotFound {
return err
}
payment, err := newQualityOwnerPayment(userID, amountCent, req, *runtimeConfig)
if err != nil {
return err
}
if err := tx.Create(&payment).Error; err != nil {
return err
}
paymentID = payment.ID
return nil
})
if err != nil {
return nil, err
}
return r.findPaymentByID(ctx, paymentID)
}
func newQualityOwnerPayment(userID uint64, amountCent int64, req StartPaymentRequest, runtimeConfig runtimePaymentConfig) (model.PaymentOrder, error) {
paymentNo, err := newPaymentNo()
if err != nil {
return model.PaymentOrder{}, err
}
orderNo := fmt.Sprintf("QO%d", userID)
payment := model.PaymentOrder{
PaymentNo: paymentNo,
OrderID: 0,
OrderNo: orderNo,
BizID: userID,
UserID: userID,
PaymentConfigID: runtimeConfig.ID,
Provider: runtimeConfig.Provider,
MerchantID: runtimeConfig.MerchantID,
ThirdOrderID: paymentNo,
PayWay: firstNonEmpty(req.PayWay, runtimeConfig.PayWay, "ZFBZF"),
JSPayFlag: firstNonEmpty(req.JSPayFlag, runtimeConfig.JSPayFlag, "2"),
AmountCent: amountCent,
BizType: qualityOwnerPayBizType,
Status: "created",
}
if runtimeConfig.isMockMode() {
payment.ProviderOrderID = "MOCK" + paymentNo
payment.TDCode = "mock://payment/pay/" + paymentNo
}
return payment, nil
}
func (r *Repository) QueryQualityOwner(ctx context.Context, userID uint64) (*PaymentDTO, error) {
var payment model.PaymentOrder
if err := r.db.WithContext(ctx).
Where("user_id = ? AND biz_type = ? AND biz_id = ?", userID, qualityOwnerPayBizType, userID).
Order("id DESC").
First(&payment).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, ErrPaymentNotFound
}
return nil, err
}
if isOrderPaymentTerminalStatus(payment.Status) {
dto := toDTO(payment)
return &dto, nil
}
runtimeConfig, err := r.runtimeConfigForPayment(ctx, &payment)
if err != nil {
return nil, ErrPaymentUnavailable
}
if runtimeConfig.isMockMode() {
dto := toDTO(payment)
return &dto, nil
}
if runtimeConfig.Channel == nil {
return nil, ErrPaymentUnavailable
}
resp, err := runtimeConfig.Channel.QueryPayment(ctx, payment.ThirdOrderID, payment.ProviderOrderID)
if err != nil {
return nil, err
}
if err := r.applyChannelStatus(ctx, &payment, resp.Status, resp.PayTime, resp.Raw, channelSourceQuery); err != nil {
return nil, err
}
latest, err := r.findPaymentByID(ctx, payment.ID)
if err != nil {
return nil, err
}
dto := toDTO(*latest)
return &dto, nil
}