qnc-server/app/user/cmd/api/internal/service/wechatpayService.go
2024-11-21 12:14:34 +08:00

189 lines
6.6 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 service
import (
"context"
"fmt"
"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"
"net/http"
"qnc-server/app/user/cmd/api/internal/config"
"qnc-server/pkg/lzkit/lzUtils"
"strconv"
"time"
)
const (
TradeStateSuccess = "SUCCESS" // 支付成功
TradeStateRefund = "REFUND" // 转入退款
TradeStateNotPay = "NOTPAY" // 未支付
TradeStateClosed = "CLOSED" // 已关闭
TradeStateRevoked = "REVOKED" // 已撤销(付款码支付)
TradeStateUserPaying = "USERPAYING" // 用户支付中(付款码支付)
TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败)
)
type WechatPayService struct {
config config.WxpayConfig
wechatClient *core.Client
notifyHandler *notify.Handler
}
// NewWechatPayService 初始化微信支付服务
func NewWechatPayService(c config.Config) *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)) // 记录错误并停止程序
}
return &WechatPayService{
config: c.Wxpay,
wechatClient: client,
notifyHandler: notifyHandler,
}
}
// 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.AppID),
Mchid: core.String(w.config.MchID),
Description: core.String(description),
OutTradeNo: core.String(outTradeNo),
NotifyUrl: core.String(w.config.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
}
// 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.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.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
}