重构充值订单模块:用 tb_recharge_order + tb_payment 替换 tb_asset_recharge_record
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m32s

- 新增 RechargeOrder 和 Payment 模型及对应 Store
- 新增 ClientRechargeOrderHandler 提供充值订单列表/详情接口
- 修改 client_wallet.go 使用新表读写充值数据
- 修改 callback/payment.go 将 CRCH 订单路由到 rechargeOrderService
- 修改 client_order/service.go 的强充流程使用新表
- 修改 auto_purchase.go 从 tb_recharge_order 读取 linked_package_ids
- 修改 order/service.go 的 WalletPay 使用 tb_payment 记录
- 修改 wechat_config_store.go 从 tb_recharge_order 统计待支付充值数
- 移除 AssetRechargeStore 和 AssetRechargeRecord 的注册引用
- 修复文档生成器缺失 ClientRechargeOrder handler
- 状态枚举改为 0-based: Pending=0, Paid=1, Closed=2, Refunded=3
This commit is contained in:
2026-04-15 11:00:32 +08:00
parent d26010b29d
commit b972a776d9
33 changed files with 1589 additions and 1017 deletions

View File

@@ -0,0 +1,196 @@
package app
import (
"context"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"github.com/break/junhong_cmp_fiber/internal/middleware"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type ClientRechargeOrderHandler struct {
rechargeOrderStore *postgres.RechargeOrderStore
paymentStore *postgres.PaymentStore
logger *zap.Logger
}
func NewClientRechargeOrderHandler(
rechargeOrderStore *postgres.RechargeOrderStore,
paymentStore *postgres.PaymentStore,
logger *zap.Logger,
) *ClientRechargeOrderHandler {
return &ClientRechargeOrderHandler{
rechargeOrderStore: rechargeOrderStore,
paymentStore: paymentStore,
logger: logger,
}
}
func (h *ClientRechargeOrderHandler) ListRechargeOrders(c *fiber.Ctx) error {
var req dto.ClientRechargeOrderListRequest
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 {
req.PageSize = constants.DefaultPageSize
}
if req.PageSize > constants.MaxPageSize {
req.PageSize = constants.MaxPageSize
}
customerID, ok := middleware.GetCustomerID(c)
if !ok || customerID == 0 {
return errors.New(errors.CodeUnauthorized)
}
skipCtx := context.WithValue(c.UserContext(), constants.ContextKeySubordinateShopIDs, []uint{})
params := &postgres.ListRechargeOrderParams{
Page: req.Page,
PageSize: req.PageSize,
UserID: &customerID,
}
if req.Status > 0 {
params.Status = &req.Status
}
orders, total, err := h.rechargeOrderStore.List(skipCtx, params)
if err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单失败")
}
items := make([]dto.ClientRechargeOrderListItem, 0, len(orders))
for _, order := range orders {
if order == nil {
continue
}
items = append(items, dto.ClientRechargeOrderListItem{
RechargeOrderID: order.ID,
RechargeOrderNo: order.RechargeOrderNo,
Amount: order.Amount,
Status: order.Status,
StatusName: getRechargeOrderStatusName(order.Status),
AutoPurchaseStatus: order.AutoPurchaseStatus,
CreatedAt: order.CreatedAt.Format(time.RFC3339),
})
}
return response.SuccessWithPagination(c, items, total, req.Page, req.PageSize)
}
func (h *ClientRechargeOrderHandler) GetRechargeOrderDetail(c *fiber.Ctx) error {
idStr := c.Params("id")
if idStr == "" {
return errors.New(errors.CodeInvalidParam)
}
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam)
}
customerID, ok := middleware.GetCustomerID(c)
if !ok || customerID == 0 {
return errors.New(errors.CodeUnauthorized)
}
skipCtx := context.WithValue(c.UserContext(), constants.ContextKeySubordinateShopIDs, []uint{})
order, err := h.rechargeOrderStore.GetByID(skipCtx, uint(id))
if err != nil {
if strings.Contains(err.Error(), "record not found") {
return errors.New(errors.CodeNotFound, "充值订单不存在")
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单失败")
}
if order.UserID != customerID {
return errors.New(errors.CodeForbidden, "无权访问该充值订单")
}
payments, _ := h.paymentStore.ListByOrderID(skipCtx, order.ID, model.PaymentOrderTypeRecharge)
var paymentDetails []dto.ClientRechargeOrderPaymentDetail
for _, p := range payments {
if p == nil {
continue
}
paymentDetails = append(paymentDetails, dto.ClientRechargeOrderPaymentDetail{
PaymentID: p.ID,
PaymentNo: p.PaymentNo,
PaymentMethod: p.PaymentMethod,
Amount: p.Amount,
Status: p.Status,
StatusName: getPaymentStatusName(p.Status),
ThirdPartyTradeNo: p.ThirdPartyTradeNo,
PaidAt: formatTimePtr(p.PaidAt),
CreatedAt: p.CreatedAt.Format(time.RFC3339),
})
}
resp := &dto.ClientRechargeOrderDetail{
RechargeOrderID: order.ID,
RechargeOrderNo: order.RechargeOrderNo,
Amount: order.Amount,
Status: order.Status,
StatusName: getRechargeOrderStatusName(order.Status),
AutoPurchaseStatus: order.AutoPurchaseStatus,
LinkedPackageIDs: order.LinkedPackageIDs,
CreatedAt: order.CreatedAt.Format(time.RFC3339),
PaidAt: formatTimePtr(order.PaidAt),
Payments: paymentDetails,
}
return response.Success(c, resp)
}
func getRechargeOrderStatusName(status int) string {
switch status {
case model.RechargeOrderStatusPending:
return "待支付"
case model.RechargeOrderStatusPaid:
return "已支付"
case model.RechargeOrderStatusClosed:
return "已关闭"
case model.RechargeOrderStatusRefunded:
return "已退款"
default:
return "未知"
}
}
func getPaymentStatusName(status int) string {
switch status {
case model.PaymentRecordStatusPending:
return "待支付"
case model.PaymentRecordStatusPaid:
return "已支付"
case model.PaymentRecordStatusFailed:
return "已失败"
case model.PaymentRecordStatusRefunded:
return "已退款"
default:
return "未知"
}
}
func formatTimePtr(t *time.Time) string {
if t == nil {
return ""
}
return t.Format(time.RFC3339)
}

View File

@@ -31,7 +31,8 @@ type ClientWalletHandler struct {
personalDeviceStore *postgres.PersonalCustomerDeviceStore
walletStore *postgres.AssetWalletStore
transactionStore *postgres.AssetWalletTransactionStore
rechargeStore *postgres.AssetRechargeStore
rechargeOrderStore *postgres.RechargeOrderStore
paymentStore *postgres.PaymentStore
rechargeService *rechargeSvc.Service
openIDStore *postgres.PersonalCustomerOpenIDStore
wechatConfigService *wechatConfigSvc.Service
@@ -48,7 +49,8 @@ func NewClientWalletHandler(
personalDeviceStore *postgres.PersonalCustomerDeviceStore,
walletStore *postgres.AssetWalletStore,
transactionStore *postgres.AssetWalletTransactionStore,
rechargeStore *postgres.AssetRechargeStore,
rechargeOrderStore *postgres.RechargeOrderStore,
paymentStore *postgres.PaymentStore,
rechargeService *rechargeSvc.Service,
openIDStore *postgres.PersonalCustomerOpenIDStore,
wechatConfigService *wechatConfigSvc.Service,
@@ -63,7 +65,8 @@ func NewClientWalletHandler(
personalDeviceStore: personalDeviceStore,
walletStore: walletStore,
transactionStore: transactionStore,
rechargeStore: rechargeStore,
rechargeOrderStore: rechargeOrderStore,
paymentStore: paymentStore,
rechargeService: rechargeService,
openIDStore: openIDStore,
wechatConfigService: wechatConfigService,
@@ -301,33 +304,45 @@ func (h *ClientWalletHandler) CreateRecharge(c *fiber.Ctx) error {
return err
}
// 支付通道确认可用后,再创建充值记录
recharge := &model.AssetRechargeRecord{
// 支付通道确认可用后,再创建充值订单和支付记录
rechargeOrder := &model.RechargeOrder{
RechargeOrderNo: rechargeNo,
UserID: resolved.CustomerID,
AssetWalletID: wallet.ID,
ResourceType: resolved.ResourceType,
ResourceID: resolved.Asset.AssetID,
RechargeNo: rechargeNo,
Amount: req.Amount,
PaymentMethod: constants.RechargeMethodWechat,
Status: model.RechargeOrderStatusPending,
PaymentConfigID: &config.ID,
Status: constants.RechargeStatusPending,
ShopIDTag: wallet.ShopIDTag,
EnterpriseIDTag: wallet.EnterpriseIDTag,
OperatorType: constants.OperatorTypePersonalCustomer,
Generation: resolved.Generation,
}
if err := h.rechargeStore.Create(resolved.SkipPermissionCtx, recharge); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建充值记录失败")
if err := h.rechargeOrderStore.Create(resolved.SkipPermissionCtx, rechargeOrder); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
}
payment := &model.Payment{
PaymentNo: rechargeNo,
OrderID: rechargeOrder.ID,
OrderType: model.PaymentOrderTypeRecharge,
PaymentMethod: model.PaymentByWechat,
Amount: req.Amount,
Status: model.PaymentRecordStatusPending,
PaymentConfigID: &config.ID,
}
if err := h.paymentStore.Create(resolved.SkipPermissionCtx, payment); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
}
payConfig := buildClientRechargePayConfig(appID, payResult)
resp := &dto.ClientRechargeResponse{
Recharge: dto.ClientRechargeResult{
RechargeID: recharge.ID,
RechargeNo: recharge.RechargeNo,
Amount: recharge.Amount,
Status: recharge.Status,
RechargeID: rechargeOrder.ID,
RechargeNo: rechargeOrder.RechargeOrderNo,
Amount: rechargeOrder.Amount,
Status: rechargeOrder.Status,
},
PayConfig: payConfig,
}
@@ -359,7 +374,7 @@ func (h *ClientWalletHandler) GetRechargeList(c *fiber.Ctx) error {
}
query := h.db.WithContext(resolved.SkipPermissionCtx).
Model(&model.AssetRechargeRecord{}).
Model(&model.RechargeOrder{}).
Where("resource_type = ? AND resource_id = ? AND generation = ?", resolved.ResourceType, resolved.Asset.AssetID, resolved.Generation)
if req.Status != nil {
query = query.Where("status = ?", *req.Status)
@@ -367,28 +382,28 @@ func (h *ClientWalletHandler) GetRechargeList(c *fiber.Ctx) error {
var total int64
if err := query.Count(&total).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录总数失败")
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单总数失败")
}
var records []*model.AssetRechargeRecord
var orders []*model.RechargeOrder
offset := (req.Page - 1) * req.PageSize
if err := query.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&records).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录失败")
if err := query.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&orders).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单失败")
}
items := make([]dto.ClientRechargeListItem, 0, len(records))
for _, record := range records {
if record == nil {
items := make([]dto.ClientRechargeListItem, 0, len(orders))
for _, order := range orders {
if order == nil {
continue
}
items = append(items, dto.ClientRechargeListItem{
RechargeID: record.ID,
RechargeNo: record.RechargeNo,
Amount: record.Amount,
Status: record.Status,
PaymentMethod: record.PaymentMethod,
CreatedAt: record.CreatedAt.Format(time.RFC3339),
AutoPurchaseStatus: record.AutoPurchaseStatus,
RechargeID: order.ID,
RechargeNo: order.RechargeOrderNo,
Amount: order.Amount,
Status: order.Status,
PaymentMethod: "", // 支付方式在 Payment 表中,列表查询不返回
CreatedAt: order.CreatedAt.Format(time.RFC3339),
AutoPurchaseStatus: order.AutoPurchaseStatus,
})
}

View File

@@ -13,6 +13,8 @@ import (
"github.com/break/junhong_cmp_fiber/internal/model"
orderService "github.com/break/junhong_cmp_fiber/internal/service/order"
rechargeService "github.com/break/junhong_cmp_fiber/internal/service/recharge"
rechargeOrderSvc "github.com/break/junhong_cmp_fiber/internal/service/recharge_order"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/fuiou"
@@ -33,26 +35,32 @@ type WechatConfigServiceInterface interface {
type PaymentHandler struct {
orderService *orderService.Service
rechargeService *rechargeService.Service
rechargeOrderService *rechargeOrderSvc.Service
agentRechargeService AgentRechargeServiceInterface
wechatPayment wechat.PaymentServiceInterface
wechatConfigService WechatConfigServiceInterface
paymentStore *postgres.PaymentStore
logger *zap.Logger
}
func NewPaymentHandler(
orderService *orderService.Service,
rechargeService *rechargeService.Service,
rechargeOrderService *rechargeOrderSvc.Service,
agentRechargeService AgentRechargeServiceInterface,
wechatPayment wechat.PaymentServiceInterface,
wechatConfigService WechatConfigServiceInterface,
paymentStore *postgres.PaymentStore,
logger *zap.Logger,
) *PaymentHandler {
return &PaymentHandler{
orderService: orderService,
rechargeService: rechargeService,
rechargeOrderService: rechargeOrderService,
agentRechargeService: agentRechargeService,
wechatPayment: wechatPayment,
wechatConfigService: wechatConfigService,
paymentStore: paymentStore,
logger: logger,
}
}
@@ -122,7 +130,10 @@ func (h *PaymentHandler) dispatchWechatCallback(ctx context.Context, outTradeNo,
case strings.HasPrefix(outTradeNo, "ORD"):
return h.orderService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat)
case strings.HasPrefix(outTradeNo, constants.AssetRechargeOrderPrefix):
return h.rechargeService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat, transactionID)
if h.rechargeOrderService != nil {
return h.rechargeOrderService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentByWechat, transactionID)
}
return fmt.Errorf("充值订单服务未配置,无法处理订单: %s", outTradeNo)
case strings.HasPrefix(outTradeNo, constants.AgentRechargeOrderPrefix):
if h.agentRechargeService != nil {
return h.agentRechargeService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat, transactionID)
@@ -165,9 +176,10 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
return err
}
case strings.HasPrefix(req.OrderNo, constants.AssetRechargeOrderPrefix):
if err := h.rechargeService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodAlipay, ""); err != nil {
return err
if h.rechargeOrderService != nil {
return h.rechargeOrderService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentByAlipay, "")
}
return fmt.Errorf("充值订单服务未配置,无法处理订单: %s", req.OrderNo)
case strings.HasPrefix(req.OrderNo, constants.AgentRechargeOrderPrefix):
if h.agentRechargeService != nil {
if err := h.agentRechargeService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodAlipay, ""); err != nil {
@@ -242,9 +254,13 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
}
case strings.HasPrefix(orderNo, constants.AssetRechargeOrderPrefix):
if err := h.rechargeService.HandlePaymentCallback(ctx, orderNo, "fuiou", notify.TransactionId); err != nil {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
if h.rechargeOrderService != nil {
if err := h.rechargeOrderService.HandlePaymentCallback(ctx, orderNo, "fuiou", notify.TransactionId); err != nil {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
}
return c.Send(fuiou.BuildNotifySuccessResponse())
}
return c.Send(fuiou.BuildNotifyFailResponse("充值订单服务未配置"))
case strings.HasPrefix(orderNo, constants.AgentRechargeOrderPrefix):
if h.agentRechargeService != nil {
if err := h.agentRechargeService.HandlePaymentCallback(ctx, orderNo, "fuiou", notify.TransactionId); err != nil {