// Package payment 提供代理在线充值使用的支付渠道薄适配器。 package payment import ( "context" "strconv" "time" "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" sdkpayment "github.com/ArtisanCloud/PowerWeChat/v3/src/payment" "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" ) // WechatWebAdapter 按当前支付配置生成微信 H5/MWEB 支付链接并查单。 type WechatWebAdapter struct { cache kernel.CacheInterface integration *integrationlog.Repository logger *zap.Logger } // NewWechatWebAdapter 创建微信 H5/MWEB 支付适配器。 func NewWechatWebAdapter(cache kernel.CacheInterface, integration *integrationlog.Repository, logger *zap.Logger) *WechatWebAdapter { return &WechatWebAdapter{cache: cache, integration: integration, logger: logger} } // Available 判断当前协议配置是否完整支持 H5/MWEB 下单、验签与查单。 func (a *WechatWebAdapter) Available(config *model.WechatConfig) bool { return wechatConfigComplete(config, true) || wechatV2ConfigComplete(config, true) } // CreatePaymentURL 按当前协议生成微信 H5 或 MWEB 支付链接。 func (a *WechatWebAdapter) CreatePaymentURL(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentResult, error) { attempt, err := a.startAttempt(ctx, request, constants.IntegrationOperationPaymentPreCreate, request.Config.ID, request.Amount) if err != nil { return agentrecharge.OnlinePaymentResult{}, err } startedAt := time.Now() response, callErr := a.createH5Order(ctx, request, &wechatpay.H5SceneInfo{ PayerClientIP: request.PayerClientIP, H5Type: "Wap", }) if callErr != nil { return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr) } 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.H5URL}, nil } // Query 按创建支付单时的协议查询微信支付状态。 func (a *WechatWebAdapter) Query(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentQueryResult, error) { attempt, err := a.startAttempt(ctx, request, constants.IntegrationOperationPaymentQuery, request.Config.ID, 0) if err != nil { return agentrecharge.OnlinePaymentQueryResult{}, err } startedAt := time.Now() info, callErr := a.queryOrder(ctx, request.Config, request.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, ok := parseWechatPaidAt(info.SuccessTime); ok { 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 parseWechatPaidAt(value string) (time.Time, bool) { for _, layout := range []string{time.RFC3339, "20060102150405"} { paidAt, err := time.ParseInLocation(layout, value, time.Local) if err == nil { return paidAt, true } } return time.Time{}, false } func (a *WechatWebAdapter) paymentApp(config *model.WechatConfig) (*sdkpayment.Payment, error) { if !wechatConfigComplete(config, false) { return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "微信 H5 支付配置不可用") } app, err := wechatpay.NewPaymentAppFromConfig(config, config.OaAppID, a.cache, a.logger) if err != nil { return nil, apperrors.Wrap(apperrors.CodeNoPaymentConfig, err, "微信 H5 支付配置不可用") } return app, nil } func (a *WechatWebAdapter) createH5Order(ctx context.Context, request agentrecharge.OnlinePaymentRequest, sceneInfo *wechatpay.H5SceneInfo) (*wechatpay.H5PayResult, error) { switch request.Config.ProviderType { case model.ProviderTypeWechat: app, err := a.paymentApp(request.Config) if err != nil { return nil, err } return wechatpay.NewPaymentService(app, a.logger).CreateH5Order(ctx, request.PaymentNo, request.Description, int(request.Amount), sceneInfo) case model.ProviderTypeWechatV2: service, err := wechatpay.NewPaymentV2ServiceFromConfig(request.Config, request.Config.OaAppID, a.logger) if err != nil { return nil, apperrors.Wrap(apperrors.CodeNoPaymentConfig, err, "微信 MWEB 支付配置不可用") } return service.CreateH5Order(ctx, request.PaymentNo, request.Description, int(request.Amount), sceneInfo) default: return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "当前支付渠道不支持微信网页支付") } } func (a *WechatWebAdapter) queryOrder(ctx context.Context, config *model.WechatConfig, paymentNo string) (*wechatpay.OrderInfo, error) { switch config.ProviderType { case model.ProviderTypeWechat: app, err := a.queryPaymentApp(config) if err != nil { return nil, err } return wechatpay.NewPaymentService(app, a.logger).QueryOrder(ctx, paymentNo) case model.ProviderTypeWechatV2: service, err := wechatpay.NewPaymentV2ServiceFromConfig(config, config.OaAppID, a.logger) if err != nil { return nil, apperrors.Wrap(apperrors.CodeNoPaymentConfig, err, "微信 v2 查单配置不可用") } return service.QueryOrder(ctx, paymentNo) default: return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "当前支付渠道不支持微信查单") } } func (a *WechatWebAdapter) 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 wechatV2ConfigComplete(config *model.WechatConfig, requireActive bool) bool { return config != nil && (!requireActive || config.IsActive) && config.ProviderType == model.ProviderTypeWechatV2 && config.OaAppID != "" && config.WxMchID != "" && config.WxAPIV2Key != "" && config.WxNotifyURL != "" } func (a *WechatWebAdapter) startAttempt(ctx context.Context, request agentrecharge.OnlinePaymentRequest, operation string, configID uint, amount int64) (*model.IntegrationLog, error) { resourceID, resourceKey := strconv.FormatUint(uint64(request.PaymentID), 10), request.PaymentNo series := "agent-recharge-payment:" + resourceID + ":" + operation correlationID := request.CorrelationID return a.integration.Start(ctx, integrationlog.Attempt{ Provider: constants.IntegrationProviderWechatPay, Direction: constants.IntegrationDirectionOutbound, Operation: operation, ResourceType: constants.IntegrationResourceTypeAgentRechargePayment, ResourceID: &resourceID, ResourceKey: &resourceKey, ExternalID: &resourceKey, TriggerSeries: &series, CorrelationID: &correlationID, RequestSummary: map[string]any{"payment_config_id": configID, "amount": amount}, }) } func (a *WechatWebAdapter) completeUnknown(ctx context.Context, integrationID string, startedAt time.Time, cause error) error { if a.logger != nil { a.logger.Warn("微信支付请求结果未知", zap.String("integration_id", integrationID), zap.Error(cause)) } _, err := a.integration.Complete(ctx, integrationID, integrationlog.Completion{ Result: constants.IntegrationResultUnknown, ProviderCode: "request_unknown", ProviderMessage: "微信支付请求结果未知", 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 } }