service/library/payclients/wxpaycli/client.go

338 lines
9.1 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 wxpaycli
import (
"context"
"crypto"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/http"
"os"
"service/library/idgenerator"
"time"
"github.com/go-pay/gopay"
wxpayv2 "github.com/go-pay/gopay/wechat"
wxpayv3 "github.com/go-pay/gopay/wechat/v3"
"service/bizcommon/util"
"service/library/configcenter"
"service/library/logger"
)
const (
DefaultOrderTimeoutSeconds = 900 // 默认订单超时时间,单位: s
)
const (
AppIdXinYiDaoLe = "1665016206"
AppIdTieFenZone = "1675813721"
)
var allWxpayClients = map[string]*WxpayClient{}
func GetDefaultWxpayClient() *WxpayClient {
return allWxpayClients[AppIdTieFenZone]
}
type WxpayClient struct {
clientV3 *wxpayv3.ClientV3
MchId string `json:"mch_id"`
AppSecret string `json:"app_secret"`
AppId string `json:"app_id"`
NotifyUrl string `json:"notify_url"`
PrivateKeyPath string `json:"private_key_path"`
}
func InitMulti(cfgList ...*configcenter.WxpayClientConfig) (err error) {
for _, cfg := range cfgList {
var cli *WxpayClient
cli, err = NewWxpayClient(cfg)
if err != nil {
return
}
if cli == nil {
err = errors.New("NewAlipayClient fail")
return
}
allWxpayClients[cli.MchId] = cli
}
return
}
func NewWxpayClient(cfg *configcenter.WxpayClientConfig) (ret *WxpayClient, err error) {
// private key
bs, err := os.ReadFile(cfg.PrivateKeyPath)
if err != nil {
logger.Error("real PrivateKeyPath fail, cfg: %v, err: %v", util.ToJson(cfg), err)
return
}
privateKey := string(bs)
wxpayCliV3, err := wxpayv3.NewClientV3(cfg.MchId, cfg.SerialNo, cfg.ApiV3Key, privateKey)
if err != nil {
logger.Error("NewClientV3 fail, cfg: %v, err: %v", util.ToJson(cfg), err)
return
}
ret = &WxpayClient{
clientV3: wxpayCliV3,
MchId: cfg.MchId,
AppSecret: cfg.AppSecret,
AppId: cfg.AppId,
NotifyUrl: cfg.NotifyUrl,
PrivateKeyPath: cfg.PrivateKeyPath,
}
return
}
// 验签
func (c *WxpayClient) ParseNotify(req *http.Request) (notify *wxpayv3.V3DecryptResult, err error) {
notifyReq, err := wxpayv3.V3ParseNotify(req)
if err != nil {
logger.Error("V3ParseNotify fail, notifyReq: %v, err: %v", util.ToJson(notifyReq), err)
return
}
if notifyReq == nil {
logger.Error("V3ParseNotify nil, err: %v", err)
return
}
notifyTmp, err := notifyReq.DecryptCipherText(string(c.clientV3.ApiV3Key))
if err != nil {
logger.Error("DecryptCipherText fail, notifyTmp: %v, err: %v", util.ToJson(notifyTmp), err)
return
}
if notifyTmp == nil {
logger.Error("DecryptCipherText nil, err: %v", err)
return
}
logger.Info("Wxpay ParseNotify, %v", util.ToJson(notifyTmp))
notify = notifyTmp
return
}
// 微信支付 native支付
type NativePayParam struct {
Description string
OutTradeNo string // 商家订单id我们自己的订单id
TotalAmount int64 // 金额,单位:分
TimeOutSeconds int // 订单有效时间,单位:秒
}
func (c *WxpayClient) NativePay(ctx context.Context, param *NativePayParam) (wxpayNativeParamStr string, err error) {
if param.TimeOutSeconds <= 0 {
param.TimeOutSeconds = DefaultOrderTimeoutSeconds
}
bm := gopay.BodyMap{
"appid": c.AppId,
"description": param.Description,
"out_trade_no": param.OutTradeNo,
"time_expire": time.Now().Add(time.Second * time.Duration(param.TimeOutSeconds)).Format(time.RFC3339),
"notify_url": c.NotifyUrl,
"amount": gopay.BodyMap{
"total": param.TotalAmount,
"currency": "CNY",
},
}
resp, err := c.clientV3.V3TransactionNative(ctx, bm)
if err != nil {
return
}
if resp.Code != wxpayv3.Success {
logger.Info("wxpayv3 NativePay fail, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}
wxpayNativeParamStr = resp.Response.CodeUrl
logger.Info("wxpayv3 NativePay success, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}
// 通过authcode获取openid
func (c *WxpayClient) GetOpenIdByAuthCode(ctx context.Context, authCode string) (openid string, err error) {
at, err := wxpayv2.GetOauth2AccessToken(ctx, c.AppId, c.AppSecret, authCode)
if err != nil {
return
}
if len(at.Openid) <= 0 {
err = errors.New(fmt.Sprintf("fail, %s", util.ToJson(at)))
return
}
openid = at.Openid
return
}
// 微信支付 jsapi支付
type JsapiPayParam struct {
Description string
OutTradeNo string // 商家订单id我们自己的订单id
TotalAmount int64 // 金额,单位:分
TimeOutSeconds int // 订单有效时间,单位:秒
OpenId string
}
type JsapiPayResp struct {
PrepayId string `json:"-"`
AppId string `json:"appId"`
TimeStamp string `json:"timeStamp"`
NonceStr string `json:"nonceStr"`
Package string `json:"package"`
SignType string `json:"signType"`
PaySign string `json:"paySign"`
}
func (c *WxpayClient) JsapiPay(ctx context.Context, param *JsapiPayParam) (wxpayJsapiResp JsapiPayResp, err error) {
if param.TimeOutSeconds <= 0 {
param.TimeOutSeconds = DefaultOrderTimeoutSeconds
}
bm := gopay.BodyMap{
"appid": c.AppId,
"description": param.Description,
"out_trade_no": param.OutTradeNo,
"time_expire": time.Now().Add(time.Second * time.Duration(param.TimeOutSeconds)).Format(time.RFC3339),
"notify_url": c.NotifyUrl,
"amount": gopay.BodyMap{
"total": param.TotalAmount,
"currency": "CNY",
},
"payer": gopay.BodyMap{
"openid": param.OpenId,
},
}
resp, err := c.clientV3.V3TransactionJsapi(ctx, bm)
if err != nil {
return
}
if resp.Code != wxpayv3.Success {
logger.Info("wxpayv3 NativePay fail, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}
r := JsapiPayResp{
PrepayId: resp.Response.PrepayId,
AppId: c.AppId,
TimeStamp: fmt.Sprintf("%d", time.Now().Unix()),
NonceStr: util.RandomString(32),
Package: "prepay_id=" + resp.Response.PrepayId,
SignType: "RSA",
PaySign: resp.SignInfo.SignBody,
}
var (
cipherText = fmt.Sprintf("%s\n%s\n%s\n%s\n", r.AppId, r.TimeStamp, r.NonceStr, r.Package)
keyBytes, _ = os.ReadFile(c.PrivateKeyPath)
)
paySignBytes, err := rsaSign(keyBytes, crypto.SHA256, []byte(cipherText))
if err != nil {
return
}
r.PaySign = base64.StdEncoding.EncodeToString(paySignBytes)
wxpayJsapiResp = r
logger.Info("wxpayv3 JsapiPay success, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(wxpayJsapiResp))
return
}
func rsaSign(prvkey []byte, hash crypto.Hash, data []byte) ([]byte, error) {
block, _ := pem.Decode(prvkey)
if block == nil {
return nil, errors.New("decode private key error")
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
// MD5 and SHA1 are not supported as they are not secure.
var hashed []byte
switch hash {
case crypto.SHA224:
h := sha256.Sum224(data)
hashed = h[:]
case crypto.SHA256:
h := sha256.Sum256(data)
hashed = h[:]
case crypto.SHA384:
h := sha512.Sum384(data)
hashed = h[:]
case crypto.SHA512:
h := sha512.Sum512(data)
hashed = h[:]
}
return rsa.SignPKCS1v15(cryptorand.Reader, privateKey.(*rsa.PrivateKey), hash, hashed)
}
// 微信支付 h5支付
type H5PayParam struct {
Description string
OutTradeNo string // 商家订单id我们自己的订单id
TotalAmount int64 // 金额,单位:分
TimeOutSeconds int // 订单有效时间,单位:秒
Cip string // 客户端ip
}
func (c *WxpayClient) H5Pay(ctx context.Context, param *H5PayParam) (wxpayH5ParamStr string, err error) {
if param.TimeOutSeconds <= 0 {
param.TimeOutSeconds = DefaultOrderTimeoutSeconds
}
bm := gopay.BodyMap{
"appid": c.AppId,
"description": param.Description,
"out_trade_no": param.OutTradeNo,
"time_expire": time.Now().Add(time.Second * time.Duration(param.TimeOutSeconds)).Format(time.RFC3339),
"notify_url": c.NotifyUrl,
"amount": gopay.BodyMap{
"total": param.TotalAmount,
"currency": "CNY",
},
"scene_info": gopay.BodyMap{
"payer_client_ip": param.Cip,
"h5_info": gopay.BodyMap{
"type": "Wap",
},
},
}
resp, err := c.clientV3.V3TransactionH5(ctx, bm)
if err != nil {
return
}
if resp.Code != wxpayv3.Success {
logger.Info("wxpayv3 NativePay fail, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}
wxpayH5ParamStr = resp.Response.H5Url
logger.Info("wxpayv3 H5 success, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}
// 退款
type RefundOneParam struct {
OutTradeNo string // 商家订单id我们自己的订单id
RefundAmount int64 // 退款金额,单位:分
RefundReason string // 退款理由
}
func (c *WxpayClient) RefundOne(ctx context.Context, param *RefundOneParam) (resp *wxpayv3.RefundRsp, err error) {
bm := gopay.BodyMap{
"out_trade_no": param.OutTradeNo,
"out_refund_no": idgenerator.GenWxpayRefundId(),
"reason": param.RefundReason,
"amount": gopay.BodyMap{
"refund": param.RefundAmount,
"total": param.RefundAmount,
"currency": "CNY",
},
}
resp, err = c.clientV3.V3Refund(ctx, bm)
if err != nil {
return
}
return
}