Files
junhong_cmp_fiber/pkg/wechat/payment.go
2026-01-30 17:25:30 +08:00

283 lines
7.8 KiB
Go

package wechat
import (
"context"
"net/http"
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models"
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment/notify/request"
orderRequest "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/order/request"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"go.uber.org/zap"
)
// PaymentService 微信支付服务实现
type PaymentService struct {
app *payment.Payment
logger *zap.Logger
}
// NewPaymentService 创建微信支付服务
func NewPaymentService(app *payment.Payment, logger *zap.Logger) *PaymentService {
return &PaymentService{
app: app,
logger: logger,
}
}
// JSAPIPayResult JSAPI 支付结果
type JSAPIPayResult struct {
PrepayID string `json:"prepay_id"`
PayConfig interface{} `json:"pay_config"`
}
// H5PayResult H5 支付结果
type H5PayResult struct {
H5URL string `json:"h5_url"`
}
// OrderInfo 订单信息
type OrderInfo struct {
TransactionID string `json:"transaction_id"`
OutTradeNo string `json:"out_trade_no"`
TradeState string `json:"trade_state"`
TradeStateDesc string `json:"trade_state_desc"`
SuccessTime string `json:"success_time"`
TradeType string `json:"trade_type"`
BankType string `json:"bank_type"`
Attach string `json:"attach"`
PayerOpenID string `json:"payer_openid"`
TotalAmount int64 `json:"total_amount"`
PayerTotal int64 `json:"payer_total"`
Currency string `json:"currency"`
}
// PaymentNotifyResult 支付通知结果
type PaymentNotifyResult struct {
TransactionID string `json:"transaction_id"`
OutTradeNo string `json:"out_trade_no"`
TradeState string `json:"trade_state"`
SuccessTime string `json:"success_time"`
PayerOpenID string `json:"payer_openid"`
TotalAmount int64 `json:"total_amount"`
Attach string `json:"attach"`
}
// CreateJSAPIOrder 创建 JSAPI 支付订单
func (s *PaymentService) CreateJSAPIOrder(ctx context.Context, orderNo, description, openID string, amount int) (*JSAPIPayResult, error) {
if orderNo == "" || openID == "" || amount <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "订单号、OpenID 和金额不能为空")
}
resp, err := s.app.Order.JSAPITransaction(ctx, &orderRequest.RequestJSAPIPrepay{
Description: description,
OutTradeNo: orderNo,
Amount: &orderRequest.JSAPIAmount{
Total: amount,
Currency: "CNY",
},
Payer: &orderRequest.JSAPIPayer{
OpenID: openID,
},
})
if err != nil {
s.logger.Error("创建 JSAPI 支付订单失败",
zap.String("order_no", orderNo),
zap.Error(err),
)
return nil, errors.Wrap(errors.CodeWechatPayFailed, err)
}
if resp == nil || resp.PrepayID == "" {
s.logger.Error("创建 JSAPI 支付订单失败:空 PrepayID", zap.String("order_no", orderNo))
return nil, errors.New(errors.CodeWechatPayFailed, "创建支付订单失败")
}
payConfig, err := s.app.JSSDK.BridgeConfig(resp.PrepayID, false)
if err != nil {
s.logger.Error("生成支付配置失败",
zap.String("order_no", orderNo),
zap.Error(err),
)
return nil, errors.Wrap(errors.CodeWechatPayFailed, err)
}
s.logger.Info("创建 JSAPI 支付订单成功",
zap.String("order_no", orderNo),
zap.String("prepay_id", resp.PrepayID),
)
return &JSAPIPayResult{
PrepayID: resp.PrepayID,
PayConfig: payConfig,
}, nil
}
// CreateH5Order 创建 H5 支付订单
func (s *PaymentService) CreateH5Order(ctx context.Context, orderNo, description string, amount int, sceneInfo *H5SceneInfo) (*H5PayResult, error) {
if orderNo == "" || amount <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "订单号和金额不能为空")
}
req := &orderRequest.RequestH5Prepay{
Description: description,
OutTradeNo: orderNo,
Amount: &orderRequest.H5Amount{
Total: amount,
Currency: "CNY",
},
}
if sceneInfo != nil {
req.SceneInfo = &orderRequest.H5SceneInfo{
PayerClientIP: sceneInfo.PayerClientIP,
H5Info: &orderRequest.H5H5Info{
Type: sceneInfo.H5Type,
},
}
}
resp, err := s.app.Order.TransactionH5(ctx, req)
if err != nil {
s.logger.Error("创建 H5 支付订单失败",
zap.String("order_no", orderNo),
zap.Error(err),
)
return nil, errors.Wrap(errors.CodeWechatPayFailed, err)
}
if resp == nil || resp.H5URL == "" {
s.logger.Error("创建 H5 支付订单失败:空 H5URL", zap.String("order_no", orderNo))
return nil, errors.New(errors.CodeWechatPayFailed, "创建 H5 支付订单失败")
}
s.logger.Info("创建 H5 支付订单成功",
zap.String("order_no", orderNo),
zap.String("h5_url", resp.H5URL),
)
return &H5PayResult{
H5URL: resp.H5URL,
}, nil
}
// H5SceneInfo H5 支付场景信息
type H5SceneInfo struct {
PayerClientIP string `json:"payer_client_ip"`
H5Type string `json:"h5_type"`
}
// QueryOrder 查询订单
func (s *PaymentService) QueryOrder(ctx context.Context, orderNo string) (*OrderInfo, error) {
if orderNo == "" {
return nil, errors.New(errors.CodeInvalidParam, "订单号不能为空")
}
resp, err := s.app.Order.QueryByOutTradeNumber(ctx, orderNo)
if err != nil {
s.logger.Error("查询订单失败",
zap.String("order_no", orderNo),
zap.Error(err),
)
return nil, errors.Wrap(errors.CodeWechatPayFailed, err)
}
if resp == nil {
return nil, errors.New(errors.CodeNotFound, "订单不存在")
}
orderInfo := &OrderInfo{
TransactionID: resp.TransactionID,
OutTradeNo: resp.OutTradeNo,
TradeState: resp.TradeState,
TradeStateDesc: resp.TradeStateDesc,
SuccessTime: resp.SuccessTime,
TradeType: resp.TradeType,
BankType: resp.BankType,
Attach: resp.Attach,
}
if resp.Amount != nil {
orderInfo.TotalAmount = resp.Amount.Total
orderInfo.PayerTotal = resp.Amount.PayerTotal
orderInfo.Currency = resp.Amount.Currency
}
if resp.Payer != nil {
orderInfo.PayerOpenID = resp.Payer.OpenID
}
s.logger.Debug("查询订单成功",
zap.String("order_no", orderNo),
zap.String("trade_state", resp.TradeState),
)
return orderInfo, nil
}
// CloseOrder 关闭订单
func (s *PaymentService) CloseOrder(ctx context.Context, orderNo string) error {
if orderNo == "" {
return errors.New(errors.CodeInvalidParam, "订单号不能为空")
}
_, err := s.app.Order.Close(ctx, orderNo)
if err != nil {
s.logger.Error("关闭订单失败",
zap.String("order_no", orderNo),
zap.Error(err),
)
return errors.Wrap(errors.CodeWechatPayFailed, err)
}
s.logger.Info("关闭订单成功", zap.String("order_no", orderNo))
return nil
}
// PaymentNotifyCallback 支付通知回调函数
type PaymentNotifyCallback func(result *PaymentNotifyResult) error
// HandlePaymentNotify 处理支付回调通知
func (s *PaymentService) HandlePaymentNotify(r *http.Request, callback PaymentNotifyCallback) (*http.Response, error) {
return s.app.HandlePaidNotify(r, func(notify *request.RequestNotify, transaction *models.Transaction, fail func(message string)) interface{} {
if transaction == nil {
s.logger.Error("支付通知数据为空")
fail("支付通知数据为空")
return nil
}
result := &PaymentNotifyResult{
OutTradeNo: transaction.OutTradeNo,
TradeState: transaction.TradeState,
SuccessTime: transaction.SuccessTime,
Attach: transaction.Attach,
}
result.TransactionID = transaction.TransactionID
if transaction.Payer != nil {
result.PayerOpenID = transaction.Payer.OpenID
}
if transaction.Amount != nil {
result.TotalAmount = transaction.Amount.Total
}
if err := callback(result); err != nil {
s.logger.Error("处理支付通知回调失败",
zap.String("out_trade_no", result.OutTradeNo),
zap.Error(err),
)
fail(err.Error())
return nil
}
s.logger.Info("支付通知处理成功",
zap.String("out_trade_no", result.OutTradeNo),
zap.String("transaction_id", result.TransactionID),
)
return true
})
}