package pay import ( "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "errors" "fmt" "io/ioutil" "net/http" "net/url" "strings" "time" "zhiyuan/models" "zhiyuan/pkg/config" "zhiyuan/pkg/utils" "zhiyuan/services/weixin" ) const ( JsapiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi" HeaderAuthorizationFormat = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"" Authorization = "Authorization" Accept = "Accept" ) type Client struct { Account *models.Weixin // 支付账号 SignType string // 签名类型 PrivateKey *rsa.PrivateKey } func NewClient(wxID int) (*Client, error) { weixinInfo, _ := weixin.GetOne(map[string]interface{}{"id": wxID}, nil, nil) private, err := LoadPrivateKey() if err != nil { return nil, err } return &Client{ Account: weixinInfo, SignType: "RSA", PrivateKey: private, }, nil } func (c *Client) SignRequest(method string, requestUrl string, body string) (string, error) { u, err := url.Parse(requestUrl) timestamp := time.Now().Unix() nonce := utils.RandomStr() sign, err := c.Sign([]string{method, u.RequestURI(), utils.ToStr(timestamp), nonce, body}) if err != nil { return "", err } return fmt.Sprintf(HeaderAuthorizationFormat, c.Account.MchID, nonce, timestamp, c.Account.SerialNO, sign, ), nil } func (c *Client) Sign(param []string) (string, error) { message := strings.Join(param, "\n") + "\n" return SignSHA256WithRSA(message, c.PrivateKey) } func (c *Client) Prepay(param JsapiParam) (*PrepayResponse, error) { if param.Description == "" || param.OutTradeNO == "" || param.Total <= 0 || param.OpenID == "" { return nil, errors.New("description,out_trade_no,open_id,money不能为空") } params := PrepayRequest{ AppID: c.Account.AppID, MchID: c.Account.MchID, Description: param.Description, OutTradeNO: param.OutTradeNO, NotifyUrl: param.NotifyUrl, } params.Amount.Total = utils.FloatMul(param.Total, 100, 0) params.Payer.OpenID = param.OpenID fmt.Println(params) authorization, err := c.SignRequest("POST", JsapiUrl, utils.JsonEncode(params)) if err != nil { return nil, err } header := SetHeaderAuthorization(authorization) var res *PrepayResponse if err := utils.HttpPostJSON(&http.Client{}, JsapiUrl, header, params, &res); err != nil { return nil, err } if res.PrepayID == "" { return nil, errors.New(res.Message) } return res, nil } func (c *Client) JsapiPay(param JsapiParam) (JsapiReturn, error) { res, err := c.Prepay(param) if err != nil { return JsapiReturn{}, err } retParam := JsapiReturn{ AppID: c.Account.AppID, Timestamp: utils.ToStr(time.Now().Unix()), NonceStr: utils.RandomStr(), Package: "prepay_id=" + res.PrepayID, SignType: c.SignType, } if sign, err := c.Sign([]string{retParam.AppID, retParam.Timestamp, retParam.NonceStr, retParam.SignType}); err != nil { return JsapiReturn{}, err } else { retParam.PaySign = sign return retParam, nil } } func SetHeaderAuthorization(authorization string) http.Header { header := http.Header{} header.Set(Authorization, authorization) header.Set(Accept, "*/*") return header } func LoadPrivateKey() (*rsa.PrivateKey, error) { privateKeyStr, err := ioutil.ReadFile(utils.GetRootPath() + config.Cfg.App.WeixinPath + "apiclient_key.pem") block, _ := pem.Decode(privateKeyStr) if block == nil { return nil, fmt.Errorf("decode private key err") } 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("%s is not rsa private key", privateKeyStr) } return privateKey, nil } 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 }