Files
junhong_cmp_fiber/pkg/payment/loader.go
break 8fc667daee
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m7s
让代理充值复用现有网页支付能力
微信按当前 v2/v3 配置分别生成 MWEB/H5 链接,支付宝复用 C 端 WAP 链接,并让可用支付方式基于生效配置判断。

Constraint: 支付链接统一通过 qr_content 返回,由前端渲染二维码;按要求不运行测试

Rejected: 微信 Native 与支付宝当面付 | 会引入非当前商户配置所需的额外产品开通

Confidence: high

Scope-risk: moderate

Directive: 微信 H5/MWEB 二维码仅承诺系统相机或外部浏览器扫码链路

Tested: 相关 Go 包编译通过;gofmt 与 git diff --check 通过

Not-tested: 按用户要求未运行自动化测试及真实支付联调
2026-07-30 11:41:50 +08:00

142 lines
5.2 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 payment 提供支付配置动态加载功能
// 根据 payment_config_id 按需加载支付实例,支持多商户场景
package payment
import (
"context"
"net/http"
"time"
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel"
"github.com/bytedance/sonic"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/wechat"
)
// PaymentConfigLoader 支付配置加载器接口
// 根据 payment_config_id 动态加载支付实例,支持多商户场景
type PaymentConfigLoader interface {
// LoadConfig 加载指定 ID 的支付配置实例
LoadConfig(ctx context.Context, configID uint) (wechat.PaymentServiceInterface, error)
}
// paymentConfigLoader PaymentConfigLoader 的具体实现
type paymentConfigLoader struct {
wechatConfigStore *postgres.WechatConfigStore
redis *redis.Client
wechatCache kernel.CacheInterface
logger *zap.Logger
}
// NewPaymentConfigLoader 创建 PaymentConfigLoader 实例
func NewPaymentConfigLoader(
store *postgres.WechatConfigStore,
rdb *redis.Client,
logger *zap.Logger,
) PaymentConfigLoader {
return &paymentConfigLoader{
wechatConfigStore: store,
redis: rdb,
wechatCache: wechat.NewRedisCache(rdb),
logger: logger,
}
}
// LoadConfig 根据 configID 加载支付配置实例
// 优先从 Redis 缓存读取 WechatConfigTTL 1 小时),未命中则查数据库
func (l *paymentConfigLoader) LoadConfig(ctx context.Context, configID uint) (wechat.PaymentServiceInterface, error) {
config, err := l.loadConfigFromCacheOrDB(ctx, configID)
if err != nil {
return nil, err
}
return l.buildPaymentService(config)
}
// loadConfigFromCacheOrDB 从 Redis 缓存或数据库加载 WechatConfig
// 缓存 key: payment:config:{configID}TTL 1 小时
func (l *paymentConfigLoader) loadConfigFromCacheOrDB(ctx context.Context, configID uint) (*model.WechatConfig, error) {
cacheKey := constants.RedisPaymentConfigKey(configID)
val, err := l.redis.Get(ctx, cacheKey).Result()
if err == nil {
var config model.WechatConfig
if unmarshalErr := sonic.UnmarshalString(val, &config); unmarshalErr == nil {
return &config, nil
}
}
config, dbErr := l.wechatConfigStore.GetByID(ctx, configID)
if dbErr != nil {
if dbErr == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeWechatConfigNotFound)
}
return nil, errors.Wrap(errors.CodeInternalError, dbErr, "加载支付配置失败")
}
if data, marshalErr := sonic.MarshalString(config); marshalErr == nil {
l.redis.Set(ctx, cacheKey, data, time.Hour)
}
return config, nil
}
// buildPaymentService 根据 WechatConfig 构建对应的支付服务实例
// 支持 wechatv3和 wechat_v2v2两种渠道类型
func (l *paymentConfigLoader) buildPaymentService(config *model.WechatConfig) (wechat.PaymentServiceInterface, error) {
switch config.ProviderType {
case model.ProviderTypeWechat:
// 微信直连 v3使用 PowerWeChat SDK
app, err := wechat.NewPaymentAppFromConfig(config, config.OaAppID, l.wechatCache, l.logger)
if err != nil {
return nil, errors.Wrap(errors.CodeWechatPayFailed, err, "构建微信支付 v3 实例失败")
}
return wechat.NewPaymentService(app, l.logger), nil
case model.ProviderTypeWechatV2:
// 微信直连 v2仅需 APIv2Key不要求证书
svc, err := wechat.NewPaymentV2ServiceFromConfig(config, config.OaAppID, l.logger)
if err != nil {
return nil, errors.Wrap(errors.CodeWechatPayFailed, err, "构建微信支付 v2 实例失败")
}
// v2 不实现完整接口,通过适配器包装
return &v2PaymentAdapter{svc: svc}, nil
default:
return nil, errors.New(errors.CodeWechatConfigNotFound, "不支持的支付渠道类型: "+config.ProviderType)
}
}
// v2PaymentAdapter 微信支付 v2 适配器
// 将 PaymentV2Service 适配为完整的 PaymentServiceInterface
// v2 支持 JSAPI、H5/MWEB 与查单,其余方法返回不支持错误。
type v2PaymentAdapter struct {
svc *wechat.PaymentV2Service
}
func (a *v2PaymentAdapter) CreateJSAPIOrder(ctx context.Context, orderNo, description, openID string, amount int) (*wechat.JSAPIPayResult, error) {
return a.svc.CreateJSAPIOrder(ctx, orderNo, description, openID, amount)
}
func (a *v2PaymentAdapter) CreateH5Order(ctx context.Context, orderNo, description string, amount int, sceneInfo *wechat.H5SceneInfo) (*wechat.H5PayResult, error) {
return a.svc.CreateH5Order(ctx, orderNo, description, amount, sceneInfo)
}
func (a *v2PaymentAdapter) QueryOrder(ctx context.Context, orderNo string) (*wechat.OrderInfo, error) {
return a.svc.QueryOrder(ctx, orderNo)
}
func (a *v2PaymentAdapter) CloseOrder(_ context.Context, _ string) error {
return errors.New(errors.CodeWechatPayFailed, "微信支付 v2 暂不支持关单接口")
}
func (a *v2PaymentAdapter) HandlePaymentNotify(_ *http.Request, _ wechat.PaymentNotifyCallback) (*http.Response, error) {
return nil, errors.New(errors.CodeWechatPayFailed, "微信支付 v2 回调请通过 VerifyCallback 处理")
}