新增收款商户、商户池轮询、微信授权配置独立管理;三类新支付 (C端套餐购买、C端资产钱包充值、代理在线预存款充值)无条件 经商户池选择并冻结路由,无旧综合配置回退。merchant_id 为空 历史支付继续按 payment_config_id 双读。凭证版本化加载与 ID+版本缓存保证轮换一致性。删除商户池新支付创建开关及全部 引用。
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
merchantpayment "github.com/break/junhong_cmp_fiber/internal/application/merchantpayment"
|
||||
domain "github.com/break/junhong_cmp_fiber/internal/domain/agentrecharge"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -81,7 +82,7 @@ func (s *ConfirmOnlinePaymentService) Execute(ctx context.Context, command Confi
|
||||
command.PaymentMethod = strings.TrimSpace(command.PaymentMethod)
|
||||
command.MerchantIdentity = strings.TrimSpace(command.MerchantIdentity)
|
||||
command.ThirdPartyTradeNo = strings.TrimSpace(command.ThirdPartyTradeNo)
|
||||
if command.PaymentNo == "" || command.ConfigID == 0 || command.PaidAt.IsZero() {
|
||||
if command.PaymentNo == "" || command.PaidAt.IsZero() {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "代理充值支付确认参数不完整")
|
||||
}
|
||||
|
||||
@@ -91,6 +92,9 @@ func (s *ConfirmOnlinePaymentService) Execute(ctx context.Context, command Confi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if payment.MerchantID == nil && command.ConfigID == 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "代理充值支付确认参数不完整")
|
||||
}
|
||||
alreadyConfirmed, err := domain.ValidatePaymentConfirmation(toDomainConfirmationFacts(payment, recharge, command))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -114,6 +118,10 @@ func (s *ConfirmOnlinePaymentService) Execute(ctx context.Context, command Confi
|
||||
if paymentUpdate.RowsAffected != 1 {
|
||||
return errors.New(errors.CodeConflict, "代理充值支付单状态已变化")
|
||||
}
|
||||
|
||||
if err := merchantpayment.RecordFirstSuccess(ctx, tx, payment, paidAt); err != nil {
|
||||
return err
|
||||
}
|
||||
rechargeUpdate := tx.WithContext(ctx).Model(&model.AgentRechargeRecord{}).
|
||||
Where("id = ? AND status IN ?", recharge.ID, []int{constants.RechargeStatusPending, constants.RechargeStatusClosed}).
|
||||
Updates(map[string]any{"status": constants.RechargeStatusPaid, "payment_transaction_id": command.ThirdPartyTradeNo, "paid_at": paidAt})
|
||||
@@ -127,7 +135,7 @@ func (s *ConfirmOnlinePaymentService) Execute(ctx context.Context, command Confi
|
||||
EventID: "agent-recharge:" + strconv.FormatUint(uint64(recharge.ID), 10) + ":payment-confirmed",
|
||||
RechargeID: recharge.ID, RechargeNo: recharge.RechargeNo, PaymentID: payment.ID, PaymentNo: payment.PaymentNo,
|
||||
ShopID: recharge.ShopID, WalletID: recharge.AgentWalletID, UserID: recharge.UserID, Amount: recharge.Amount,
|
||||
PaymentMethod: command.PaymentMethod, ThirdPartyTradeNo: command.ThirdPartyTradeNo,
|
||||
PaymentMethod: payment.PaymentMethod, ThirdPartyTradeNo: command.ThirdPartyTradeNo,
|
||||
PaidAt: paidAt, RequestID: command.RequestID, CorrelationID: command.CorrelationID,
|
||||
ParentEventID: command.ParentEventID,
|
||||
}
|
||||
@@ -186,7 +194,9 @@ func toDomainConfirmationFacts(payment *model.Payment, recharge *model.AgentRech
|
||||
OrderType: payment.OrderType, ExpectedOrderType: model.PaymentOrderTypeAgentRecharge,
|
||||
PaymentMethod: payment.PaymentMethod, RechargePaymentMethod: recharge.PaymentMethod, RechargePaymentChannel: rechargeChannel,
|
||||
PaymentConfigID: paymentConfigID, RechargePaymentConfigID: rechargeConfigID, ConfirmedConfigID: command.ConfigID,
|
||||
MerchantIdentity: payment.MerchantIdentity, ConfirmedMerchantIdentity: command.MerchantIdentity,
|
||||
FrozenMerchant: payment.MerchantID != nil, FrozenMerchantPaymentMethod: payment.MerchantPaymentMethodSnapshot,
|
||||
FrozenMerchantProviderType: payment.MerchantProviderTypeSnapshot,
|
||||
MerchantIdentity: payment.MerchantIdentity, ConfirmedMerchantIdentity: command.MerchantIdentity,
|
||||
PaymentAmount: payment.Amount, RechargeAmount: recharge.Amount, ConfirmedAmount: command.Amount,
|
||||
PaymentOrderID: payment.OrderID, RechargeID: recharge.ID, PaymentState: domain.PaymentState(payment.Status),
|
||||
RechargeStatus: recharge.Status, StoredTradeNo: payment.ThirdPartyTradeNo, ConfirmedTradeNo: command.ThirdPartyTradeNo,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/application/merchantpayment"
|
||||
domain "github.com/break/junhong_cmp_fiber/internal/domain/agentrecharge"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -46,21 +47,22 @@ type AvailablePaymentMethodsResult struct {
|
||||
|
||||
// OnlineCreationService 创建代理在线扫码充值单。
|
||||
type OnlineCreationService struct {
|
||||
db *gorm.DB
|
||||
wechat OnlinePaymentPort
|
||||
alipay OnlinePaymentPort
|
||||
fuiou OnlinePaymentPort
|
||||
audit PaymentAuditWriter
|
||||
db *gorm.DB
|
||||
runtime *merchantpayment.RuntimeLoader
|
||||
wechat OnlinePaymentPort
|
||||
alipay OnlinePaymentPort
|
||||
fuiou OnlinePaymentPort
|
||||
audit PaymentAuditWriter
|
||||
}
|
||||
|
||||
// NewOnlineCreationService 创建代理在线充值用例并以结构体字段注入三个渠道 Adapter。
|
||||
func NewOnlineCreationService(db *gorm.DB, wechat, alipay, fuiou OnlinePaymentPort, audit PaymentAuditWriter) *OnlineCreationService {
|
||||
return &OnlineCreationService{db: db, wechat: wechat, alipay: alipay, fuiou: fuiou, audit: audit}
|
||||
// NewOnlineCreationService 创建代理在线充值用例并以结构体字段注入运行时路由和三个渠道 Adapter。
|
||||
func NewOnlineCreationService(db *gorm.DB, runtime *merchantpayment.RuntimeLoader, wechat, alipay, fuiou OnlinePaymentPort, audit PaymentAuditWriter) *OnlineCreationService {
|
||||
return &OnlineCreationService{db: db, runtime: runtime, wechat: wechat, alipay: alipay, fuiou: fuiou, audit: audit}
|
||||
}
|
||||
|
||||
// Execute 以短事务建单,事务外生成支付链接,再条件保存链接或关闭失败订单。
|
||||
func (s *OnlineCreationService) Execute(ctx context.Context, command CreateOnlineCommand) (*CreateOnlineResult, error) {
|
||||
if s == nil || s.db == nil || s.wechat == nil || s.alipay == nil || s.fuiou == nil || s.audit == nil {
|
||||
if s == nil || s.db == nil || s.runtime == nil || s.wechat == nil || s.alipay == nil || s.fuiou == nil || s.audit == nil {
|
||||
return nil, apperrors.New(apperrors.CodeServiceUnavailable, "代理在线充值能力未配置")
|
||||
}
|
||||
command.PaymentMethod = strings.TrimSpace(command.PaymentMethod)
|
||||
@@ -82,11 +84,11 @@ func (s *OnlineCreationService) Execute(ctx context.Context, command CreateOnlin
|
||||
if replay, found, err := s.loadReplay(ctx, command, fingerprint); err != nil || found {
|
||||
return replay, err
|
||||
}
|
||||
account, shop, wallet, config, adapter, err := s.loadCreationFacts(ctx, command)
|
||||
account, shop, wallet, err := s.loadCreationFacts(ctx, command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := s.createLocalFacts(ctx, command, fingerprint.Value, account, shop, wallet, config)
|
||||
result, config, adapter, err := s.createLocalFacts(ctx, command, fingerprint.Value, account, shop, wallet)
|
||||
if err != nil {
|
||||
if replay, found, replayErr := s.loadReplay(ctx, command, fingerprint); replayErr != nil || found {
|
||||
return replay, replayErr
|
||||
@@ -125,7 +127,7 @@ func (s *OnlineCreationService) Execute(ctx context.Context, command CreateOnlin
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AvailablePaymentMethods 按固定顺序返回配置完整的在线支付方式。
|
||||
// AvailablePaymentMethods 按固定顺序返回已有可用商户池且凭证完整的在线支付方式。
|
||||
func (s *OnlineCreationService) AvailablePaymentMethods(ctx context.Context, userType int) (AvailablePaymentMethodsResult, error) {
|
||||
result := AvailablePaymentMethodsResult{
|
||||
Methods: []string{}, MinAmount: constants.AgentOnlineRechargeMinAmount, MaxAmount: constants.AgentRechargeMaxAmount,
|
||||
@@ -133,18 +135,38 @@ func (s *OnlineCreationService) AvailablePaymentMethods(ctx context.Context, use
|
||||
if userType != constants.UserTypeAgent {
|
||||
return result, apperrors.New(apperrors.CodeForbidden, "仅代理账号可以查询在线支付方式")
|
||||
}
|
||||
var config model.WechatConfig
|
||||
if err := s.db.WithContext(ctx).Where("is_active = ?", true).First(&config).Error; err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return result, nil
|
||||
for _, method := range []string{constants.RechargeMethodWechat, constants.RechargeMethodAlipay} {
|
||||
var merchants []model.PaymentMerchant
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&model.PaymentMerchant{}).
|
||||
Joins("JOIN tb_payment_merchant_pool_member AS member ON member.merchant_id = tb_payment_merchant.id AND member.deleted_at IS NULL").
|
||||
Joins("JOIN tb_payment_merchant_pool AS pool ON pool.id = member.pool_id AND pool.deleted_at IS NULL").
|
||||
Where("pool.payment_method = ? AND pool.status = ? AND tb_payment_merchant.payment_method = ? AND tb_payment_merchant.status = ?", method, model.PaymentMerchantStatusEnabled, method, model.PaymentMerchantStatusEnabled).
|
||||
Order("member.sort_order ASC").Find(&merchants).Error
|
||||
if err != nil {
|
||||
return result, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询代理在线支付商户池失败")
|
||||
}
|
||||
for index := range merchants {
|
||||
// 仅微信直连(v3/v2)商户需要全局授权配置中的 AppID;富友商户不依赖该配置。
|
||||
var authorization *model.WechatAuthorization
|
||||
merchant := &merchants[index]
|
||||
if merchant.ProviderType == model.ProviderTypeWechat || merchant.ProviderType == model.ProviderTypeWechatV2 {
|
||||
var authErr error
|
||||
authorization, authErr = s.runtime.LoadAuthorization(ctx)
|
||||
if authErr != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
config, configErr := merchantpayment.MerchantConfig(merchant, authorization)
|
||||
if configErr != nil {
|
||||
continue
|
||||
}
|
||||
adapter := s.adapter(method, config)
|
||||
if adapter != nil && adapter.Available(config) {
|
||||
result.Methods = append(result.Methods, method)
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询生效支付配置失败")
|
||||
}
|
||||
if s.wechat.Available(&config) || s.fuiou.Available(&config) {
|
||||
result.Methods = append(result.Methods, constants.RechargeMethodWechat)
|
||||
}
|
||||
if s.alipay.Available(&config) {
|
||||
result.Methods = append(result.Methods, constants.RechargeMethodAlipay)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -152,40 +174,29 @@ func (s *OnlineCreationService) AvailablePaymentMethods(ctx context.Context, use
|
||||
func (s *OnlineCreationService) loadCreationFacts(
|
||||
ctx context.Context,
|
||||
command CreateOnlineCommand,
|
||||
) (*model.Account, *model.Shop, *model.AgentWallet, *model.WechatConfig, OnlinePaymentPort, error) {
|
||||
) (*model.Account, *model.Shop, *model.AgentWallet, error) {
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).Where("id = ? AND user_type = ? AND status = ?", command.AccountID, constants.UserTypeAgent, constants.StatusEnabled).First(&account).Error; err != nil || account.ShopID == nil || *account.ShopID != command.CurrentShopID {
|
||||
if err != nil && !stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询在线充值账号失败")
|
||||
return nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询在线充值账号失败")
|
||||
}
|
||||
return nil, nil, nil, nil, nil, apperrors.New(apperrors.CodeForbidden, "当前代理账号不可为该店铺充值")
|
||||
return nil, nil, nil, apperrors.New(apperrors.CodeForbidden, "当前代理账号不可为该店铺充值")
|
||||
}
|
||||
var shop model.Shop
|
||||
if err := s.db.WithContext(ctx).Where("id = ? AND status = ?", command.CurrentShopID, constants.StatusEnabled).First(&shop).Error; err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, nil, nil, nil, apperrors.New(apperrors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
return nil, nil, nil, apperrors.New(apperrors.CodeForbidden, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
return nil, nil, nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询当前店铺失败")
|
||||
return nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询当前店铺失败")
|
||||
}
|
||||
var wallet model.AgentWallet
|
||||
if err := s.db.WithContext(ctx).Where("shop_id = ? AND wallet_type = ? AND status = ?", command.CurrentShopID, constants.AgentWalletTypeMain, constants.AgentWalletStatusNormal).First(&wallet).Error; err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, nil, nil, nil, apperrors.New(apperrors.CodeWalletNotFound, "当前店铺主钱包不存在或不可用")
|
||||
return nil, nil, nil, apperrors.New(apperrors.CodeWalletNotFound, "当前店铺主钱包不存在或不可用")
|
||||
}
|
||||
return nil, nil, nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询当前店铺主钱包失败")
|
||||
return nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询当前店铺主钱包失败")
|
||||
}
|
||||
var config model.WechatConfig
|
||||
if err := s.db.WithContext(ctx).Where("is_active = ?", true).First(&config).Error; err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, nil, nil, nil, apperrors.New(apperrors.CodeNoPaymentConfig)
|
||||
}
|
||||
return nil, nil, nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询生效支付配置失败")
|
||||
}
|
||||
adapter := s.adapter(command.PaymentMethod, &config)
|
||||
if adapter == nil || !adapter.Available(&config) {
|
||||
return nil, nil, nil, nil, nil, apperrors.New(apperrors.CodeNoPaymentConfig)
|
||||
}
|
||||
return &account, &shop, &wallet, &config, adapter, nil
|
||||
return &account, &shop, &wallet, nil
|
||||
}
|
||||
|
||||
func (s *OnlineCreationService) createLocalFacts(
|
||||
@@ -195,36 +206,58 @@ func (s *OnlineCreationService) createLocalFacts(
|
||||
account *model.Account,
|
||||
shop *model.Shop,
|
||||
wallet *model.AgentWallet,
|
||||
config *model.WechatConfig,
|
||||
) (*CreateOnlineResult, error) {
|
||||
) (*CreateOnlineResult, *model.WechatConfig, OnlinePaymentPort, error) {
|
||||
rechargeNo, err := newBusinessNo(constants.AgentRechargeOrderPrefix, time.Now().Format("20060102150405"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
paymentNo, err := newBusinessNo("PAY", fmt.Sprintf("%d", time.Now().UnixMilli()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expireMinutes := config.AliPayExpireMinutes
|
||||
if expireMinutes <= 0 {
|
||||
expireMinutes = model.DefaultAliPayExpireMinutes
|
||||
}
|
||||
expireAt := time.Now().Add(time.Duration(expireMinutes) * time.Minute)
|
||||
channel, requestID := paymentChannel(command.PaymentMethod, config), command.RequestID
|
||||
record := &model.AgentRechargeRecord{
|
||||
UserID: account.ID, AgentWalletID: wallet.ID, ShopID: shop.ID, RechargeNo: rechargeNo,
|
||||
Amount: command.Amount, PaymentMethod: command.PaymentMethod, PaymentChannel: &channel,
|
||||
PaymentConfigID: &config.ID, Status: constants.RechargeStatusPending,
|
||||
RequestID: &requestID, RequestFingerprint: &fingerprint,
|
||||
ShopIDTag: wallet.ShopIDTag, EnterpriseIDTag: wallet.EnterpriseIDTag,
|
||||
}
|
||||
payment := &model.Payment{
|
||||
PaymentNo: paymentNo, OrderType: model.PaymentOrderTypeAgentRecharge,
|
||||
PaymentMethod: command.PaymentMethod, MerchantIdentity: paymentMerchantIdentity(command.PaymentMethod, config),
|
||||
Amount: command.Amount, Status: model.PaymentRecordStatusPending,
|
||||
PaymentConfigID: &config.ID, ExpireAt: &expireAt,
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
var record *model.AgentRechargeRecord
|
||||
var payment *model.Payment
|
||||
var config *model.WechatConfig
|
||||
var adapter OnlinePaymentPort
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
route, err := s.runtime.SelectForNewPaymentWithTx(ctx, tx, command.PaymentMethod, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 仅微信直连(v3/v2)商户需要全局授权配置中的 AppID;富友商户不依赖该配置。
|
||||
var authorization *model.WechatAuthorization
|
||||
if route.Merchant.ProviderType == model.ProviderTypeWechat || route.Merchant.ProviderType == model.ProviderTypeWechatV2 {
|
||||
authorization, err = s.runtime.LoadAuthorization(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config, err = merchantpayment.MerchantConfig(route.Merchant, authorization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adapter = s.adapter(command.PaymentMethod, config)
|
||||
if adapter == nil || !adapter.Available(config) {
|
||||
return apperrors.New(apperrors.CodeNoPaymentConfig)
|
||||
}
|
||||
expireMinutes := config.AliPayExpireMinutes
|
||||
if expireMinutes <= 0 {
|
||||
expireMinutes = model.DefaultAliPayExpireMinutes
|
||||
}
|
||||
expireAt := time.Now().Add(time.Duration(expireMinutes) * time.Minute)
|
||||
channel, requestID := paymentChannel(command.PaymentMethod, config), command.RequestID
|
||||
record = &model.AgentRechargeRecord{
|
||||
UserID: account.ID, AgentWalletID: wallet.ID, ShopID: shop.ID, RechargeNo: rechargeNo,
|
||||
Amount: command.Amount, PaymentMethod: command.PaymentMethod, PaymentChannel: &channel,
|
||||
Status: constants.RechargeStatusPending, RequestID: &requestID, RequestFingerprint: &fingerprint,
|
||||
ShopIDTag: wallet.ShopIDTag, EnterpriseIDTag: wallet.EnterpriseIDTag,
|
||||
}
|
||||
payment = &model.Payment{
|
||||
PaymentNo: paymentNo, OrderType: model.PaymentOrderTypeAgentRecharge,
|
||||
PaymentMethod: command.PaymentMethod, Amount: command.Amount,
|
||||
Status: model.PaymentRecordStatusPending, ExpireAt: &expireAt,
|
||||
}
|
||||
merchantpayment.FreezeRoute(payment, route)
|
||||
if err := tx.Create(record).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -238,9 +271,13 @@ func (s *OnlineCreationService) createLocalFacts(
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "创建在线充值本地订单失败")
|
||||
var appErr *apperrors.AppError
|
||||
if stderrors.As(err, &appErr) {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return nil, nil, nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "创建在线充值本地订单失败")
|
||||
}
|
||||
return &CreateOnlineResult{Recharge: record, Payment: payment}, nil
|
||||
return &CreateOnlineResult{Recharge: record, Payment: payment}, config, adapter, nil
|
||||
}
|
||||
|
||||
func paymentMerchantIdentity(paymentMethod string, config *model.WechatConfig) string {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"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/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
// RecoverOnlinePaymentService 批量收敛长期缺少支付链接或待支付的代理在线充值。
|
||||
type RecoverOnlinePaymentService struct {
|
||||
db *gorm.DB
|
||||
runtime *merchantpayment.RuntimeLoader
|
||||
wechat OnlinePaymentPort
|
||||
alipay OnlinePaymentPort
|
||||
fuiou OnlinePaymentPort
|
||||
@@ -23,13 +25,13 @@ type RecoverOnlinePaymentService struct {
|
||||
}
|
||||
|
||||
// NewRecoverOnlinePaymentService 创建代理在线充值支付恢复用例。
|
||||
func NewRecoverOnlinePaymentService(db *gorm.DB, wechat, alipay, fuiou OnlinePaymentPort, confirm *ConfirmOnlinePaymentService, audit PaymentAuditWriter) *RecoverOnlinePaymentService {
|
||||
return &RecoverOnlinePaymentService{db: db, wechat: wechat, alipay: alipay, fuiou: fuiou, confirm: confirm, audit: audit, now: time.Now}
|
||||
func NewRecoverOnlinePaymentService(db *gorm.DB, runtime *merchantpayment.RuntimeLoader, wechat, alipay, fuiou OnlinePaymentPort, confirm *ConfirmOnlinePaymentService, audit PaymentAuditWriter) *RecoverOnlinePaymentService {
|
||||
return &RecoverOnlinePaymentService{db: db, runtime: runtime, wechat: wechat, alipay: alipay, fuiou: fuiou, confirm: confirm, audit: audit, now: time.Now}
|
||||
}
|
||||
|
||||
// ProcessBatch 按固定批次读取本地待处理事实并调用对应渠道收敛状态。
|
||||
func (s *RecoverOnlinePaymentService) ProcessBatch(ctx context.Context) (int, error) {
|
||||
if s == nil || s.db == nil || s.wechat == nil || s.alipay == nil || s.fuiou == nil || s.confirm == nil || s.audit == nil {
|
||||
if s == nil || s.db == nil || s.runtime == nil || s.wechat == nil || s.alipay == nil || s.fuiou == nil || s.confirm == nil || s.audit == nil {
|
||||
return 0, errors.New(errors.CodeServiceUnavailable, "代理在线充值支付恢复能力未配置")
|
||||
}
|
||||
now := s.now().UTC()
|
||||
@@ -52,7 +54,13 @@ func (s *RecoverOnlinePaymentService) ProcessBatch(ctx context.Context) (int, er
|
||||
for index := range payments {
|
||||
payment := &payments[index]
|
||||
recharge := recharges[payment.OrderID]
|
||||
config := recoveryConfig(payment, configs)
|
||||
config, configErr := s.recoveryConfig(ctx, payment, configs)
|
||||
if configErr != nil {
|
||||
if firstErr == nil {
|
||||
firstErr = configErr
|
||||
}
|
||||
continue
|
||||
}
|
||||
if recharge == nil || config == nil {
|
||||
if firstErr == nil {
|
||||
firstErr = errors.New(errors.CodeConflict, "待恢复支付单缺少充值单或创建配置")
|
||||
@@ -141,7 +149,7 @@ func (s *RecoverOnlinePaymentService) loadRecoveryFacts(ctx context.Context, pay
|
||||
configIDs := make([]uint, 0, len(payments))
|
||||
for index := range payments {
|
||||
rechargeIDs = append(rechargeIDs, payments[index].OrderID)
|
||||
if payments[index].PaymentConfigID != nil {
|
||||
if payments[index].MerchantID == nil && payments[index].PaymentConfigID != nil {
|
||||
configIDs = append(configIDs, *payments[index].PaymentConfigID)
|
||||
}
|
||||
}
|
||||
@@ -151,8 +159,10 @@ func (s *RecoverOnlinePaymentService) loadRecoveryFacts(ctx context.Context, pay
|
||||
}
|
||||
var configRows []model.WechatConfig
|
||||
if len(configIDs) > 0 {
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", configIDs).Find(&configRows).Error; err != nil {
|
||||
return nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询代理充值创建配置失败")
|
||||
// merchant_id 为空仅为留存期内历史支付;独立 Change 删除旧路径前,
|
||||
// 必须按其 payment_config_id 读取,包括已软删除的原始配置。
|
||||
if err := s.db.WithContext(ctx).Unscoped().Where("id IN ?", configIDs).Find(&configRows).Error; err != nil {
|
||||
return nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询代理充值历史配置失败")
|
||||
}
|
||||
}
|
||||
recharges := make(map[uint]*model.AgentRechargeRecord, len(rechargeRows))
|
||||
@@ -166,11 +176,27 @@ func (s *RecoverOnlinePaymentService) loadRecoveryFacts(ctx context.Context, pay
|
||||
return recharges, configs, nil
|
||||
}
|
||||
|
||||
func recoveryConfig(payment *model.Payment, configs map[uint]*model.WechatConfig) *model.WechatConfig {
|
||||
if payment.PaymentConfigID == nil {
|
||||
return nil
|
||||
// recoveryConfig 对冻结商户支付单按当前凭证版本加载;历史支付单保持 payment_config_id 路径。
|
||||
func (s *RecoverOnlinePaymentService) recoveryConfig(ctx context.Context, payment *model.Payment, configs map[uint]*model.WechatConfig) (*model.WechatConfig, error) {
|
||||
if payment.MerchantID != nil {
|
||||
merchant, err := s.runtime.LoadMerchant(ctx, *payment.MerchantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 仅微信直连(v3/v2)商户需要全局授权配置中的 AppID;富友商户不依赖该配置。
|
||||
var authorization *model.WechatAuthorization
|
||||
if merchant.ProviderType == model.ProviderTypeWechat || merchant.ProviderType == model.ProviderTypeWechatV2 {
|
||||
authorization, err = s.runtime.LoadAuthorization(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return merchantpayment.MerchantConfig(merchant, authorization)
|
||||
}
|
||||
return configs[*payment.PaymentConfigID]
|
||||
if payment.PaymentConfigID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return configs[*payment.PaymentConfigID], nil
|
||||
}
|
||||
|
||||
func (s *RecoverOnlinePaymentService) closePending(ctx context.Context, payment *model.Payment, recharge *model.AgentRechargeRecord) error {
|
||||
|
||||
Reference in New Issue
Block a user