feat: 业务逻辑补全 — 佣金待审记录、C端订单重构、支付抽象、富友支付、卡设备状态联动
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
F-1: 佣金链断裂时创建 status=99 零额待审记录,新增修正接口 POST /commission-records/:id/resolve F-4: C端订单查询从 Handler 迁移至 Service 层,移除 SkipPermissionCtx J-1: 富友支付 JSAPI/MiniApp 预下单实现,回调补全签名验证 J-2: 平台钱包代购支持(buyerType 为空时使用资产所属代理钱包) J-3: 套餐激活后自动更新卡/设备 status=3,最后套餐过期后更新 status=4 支付抽象: 引入 PaymentProvider 接口 + 微信/富友适配器,CreateOrder 支持多支付渠道 修复: 设备/IoT卡响应 DTO 移除 omitempty,空值字段返回 null 而非省略
This commit is contained in:
146
internal/service/client_order/payment_provider.go
Normal file
146
internal/service/client_order/payment_provider.go
Normal file
@@ -0,0 +1,146 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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, "", "JSAPI", 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 根据支付配置创建支付提供者。
|
||||
func (s *Service) newPaymentProvider(config *model.WechatConfig, appID string) (PaymentProvider, error) {
|
||||
switch config.ProviderType {
|
||||
case model.ProviderTypeFuiou:
|
||||
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}, 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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user