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