PaymentCenter/app/third/paymentService/wechat_service.go

329 lines
12 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 paymentService
import (
"PaymentCenter/app/constants/common"
"PaymentCenter/app/constants/errorcode"
"PaymentCenter/app/services/thirdpay/thirdpay_notify"
"PaymentCenter/app/third/paymentService/payCommon"
"PaymentCenter/config"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/wechat/v3"
"github.com/qit-team/snow-core/log/logger"
"strconv"
"time"
)
// InitClient 使用提供的支付请求参数初始化微信客户端
func InitClient(wxConfig WxPay) (*wechat.ClientV3, error) {
// NewClientV3 初始化微信客户端 v3
// mchid商户ID 或者服务商模式的 sp_mchid
// serialNo商户证书的证书序列号
// apiV3KeyapiV3Key商户平台获取
// privateKey私钥 apiclient_key.pem 读取后的内容
wxClient, clientErr := wechat.NewClientV3(
wxConfig.MchId,
wxConfig.SerialNo,
wxConfig.ApiV3Key,
wxConfig.PrivateKey,
)
// 启用自动同步返回验签并定时更新微信平台API证书开启自动验签时无需单独设置微信平台API证书和序列号
clientErr = wxClient.AutoVerifySign()
if wxClient == nil {
return nil, errors.New("client not initialized")
}
if clientErr != nil {
return nil, clientErr
}
// 自定义配置http请求接收返回结果body大小默认 10MB
wxClient.SetBodySize(10) // 没有特殊需求,可忽略此配置
// 打开Debug开关输出日志默认是关闭的
wxClient.DebugSwitch = gopay.DebugOn
return wxClient, nil
}
// WxH5PayInfo 微信H5支付
func WxH5PayInfo(c context.Context, payOrderRequest PayOrderRequest) (string, error) {
// 初始化微信客户端
wxClient, err := InitClient(payOrderRequest.Wx)
if err != nil {
return "", err
}
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap
envConfig := config.GetConf()
bm := make(gopay.BodyMap)
bm.Set("appid", payOrderRequest.Wx.AppId).
Set("mchid", payOrderRequest.Wx.MchId).
Set("description", payOrderRequest.Description).
Set("out_trade_no", strconv.FormatInt(payOrderRequest.OrderId, 10)).
Set("time_expire", expire).
Set("notify_url", fmt.Sprintf(envConfig.PayService.Host+payCommon.WX_NOTIFY_URL_TEST+"%d", payOrderRequest.PayChannelId)).
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", payOrderRequest.Amount).
Set("currency", "CNY")
}).
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.Set("payer_client_ip", payOrderRequest.PayerClientIp).
SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "common")
})
})
wxRsp, err := wxClient.V3TransactionH5(c, bm)
if err != nil {
return "", err
}
if wxRsp.Code != wechat.Success || wxRsp.Response.H5Url == "" {
logger.Error(c, "WxH5PayInfo 发生错误", fmt.Sprintf("错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
return "", errors.New(fmt.Sprintf("发起支付失败,失败状态码:%d, 失败原因:%s", wxRsp.Code, wxRsp.Error))
}
return wxRsp.Response.H5Url, nil
}
// WxPayCallBack 微信支付回调
func WxPayCallBack(notifyReq *wechat.V3NotifyReq, wxConfig WxPay) error {
// 初始化微信客户端
var orderStatus int
wxClient, err := InitClient(wxConfig)
if err != nil {
return err
}
// 获取微信平台证书
certMap := wxClient.WxPublicKeyMap()
// 验证异步通知的签名
err = notifyReq.VerifySignByPKMap(certMap)
if err != nil {
return err
}
var CallBackInfo struct {
AppId string `json:"appid"` // 应用ID
Mchid string `json:"mchid"` // 商户号
OutTradeNo string `json:"out_trade_no"` // 商户系统内部订单号
TransactionId string `json:"transaction_id"` // 微信系统订单号
TradeType string `json:"trade_type"` // JSAPI公众号支付 NATIVE扫码支付 AppApp支付 MICROPAY付款码支付 MWEBH5支付 FACEPAY刷脸支付
TradeState string `json:"trade_state"` // SUCCESS支付成功 REFUND转入退款 NOTPAY未支付 CLOSED已关闭 REVOKED已撤销付款码支付 USERPAYING用户支付中付款码支付 PAYERROR支付失败(其他原因,如银行返回失败)
TradeStateDesc string `json:"trade_state_desc"` // 交易状态描述
SuccessTime string `json:"success_time"` // 支付完成时间
Amount struct {
Total int64 `json:"total"`
PayerTotal int64 `json:"payer_total"`
} `json:"amount"`
}
// 通用通知解密(推荐此方法)
err = notifyReq.DecryptCipherTextToStruct(wxConfig.ApiV3Key, &CallBackInfo)
if err != nil {
return err
}
errCode := 0
msg := ""
// 订单状态
switch CallBackInfo.TradeState {
case "SUCCESS":
orderStatus = common.ORDER_STATUS_PAYED
errCode = errorcode.Success
msg = "支付成功"
case "CLOSED":
errCode = errorcode.ParamError
orderStatus = common.ORDER_STATUS_CLOSE
msg = "已关闭"
case "REVOKED":
errCode = errorcode.ParamError
orderStatus = common.ORDER_STATUS_FAILED
msg = "已撤销(付款码支付)"
case "PAYERROR":
errCode = errorcode.ParamError
msg = "支付失败(其他原因,如银行返回失败)"
orderStatus = common.ORDER_STATUS_FAILED
}
// 触发下游回调的格式
orderId, _ := strconv.Atoi(CallBackInfo.OutTradeNo)
res := thirdpay_notify.NewOrderNotifyWithHandle(int64(orderId), orderStatus, errCode, int(CallBackInfo.Amount.PayerTotal), msg)
merchantCallback, _ := json.Marshal(res)
// 记录日志
go func() {
payCallback, _ := json.Marshal(CallBackInfo)
payParam := "{}"
status := common.THIRD_ORDER_LOG_STATUS_SUCCESS
SaveLog(int64(orderId), common.THIRD_ORDER_TYPE_CALL_BACK, string(payCallback), payParam, "{}", status)
}()
if res.ErrCode != errorcode.Success {
logger.Error(context.Background(), "WxPayCallBack 发生错误", fmt.Sprintf("回调时,下游处理订单失败,返回数据为:%s", string(merchantCallback)))
return errors.New("回调时,下游处理订单失败")
}
return nil
}
// WxOrderQuery 查询微信订单
func WxOrderQuery(ctx context.Context, wxConfig WxPay, orderNo string) (PayOrderQueryInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(wxConfig)
if err != nil {
return PayOrderQueryInfo{}, err
}
wxRsp, err := wxClient.V3TransactionQueryOrder(ctx, payCommon.ORDER_NO_TYPE_ORDER_NO, orderNo)
if err != nil {
return PayOrderQueryInfo{}, err
}
if wxRsp.Code != wechat.Success {
return PayOrderQueryInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
}
// 映射交易状态
tradeState := ""
switch wxRsp.Response.TradeState {
case "SUCCESS":
tradeState = "SUCCESS"
case "REFUND":
tradeState = "REFUND"
case "NOTPAY":
tradeState = "NOTPAY"
case "CLOSED":
tradeState = "CLOSED"
}
amountTotal := wxRsp.Response.Amount.Total
payerTotal := wxRsp.Response.Amount.PayerTotal
outTradeNo, _ := strconv.Atoi(wxRsp.Response.OutTradeNo)
return PayOrderQueryInfo{
AppId: wxRsp.Response.Appid,
OutTradeNo: int64(outTradeNo),
TransactionId: wxRsp.Response.TransactionId,
TradeState: tradeState,
TradeStateDesc: wxRsp.Response.TradeStateDesc,
SuccessTime: wxRsp.Response.SuccessTime,
AmountTotal: int64(amountTotal),
PayerTotal: int64(payerTotal),
}, nil
}
// WxOrderRefund 微信退款申请
func WxOrderRefund(ctx context.Context, orderRefundRequest OrderRefundRequest) (OrderRefundInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(orderRefundRequest.Wx)
if err != nil {
return OrderRefundInfo{}, err
}
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", strconv.FormatInt(orderRefundRequest.OrderId, 10)).
Set("sign_type", "MD5").
// 必填 退款订单号(程序员定义的)
Set("out_refund_no", strconv.FormatInt(orderRefundRequest.RefundOrderId, 10)).
// 选填 退款描述
Set("reason", orderRefundRequest.RefundReason).
SetBodyMap("amount", func(bm gopay.BodyMap) {
// 退款金额:单位是分
bm.Set("refund", orderRefundRequest.RefundAmount). //实际退款金额
Set("total", orderRefundRequest.RefundAmount). // 折扣前总金额(不是实际退款数)
Set("currency", "CNY")
})
// body参数Body
refund, err := wxClient.V3Refund(ctx, bm)
if err != nil {
return OrderRefundInfo{}, err
}
// 将非正常退款异常记录
if refund.Code != wechat.Success {
logger.Error(ctx, "WxOrderRefund 发生错误", fmt.Sprintf("申请退款接口失败,错误状态码:%d, 错误信息:%s", refund.Code, refund.Error))
// 这里时对非正常退款的一些处理message我们将code统一使用自定义的然后把message抛出去
return OrderRefundInfo{}, errors.New(fmt.Sprintf("申请退款接口失败,错误状态码:%d, 错误信息:%s", refund.Code, refund.Error))
}
outTradeNo, _ := strconv.Atoi(refund.Response.OutTradeNo)
outRefundNo, _ := strconv.Atoi(refund.Response.OutRefundNo)
refundStatus := payCommon.PAY_REFUND_STATU_COMMON
switch refund.Response.Status {
case "SUCCESS":
refundStatus = payCommon.PAY_REFUND_STATU_SUCCESS
case "CLOSED":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
case "PROCESSING":
refundStatus = payCommon.PAY_REFUND_STATU_ING
case "ABNORMAL":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
}
return OrderRefundInfo{
OutTradeNo: int64(outTradeNo),
TransactionId: refund.Response.TransactionId,
RefundFee: int64(refund.Response.Amount.PayerRefund),
RefundOrderId: int64(outRefundNo),
RefundStatus: refundStatus,
RefundSuccessTime: refund.Response.SuccessTime,
}, nil
}
// WxOrderRefundQuery 微信订单退款查询
func WxOrderRefundQuery(ctx context.Context, orderRefundQueryRequest OrderRefundQueryRequest) (OrderRefundInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(orderRefundQueryRequest.Wx)
if err != nil {
return OrderRefundInfo{}, err
}
wxRsp, err := wxClient.V3RefundQuery(ctx, strconv.FormatInt(orderRefundQueryRequest.RefundOrderId, 10), nil)
if err != nil {
return OrderRefundInfo{}, err
}
if wxRsp.Code != wechat.Success {
return OrderRefundInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
}
outTradeNo, _ := strconv.Atoi(wxRsp.Response.OutTradeNo)
outRefundNo, _ := strconv.Atoi(wxRsp.Response.OutRefundNo)
refundStatus := payCommon.PAY_REFUND_STATU_COMMON
switch wxRsp.Response.Status {
case "SUCCESS":
refundStatus = payCommon.PAY_REFUND_STATU_SUCCESS
case "CLOSED":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
case "PROCESSING":
refundStatus = payCommon.PAY_REFUND_STATU_ING
case "ABNORMAL":
refundStatus = payCommon.PAY_REFUND_STATU_FAIL
}
return OrderRefundInfo{
OutTradeNo: int64(outTradeNo),
TransactionId: wxRsp.Response.TransactionId,
RefundFee: int64(wxRsp.Response.Amount.PayerRefund),
RefundOrderId: int64(outRefundNo),
RefundStatus: refundStatus,
RefundSuccessTime: wxRsp.Response.SuccessTime,
}, nil
}
// WxCloseOrder 微信订单关闭
func WxCloseOrder(ctx context.Context, orderCloseRequest OrderCloseRequest) (OrderCloseInfo, error) {
// 初始化微信客户端
wxClient, err := InitClient(orderCloseRequest.Wx)
if err != nil {
return OrderCloseInfo{}, err
}
wxRsp, err := wxClient.V3TransactionCloseOrder(ctx, strconv.FormatInt(orderCloseRequest.OrderId, 10))
if err != nil {
return OrderCloseInfo{}, err
}
if wxRsp.Code != wechat.Success {
logger.Error(ctx, "WxCloseOrder 发生错误", fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
return OrderCloseInfo{}, errors.New(fmt.Sprintf("查询订单接口错误,错误状态码:%d, 错误信息:%s", wxRsp.Code, wxRsp.Error))
}
return OrderCloseInfo{
OutTradeNo: orderCloseRequest.OrderId,
}, nil
}