package payment import ( "context" "strconv" "strings" "time" "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" "github.com/break/junhong_cmp_fiber/pkg/fuiou" ) // FuiouScanAdapter 按富友主扫统一下单生成微信扫码支付链接并主动查单。 type FuiouScanAdapter struct { integration *integrationlog.Repository logger *zap.Logger } // NewFuiouScanAdapter 创建富友扫码支付适配器。 func NewFuiouScanAdapter(integration *integrationlog.Repository, logger *zap.Logger) *FuiouScanAdapter { return &FuiouScanAdapter{integration: integration, logger: logger} } // Available 判断富友主扫下单、验签与查单所需的配置是否完整。 func (a *FuiouScanAdapter) Available(config *model.WechatConfig) bool { return fuiouConfigComplete(config, true) } // CreatePaymentURL 调用富友主扫统一下单并返回二维码链接。 func (a *FuiouScanAdapter) 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() client, err := a.newClient(request.Config) if err != nil { return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, err) } expireMinutes := int(time.Until(request.ExpireAt).Minutes()) if expireMinutes < 1 { expireMinutes = 1 } resp, callErr := client.PreCreate( request.PaymentNo, strconv.FormatInt(request.Amount, 10), request.Description, fuiou.GetServerIP(), fuiou.OrderTypeWechat, strconv.Itoa(expireMinutes), ) if callErr != nil { return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr) } if strings.TrimSpace(resp.QrCode) == "" { return agentrecharge.OnlinePaymentResult{}, a.completeFailed(ctx, attempt.IntegrationID, startedAt, "empty_qr_code", "富友主扫下单未返回二维码链接") } 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: resp.QrCode}, nil } // Query 调用富友订单查询并按 trans_stat 返回统一查询结果。 func (a *FuiouScanAdapter) 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() client, err := a.newClient(request.Config) if err != nil { return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, err) } resp, callErr := client.CommonQuery(request.PaymentNo, fuiou.OrderTypeWechat) if callErr != nil { return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr) } result := agentrecharge.OnlinePaymentQueryResult{State: mapFuiouTransStat(resp.TransStat)} if result.State == agentrecharge.OnlinePaymentStatePaid { result.ThirdPartyTradeNo = strings.TrimSpace(resp.TransactionId) result.Amount, _ = strconv.ParseInt(strings.TrimSpace(resp.OrderAmt), 10, 64) if paidAt, ok := parseWechatPaidAt(strings.TrimSpace(resp.ReservedTxnFinTs)); ok { result.PaidAt = &paidAt } } providerCode := resp.TransStat if strings.TrimSpace(providerCode) == "" { providerCode = "unknown" } if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{ Result: constants.IntegrationResultSuccess, ProviderCode: providerCode, 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 fuiouConfigComplete(config *model.WechatConfig, requireActive bool) bool { return config != nil && (!requireActive || config.IsActive) && config.ProviderType == model.ProviderTypeFuiou && config.FyInsCd != "" && config.FyMchntCd != "" && config.FyTermID != "" && config.FyPrivateKey != "" && config.FyPublicKey != "" && config.FyAPIURL != "" && config.FyNotifyURL != "" } func (a *FuiouScanAdapter) newClient(config *model.WechatConfig) (*fuiou.Client, error) { if !fuiouConfigComplete(config, false) { return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "富友扫码支付配置不可用") } return fuiou.NewClient( config.FyInsCd, config.FyMchntCd, config.FyTermID, config.FyAPIURL, config.FyNotifyURL, config.FyPrivateKey, config.FyPublicKey, a.logger, ) } func mapFuiouTransStat(transStat string) string { switch transStat { case "SUCCESS": return agentrecharge.OnlinePaymentStatePaid case "PAYERROR", "CLOSED", "REVOKED": return agentrecharge.OnlinePaymentStateClosed case "USERPAYING", "NOTPAY": return agentrecharge.OnlinePaymentStatePending default: return agentrecharge.OnlinePaymentStateUnknown } } func (a *FuiouScanAdapter) 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.IntegrationProviderFuiou, 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 *FuiouScanAdapter) 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", SafeProviderMessage: "富友支付请求结果未知", ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(), RecoveryStrategy: "使用原支付单号主动查单,确认不存在或关闭后才允许关闭本地支付单", }) if err != nil { return err } return apperrors.Wrap(apperrors.CodeTimeout, cause, "富友支付请求结果未知") } func (a *FuiouScanAdapter) completeFailed(ctx context.Context, integrationID string, startedAt time.Time, providerCode, providerMessage string) error { _, err := a.integration.Complete(ctx, integrationID, integrationlog.Completion{ Result: constants.IntegrationResultFailed, ProviderCode: providerCode, SafeProviderMessage: providerMessage, ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(), }) if err != nil { return err } return apperrors.New(apperrors.CodeServiceUnavailable, providerMessage) }