283 lines
7.8 KiB
Go
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
|
|
})
|
|
}
|