first commit
This commit is contained in:
377
app/main/api/internal/service/wechatpayService.go
Normal file
377
app/main/api/internal/service/wechatpayService.go
Normal file
@@ -0,0 +1,377 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hm-server/app/main/api/internal/config"
|
||||
"hm-server/app/main/model"
|
||||
"hm-server/common/ctxdata"
|
||||
"hm-server/pkg/lzkit/lzUtils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/app"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/utils"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
const (
|
||||
TradeStateSuccess = "SUCCESS" // 支付成功
|
||||
TradeStateRefund = "REFUND" // 转入退款
|
||||
TradeStateNotPay = "NOTPAY" // 未支付
|
||||
TradeStateClosed = "CLOSED" // 已关闭
|
||||
TradeStateRevoked = "REVOKED" // 已撤销(付款码支付)
|
||||
TradeStateUserPaying = "USERPAYING" // 用户支付中(付款码支付)
|
||||
TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败)
|
||||
)
|
||||
|
||||
// InitType 初始化类型
|
||||
type InitType string
|
||||
|
||||
const (
|
||||
InitTypePlatformCert InitType = "platform_cert" // 平台证书初始化
|
||||
InitTypeWxPayPubKey InitType = "wxpay_pubkey" // 微信支付公钥初始化
|
||||
)
|
||||
|
||||
type WechatPayService struct {
|
||||
config config.Config
|
||||
wechatClient *core.Client
|
||||
notifyHandler *notify.Handler
|
||||
userAuthModel model.UserAuthModel
|
||||
}
|
||||
|
||||
// NewWechatPayService 创建微信支付服务实例
|
||||
func NewWechatPayService(c config.Config, userAuthModel model.UserAuthModel, initType InitType) *WechatPayService {
|
||||
switch initType {
|
||||
case InitTypePlatformCert:
|
||||
return newWechatPayServiceWithPlatformCert(c, userAuthModel)
|
||||
case InitTypeWxPayPubKey:
|
||||
return newWechatPayServiceWithWxPayPubKey(c, userAuthModel)
|
||||
default:
|
||||
logx.Errorf("不支持的初始化类型: %s", initType)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %s", initType))
|
||||
}
|
||||
}
|
||||
|
||||
// newWechatPayServiceWithPlatformCert 使用平台证书初始化微信支付服务
|
||||
func newWechatPayServiceWithPlatformCert(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService {
|
||||
// 从配置中加载商户信息
|
||||
mchID := c.Wxpay.MchID
|
||||
mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber
|
||||
mchAPIv3Key := c.Wxpay.MchApiv3Key
|
||||
|
||||
// 从文件中加载商户私钥
|
||||
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(c.Wxpay.MchPrivateKeyPath)
|
||||
if err != nil {
|
||||
logx.Errorf("加载商户私钥失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
|
||||
// 使用商户私钥和其他参数初始化微信支付客户端
|
||||
opts := []core.ClientOption{
|
||||
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
|
||||
}
|
||||
client, err := core.NewClient(context.Background(), opts...)
|
||||
if err != nil {
|
||||
logx.Errorf("创建微信支付客户端失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
|
||||
// 在初始化时获取证书访问器并创建 notifyHandler
|
||||
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
||||
notifyHandler, err := notify.NewRSANotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
|
||||
if err != nil {
|
||||
logx.Errorf("获取证书访问器失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
||||
}
|
||||
|
||||
logx.Infof("微信支付客户端初始化成功(平台证书方式)")
|
||||
return &WechatPayService{
|
||||
config: c,
|
||||
wechatClient: client,
|
||||
notifyHandler: notifyHandler,
|
||||
userAuthModel: userAuthModel,
|
||||
}
|
||||
}
|
||||
|
||||
// newWechatPayServiceWithWxPayPubKey 使用微信支付公钥初始化微信支付服务
|
||||
func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService {
|
||||
// 从配置中加载商户信息
|
||||
mchID := c.Wxpay.MchID
|
||||
mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber
|
||||
mchAPIv3Key := c.Wxpay.MchApiv3Key
|
||||
mchPrivateKeyPath := c.Wxpay.MchPrivateKeyPath
|
||||
mchPublicKeyID := c.Wxpay.MchPublicKeyID
|
||||
mchPublicKeyPath := c.Wxpay.MchPublicKeyPath
|
||||
// 从文件中加载商户私钥
|
||||
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(mchPrivateKeyPath)
|
||||
if err != nil {
|
||||
logx.Errorf("加载商户私钥失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
||||
}
|
||||
|
||||
// 从文件中加载微信支付平台证书
|
||||
mchPublicKey, err := utils.LoadPublicKeyWithPath(mchPublicKeyPath)
|
||||
if err != nil {
|
||||
logx.Errorf("加载微信支付平台证书失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
||||
}
|
||||
|
||||
// 使用商户私钥和其他参数初始化微信支付客户端
|
||||
opts := []core.ClientOption{
|
||||
option.WithWechatPayPublicKeyAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchPublicKeyID, mchPublicKey),
|
||||
}
|
||||
client, err := core.NewClient(context.Background(), opts...)
|
||||
if err != nil {
|
||||
logx.Errorf("创建微信支付客户端失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
||||
}
|
||||
|
||||
// 初始化 notify.Handler
|
||||
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
||||
notifyHandler := notify.NewNotifyHandler(
|
||||
mchAPIv3Key,
|
||||
verifiers.NewSHA256WithRSACombinedVerifier(certificateVisitor, mchPublicKeyID, *mchPublicKey))
|
||||
|
||||
logx.Infof("微信支付客户端初始化成功(微信支付公钥方式)")
|
||||
return &WechatPayService{
|
||||
config: c,
|
||||
wechatClient: client,
|
||||
notifyHandler: notifyHandler,
|
||||
userAuthModel: userAuthModel,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWechatAppOrder 创建微信APP支付订单
|
||||
func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (string, error) {
|
||||
totalAmount := lzUtils.ToWechatAmount(amount)
|
||||
|
||||
// 构建支付请求参数
|
||||
payRequest := app.PrepayRequest{
|
||||
Appid: core.String(w.config.Wxpay.AppID),
|
||||
Mchid: core.String(w.config.Wxpay.MchID),
|
||||
Description: core.String(description),
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
NotifyUrl: core.String(w.config.Wxpay.NotifyUrl),
|
||||
Amount: &app.Amount{
|
||||
Total: core.Int64(totalAmount),
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化 AppApiService
|
||||
svc := app.AppApiService{Client: w.wechatClient}
|
||||
|
||||
// 发起预支付请求
|
||||
resp, result, err := svc.Prepay(ctx, payRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
|
||||
// 返回预支付交易会话标识
|
||||
return *resp.PrepayId, nil
|
||||
}
|
||||
|
||||
// CreateWechatMiniProgramOrder 创建微信小程序支付订单
|
||||
func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
|
||||
totalAmount := lzUtils.ToWechatAmount(amount)
|
||||
|
||||
// 构建支付请求参数
|
||||
payRequest := jsapi.PrepayRequest{
|
||||
Appid: core.String(w.config.WechatMini.AppID),
|
||||
Mchid: core.String(w.config.Wxpay.MchID),
|
||||
Description: core.String(description),
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
NotifyUrl: core.String(w.config.Wxpay.NotifyUrl),
|
||||
Amount: &jsapi.Amount{
|
||||
Total: core.Int64(totalAmount),
|
||||
},
|
||||
Payer: &jsapi.Payer{
|
||||
Openid: core.String(openid), // 用户的 OpenID,通过前端传入
|
||||
}}
|
||||
|
||||
// 初始化 AppApiService
|
||||
svc := jsapi.JsapiApiService{Client: w.wechatClient}
|
||||
|
||||
// 发起预支付请求
|
||||
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
// 返回预支付交易会话标识
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CreateWechatH5Order 创建微信H5支付订单
|
||||
func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
|
||||
totalAmount := lzUtils.ToWechatAmount(amount)
|
||||
|
||||
// 构建支付请求参数
|
||||
payRequest := jsapi.PrepayRequest{
|
||||
Appid: core.String(w.config.WechatH5.AppID),
|
||||
Mchid: core.String(w.config.Wxpay.MchID),
|
||||
Description: core.String(description),
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
NotifyUrl: core.String(w.config.Wxpay.NotifyUrl),
|
||||
Amount: &jsapi.Amount{
|
||||
Total: core.Int64(totalAmount),
|
||||
},
|
||||
Payer: &jsapi.Payer{
|
||||
Openid: core.String(openid), // 用户的 OpenID,通过前端传入
|
||||
}}
|
||||
|
||||
// 初始化 AppApiService
|
||||
svc := jsapi.JsapiApiService{Client: w.wechatClient}
|
||||
|
||||
// 发起预支付请求
|
||||
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
|
||||
logx.Infof("微信h5支付订单:resp: %+v, result: %+v, err: %+v", resp, result, err)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
// 返回预支付交易会话标识
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序)
|
||||
func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) {
|
||||
// 根据 ctx 中的 platform 判断平台
|
||||
platform := ctx.Value("platform").(string)
|
||||
|
||||
var prepayData interface{}
|
||||
var err error
|
||||
|
||||
switch platform {
|
||||
case model.PlatformWxMini:
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(ctx)
|
||||
if getUidErr != nil {
|
||||
return "", getUidErr
|
||||
}
|
||||
userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID)
|
||||
if findAuthModelErr != nil {
|
||||
return "", findAuthModelErr
|
||||
}
|
||||
prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case model.PlatformWxH5:
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(ctx)
|
||||
if getUidErr != nil {
|
||||
return "", getUidErr
|
||||
}
|
||||
userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID)
|
||||
if findAuthModelErr != nil {
|
||||
return "", findAuthModelErr
|
||||
}
|
||||
prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case model.PlatformApp:
|
||||
// 如果是 APP 平台,调用 APP 支付订单创建
|
||||
prepayData, err = w.CreateWechatAppOrder(ctx, amount, description, outTradeNo)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的支付平台: %s", platform)
|
||||
}
|
||||
|
||||
// 如果创建支付订单失败,返回错误
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("支付订单创建失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回预支付ID
|
||||
return prepayData, nil
|
||||
}
|
||||
|
||||
// HandleWechatPayNotification 处理微信支付回调
|
||||
func (w *WechatPayService) HandleWechatPayNotification(ctx context.Context, req *http.Request) (*payments.Transaction, error) {
|
||||
transaction := new(payments.Transaction)
|
||||
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, transaction)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("微信支付通知处理失败: %v", err)
|
||||
}
|
||||
// 返回交易信息
|
||||
return transaction, nil
|
||||
}
|
||||
|
||||
// HandleRefundNotification 处理微信退款回调
|
||||
func (w *WechatPayService) HandleRefundNotification(ctx context.Context, req *http.Request) (*refunddomestic.Refund, error) {
|
||||
refund := new(refunddomestic.Refund)
|
||||
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, refund)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("微信退款回调通知处理失败: %v", err)
|
||||
}
|
||||
return refund, nil
|
||||
}
|
||||
|
||||
// QueryOrderStatus 主动查询订单状态
|
||||
func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID string) (*payments.Transaction, error) {
|
||||
svc := jsapi.JsapiApiService{Client: w.wechatClient}
|
||||
|
||||
// 调用 QueryOrderById 方法查询订单状态
|
||||
resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{
|
||||
TransactionId: core.String(transactionID),
|
||||
Mchid: core.String(w.config.Wxpay.MchID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
return resp, nil
|
||||
|
||||
}
|
||||
|
||||
// WeChatRefund 申请微信退款
|
||||
func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string, refundAmount float64, totalAmount float64) error {
|
||||
|
||||
// 生成唯一的退款单号
|
||||
outRefundNo := fmt.Sprintf("%s-refund", outTradeNo)
|
||||
|
||||
// 初始化退款服务
|
||||
svc := refunddomestic.RefundsApiService{Client: w.wechatClient}
|
||||
|
||||
// 创建退款请求
|
||||
resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
OutRefundNo: core.String(outRefundNo),
|
||||
NotifyUrl: core.String(w.config.Wxpay.RefundNotifyUrl),
|
||||
Amount: &refunddomestic.AmountReq{
|
||||
Currency: core.String("CNY"),
|
||||
Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)),
|
||||
Total: core.Int64(lzUtils.ToWechatAmount(totalAmount)),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("微信订单申请退款错误: %v", err)
|
||||
}
|
||||
// 打印退款结果
|
||||
logx.Infof("退款申请成功,状态码=%d,退款单号=%s,微信退款单号=%s", result.Response.StatusCode, *resp.OutRefundNo, *resp.RefundId)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateOutTradeNo 生成唯一订单号
|
||||
func (w *WechatPayService) GenerateOutTradeNo() string {
|
||||
length := 16
|
||||
timestamp := time.Now().UnixNano()
|
||||
timeStr := strconv.FormatInt(timestamp, 10)
|
||||
randomPart := strconv.Itoa(int(timestamp % 1e6))
|
||||
combined := timeStr + randomPart
|
||||
|
||||
if len(combined) >= length {
|
||||
return combined[:length]
|
||||
}
|
||||
|
||||
for len(combined) < length {
|
||||
combined += strconv.Itoa(int(timestamp % 10))
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
Reference in New Issue
Block a user