新增收款商户、商户池轮询、微信授权配置独立管理;三类新支付 (C端套餐购买、C端资产钱包充值、代理在线预存款充值)无条件 经商户池选择并冻结路由,无旧综合配置回退。merchant_id 为空 历史支付继续按 payment_config_id 双读。凭证版本化加载与 ID+版本缓存保证轮换一致性。删除商户池新支付创建开关及全部 引用。
This commit is contained in:
@@ -62,6 +62,9 @@ func (s *Service) applyApprovedDecision(ctx context.Context, event approvalapp.T
|
||||
if err := validateApprovedRefundAmount(approvedAmount, refund.RequestedRefundAmount, &order); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.preparePaymentRefundCredentials(ctx, tx, order.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if refund.Status == model.RefundStatusPending {
|
||||
changed = true
|
||||
result := tx.WithContext(ctx).Model(&model.RefundRequest{}).
|
||||
|
||||
99
internal/service/refund/payment_merchant.go
Normal file
99
internal/service/refund/payment_merchant.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package refund
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
merchantpayment "github.com/break/junhong_cmp_fiber/internal/application/merchantpayment"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// SetPaymentMerchantRuntime 注入冻结商户的当前凭证版本加载器。
|
||||
func (s *Service) SetPaymentMerchantRuntime(runtime *merchantpayment.RuntimeLoader) {
|
||||
s.paymentMerchantRuntime = runtime
|
||||
}
|
||||
|
||||
// preparePaymentRefundCredentials 只为既有套餐订单退款流程装载和校验支付凭证;本 Change
|
||||
// 不发起任何渠道退款请求。钱包支付和线下支付没有收款商户凭证,直接放行。
|
||||
// merchant_id 为空仅是留存期内的历史线上支付,独立 Change 清理后才可删除按
|
||||
// payment_config_id 读取的兼容路径,绝不能按当前商户池推断商户。
|
||||
func (s *Service) preparePaymentRefundCredentials(ctx context.Context, tx *gorm.DB, orderID uint) error {
|
||||
var payment model.Payment
|
||||
err := tx.WithContext(ctx).
|
||||
Where("order_id = ? AND order_type = ? AND status = ?", orderID, model.PaymentOrderTypePackage, model.PaymentRecordStatusPaid).
|
||||
Order("id DESC").
|
||||
First(&payment).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询退款关联支付单失败")
|
||||
}
|
||||
// 钱包支付和线下支付没有收款商户凭证,不被本检查阻断。
|
||||
if payment.PaymentMethod == model.PaymentByWallet || payment.PaymentMethod == model.PaymentByOffline {
|
||||
return nil
|
||||
}
|
||||
if payment.MerchantID != nil {
|
||||
if s.paymentMerchantRuntime == nil {
|
||||
return errors.New(errors.CodeServiceUnavailable, "支付商户加载能力未配置")
|
||||
}
|
||||
merchant, loadErr := s.paymentMerchantRuntime.LoadMerchant(ctx, *payment.MerchantID)
|
||||
if loadErr != nil {
|
||||
return loadErr
|
||||
}
|
||||
return validateFrozenMerchantRefundCredentials(merchant)
|
||||
}
|
||||
if payment.PaymentConfigID == nil {
|
||||
return errors.New(errors.CodeNoPaymentConfig, "历史支付单缺少支付配置")
|
||||
}
|
||||
var legacy model.WechatConfig
|
||||
if err := tx.WithContext(ctx).Unscoped().First(&legacy, *payment.PaymentConfigID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNoPaymentConfig, "历史支付配置不可用")
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "读取历史支付配置失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateFrozenMerchantRefundCredentials 只判断既有渠道流程所需凭证是否完整。
|
||||
// 当前系统没有任何渠道原路退款 Adapter,因此返回前不调用微信、支付宝或富友。
|
||||
func validateFrozenMerchantRefundCredentials(merchant *model.PaymentMerchant) error {
|
||||
config, err := merchantpayment.MerchantConfig(merchant, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch config.ProviderType {
|
||||
case model.ProviderTypeWechat:
|
||||
if blank(config.WxMchID, config.WxAPIV3Key, config.WxCertContent, config.WxKeyContent, config.WxSerialNo, config.WxNotifyURL) {
|
||||
return errors.New(errors.CodeNoPaymentConfig, "冻结微信商户退款凭证不完整")
|
||||
}
|
||||
case model.ProviderTypeWechatV2:
|
||||
if blank(config.WxMchID, config.WxAPIV2Key, config.WxNotifyURL) {
|
||||
return errors.New(errors.CodeNoPaymentConfig, "冻结微信商户退款凭证不完整")
|
||||
}
|
||||
case model.ProviderTypeFuiou:
|
||||
if blank(config.FyInsCd, config.FyMchntCd, config.FyTermID, config.FyPrivateKey, config.FyPublicKey, config.FyAPIURL, config.FyNotifyURL) {
|
||||
return errors.New(errors.CodeNoPaymentConfig, "冻结富友商户退款凭证不完整")
|
||||
}
|
||||
case "alipay":
|
||||
if blank(config.AliAppID, config.AliPrivateKey, config.AliPublicKey, config.AliNotifyURL, config.AliReturnURL) {
|
||||
return errors.New(errors.CodeNoPaymentConfig, "冻结支付宝商户退款凭证不完整")
|
||||
}
|
||||
default:
|
||||
return errors.New(errors.CodeNoPaymentConfig, "冻结商户不支持现有退款流程")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func blank(values ...string) bool {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
merchantpayment "github.com/break/junhong_cmp_fiber/internal/application/merchantpayment"
|
||||
notificationapp "github.com/break/junhong_cmp_fiber/internal/application/notification"
|
||||
refundapprovalapp "github.com/break/junhong_cmp_fiber/internal/application/refundapproval"
|
||||
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
|
||||
@@ -56,6 +57,7 @@ type Service struct {
|
||||
notificationOutbox *outbox.Repository
|
||||
auditWriter *audit.Writer
|
||||
logger *zap.Logger
|
||||
paymentMerchantRuntime *merchantpayment.RuntimeLoader
|
||||
}
|
||||
|
||||
// New 创建退款业务服务实例
|
||||
@@ -355,6 +357,9 @@ func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveRefundRe
|
||||
if orderResult.RowsAffected == 0 {
|
||||
return errors.New(errors.CodeInvalidStatus, "订单状态已变更,无法执行退款审批")
|
||||
}
|
||||
if err := s.preparePaymentRefundCredentials(ctx, tx, order.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.refundWalletPayment(ctx, tx, refund, order, approvedAmount, userID); err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user