|
@@ -1,28 +1,45 @@
|
|
|
package server
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "crypto"
|
|
|
|
|
+ "crypto/rand"
|
|
|
|
|
+ "crypto/rsa"
|
|
|
|
|
+ "crypto/x509"
|
|
|
|
|
+ "encoding/base64"
|
|
|
"encoding/json"
|
|
"encoding/json"
|
|
|
|
|
+ "encoding/pem"
|
|
|
|
|
+ "errors"
|
|
|
|
|
+ "fmt"
|
|
|
"github.com/shopspring/decimal"
|
|
"github.com/shopspring/decimal"
|
|
|
"github.com/spf13/viper"
|
|
"github.com/spf13/viper"
|
|
|
"go_zh/pkg/http"
|
|
"go_zh/pkg/http"
|
|
|
|
|
+ "go_zh/pkg/utils"
|
|
|
|
|
+ "net/url"
|
|
|
|
|
+ "os"
|
|
|
"sync"
|
|
"sync"
|
|
|
|
|
+ "time"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
type WeChatServer struct {
|
|
type WeChatServer struct {
|
|
|
- MchId string
|
|
|
|
|
- PrivateKey string
|
|
|
|
|
- CertFile string
|
|
|
|
|
- KeyFile string
|
|
|
|
|
- AppId string
|
|
|
|
|
- AppSecret string
|
|
|
|
|
- AppletId string
|
|
|
|
|
- AppletKey string
|
|
|
|
|
|
|
+ MchId string //微信支付商户号
|
|
|
|
|
+ PrivateKey string //微信支付私钥
|
|
|
|
|
+ CertFile string //公钥证书地址
|
|
|
|
|
+ KeyFile string //私钥证书地址
|
|
|
|
|
+ AppId string //公众号id
|
|
|
|
|
+ AppSecret string //公众号密钥
|
|
|
|
|
+ AppletId string //小程序appid
|
|
|
|
|
+ AppletKey string //小程序密钥
|
|
|
|
|
+ CertificateSerialNo string //商户API证书序列号
|
|
|
|
|
+ WechatPayPublicKeyId string //微信支付公钥ID
|
|
|
|
|
+ privateKeyInfo *rsa.PrivateKey
|
|
|
|
|
+ wechatPayPublicKeyInfo *rsa.PublicKey
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 小程序登录信息返回结构体
|
|
|
type AppletsOpenData struct {
|
|
type AppletsOpenData struct {
|
|
|
- Errormsg string `json:"errmsg"`
|
|
|
|
|
- Errorcode int `json:"errcode"`
|
|
|
|
|
- Unionid string `json:"unionid"`
|
|
|
|
|
|
|
+ ErrorMsg string `json:"errmsg"`
|
|
|
|
|
+ ErrorCode int `json:"errcode"`
|
|
|
|
|
+ UnionId string `json:"unionid"`
|
|
|
SessionKey string `json:"session_key"`
|
|
SessionKey string `json:"session_key"`
|
|
|
Openid string `json:"openid"`
|
|
Openid string `json:"openid"`
|
|
|
}
|
|
}
|
|
@@ -36,28 +53,39 @@ var (
|
|
|
func GetWeChatServerInstance() *WeChatServer {
|
|
func GetWeChatServerInstance() *WeChatServer {
|
|
|
weChatServerOnce.Do(func() {
|
|
weChatServerOnce.Do(func() {
|
|
|
weChatServerInstance = &WeChatServer{
|
|
weChatServerInstance = &WeChatServer{
|
|
|
- MchId: viper.GetString("weChat.mchId"),
|
|
|
|
|
- PrivateKey: viper.GetString("weChat.privateKey"),
|
|
|
|
|
- CertFile: viper.GetString("weChat.certFile"),
|
|
|
|
|
- KeyFile: viper.GetString("weChat.keyFile"),
|
|
|
|
|
- AppId: viper.GetString("weChat.appId"),
|
|
|
|
|
- AppSecret: viper.GetString("weChat.appSecret"),
|
|
|
|
|
- AppletId: viper.GetString("weChat.appletId"),
|
|
|
|
|
- AppletKey: viper.GetString("weChat.appletKey"),
|
|
|
|
|
|
|
+ MchId: viper.GetString("weChat.mchId"),
|
|
|
|
|
+ PrivateKey: viper.GetString("weChat.privateKey"),
|
|
|
|
|
+ CertFile: viper.GetString("weChat.certFile"),
|
|
|
|
|
+ KeyFile: viper.GetString("weChat.keyFile"),
|
|
|
|
|
+ AppId: viper.GetString("weChat.appId"),
|
|
|
|
|
+ AppSecret: viper.GetString("weChat.appSecret"),
|
|
|
|
|
+ AppletId: viper.GetString("weChat.appletId"),
|
|
|
|
|
+ AppletKey: viper.GetString("weChat.appletKey"),
|
|
|
|
|
+ CertificateSerialNo: viper.GetString("weChat.certificateSerialNo"),
|
|
|
|
|
+ WechatPayPublicKeyId: viper.GetString("weChat.wechatPayPublicKeyId"),
|
|
|
}
|
|
}
|
|
|
|
|
+ privateKey, _ := LoadPrivateKeyWithPath(weChatServerInstance.KeyFile)
|
|
|
|
|
+ weChatServerInstance.privateKeyInfo = privateKey
|
|
|
|
|
+ wechatPayPublicKey, _ := LoadPublicKeyWithPath(weChatServerInstance.CertFile)
|
|
|
|
|
+ weChatServerInstance.wechatPayPublicKeyInfo = wechatPayPublicKey
|
|
|
})
|
|
})
|
|
|
return weChatServerInstance
|
|
return weChatServerInstance
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// JsapiPrepayResponse 微信支付jsapi 返回信息结构体
|
|
|
|
|
+type JsapiPrepayResponse struct {
|
|
|
|
|
+ PrepayId string `json:"prepay_id,omitempty"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// GetAppletsOpenId 微信小程序获取登录openId
|
|
// GetAppletsOpenId 微信小程序获取登录openId
|
|
|
func (wx *WeChatServer) GetAppletsOpenId(code string) (AppletsOpenData, error) {
|
|
func (wx *WeChatServer) GetAppletsOpenId(code string) (AppletsOpenData, error) {
|
|
|
|
|
|
|
|
- url := "https://api.weixin.qq.com/sns/jscode2session"
|
|
|
|
|
|
|
+ curlUrl := "https://api.weixin.qq.com/sns/jscode2session"
|
|
|
//发起http get请求
|
|
//发起http get请求
|
|
|
|
|
|
|
|
var appletsOpenRes AppletsOpenData
|
|
var appletsOpenRes AppletsOpenData
|
|
|
requestData := http.RequestData{}
|
|
requestData := http.RequestData{}
|
|
|
- err := requestData.ConstructRequest(url)
|
|
|
|
|
|
|
+ err := requestData.ConstructRequest(curlUrl)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return appletsOpenRes, err
|
|
return appletsOpenRes, err
|
|
|
}
|
|
}
|
|
@@ -72,6 +100,166 @@ func (wx *WeChatServer) GetAppletsOpenId(code string) (AppletsOpenData, error) {
|
|
|
_ = json.Unmarshal(ret, &appletsOpenRes)
|
|
_ = json.Unmarshal(ret, &appletsOpenRes)
|
|
|
return appletsOpenRes, nil
|
|
return appletsOpenRes, nil
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// WeChatJsPay 微信公众号jsapi支付信息
|
|
|
func (wx *WeChatServer) WeChatJsPay(orderSn string, payMoney decimal.Decimal, notifyUrl string, boby string, wxOpenid string) {
|
|
func (wx *WeChatServer) WeChatJsPay(orderSn string, payMoney decimal.Decimal, notifyUrl string, boby string, wxOpenid string) {
|
|
|
|
|
+ curlUrl := "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"
|
|
|
|
|
+
|
|
|
|
|
+ result := make(map[string]interface{})
|
|
|
|
|
+
|
|
|
|
|
+ amount := make(map[string]interface{})
|
|
|
|
|
+ amount["total"] = payMoney.Mul(decimal.NewFromInt(100)).IntPart()
|
|
|
|
|
+ amount["currency"] = "CNY"
|
|
|
|
|
+
|
|
|
|
|
+ payer := make(map[string]interface{})
|
|
|
|
|
+ payer["openid"] = wxOpenid
|
|
|
|
|
+
|
|
|
|
|
+ result["appid"] = wx.AppId
|
|
|
|
|
+ result["mchid"] = wx.MchId
|
|
|
|
|
+ result["description"] = boby
|
|
|
|
|
+ result["out_trade_no"] = orderSn
|
|
|
|
|
+ result["notify_url"] = notifyUrl
|
|
|
|
|
+ result["amount"] = amount
|
|
|
|
|
+ result["payer"] = payer
|
|
|
|
|
+
|
|
|
|
|
+ body, _ := json.Marshal(result)
|
|
|
|
|
+ fmt.Println("请求信息:", string(body))
|
|
|
|
|
+ res, _ := wx.JsapiPrepay(curlUrl, orderSn, body)
|
|
|
|
|
+ fmt.Println(res)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (wx *WeChatServer) JsapiPrepay(curlUrl string, orderSn string, body []byte) (JsapiPrepayResponse, error) {
|
|
|
|
|
+ //定义返回信息
|
|
|
|
|
+ var jsapiPrepayResponse JsapiPrepayResponse
|
|
|
|
|
+ //解析请求url,主要用于加密请求头信息
|
|
|
|
|
+ reqUrl, err := url.Parse(curlUrl)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return jsapiPrepayResponse, err
|
|
|
|
|
+ }
|
|
|
|
|
+ //初始化请求
|
|
|
|
|
+ requestData := http.RequestData{}
|
|
|
|
|
+ err = requestData.ConstructRequest(curlUrl)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return jsapiPrepayResponse, err
|
|
|
|
|
+ }
|
|
|
|
|
+ requestData.DataByte = body //请求json信息
|
|
|
|
|
+ //组装请求头
|
|
|
|
|
+ requestData.SetHeader("Accept", "application/json")
|
|
|
|
|
+ var authorization string
|
|
|
|
|
+ authorization, err = buildAuthorization(wx.MchId, orderSn, wx.CertificateSerialNo, wx.privateKeyInfo, "POST", reqUrl.RequestURI(), body)
|
|
|
|
|
+ requestData.SetHeader("Authorization", authorization)
|
|
|
|
|
+
|
|
|
|
|
+ var ret []byte
|
|
|
|
|
+ ret, err = requestData.PostJsonRequest()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return jsapiPrepayResponse, err
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Println(string(ret))
|
|
|
|
|
+ return jsapiPrepayResponse, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// buildAuthorization 构建请求头中的 Authorization 信息
|
|
|
|
|
+func buildAuthorization(
|
|
|
|
|
+ mchid string,
|
|
|
|
|
+ orderSn string,
|
|
|
|
|
+ certificateSerialNo string,
|
|
|
|
|
+ privateKey *rsa.PrivateKey,
|
|
|
|
|
+ method string,
|
|
|
|
|
+ canonicalURL string,
|
|
|
|
|
+ body []byte,
|
|
|
|
|
+) (string, error) {
|
|
|
|
|
+ const (
|
|
|
|
|
+ SignatureMessageFormat = "%s\n%s\n%d\n%s\n%s\n" // 数字签名原文格式
|
|
|
|
|
+ // HeaderAuthorizationFormat 请求头中的 Authorization 拼接格式
|
|
|
|
|
+ HeaderAuthorizationFormat = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
|
|
+ nonce := utils.Md5Sign(orderSn) //随机字符串
|
|
|
|
|
+ timestamp := time.Now().Unix() //输出当前Unix时间戳
|
|
|
|
|
+ message := fmt.Sprintf(SignatureMessageFormat, method, canonicalURL, timestamp, nonce, body)
|
|
|
|
|
+ signature, err := SignSHA256WithRSA(message, privateKey)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ authorization := fmt.Sprintf(
|
|
|
|
|
+ HeaderAuthorizationFormat,
|
|
|
|
|
+ mchid, nonce, timestamp, certificateSerialNo, signature,
|
|
|
|
|
+ )
|
|
|
|
|
+ return authorization, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// LoadPrivateKey 通过私钥的文本内容加载私钥
|
|
|
|
|
+func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) {
|
|
|
|
|
+ block, _ := pem.Decode([]byte(privateKeyStr))
|
|
|
|
|
+ if block == nil {
|
|
|
|
|
+ return nil, fmt.Errorf("decode private key err")
|
|
|
|
|
+ }
|
|
|
|
|
+ if block.Type != "PRIVATE KEY" {
|
|
|
|
|
+ return nil, fmt.Errorf("the kind of PEM should be PRVATE KEY")
|
|
|
|
|
+ }
|
|
|
|
|
+ key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("parse private key err:%s", err.Error())
|
|
|
|
|
+ }
|
|
|
|
|
+ privateKey, ok := key.(*rsa.PrivateKey)
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return nil, fmt.Errorf("not a RSA private key")
|
|
|
|
|
+ }
|
|
|
|
|
+ return privateKey, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// LoadPublicKey 通过公钥的文本内容加载公钥
|
|
|
|
|
+func LoadPublicKey(publicKeyStr string) (publicKey *rsa.PublicKey, err error) {
|
|
|
|
|
+ block, _ := pem.Decode([]byte(publicKeyStr))
|
|
|
|
|
+ if block == nil {
|
|
|
|
|
+ return nil, errors.New("decode public key error")
|
|
|
|
|
+ }
|
|
|
|
|
+ if block.Type != "PUBLIC KEY" {
|
|
|
|
|
+ return nil, fmt.Errorf("the kind of PEM should be PUBLIC KEY")
|
|
|
|
|
+ }
|
|
|
|
|
+ key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("parse public key err:%s", err.Error())
|
|
|
|
|
+ }
|
|
|
|
|
+ publicKey, ok := key.(*rsa.PublicKey)
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return nil, fmt.Errorf("%s is not rsa public key", publicKeyStr)
|
|
|
|
|
+ }
|
|
|
|
|
+ return publicKey, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// LoadPrivateKeyWithPath 通过私钥的文件路径内容加载私钥
|
|
|
|
|
+func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) {
|
|
|
|
|
+ privateKeyBytes, err := os.ReadFile(path)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("read private pem file err:%s", err.Error())
|
|
|
|
|
+ }
|
|
|
|
|
+ return LoadPrivateKey(string(privateKeyBytes))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// LoadPublicKeyWithPath 通过公钥的文件路径加载公钥
|
|
|
|
|
+func LoadPublicKeyWithPath(path string) (publicKey *rsa.PublicKey, err error) {
|
|
|
|
|
+ publicKeyBytes, err := os.ReadFile(path)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("read certificate pem file err:%s", err.Error())
|
|
|
|
|
+ }
|
|
|
|
|
+ return LoadPublicKey(string(publicKeyBytes))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SignSHA256WithRSA 通过私钥对字符串以 SHA256WithRSA 算法生成签名信息
|
|
|
|
|
+func SignSHA256WithRSA(source string, privateKey *rsa.PrivateKey) (signature string, err error) {
|
|
|
|
|
+ if privateKey == nil {
|
|
|
|
|
+ return "", fmt.Errorf("private key should not be nil")
|
|
|
|
|
+ }
|
|
|
|
|
+ h := crypto.Hash.New(crypto.SHA256)
|
|
|
|
|
+ _, err = h.Write([]byte(source))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", nil
|
|
|
|
|
+ }
|
|
|
|
|
+ hashed := h.Sum(nil)
|
|
|
|
|
+ signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return "", err
|
|
|
|
|
+ }
|
|
|
|
|
+ return base64.StdEncoding.EncodeToString(signatureByte), nil
|
|
|
}
|
|
}
|