panghu 7 kuukautta sitten
vanhempi
commit
e1d1cfc315
4 muutettua tiedostoa jossa 214 lisäystä ja 22 poistoa
  1. 2 1
      api/shop_order.go
  2. 1 0
      go.mod
  3. 2 0
      go.sum
  4. 209 21
      server/weChatServer.go

+ 2 - 1
api/shop_order.go

@@ -141,7 +141,8 @@ func (shopOrder ShopOrderController) GetOrderSn(c *gin.Context) {
 		if isBank == 1 {
 			payUrl = chinaumsServer.ShopOrderWxH5Pay(shopMchidInfo, shopOrderModel.RsSn, shopOrderModel.PayMoney, notifyUrl, memberOpenId)
 		} else {
-			wxPay = weChatServer.WeChatJsPay(shopOrderModel.RsSn, shopOrderModel.PayMoney, notifyUrl, '订单支付', memberOpenId)
+			notifyUrl = "http://127.0.0.1/api/notify/shopOrder"
+			weChatServer.WeChatJsPay(shopOrderModel.RsSn, shopOrderModel.PayMoney, notifyUrl, "订单支付", memberOpenId)
 		}
 
 	} else { //支付宝支付

+ 1 - 0
go.mod

@@ -16,6 +16,7 @@ require (
 	github.com/gabriel-vasile/mimetype v1.4.13 // indirect
 	github.com/gin-contrib/sse v1.1.0 // indirect
 	github.com/gin-gonic/gin v1.11.0 // indirect
+	github.com/go-pay/gopay v1.5.115 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.30.1 // indirect

+ 2 - 0
go.sum

@@ -25,6 +25,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
 github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
 github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
 github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
+github.com/go-pay/gopay v1.5.115 h1:8WjWftPChKCiVt5Qz2xLqXeUdidsR+y9/R2S/7Q9szc=
+github.com/go-pay/gopay v1.5.115/go.mod h1:p48xvWeepPolZuakAjCeucWynWwW7msoXsqahcoJpKE=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=

+ 209 - 21
server/weChatServer.go

@@ -1,28 +1,45 @@
 package server
 
 import (
+	"crypto"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/base64"
 	"encoding/json"
+	"encoding/pem"
+	"errors"
+	"fmt"
 	"github.com/shopspring/decimal"
 	"github.com/spf13/viper"
 	"go_zh/pkg/http"
+	"go_zh/pkg/utils"
+	"net/url"
+	"os"
 	"sync"
+	"time"
 )
 
 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 {
-	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"`
 	Openid     string `json:"openid"`
 }
@@ -36,28 +53,39 @@ var (
 func GetWeChatServerInstance() *WeChatServer {
 	weChatServerOnce.Do(func() {
 		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
 }
 
+// JsapiPrepayResponse 微信支付jsapi 返回信息结构体
+type JsapiPrepayResponse struct {
+	PrepayId string `json:"prepay_id,omitempty"`
+}
+
 // GetAppletsOpenId 微信小程序获取登录openId
 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请求
 
 	var appletsOpenRes AppletsOpenData
 	requestData := http.RequestData{}
-	err := requestData.ConstructRequest(url)
+	err := requestData.ConstructRequest(curlUrl)
 	if err != nil {
 		return appletsOpenRes, err
 	}
@@ -72,6 +100,166 @@ func (wx *WeChatServer) GetAppletsOpenId(code string) (AppletsOpenData, error) {
 	_ = json.Unmarshal(ret, &appletsOpenRes)
 	return appletsOpenRes, nil
 }
+
+// WeChatJsPay 微信公众号jsapi支付信息
 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
 }