经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
golang实现微信支付v3版本的方法
来源:jb51  时间:2021/3/1 12:56:52  对本文有异议

一、准备阶段

 获取私钥

官方文档 https://kf.qq.com/faq/161222N...

获取私钥证书的序列号 https://pay.weixin.qq.com/wik...

  1. openssl x509 -in 1900009191_20180326_cert.pem -noout -serial
  2. serial=1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C

私钥获取后有三个文件

  1. apiclient_key.p12
  2. apiclient_cert.pem
  3. apiclient_key.pem

本次示例程序中,使用的是文件 apiclient_key.pem内容

获取公钥(平台证书)

官方文档

更新证书 https://pay.weixin.qq.com/wik...

平台证书会提前10天生成新证书,微信官方推荐在旧证书过期前5-10天部署新证书

获取证书API文档 https://pay.weixin.qq.com/wik...

身份证认证信息生成文档 https://pay.weixin.qq.com/wik...

常量

  1. const appId = "" // 小程序或者公众号的appid
  2. const mchId = "" // 微信支付的商户id
  3. const privateSerialNo = "" // 私钥证书号
  4. const aesKey = "" // 微信支付aes key

生成数字签名

  1. // 对消息的散列值进行数字签名
  2. func signPKCS1v15(msg, privateKey []byte, hashType crypto.Hash) ([]byte, error) {
  3. block, _ := pem.Decode(privateKey)
  4. if block == nil {
  5. return nil, errors.New("private key decode error")
  6. }
  7. pri, err := x509.ParsePKCS8PrivateKey(block.Bytes)
  8. if err != nil {
  9. return nil, errors.New("parse private key error")
  10. }
  11. key, ok := pri.(*rsa.PrivateKey)
  12. if ok == false {
  13. return nil, errors.New("private key format error")
  14. }
  15. sign, err := rsa.SignPKCS1v15(cryptoRand.Reader, key, hashType, msg)
  16. if err != nil {
  17. return nil, errors.New("sign error")
  18. }
  19. return sign, nil
  20. }
  21. // base编码
  22. func base64EncodeStr(src []byte) string {
  23. return base64.StdEncoding.EncodeToString(src)
  24. }

生成身份认证信息

  1. func authorization(method string, paramMap map[string]interface{}, rawUrl string) (token string, err error) {
  2. var body string
  3. if len(paramMap) != 0 {
  4. paramJsonBytes, err := json.Marshal(paramMap)
  5. if err != nil {
  6. return token, err
  7. }
  8. body = string(paramJsonBytes)
  9. }
  10. urlPart, err := url.Parse(rawUrl)
  11. if err != nil {
  12. return token, err
  13. }
  14. canonicalUrl := urlPart.RequestURI()
  15. timestamp := time.Now().Unix()
  16. nonce := getRandomString(32)
  17. message := fmt.Sprintf("%s\n%s\n%d\n%s\n%s\n", method, canonicalUrl, timestamp, nonce, body)
  18. open, err := os.Open("/Users/apple/data/www/go/work/src/study/testwechantpay/private.pem")
  19. if err != nil {
  20. return token, err
  21. }
  22. defer open.Close()
  23. privateKey, err := ioutil.ReadAll(open)
  24. if err != nil {
  25. return token, err
  26. }
  27. signBytes, err := signPKCS1v15(hasha256(message), privateKey, crypto.SHA256)
  28. if err != nil {
  29. return token, err
  30. }
  31. sign := base64EncodeStr(signBytes)
  32. token = fmt.Sprintf("mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"",
  33. mchId, nonce, timestamp, privateSerialNo, sign)
  34. return token, nil
  35. }

报文解密

  1. func decryptGCM(aesKey, nonceV, ciphertextV, additionalDataV string) ([]byte, error) {
  2. key := []byte(aesKey)
  3. nonce := []byte(nonceV)
  4. additionalData := []byte(additionalDataV)
  5. ciphertext, err := base64.StdEncoding.DecodeString(ciphertextV)
  6. if err != nil {
  7. return nil, err
  8. }
  9. block, err := aes.NewCipher(key)
  10. if err != nil {
  11. return nil, err
  12. }
  13. aesGCM, err := cipher.NewGCM(block)
  14. if err != nil {
  15. return nil, err
  16. }
  17. plaintext, err := aesGCM.Open(nil, nonce, ciphertext, additionalData)
  18. if err != nil {
  19. return nil, err
  20. }
  21. return plaintext, err
  22. }

获取平台证书

  1. // 获取公钥
  2. const publicKeyUrl = "https://api.mch.weixin.qq.com/v3/certificates"
  3. type TokenResponse struct {
  4. Data []TokenResponseData `json:"data"`
  5. }
  6. type TokenResponseData struct {
  7. EffectiveTime string `json:"effective_time"`
  8. EncryptCertificate EncryptCertificate `json:"encrypt_certificate"`
  9. ExpireTime string `json:"expire_time"`
  10. SerialNo string `json:"serial_no"`
  11. }
  12. type EncryptCertificate struct {
  13. Algorithm string `json:"algorithm"`
  14. AssociatedData string `json:"associated_data"`
  15. Ciphertext string `json:"ciphertext"`
  16. Nonce string `json:"nonce"`
  17. }
  18. var publicSyncMap sync.Map
  19. // 获取公钥
  20. func getPublicKey() (key string, err error) {
  21. var prepareTime int64 = 24 * 3600 * 3 // 证书提前三天过期旧证书,获取新证书
  22. nowTime := time.Now().Unix()
  23. // 读取公钥缓存数据
  24. cacheValueKey := fmt.Sprintf("app_id:%s:public_key:value", appId)
  25. cacheExpireTimeKey := fmt.Sprintf("app_id:%s:public_key:expire_time", appId)
  26. cacheValue, keyValueOk := publicSyncMap.Load(cacheValueKey)
  27. cacheExpireTime, expireTimeOk := publicSyncMap.Load(cacheExpireTimeKey)
  28. if keyValueOk && expireTimeOk {
  29. // 格式化时间
  30. local, _ := time.LoadLocation("Local")
  31. location, _ := time.ParseInLocation(time.RFC3339, cacheExpireTime.(string), local)
  32. // 判断是否过期,证书没有过期直接返回
  33. if location.Unix()-prepareTime > nowTime {
  34. return cacheValue.(string), nil
  35. }
  36. }
  37. token, err := authorization(http.MethodGet, nil, publicKeyUrl)
  38. if err != nil {
  39. return key, err
  40. }
  41. request, err := http.NewRequest(http.MethodGet, publicKeyUrl, nil)
  42. if err != nil {
  43. return key, err
  44. }
  45. request.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
  46. request.Header.Add("User-Agent", "用户代理(https://zh.wikipedia.org/wiki/User_agent)")
  47. request.Header.Add("Content-type", "application/json;charset='utf-8'")
  48. request.Header.Add("Accept", "application/json")
  49. client := http.DefaultClient
  50. response, err := client.Do(request)
  51. if err != nil {
  52. return key, err
  53. }
  54. defer response.Body.Close()
  55. bodyBytes, err := ioutil.ReadAll(response.Body)
  56. if err != nil {
  57. return key, err
  58. }
  59. //fmt.Println(string(bodyBytes))
  60. var tokenResponse TokenResponse
  61. if err = json.Unmarshal(bodyBytes, &tokenResponse); err != nil {
  62. return key, err
  63. }
  64. for _, encryptCertificate := range tokenResponse.Data {
  65. // 格式化时间
  66. local, _ := time.LoadLocation("Local")
  67. location, err := time.ParseInLocation(time.RFC3339, encryptCertificate.ExpireTime, local)
  68. if err != nil {
  69. return key, err
  70. }
  71. // 判断是否过期,证书没有过期直接返回
  72. if location.Unix()-prepareTime > nowTime {
  73. decryptBytes, err := decryptGCM(aesKey, encryptCertificate.EncryptCertificate.Nonce, encryptCertificate.EncryptCertificate.Ciphertext,
  74. encryptCertificate.EncryptCertificate.AssociatedData)
  75. if err != nil {
  76. return key, err
  77. }
  78. key = string(decryptBytes)
  79. publicSyncMap.Store(cacheValueKey, key)
  80. publicSyncMap.Store(cacheExpireTimeKey, encryptCertificate.ExpireTime)
  81. return key, nil
  82. }
  83. }
  84. return key, errors.New("get public key error")
  85. }

二、发起微信支付

jsapi 发起支付

调用统一下单接口

统一下单接口文档 https://pay.weixin.qq.com/wik...

  1. // 统一下单接口
  2. func commonPay() (payResMap map[string]string, err error) {
  3. payResMap = make(map[string]string)
  4. amount := 10
  5. paramMap := make(map[string]interface{})
  6. paramMap["appid"] = appId
  7. paramMap["mchid"] = mchId
  8. paramMap["description"] = fmt.Sprintf("微信充值:¥%d", amount)
  9. paramMap["out_trade_no"] = fmt.Sprintf("test%s%s", time.Now().Format("20060102150405"), randNumber())
  10. paramMap["notify_url"] = "http://tools.localhost/notify"
  11. paramMap["amount"] = map[string]interface{}{"total": amount * 100, "currency": "CNY"}
  12. paramMap["payer"] = map[string]string{"openid": "opCO05utXkPQh3Vje13WjEdQpAZ4"}
  13. token, err := authorization(http.MethodPost, paramMap, commonPayUrl)
  14. if err != nil {
  15. return payResMap, err
  16. }
  17. marshal, _ := json.Marshal(paramMap)
  18. request, err := http.NewRequest(http.MethodPost, commonPayUrl, bytes.NewReader(marshal))
  19. if err != nil {
  20. return payResMap, err
  21. }
  22. request.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
  23. request.Header.Add("User-Agent", "用户代理(https://zh.wikipedia.org/wiki/User_agent)")
  24. request.Header.Add("Content-type", "application/json;charset='utf-8'")
  25. request.Header.Add("Accept", "application/json")
  26. client := http.DefaultClient
  27. response, err := client.Do(request)
  28. if err != nil {
  29. return payResMap, err
  30. }
  31. defer func() {
  32. response.Body.Close()
  33. }()
  34. bodyBytes, err := ioutil.ReadAll(response.Body)
  35. if err != nil {
  36. return payResMap, err
  37. }
  38. if err = json.Unmarshal(bodyBytes, &payResMap); err != nil {
  39. return payResMap, err
  40. }
  41. if payResMap["prepay_id"] == "" {
  42. return payResMap, errors.New("code:" + payResMap["code"] + "err:" + payResMap["message"])
  43. }
  44. return payResMap, nil
  45. }

生成jsapi发起支付

JSAPI 调起支付接口文档 https://pay.weixin.qq.com/wik...

  1. func jsApi(payResMap map[string]string) (payJson string, err error) {
  2. payMap := make(map[string]string)
  3. timeStamp := time.Now().Unix()
  4. nonce := getRandomString(32)
  5. packageStr := "prepay_id=" + payResMap["prepay_id"]
  6. payMap["appId"] = appId
  7. payMap["timeStamp"] = fmt.Sprintf("%v", timeStamp)
  8. payMap["nonceStr"] = nonce
  9. payMap["package"] = packageStr
  10. // 签名
  11. message := fmt.Sprintf("%s\n%s\n%s\n%s\n", appId, fmt.Sprintf("%v", timeStamp), nonce, packageStr)
  12. open, err := os.Open("/Users/apple/data/www/go/work/src/study/testwechantpay/private.pem")
  13. if err != nil {
  14. return payJson, err
  15. }
  16. defer open.Close()
  17. privateKey, err := ioutil.ReadAll(open)
  18. if err != nil {
  19. return payJson, err
  20. }
  21. signBytes, err := signPKCS1v15(hasha256(message), privateKey, crypto.SHA256)
  22. if err != nil {
  23. return payJson, err
  24. }
  25. sign := base64EncodeStr(signBytes)
  26. payMap["signType"] = sign
  27. payMap["paySign"] = "RSA"
  28. payJsonBytes, err := json.Marshal(payMap)
  29. if err != nil {
  30. return payJson, err
  31. }
  32. payJson = string(payJsonBytes)
  33. return payJson, nil
  34. }

前台发起支付js

需要加载微信js http://res.wx.qq.com/open/js/jweixin-1.6.0.js

调用微信js需要在微信支付平台,设置支付目录

指引文档 https://pay.weixin.qq.com/wik...

  1. <script type="text/javascript" src="__STATIC__/frontend/js/jquery.min.js"></script>
  2. <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
  3. <script>
  4. $(function () {
  5. $(".am-btn").click(function () {
  6. var score = $(".score div input:checked").val();
  7. $.post("发起微信支付后端接口URL", {"score": score}, function (res) {
  8. if (res.status === 500) {
  9. alert(res.message);
  10. return;
  11. }
  12. if (typeof WeixinJSBridge == "undefined") {
  13. if (document.addEventListener) {
  14. document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
  15. } else if (document.attachEvent) {
  16. document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
  17. document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
  18. }
  19. } else {
  20. onBridgeReady(res);
  21. }
  22. })
  23. })
  24. function onBridgeReady(param) {
  25. var orderId = param.data.orderId;
  26. WeixinJSBridge.invoke('getBrandWCPayRequest', {
  27. "appId": param.data.appId,
  28. "timeStamp": param.data.timeStamp,
  29. "nonceStr": param.data.nonceStr,
  30. "package": param.data.package,
  31. "signType": param.data.signType,
  32. "paySign": param.data.paySign
  33. },
  34. function (res) {
  35. if (res.err_msg === "get_brand_wcpay_request:ok") {
  36. window.location.href = "{:url('index/order/successful')}?order_id=" + orderId;
  37. }
  38. });
  39. }
  40. })
  41. </script>

三、异步通知

签名校验

文档 https://pay.weixin.qq.com/wik...

验证签名

  1. //验证数字签名
  2. func VerifyRsaSign(msg []byte, sign []byte, publicStr []byte, hashType crypto.Hash) bool {
  3. //pem解码
  4. block, _ := pem.Decode(publicStr)
  5. //x509解码
  6. publicKeyInterface, err := x509.ParseCertificate(block.Bytes)
  7. if err != nil {
  8. panic(err)
  9. }
  10. publicKey := publicKeyInterface.PublicKey.(*rsa.PublicKey)
  11. //验证数字签名
  12. err = rsa.VerifyPKCS1v15(publicKey, hashType, msg, sign) //crypto.SHA1
  13. return err == nil
  14. }
  15. // 验证签名
  16. func notifyValidate(timeStamp ,nonce,rawPost,signature string) (bool, error) {
  17. signature = base64DecodeStr(signature)
  18. message := fmt.Sprintf("%s\n%s\n%s\n", timeStamp, nonce, rawPost)
  19. publicKey, err := getPublicKey()
  20. if err != nil {
  21. return false, err
  22. }
  23. return VerifyRsaSign(hasha256(message), []byte(signature), []byte(publicKey), crypto.SHA256), nil
  24. }

报文解密

  1. type NotifyResponse struct {
  2. CreateTime string `json:"create_time"`
  3. Resource NotifyResource `json:"resource"`
  4. }
  5. type NotifyResource struct {
  6. Ciphertext string `json:"ciphertext"`
  7. AssociatedData string `json:"associated_data"`
  8. Nonce string `json:"nonce"`
  9. }
  10. func notifyDecrypt(rawPost string) (decrypt string, err error) {
  11. var notifyResponse NotifyResponse
  12. if err = json.Unmarshal([]byte(rawPost), &notifyResponse); err != nil {
  13. return decrypt, err
  14. }
  15. decryptBytes, err := decryptGCM(aesKey, notifyResponse.Resource.Nonce, notifyResponse.Resource.Ciphertext,
  16. notifyResponse.Resource.AssociatedData)
  17. if err != nil {
  18. return decrypt, err
  19. }
  20. decrypt = string(decryptBytes)
  21. return decrypt, nil
  22. }

四、查询订单

文档 https://pay.weixin.qq.com/wik...

查询订单

  1. const searchTradeUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s"
  2. // 查询交易
  3. func searchTrade(orderId string) (trade string, err error) {
  4. rawUrl := fmt.Sprintf(searchTradeUrl, orderId, mchId)
  5. token, err := authorization(http.MethodGet, nil, rawUrl)
  6. if err != nil {
  7. return trade, err
  8. }
  9. request, err := http.NewRequest(http.MethodGet, rawUrl, nil)
  10. if err != nil {
  11. return trade, err
  12. }
  13. request.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
  14. request.Header.Add("User-Agent", "用户代理(https://zh.wikipedia.org/wiki/User_agent)")
  15. request.Header.Add("Content-type", "application/json;charset='utf-8'")
  16. request.Header.Add("Accept", "application/json")
  17. client := http.DefaultClient
  18. response, err := client.Do(request)
  19. if err != nil {
  20. return trade, err
  21. }
  22. defer response.Body.Close()
  23. bodyBytes, err := ioutil.ReadAll(response.Body)
  24. if err != nil {
  25. return trade, err
  26. }
  27. return string(bodyBytes), nil
  28. }

五、申请退款

文档 https://pay.weixin.qq.com/wik...

申请退款

  1. const refundUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"
  2. func refundTrade(orderId string, amount float64) (trade string, err error) {
  3. paramMap := make(map[string]interface{})
  4. paramMap["out_trade_no"] = orderId
  5. paramMap["out_refund_no"] = orderId + "-1"
  6. paramMap["amount"] = map[string]interface{}{"refund": amount * 100, "total": amount * 100, "currency": "CNY"}
  7. token, err := authorization(http.MethodPost, paramMap, refundUrl)
  8. if err != nil {
  9. return trade, err
  10. }
  11. marshal, _ := json.Marshal(paramMap)
  12. request, err := http.NewRequest(http.MethodPost, refundUrl, bytes.NewReader(marshal))
  13. if err != nil {
  14. return trade, err
  15. }
  16. request.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
  17. request.Header.Add("User-Agent", "用户代理(https://zh.wikipedia.org/wiki/User_agent)")
  18. request.Header.Add("Content-type", "application/json;charset='utf-8'")
  19. request.Header.Add("Accept", "application/json")
  20. client := http.DefaultClient
  21. response, err := client.Do(request)
  22. if err != nil {
  23. return trade, err
  24. }
  25. defer func() {
  26. response.Body.Close()
  27. }()
  28. bodyBytes, err := ioutil.ReadAll(response.Body)
  29. if err != nil {
  30. return trade, err
  31. }
  32. return string(bodyBytes), nil
  33. }

到此这篇关于golang实现微信支付v3版本的方法的文章就介绍到这了,更多相关golang实现微信支付内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号