实现支付商户池与微信授权配置
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 3m55s

新增收款商户、商户池轮询、微信授权配置独立管理;三类新支付
(C端套餐购买、C端资产钱包充值、代理在线预存款充值)无条件
经商户池选择并冻结路由,无旧综合配置回退。merchant_id 为空
历史支付继续按 payment_config_id 双读。凭证版本化加载与
ID+版本缓存保证轮换一致性。删除商户池新支付创建开关及全部
引用。
This commit is contained in:
2026-09-09 18:13:04 +08:00
parent ff25586dc9
commit 98c145fe70
39 changed files with 2718 additions and 415 deletions

View 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
}