Files
junhong_cmp_fiber/internal/service/client_order/payment_provider.go
huang bc2dbcb416
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m17s
fix: 修复 V2 微信支付参数全为空字符串的问题
extractPaymentResult 只处理 map[string]any,而 V2 自建实现返回
map[string]string,类型断言静默失败导致所有字段为空字符串。
增加对 map[string]string 的处理分支,兼容 V1 和 V2 两种支付渠道。
2026-03-31 14:59:43 +08:00

185 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
// wechatV2PaymentProvider 微信支付 v2 适配器。
type wechatV2PaymentProvider struct {
paymentService *wechat.PaymentV2Service
}
// CreateJSAPIPayment 创建微信 v2 JSAPI 预下单。
func (w *wechatV2PaymentProvider) 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
case model.ProviderTypeWechatV2:
svc, err := wechat.NewPaymentV2ServiceFromConfig(config, appID, s.logger)
if err != nil {
return nil, errors.Wrap(errors.CodeWechatPayFailed, err, "创建微信支付 v2 应用失败")
}
return &wechatV2PaymentProvider{paymentService: svc}, 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 配置中提取统一支付参数。
// 支持两种类型:
// - map[string]anyV1 标准微信 SDKPowerWeChat返回
// - map[string]stringV2 自建实现返回
func extractPaymentResult(payConfig any) *PaymentResult {
if configMap, ok := payConfig.(map[string]any); ok {
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"]),
}
}
// V2 自建实现返回 map[string]string原 map[string]any 断言会失败导致字段全空
if configMap, ok := payConfig.(map[string]string); ok {
return &PaymentResult{
AppID: configMap["appId"],
TimeStamp: payFirstNonEmpty(configMap["timeStamp"], configMap["timestamp"]),
NonceStr: configMap["nonceStr"],
Package: configMap["package"],
SignType: configMap["signType"],
PaySign: configMap["paySign"],
}
}
return &PaymentResult{}
}
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 ""
}