Files
junhong_cmp_fiber/internal/service/client_order/payment_provider.go
huang d56b841443
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m8s
fix: 富友回调应答XMLName缺失 + term_ip自动探测服务器IP替代硬编码
2026-03-30 12:46:50 +08:00

153 lines
4.6 KiB
Go

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