代理在线充值
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m6s

This commit is contained in:
2026-07-27 16:02:55 +08:00
parent a2166c8011
commit cbf909b878
42 changed files with 3121 additions and 195 deletions

View File

@@ -0,0 +1,110 @@
package payment
import (
"context"
"time"
"github.com/bytedance/sonic"
"gorm.io/gorm"
"gorm.io/gorm/clause"
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// AgentRechargePaymentConsumer 将已确认收款的代理在线充值幂等入账主钱包。
type AgentRechargePaymentConsumer struct {
db *gorm.DB
posting *walletapp.PostingService
}
// NewAgentRechargePaymentConsumer 创建代理在线充值入账消费者。
func NewAgentRechargePaymentConsumer(db *gorm.DB, posting *walletapp.PostingService) *AgentRechargePaymentConsumer {
return &AgentRechargePaymentConsumer{db: db, posting: posting}
}
// Consume 校验支付与充值权威事实后,在独立事务中完成唯一入账和充值终态。
func (c *AgentRechargePaymentConsumer) Consume(ctx context.Context, envelope outbox.DeliveryEnvelope) error {
if c == nil || c.db == nil || c.posting == nil {
return errors.New(errors.CodeInternalError, "代理在线充值入账消费者未配置")
}
if envelope.EventType != constants.OutboxEventTypeAgentRechargePaymentConfirmed ||
envelope.PayloadVersion != constants.AgentRechargePaymentConfirmedPayloadVersionV1 {
return errors.New(errors.CodeInvalidParam, "代理充值支付确认事件类型或版本不受支持")
}
var event agentrecharge.PaymentConfirmedEvent
if err := sonic.Unmarshal(envelope.Payload, &event); err != nil {
return errors.Wrap(errors.CodeInvalidParam, err, "代理充值支付确认事件载荷无法解析")
}
if event.EventID == "" || event.EventID != envelope.EventID || event.RechargeID == 0 || event.PaymentID == 0 ||
event.ShopID == 0 || event.WalletID == 0 || event.Amount <= 0 || event.ThirdPartyTradeNo == "" {
return errors.New(errors.CodeInvalidParam, "代理充值支付确认事件载荷不完整")
}
return c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
recharge, _, err := lockCreditingFacts(ctx, tx, event)
if err != nil {
return err
}
if recharge.Status != constants.RechargeStatusPaid && recharge.Status != constants.RechargeStatusCompleted {
return errors.New(errors.CodeInvalidStatus, "代理在线充值当前状态不可入账")
}
if _, err := c.posting.PostInTx(ctx, tx, walletapp.PostingCommand{
ShopID: recharge.ShopID, WalletID: recharge.AgentWalletID, Amount: recharge.Amount,
ReferenceType: constants.ReferenceTypeTopup, ReferenceID: recharge.ID,
TransactionType: constants.AgentTransactionTypeRecharge,
UserID: recharge.UserID, Creator: recharge.UserID, Remark: "代理在线扫码充值",
RequestID: envelope.RequestID, CorrelationID: envelope.CorrelationID,
}); err != nil {
return err
}
if recharge.Status == constants.RechargeStatusCompleted {
return nil
}
completedAt := time.Now().UTC()
update := tx.WithContext(ctx).Model(&model.AgentRechargeRecord{}).
Where("id = ? AND status = ?", recharge.ID, constants.RechargeStatusPaid).
Updates(map[string]any{"status": constants.RechargeStatusCompleted, "completed_at": completedAt})
if update.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, update.Error, "完成代理在线充值单失败")
}
if update.RowsAffected != 1 {
return errors.New(errors.CodeConflict, "代理在线充值状态已变化")
}
return nil
})
}
func lockCreditingFacts(ctx context.Context, tx *gorm.DB, event agentrecharge.PaymentConfirmedEvent) (*model.AgentRechargeRecord, *model.Payment, error) {
var recharge model.AgentRechargeRecord
if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", event.RechargeID).First(&recharge).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil, errors.New(errors.CodeNotFound, "代理在线充值单不存在")
}
return nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "锁定代理在线充值单失败")
}
var payment model.Payment
if err := tx.WithContext(ctx).Where("id = ?", event.PaymentID).First(&payment).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil, errors.New(errors.CodeConflict, "代理在线充值支付单不存在")
}
return nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理在线充值支付单失败")
}
if recharge.ID != event.RechargeID || recharge.RechargeNo != event.RechargeNo || recharge.ShopID != event.ShopID ||
recharge.AgentWalletID != event.WalletID || recharge.UserID != event.UserID || recharge.Amount != event.Amount ||
recharge.PaymentTransactionID == nil || *recharge.PaymentTransactionID != event.ThirdPartyTradeNo ||
(recharge.PaymentMethod != constants.RechargeMethodWechat && recharge.PaymentMethod != constants.RechargeMethodAlipay) {
return nil, nil, errors.New(errors.CodeConflict, "代理充值事件与充值权威事实不一致")
}
if payment.OrderType != model.PaymentOrderTypeAgentRecharge || payment.OrderID != recharge.ID ||
payment.PaymentNo != event.PaymentNo || payment.PaymentMethod != event.PaymentMethod || payment.Amount != event.Amount ||
payment.Status != model.PaymentRecordStatusPaid || payment.ThirdPartyTradeNo != event.ThirdPartyTradeNo {
return nil, nil, errors.New(errors.CodeConflict, "代理充值事件与支付权威事实不一致")
}
return &recharge, &payment, nil
}
var _ outbox.EventConsumer = (*AgentRechargePaymentConsumer)(nil)

View File

@@ -0,0 +1,38 @@
package payment
import (
"context"
"strconv"
"gorm.io/gorm"
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// AgentRechargePaymentEventWriter 将支付确认事实写入公共 Outbox。
type AgentRechargePaymentEventWriter struct {
outbox *outbox.Repository
}
// NewAgentRechargePaymentEventWriter 创建代理充值支付确认事件 Writer。
func NewAgentRechargePaymentEventWriter(repository *outbox.Repository) *AgentRechargePaymentEventWriter {
return &AgentRechargePaymentEventWriter{outbox: repository}
}
// Append 在支付确认事务内追加稳定的代理充值入账事件。
func (w *AgentRechargePaymentEventWriter) Append(ctx context.Context, tx *gorm.DB, event agentrecharge.PaymentConfirmedEvent) error {
if w == nil || w.outbox == nil {
return errors.New(errors.CodeInternalError, "代理充值支付确认 Outbox Writer 未配置")
}
_, err := w.outbox.Append(ctx, tx, outbox.Envelope{
EventID: event.EventID, EventType: constants.OutboxEventTypeAgentRechargePaymentConfirmed,
PayloadVersion: constants.AgentRechargePaymentConfirmedPayloadVersionV1,
AggregateType: "agent_recharge", AggregateID: strconv.FormatUint(uint64(event.RechargeID), 10),
ResourceType: "payment", ResourceID: strconv.FormatUint(uint64(event.PaymentID), 10),
BusinessKey: event.EventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID, Payload: event,
})
return err
}

View File

@@ -0,0 +1,29 @@
package payment
import (
"context"
"github.com/hibiken/asynq"
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// AgentRechargeRecoveryTaskHandler 执行代理在线充值支付恢复任务。
type AgentRechargeRecoveryTaskHandler struct {
service *agentrecharge.RecoverOnlinePaymentService
}
// NewAgentRechargeRecoveryTaskHandler 创建代理在线充值支付恢复任务 Handler。
func NewAgentRechargeRecoveryTaskHandler(service *agentrecharge.RecoverOnlinePaymentService) *AgentRechargeRecoveryTaskHandler {
return &AgentRechargeRecoveryTaskHandler{service: service}
}
// Handle 扫描长期待支付记录并复用原支付单号收敛渠道状态。
func (h *AgentRechargeRecoveryTaskHandler) Handle(ctx context.Context, _ *asynq.Task) error {
if h == nil || h.service == nil {
return errors.New(errors.CodeServiceUnavailable, "代理在线充值支付恢复任务未配置")
}
_, err := h.service.ProcessBatch(ctx)
return err
}

View File

@@ -0,0 +1,178 @@
package payment
import (
"context"
"strings"
"time"
sdkalipay "github.com/smartwalle/alipay/v3"
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
"github.com/break/junhong_cmp_fiber/internal/model"
alipaypkg "github.com/break/junhong_cmp_fiber/pkg/alipay"
"github.com/break/junhong_cmp_fiber/pkg/constants"
apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
)
// AlipayPreCreateAdapter 使用现有 smartwalle/alipay 实现当面付预下单与查单。
type AlipayPreCreateAdapter struct {
integration *integrationlog.Repository
}
// NewAlipayPreCreateAdapter 创建支付宝当面付适配器。
func NewAlipayPreCreateAdapter(integration *integrationlog.Repository) *AlipayPreCreateAdapter {
return &AlipayPreCreateAdapter{integration: integration}
}
// Available 判断配置是否完整支持支付宝预下单、验签与查单。
func (a *AlipayPreCreateAdapter) Available(config *model.WechatConfig) bool {
return alipayConfigComplete(config, true)
}
// PreCreate 创建支付宝当面付扫码订单。
func (a *AlipayPreCreateAdapter) PreCreate(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentResult, error) {
client, err := a.client(request.Config)
if err != nil {
return agentrecharge.OnlinePaymentResult{}, err
}
attempt, err := a.startAttempt(ctx, request.PaymentNo, constants.IntegrationOperationPaymentPreCreate, request.Config.ID, request.Amount)
if err != nil {
return agentrecharge.OnlinePaymentResult{}, err
}
startedAt := time.Now()
response, callErr := client.TradePreCreate(ctx, sdkalipay.TradePreCreate{Trade: sdkalipay.Trade{
NotifyURL: request.Config.AliNotifyURL, Subject: request.Description, OutTradeNo: request.PaymentNo,
TotalAmount: alipaypkg.FenToYuan(request.Amount), ProductCode: "FACE_TO_FACE_PAYMENT",
TimeExpire: request.ExpireAt.Format("2006-01-02 15:04:05"),
}})
if callErr != nil {
return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
}
if response == nil || response.IsFailure() || strings.TrimSpace(response.QRCode) == "" {
providerCode, providerMessage := "empty_qr_code", "支付宝预下单未返回付款内容"
responseSummary := map[string]any{"success": false}
if response != nil && response.IsFailure() {
providerCode, providerMessage = string(response.Code), response.SubMsg
if response.SubCode != "" {
providerCode += ":" + response.SubCode
responseSummary["sub_code"] = response.SubCode
}
}
_, completeErr := a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultFailed, ProviderCode: providerCode, ProviderMessage: providerMessage,
ResponseSummary: responseSummary, DurationMS: time.Since(startedAt).Milliseconds(),
})
if completeErr != nil {
return agentrecharge.OnlinePaymentResult{}, completeErr
}
return agentrecharge.OnlinePaymentResult{}, apperrors.New(apperrors.CodeServiceUnavailable, "支付宝预下单失败")
}
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultSuccess, ProviderCode: string(response.Code),
ResponseSummary: map[string]any{"success": true}, DurationMS: time.Since(startedAt).Milliseconds(), StateChanged: true,
}); err != nil {
return agentrecharge.OnlinePaymentResult{}, err
}
return agentrecharge.OnlinePaymentResult{QRContent: response.QRCode}, nil
}
// Query 查询支付宝当面付订单状态。
func (a *AlipayPreCreateAdapter) Query(ctx context.Context, paymentNo string, config *model.WechatConfig) (agentrecharge.OnlinePaymentQueryResult, error) {
client, err := a.queryClient(config)
if err != nil {
return agentrecharge.OnlinePaymentQueryResult{}, err
}
attempt, err := a.startAttempt(ctx, paymentNo, constants.IntegrationOperationPaymentQuery, config.ID, 0)
if err != nil {
return agentrecharge.OnlinePaymentQueryResult{}, err
}
startedAt := time.Now()
response, callErr := client.TradeQuery(ctx, sdkalipay.TradeQuery{OutTradeNo: paymentNo})
if callErr != nil {
return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
}
if response == nil || response.IsFailure() {
providerCode, providerMessage := "empty_response", "支付宝查单未返回有效响应"
if response != nil {
providerCode, providerMessage = string(response.Code), response.SubMsg
}
_, completeErr := a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultFailed, ProviderCode: providerCode, ProviderMessage: providerMessage,
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
})
if completeErr != nil {
return agentrecharge.OnlinePaymentQueryResult{}, completeErr
}
return agentrecharge.OnlinePaymentQueryResult{}, apperrors.New(apperrors.CodeServiceUnavailable, "支付宝查单失败")
}
result := agentrecharge.OnlinePaymentQueryResult{
State: mapAlipayTradeState(response.TradeStatus), ThirdPartyTradeNo: response.TradeNo,
}
result.Amount, _ = alipaypkg.YuanToFen(response.TotalAmount)
if paidAt, parseErr := time.ParseInLocation("2006-01-02 15:04:05", response.SendPayDate, time.Local); parseErr == nil {
result.PaidAt = &paidAt
}
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultSuccess, ProviderCode: string(response.TradeStatus),
ResponseSummary: map[string]any{"state": result.State, "has_trade_no": result.ThirdPartyTradeNo != ""},
DurationMS: time.Since(startedAt).Milliseconds(),
}); err != nil {
return agentrecharge.OnlinePaymentQueryResult{}, err
}
return result, nil
}
func (a *AlipayPreCreateAdapter) client(config *model.WechatConfig) (*sdkalipay.Client, error) {
if !a.Available(config) {
return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "支付宝扫码支付配置不可用")
}
return alipaypkg.NewClientFromConfig(config)
}
func (a *AlipayPreCreateAdapter) queryClient(config *model.WechatConfig) (*sdkalipay.Client, error) {
if !alipayConfigComplete(config, false) {
return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "支付宝查单配置不可用")
}
return alipaypkg.NewClientFromConfig(config)
}
func alipayConfigComplete(config *model.WechatConfig, requireActive bool) bool {
return config != nil && (!requireActive || config.IsActive) && config.AliAppID != "" && config.AliPrivateKey != "" &&
config.AliPublicKey != "" && config.AliNotifyURL != ""
}
func (a *AlipayPreCreateAdapter) startAttempt(ctx context.Context, paymentNo, operation string, configID uint, amount int64) (*model.IntegrationLog, error) {
resourceID := paymentNo
return a.integration.Start(ctx, integrationlog.Attempt{
Provider: constants.IntegrationProviderAlipay, Direction: constants.IntegrationDirectionOutbound,
Operation: operation, ResourceType: constants.IntegrationResourceTypeAgentRechargePayment,
ResourceID: &resourceID, ExternalID: &resourceID,
RequestSummary: map[string]any{"payment_config_id": configID, "amount": amount},
})
}
func (a *AlipayPreCreateAdapter) completeUnknown(ctx context.Context, integrationID string, startedAt time.Time, cause error) error {
_, err := a.integration.Complete(ctx, integrationID, integrationlog.Completion{
Result: constants.IntegrationResultUnknown, ProviderCode: "request_unknown", ProviderMessage: cause.Error(),
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
RecoveryStrategy: "使用原支付单号主动查单,确认不存在或关闭后才允许关闭本地支付单",
})
if err != nil {
return err
}
return apperrors.Wrap(apperrors.CodeTimeout, cause, "支付宝支付请求结果未知")
}
func mapAlipayTradeState(state sdkalipay.TradeStatus) string {
switch state {
case sdkalipay.TradeStatusSuccess, sdkalipay.TradeStatusFinished:
return agentrecharge.OnlinePaymentStatePaid
case sdkalipay.TradeStatusClosed:
return agentrecharge.OnlinePaymentStateClosed
case sdkalipay.TradeStatusWaitBuyerPay:
return agentrecharge.OnlinePaymentStatePending
default:
return agentrecharge.OnlinePaymentStateUnknown
}
}

View File

@@ -0,0 +1,171 @@
// Package payment 提供代理在线充值使用的支付渠道薄适配器。
package payment
import (
"context"
"strings"
"time"
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel"
sdkpayment "github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
orderRequest "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/order/request"
"go.uber.org/zap"
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
wechatpay "github.com/break/junhong_cmp_fiber/pkg/wechat"
)
// WechatNativeAdapter 使用现有 PowerWeChat 实现 Native 预下单与查单。
type WechatNativeAdapter struct {
cache kernel.CacheInterface
integration *integrationlog.Repository
logger *zap.Logger
}
// NewWechatNativeAdapter 创建微信 Native 支付适配器。
func NewWechatNativeAdapter(cache kernel.CacheInterface, integration *integrationlog.Repository, logger *zap.Logger) *WechatNativeAdapter {
return &WechatNativeAdapter{cache: cache, integration: integration, logger: logger}
}
// Available 判断配置是否完整支持微信 Native 预下单、验签与查单。
func (a *WechatNativeAdapter) Available(config *model.WechatConfig) bool {
return wechatConfigComplete(config, true)
}
// PreCreate 创建微信 Native 扫码支付单。
func (a *WechatNativeAdapter) PreCreate(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentResult, error) {
app, err := a.paymentApp(request.Config)
if err != nil {
return agentrecharge.OnlinePaymentResult{}, err
}
attempt, err := a.startAttempt(ctx, request.PaymentNo, constants.IntegrationOperationPaymentPreCreate, request.Config.ID, request.Amount)
if err != nil {
return agentrecharge.OnlinePaymentResult{}, err
}
startedAt := time.Now()
response, callErr := app.Order.TransactionNative(ctx, &orderRequest.RequestNativePrepay{
Description: request.Description,
OutTradeNo: request.PaymentNo,
TimeExpire: request.ExpireAt.Format(time.RFC3339),
Amount: &orderRequest.NativeAmount{Total: int(request.Amount), Currency: "CNY"},
})
if callErr != nil {
return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
}
if response == nil || strings.TrimSpace(response.CodeURL) == "" {
err = apperrors.New(apperrors.CodeWechatPayFailed, "微信 Native 预下单未返回付款内容")
_, completeErr := a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultFailed, ProviderCode: "empty_code_url", ProviderMessage: err.Error(),
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
})
if completeErr != nil {
return agentrecharge.OnlinePaymentResult{}, completeErr
}
return agentrecharge.OnlinePaymentResult{}, err
}
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultSuccess, ResponseSummary: map[string]any{"success": true},
DurationMS: time.Since(startedAt).Milliseconds(), StateChanged: true,
}); err != nil {
return agentrecharge.OnlinePaymentResult{}, err
}
return agentrecharge.OnlinePaymentResult{QRContent: response.CodeURL}, nil
}
// Query 查询微信 Native 支付单状态。
func (a *WechatNativeAdapter) Query(ctx context.Context, paymentNo string, config *model.WechatConfig) (agentrecharge.OnlinePaymentQueryResult, error) {
app, err := a.queryPaymentApp(config)
if err != nil {
return agentrecharge.OnlinePaymentQueryResult{}, err
}
attempt, err := a.startAttempt(ctx, paymentNo, constants.IntegrationOperationPaymentQuery, config.ID, 0)
if err != nil {
return agentrecharge.OnlinePaymentQueryResult{}, err
}
startedAt := time.Now()
info, callErr := wechatpay.NewPaymentService(app, a.logger).QueryOrder(ctx, paymentNo)
if callErr != nil {
return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
}
result := agentrecharge.OnlinePaymentQueryResult{
State: mapWechatTradeState(info.TradeState), ThirdPartyTradeNo: info.TransactionID, Amount: info.TotalAmount,
}
if paidAt, parseErr := time.Parse(time.RFC3339, info.SuccessTime); parseErr == nil {
result.PaidAt = &paidAt
}
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultSuccess, ProviderCode: info.TradeState,
ResponseSummary: map[string]any{"state": result.State, "has_trade_no": result.ThirdPartyTradeNo != ""},
DurationMS: time.Since(startedAt).Milliseconds(),
}); err != nil {
return agentrecharge.OnlinePaymentQueryResult{}, err
}
return result, nil
}
func (a *WechatNativeAdapter) paymentApp(config *model.WechatConfig) (*sdkpayment.Payment, error) {
if !a.Available(config) {
return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "微信 Native 支付配置不可用")
}
app, err := wechatpay.NewPaymentAppFromConfig(config, config.OaAppID, a.cache, a.logger)
if err != nil {
return nil, apperrors.Wrap(apperrors.CodeNoPaymentConfig, err, "微信 Native 支付配置不可用")
}
return app, nil
}
func (a *WechatNativeAdapter) queryPaymentApp(config *model.WechatConfig) (*sdkpayment.Payment, error) {
if !wechatConfigComplete(config, false) {
return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "微信查单配置不可用")
}
app, err := wechatpay.NewPaymentAppFromConfig(config, config.OaAppID, a.cache, a.logger)
if err != nil {
return nil, apperrors.Wrap(apperrors.CodeNoPaymentConfig, err, "微信查单配置不可用")
}
return app, nil
}
func wechatConfigComplete(config *model.WechatConfig, requireActive bool) bool {
return config != nil && (!requireActive || config.IsActive) && config.ProviderType == model.ProviderTypeWechat &&
config.OaAppID != "" && config.WxMchID != "" && config.WxAPIV3Key != "" &&
config.WxCertContent != "" && config.WxKeyContent != "" && config.WxSerialNo != "" && config.WxNotifyURL != ""
}
func (a *WechatNativeAdapter) startAttempt(ctx context.Context, paymentNo, operation string, configID uint, amount int64) (*model.IntegrationLog, error) {
resourceID := paymentNo
return a.integration.Start(ctx, integrationlog.Attempt{
Provider: constants.IntegrationProviderWechatPay, Direction: constants.IntegrationDirectionOutbound,
Operation: operation, ResourceType: constants.IntegrationResourceTypeAgentRechargePayment,
ResourceID: &resourceID, ExternalID: &resourceID,
RequestSummary: map[string]any{"payment_config_id": configID, "amount": amount},
})
}
func (a *WechatNativeAdapter) completeUnknown(ctx context.Context, integrationID string, startedAt time.Time, cause error) error {
_, err := a.integration.Complete(ctx, integrationID, integrationlog.Completion{
Result: constants.IntegrationResultUnknown, ProviderCode: "request_unknown", ProviderMessage: cause.Error(),
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
RecoveryStrategy: "使用原支付单号主动查单,确认不存在或关闭后才允许关闭本地支付单",
})
if err != nil {
return err
}
return apperrors.Wrap(apperrors.CodeTimeout, cause, "微信支付请求结果未知")
}
func mapWechatTradeState(state string) string {
switch state {
case "SUCCESS":
return agentrecharge.OnlinePaymentStatePaid
case "CLOSED", "REVOKED", "PAYERROR":
return agentrecharge.OnlinePaymentStateClosed
case "NOTPAY", "USERPAYING":
return agentrecharge.OnlinePaymentStatePending
default:
return agentrecharge.OnlinePaymentStateUnknown
}
}