微信相关能力
This commit is contained in:
282
pkg/wechat/payment.go
Normal file
282
pkg/wechat/payment.go
Normal file
@@ -0,0 +1,282 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user