feat: 代理充值支持线下支付凭证上传与存储

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-17 18:29:06 +08:00
parent b538e45bd9
commit ad30f5d41b
3 changed files with 36 additions and 25 deletions

View File

@@ -79,6 +79,7 @@ type AgentRechargeRecord struct {
PaymentTransactionID *string `gorm:"column:payment_transaction_id;type:varchar(100);comment:第三方支付交易号" json:"payment_transaction_id,omitempty"`
PaymentConfigID *uint `gorm:"column:payment_config_id;index;comment:支付配置ID(关联tb_wechat_config.id)" json:"payment_config_id,omitempty"`
Status int `gorm:"column:status;type:int;not null;default:1;comment:充值状态(1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款)" json:"status"`
PaymentVoucherKey string `gorm:"column:payment_voucher_key;type:varchar(500);comment:支付凭证对象存储Key线下支付时必填微信支付时为空" json:"payment_voucher_key,omitempty"`
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at,omitempty"`
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at,omitempty"`
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`

View File

@@ -2,9 +2,10 @@ package dto
// CreateAgentRechargeRequest 创建代理充值请求
type CreateAgentRechargeRequest struct {
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"目标店铺ID代理只能填自己店铺"`
Amount int64 `json:"amount" validate:"required,min=10000,max=100000000" required:"true" minimum:"10000" maximum:"100000000" description:"充值金额范围100元~100万元"`
PaymentMethod string `json:"payment_method" validate:"required,oneof=wechat offline" required:"true" description:"支付方式 (wechat:微信在线支付, offline:线下转账仅平台可用)"`
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"目标店铺ID代理只能填自己店铺"`
Amount int64 `json:"amount" validate:"required,min=10000,max=100000000" required:"true" minimum:"10000" maximum:"100000000" description:"充值金额范围100元~100万元"`
PaymentMethod string `json:"payment_method" validate:"required,oneof=wechat offline" required:"true" description:"支付方式 (wechat:微信在线支付, offline:线下转账仅平台可用)"`
PaymentVoucherKey string `json:"payment_voucher_key" validate:"omitempty,max=500" description:"支付凭证对象存储Keypayment_method=offline 时必填,微信支付时忽略)"`
}
// AgentOfflinePayRequest 代理线下充值确认请求
@@ -30,6 +31,7 @@ type AgentRechargeResponse struct {
PaymentChannel string `json:"payment_channel" description:"实际支付通道 (wechat_direct:微信直连, fuyou:富友, offline:线下转账)"`
PaymentConfigID *uint `json:"payment_config_id" description:"关联支付配置ID线下充值为null"`
PaymentTransactionID string `json:"payment_transaction_id" description:"第三方支付流水号"`
PaymentVoucherKey string `json:"payment_voucher_key,omitempty" description:"支付凭证对象存储Key线下支付时存在"`
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
StatusName string `json:"status_name" description:"状态名称(中文)"`
PaidAt *string `json:"paid_at" description:"支付时间"`

View File

@@ -6,6 +6,7 @@ import (
"context"
"fmt"
"math/rand"
"strings"
"time"
"github.com/redis/go-redis/v9"
@@ -90,6 +91,11 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeReques
return nil, errors.New(errors.CodeForbidden, "线下充值仅平台管理员可操作")
}
// 线下充值必须上传支付凭证
if req.PaymentMethod == "offline" && strings.TrimSpace(req.PaymentVoucherKey) == "" {
return nil, errors.New(errors.CodeInvalidParam, "线下充值必须上传支付凭证")
}
if req.Amount < constants.AgentRechargeMinAmount || req.Amount > constants.AgentRechargeMaxAmount {
return nil, errors.New(errors.CodeInvalidParam, "充值金额超出允许范围")
}
@@ -129,17 +135,18 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeReques
rechargeNo := s.generateRechargeNo()
record := &model.AgentRechargeRecord{
UserID: userID,
AgentWalletID: wallet.ID,
ShopID: req.ShopID,
RechargeNo: rechargeNo,
Amount: req.Amount,
PaymentMethod: req.PaymentMethod,
PaymentChannel: &paymentChannel,
PaymentConfigID: paymentConfigID,
Status: constants.RechargeStatusPending,
ShopIDTag: wallet.ShopIDTag,
EnterpriseIDTag: wallet.EnterpriseIDTag,
UserID: userID,
AgentWalletID: wallet.ID,
ShopID: req.ShopID,
RechargeNo: rechargeNo,
Amount: req.Amount,
PaymentMethod: req.PaymentMethod,
PaymentChannel: &paymentChannel,
PaymentConfigID: paymentConfigID,
PaymentVoucherKey: strings.TrimSpace(req.PaymentVoucherKey),
Status: constants.RechargeStatusPending,
ShopIDTag: wallet.ShopIDTag,
EnterpriseIDTag: wallet.EnterpriseIDTag,
}
if err := s.agentRechargeStore.Create(ctx, record); err != nil {
@@ -472,17 +479,18 @@ func (s *Service) generateRechargeNo() string {
// toResponse 将模型转换为响应 DTO
func toResponse(record *model.AgentRechargeRecord, shopName string) *dto.AgentRechargeResponse {
resp := &dto.AgentRechargeResponse{
ID: record.ID,
RechargeNo: record.RechargeNo,
ShopID: record.ShopID,
ShopName: shopName,
AgentWalletID: record.AgentWalletID,
Amount: record.Amount,
PaymentMethod: record.PaymentMethod,
Status: record.Status,
StatusName: constants.GetRechargeStatusName(record.Status),
CreatedAt: record.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: record.UpdatedAt.Format("2006-01-02 15:04:05"),
ID: record.ID,
RechargeNo: record.RechargeNo,
ShopID: record.ShopID,
ShopName: shopName,
AgentWalletID: record.AgentWalletID,
Amount: record.Amount,
PaymentMethod: record.PaymentMethod,
PaymentVoucherKey: record.PaymentVoucherKey,
Status: record.Status,
StatusName: constants.GetRechargeStatusName(record.Status),
CreatedAt: record.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: record.UpdatedAt.Format("2006-01-02 15:04:05"),
}
if record.PaymentChannel != nil {