228 lines
6.5 KiB
Go
228 lines
6.5 KiB
Go
|
package wechat
|
|||
|
|
|||
|
import (
|
|||
|
"crypto"
|
|||
|
"crypto/hmac"
|
|||
|
"crypto/rand"
|
|||
|
"crypto/rsa"
|
|||
|
"crypto/sha256"
|
|||
|
"encoding/base64"
|
|||
|
"encoding/hex"
|
|||
|
"errors"
|
|||
|
"fmt"
|
|||
|
"hash"
|
|||
|
"strings"
|
|||
|
"time"
|
|||
|
|
|||
|
"github.com/go-pay/gopay"
|
|||
|
"github.com/go-pay/gopay/pkg/util"
|
|||
|
"github.com/go-pay/gopay/pkg/xlog"
|
|||
|
"github.com/go-pay/gopay/pkg/xpem"
|
|||
|
)
|
|||
|
|
|||
|
// Deprecated
|
|||
|
// 推荐使用 wechat.V3VerifySignByPK()
|
|||
|
func V3VerifySign(timestamp, nonce, signBody, sign, wxPubKeyContent string) (err error) {
|
|||
|
publicKey, err := xpem.DecodePublicKey([]byte(wxPubKeyContent))
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
str := timestamp + "\n" + nonce + "\n" + signBody + "\n"
|
|||
|
signBytes, _ := base64.StdEncoding.DecodeString(sign)
|
|||
|
|
|||
|
h := sha256.New()
|
|||
|
h.Write([]byte(str))
|
|||
|
if err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
|
|||
|
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// 推荐直接开启自动同步验签功能
|
|||
|
// 微信V3 版本验签(同步)
|
|||
|
// wxPublicKey:微信平台证书公钥内容,通过 client.WxPublicKeyMap() 获取,然后根据 signInfo.HeaderSerial 获取相应的公钥
|
|||
|
func V3VerifySignByPK(timestamp, nonce, signBody, sign string, wxPublicKey *rsa.PublicKey) (err error) {
|
|||
|
if wxPublicKey == nil || wxPublicKey.N == nil {
|
|||
|
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, "wxPublicKey is nil")
|
|||
|
}
|
|||
|
str := timestamp + "\n" + nonce + "\n" + signBody + "\n"
|
|||
|
signBytes, _ := base64.StdEncoding.DecodeString(sign)
|
|||
|
|
|||
|
h := sha256.New()
|
|||
|
h.Write([]byte(str))
|
|||
|
if err = rsa.VerifyPKCS1v15(wxPublicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
|
|||
|
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// PaySignOfJSAPI 获取 JSAPI 支付所需要的参数
|
|||
|
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml
|
|||
|
func (c *ClientV3) PaySignOfJSAPI(appid, prepayid string) (jsapi *JSAPIPayParams, err error) {
|
|||
|
ts := util.Int642String(time.Now().Unix())
|
|||
|
nonceStr := util.RandomString(32)
|
|||
|
pkg := "prepay_id=" + prepayid
|
|||
|
|
|||
|
_str := appid + "\n" + ts + "\n" + nonceStr + "\n" + pkg + "\n"
|
|||
|
sign, err := c.rsaSign(_str)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
jsapi = &JSAPIPayParams{
|
|||
|
AppId: appid,
|
|||
|
TimeStamp: ts,
|
|||
|
NonceStr: nonceStr,
|
|||
|
Package: pkg,
|
|||
|
SignType: SignTypeRSA,
|
|||
|
PaySign: sign,
|
|||
|
}
|
|||
|
return jsapi, nil
|
|||
|
}
|
|||
|
|
|||
|
// PaySignOfApp 获取 App 支付所需要的参数
|
|||
|
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
|
|||
|
func (c *ClientV3) PaySignOfApp(appid, prepayid string) (app *AppPayParams, err error) {
|
|||
|
ts := util.Int642String(time.Now().Unix())
|
|||
|
nonceStr := util.RandomString(32)
|
|||
|
|
|||
|
_str := appid + "\n" + ts + "\n" + nonceStr + "\n" + prepayid + "\n"
|
|||
|
sign, err := c.rsaSign(_str)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
app = &AppPayParams{
|
|||
|
Appid: appid,
|
|||
|
Partnerid: c.Mchid,
|
|||
|
Prepayid: prepayid,
|
|||
|
Package: "Sign=WXPay",
|
|||
|
Noncestr: nonceStr,
|
|||
|
Timestamp: ts,
|
|||
|
Sign: sign,
|
|||
|
}
|
|||
|
return app, nil
|
|||
|
}
|
|||
|
|
|||
|
// PaySignOfApplet 获取 小程序 支付所需要的参数
|
|||
|
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
|
|||
|
func (c *ClientV3) PaySignOfApplet(appid, prepayid string) (applet *AppletParams, err error) {
|
|||
|
jsapi, err := c.PaySignOfJSAPI(appid, prepayid)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
applet = &AppletParams{
|
|||
|
AppId: jsapi.AppId,
|
|||
|
TimeStamp: jsapi.TimeStamp,
|
|||
|
NonceStr: jsapi.NonceStr,
|
|||
|
Package: jsapi.Package,
|
|||
|
SignType: jsapi.SignType,
|
|||
|
PaySign: jsapi.PaySign,
|
|||
|
}
|
|||
|
return applet, nil
|
|||
|
}
|
|||
|
|
|||
|
// PaySignOfAppletScore 获取 小程序调起支付分 所需要的 ExtraData
|
|||
|
// 文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter6_1_13.shtml
|
|||
|
func (c *ClientV3) PaySignOfAppletScore(mchId, pkg string) (extraData *AppletScoreExtraData, err error) {
|
|||
|
var (
|
|||
|
buffer strings.Builder
|
|||
|
h hash.Hash
|
|||
|
ts = util.Int642String(time.Now().Unix())
|
|||
|
nonceStr = util.RandomString(32)
|
|||
|
)
|
|||
|
buffer.WriteString("mch_id=")
|
|||
|
buffer.WriteString(mchId)
|
|||
|
buffer.WriteString("&nonce_str=")
|
|||
|
buffer.WriteString(nonceStr)
|
|||
|
buffer.WriteString("&package=")
|
|||
|
buffer.WriteString(pkg)
|
|||
|
buffer.WriteString("&sign_type=HMAC-SHA256")
|
|||
|
buffer.WriteString("×tamp=")
|
|||
|
buffer.WriteString(ts)
|
|||
|
buffer.WriteString("&key=")
|
|||
|
buffer.WriteString(string(c.ApiV3Key))
|
|||
|
|
|||
|
h = hmac.New(sha256.New, c.ApiV3Key)
|
|||
|
h.Write([]byte(buffer.String()))
|
|||
|
|
|||
|
extraData = &AppletScoreExtraData{
|
|||
|
MchId: mchId,
|
|||
|
TimeStamp: ts,
|
|||
|
NonceStr: nonceStr,
|
|||
|
Package: pkg,
|
|||
|
SignType: "HMAC-SHA256",
|
|||
|
Sign: strings.ToUpper(hex.EncodeToString(h.Sum(nil))),
|
|||
|
}
|
|||
|
return extraData, nil
|
|||
|
}
|
|||
|
|
|||
|
// v3 鉴权请求Header
|
|||
|
func (c *ClientV3) authorization(method, path string, bm gopay.BodyMap) (string, error) {
|
|||
|
var (
|
|||
|
jb = ""
|
|||
|
timestamp = time.Now().Unix()
|
|||
|
nonceStr = util.RandomString(32)
|
|||
|
)
|
|||
|
if bm != nil {
|
|||
|
jb = bm.JsonBody()
|
|||
|
}
|
|||
|
path = strings.TrimSuffix(path, "?")
|
|||
|
ts := util.Int642String(timestamp)
|
|||
|
_str := method + "\n" + path + "\n" + ts + "\n" + nonceStr + "\n" + jb + "\n"
|
|||
|
if c.DebugSwitch == gopay.DebugOn {
|
|||
|
xlog.Debugf("Wechat_V3_SignString:\n%s", _str)
|
|||
|
}
|
|||
|
sign, err := c.rsaSign(_str)
|
|||
|
if err != nil {
|
|||
|
return "", err
|
|||
|
}
|
|||
|
return Authorization + ` mchid="` + c.Mchid + `",nonce_str="` + nonceStr + `",timestamp="` + ts + `",serial_no="` + c.SerialNo + `",signature="` + sign + `"`, nil
|
|||
|
}
|
|||
|
|
|||
|
func (c *ClientV3) rsaSign(str string) (string, error) {
|
|||
|
if c.privateKey == nil {
|
|||
|
return "", errors.New("privateKey can't be nil")
|
|||
|
}
|
|||
|
h := sha256.New()
|
|||
|
h.Write([]byte(str))
|
|||
|
result, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.SHA256, h.Sum(nil))
|
|||
|
if err != nil {
|
|||
|
return util.NULL, fmt.Errorf("[%w]: %+v", gopay.SignatureErr, err)
|
|||
|
}
|
|||
|
return base64.StdEncoding.EncodeToString(result), nil
|
|||
|
}
|
|||
|
|
|||
|
// 自动同步请求验签
|
|||
|
func (c *ClientV3) verifySyncSign(si *SignInfo) (err error) {
|
|||
|
if !c.autoSign {
|
|||
|
return nil
|
|||
|
}
|
|||
|
if si == nil {
|
|||
|
return errors.New("auto verify sign, but SignInfo is nil")
|
|||
|
}
|
|||
|
c.rwMu.RLock()
|
|||
|
wxPublicKey, exist := c.SnCertMap[si.HeaderSerial]
|
|||
|
c.rwMu.RUnlock()
|
|||
|
if !exist {
|
|||
|
err = c.AutoVerifySign(false)
|
|||
|
if err != nil {
|
|||
|
return fmt.Errorf("[get all public key err]: %v", err)
|
|||
|
}
|
|||
|
c.rwMu.RLock()
|
|||
|
wxPublicKey, exist = c.SnCertMap[si.HeaderSerial]
|
|||
|
c.rwMu.RUnlock()
|
|||
|
if !exist {
|
|||
|
return errors.New("auto verify sign, but public key not found")
|
|||
|
}
|
|||
|
}
|
|||
|
str := si.HeaderTimestamp + "\n" + si.HeaderNonce + "\n" + si.SignBody + "\n"
|
|||
|
signBytes, _ := base64.StdEncoding.DecodeString(si.HeaderSignature)
|
|||
|
h := sha256.New()
|
|||
|
h.Write([]byte(str))
|
|||
|
if err = rsa.VerifyPKCS1v15(wxPublicKey, crypto.SHA256, h.Sum(nil), signBytes); err != nil {
|
|||
|
return fmt.Errorf("[%w]: %v", gopay.VerifySignatureErr, err)
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|