package client_order import ( "context" "fmt" "strconv" "strings" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/fuiou" "github.com/break/junhong_cmp_fiber/pkg/wechat" ) // PaymentProvider C 端支付提供者接口。 // 抽象不同支付渠道(微信、富友等)的预下单能力。 type PaymentProvider interface { // CreateJSAPIPayment 创建 JSAPI 预下单,返回前端调起支付的参数。 CreateJSAPIPayment(ctx context.Context, orderNo, description, openID string, amount int) (*PaymentResult, error) } // PaymentResult 预下单结果,前端用这些参数调起支付。 type PaymentResult struct { AppID string TimeStamp string NonceStr string Package string SignType string PaySign string } // wechatPaymentProvider 微信支付适配器。 type wechatPaymentProvider struct { paymentService *wechat.PaymentService } // CreateJSAPIPayment 创建微信 JSAPI 预下单。 func (w *wechatPaymentProvider) CreateJSAPIPayment(ctx context.Context, orderNo, description, openID string, amount int) (*PaymentResult, error) { result, err := w.paymentService.CreateJSAPIOrder(ctx, orderNo, description, openID, amount) if err != nil { return nil, err } return extractPaymentResult(result.PayConfig), nil } // fuiouPaymentProvider 富友支付适配器。 type fuiouPaymentProvider struct { client *fuiou.Client appID string tradeType string } // CreateJSAPIPayment 创建富友 JSAPI 预下单。 func (f *fuiouPaymentProvider) CreateJSAPIPayment(ctx context.Context, orderNo, description, openID string, amount int) (*PaymentResult, error) { amountStr := strconv.Itoa(amount) resp, err := f.client.WxPreCreate(orderNo, amountStr, description, fuiou.GetServerIP(), f.tradeType, f.appID, openID) if err != nil { return nil, errors.Wrap(errors.CodeFuiouPayFailed, err, "富友预下单失败") } return &PaymentResult{ AppID: resp.SdkAppid, TimeStamp: resp.SdkTimestamp, NonceStr: resp.SdkNoncestr, Package: resp.SdkPackage, SignType: resp.SdkSigntype, PaySign: resp.SdkPaysign, }, nil } // newPaymentProvider 根据支付配置创建支付提供者。 // appType: "official_account" 或 "miniapp" func (s *Service) newPaymentProvider(config *model.WechatConfig, appID, appType string) (PaymentProvider, error) { switch config.ProviderType { case model.ProviderTypeFuiou: tradeType := "JSAPI" if appType == "miniapp" { tradeType = "LETPAY" } client, err := fuiou.NewClient( config.FyInsCd, config.FyMchntCd, config.FyTermID, config.FyAPIURL, config.FyNotifyURL, config.FyPrivateKey, config.FyPublicKey, s.logger, ) if err != nil { return nil, errors.Wrap(errors.CodeFuiouPayFailed, err, "创建富友客户端失败") } return &fuiouPaymentProvider{client: client, appID: appID, tradeType: tradeType}, nil default: cache := wechat.NewRedisCache(s.redis) paymentApp, err := wechat.NewPaymentAppFromConfig(config, appID, cache, s.logger) if err != nil { return nil, errors.Wrap(errors.CodeWechatPayFailed, err, "创建微信支付应用失败") } return &wechatPaymentProvider{paymentService: wechat.NewPaymentService(paymentApp, s.logger)}, nil } } // buildClientPayConfigFromResult 将预下单结果转换为客户端支付参数。 func buildClientPayConfigFromResult(result *PaymentResult) *dto.ClientPayConfig { if result == nil { result = &PaymentResult{} } return &dto.ClientPayConfig{ AppID: result.AppID, Timestamp: result.TimeStamp, NonceStr: result.NonceStr, PackageVal: result.Package, SignType: result.SignType, PaySign: result.PaySign, } } // extractPaymentResult 从微信 SDK 的 map 配置中提取统一支付参数。 func extractPaymentResult(payConfig any) *PaymentResult { configMap, _ := payConfig.(map[string]any) if configMap == nil { configMap = map[string]any{} } return &PaymentResult{ AppID: payFirstNonEmpty(payStringFromAny(configMap["appId"])), TimeStamp: payFirstNonEmpty(payStringFromAny(configMap["timeStamp"]), payStringFromAny(configMap["timestamp"])), NonceStr: payStringFromAny(configMap["nonceStr"]), Package: payStringFromAny(configMap["package"]), SignType: payStringFromAny(configMap["signType"]), PaySign: payStringFromAny(configMap["paySign"]), } } func payStringFromAny(value any) string { if value == nil { return "" } return fmt.Sprint(value) } func payFirstNonEmpty(values ...string) string { for _, value := range values { if strings.TrimSpace(value) != "" { return value } } return "" }