SoFunction
Updated on 2025-03-03

How to implement WeChat payment v3 version

1. Preparation phase

Get the private key

Official document /faq/161222N...

Get the serial number of the private key certificate /wik...

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

There are three files after obtaining the private key

apiclient_key.p12 
apiclient_cert.pem 
apiclient_key.pem

In this sample program, the file apiclient_key.pem content is used

Obtain public key (platform certificate)

Official Documentation

Update certificate /wik...

The platform certificate will generate a new certificate 10 days in advance. WeChat officially recommends deploying a new certificate 5-10 days before the old certificate expires.

Get certificate API documentation /wik...

Identity authentication information generation document /wik...

constant

const appId = ""   // Appid of mini program or official accountconst mchId = ""   // Merchant ID of WeChat Payconst privateSerialNo = "" // Private key certificate numberconst aesKey = ""   // WeChat Paymentaes key

Generate digital signatures

// Digitally sign the hash value of the messagefunc signPKCS1v15(msg, privateKey []byte, hashType ) ([]byte, error) {
 block, _ := (privateKey)
 if block == nil {
  return nil, ("private key decode error")
 }
 pri, err := x509.ParsePKCS8PrivateKey()
 if err != nil {
  return nil, ("parse private key error")
 }
 key, ok := pri.(*)
 if ok == false {
  return nil, ("private key format error")
 }
 sign, err := rsa.SignPKCS1v15(, key, hashType, msg)
 if err != nil {
  return nil, ("sign error")
 }
 return sign, nil
}
// Base encodingfunc base64EncodeStr(src []byte) string {
 return (src)
}

Generate identity authentication information

func authorization(method string, paramMap map[string]interface{}, rawUrl string) (token string, err error) {
 var body string
 if len(paramMap) != 0 {
  paramJsonBytes, err := (paramMap)
  if err != nil {
   return token, err
  }
  body = string(paramJsonBytes)
 }
 urlPart, err := (rawUrl)
 if err != nil {
  return token, err
 }
 canonicalUrl := ()
 timestamp := ().Unix()
 nonce := getRandomString(32)
 message := ("%s\n%s\n%d\n%s\n%s\n", method, canonicalUrl, timestamp, nonce, body)
 open, err := ("/Users/apple/data/www/go/work/src/study/testwechantpay/")
 if err != nil {
  return token, err
 }
 defer ()
 privateKey, err := (open)
 if err != nil {
  return token, err
 }
 signBytes, err := signPKCS1v15(hasha256(message), privateKey, crypto.SHA256)
 if err != nil {
  return token, err
 }
 sign := base64EncodeStr(signBytes)
 token = ("mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"",
  mchId, nonce, timestamp, privateSerialNo, sign)
 return token, nil
}

Decryption of message

func decryptGCM(aesKey, nonceV, ciphertextV, additionalDataV string) ([]byte, error) {
 key := []byte(aesKey)
 nonce := []byte(nonceV)
 additionalData := []byte(additionalDataV)
 ciphertext, err := (ciphertextV)
 if err != nil {
  return nil, err
 }
 block, err := (key)
 if err != nil {
  return nil, err
 }
 aesGCM, err := (block)
 if err != nil {
  return nil, err
 }
 plaintext, err := (nil, nonce, ciphertext, additionalData)
 if err != nil {
  return nil, err
 }
 return plaintext, err
}

Obtain the platform certificate

// Get the public keyconst publicKeyUrl = "/v3/certificates"
type TokenResponse struct {
 Data []TokenResponseData `json:"data"`
}
type TokenResponseData struct {
 EffectiveTime  string    `json:"effective_time"`
 EncryptCertificate EncryptCertificate `json:"encrypt_certificate"`
 ExpireTime   string    `json:"expire_time"`
 SerialNo   string    `json:"serial_no"`
}
type EncryptCertificate struct {
 Algorithm  string `json:"algorithm"`
 AssociatedData string `json:"associated_data"`
 Ciphertext  string `json:"ciphertext"`
 Nonce   string `json:"nonce"`
}
var publicSyncMap 
// Get the public keyfunc getPublicKey() (key string, err error) {
 var prepareTime int64 = 24 * 3600 * 3 // The certificate expires three days in advance and obtains the new certificate nowTime := ().Unix()
 // Read public key cache data cacheValueKey := ("app_id:%s:public_key:value", appId)
 cacheExpireTimeKey := ("app_id:%s:public_key:expire_time", appId)
 cacheValue, keyValueOk := (cacheValueKey)
 cacheExpireTime, expireTimeOk := (cacheExpireTimeKey)
 if keyValueOk && expireTimeOk {
  // Format time  local, _ := ("Local")
  location, _ := (time.RFC3339, cacheExpireTime.(string), local)
  // Determine whether it expires, and return it directly if the certificate has not expired.  if ()-prepareTime > nowTime {
   return cacheValue.(string), nil
  }
 }
 token, err := authorization(, nil, publicKeyUrl)
 if err != nil {
  return key, err
 }
 request, err := (, publicKeyUrl, nil)
 if err != nil {
  return key, err
 }
 ("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
 ("User-Agent", "User Agent(/wiki/User_agent)")
 ("Content-type", "application/json;charset='utf-8'")
 ("Accept", "application/json")
 client := 
 response, err := (request)
 if err != nil {
  return key, err
 }
 defer ()
 bodyBytes, err := ()
 if err != nil {
  return key, err
 }
 //(string(bodyBytes))
 var tokenResponse TokenResponse
 if err = (bodyBytes, &tokenResponse); err != nil {
  return key, err
 }
 for _, encryptCertificate := range  {
  // Format time  local, _ := ("Local")
  location, err := (time.RFC3339, , local)
  if err != nil {
   return key, err
  }
  // Determine whether it expires, and return it directly if the certificate has not expired.  if ()-prepareTime > nowTime {
   decryptBytes, err := decryptGCM(aesKey, , ,
    )
   if err != nil {
    return key, err
   }
   key = string(decryptBytes)
   (cacheValueKey, key)
   (cacheExpireTimeKey, )
   return key, nil
  }
 }
 return key, ("get public key error")
}

2. Initiate WeChat Payment

jsapi initiates payment

Call unified single interface

Unify single interface documentation /wik...

// Unify single interfacefunc commonPay() (payResMap map[string]string, err error) {
 payResMap = make(map[string]string)
 amount := 10
 paramMap := make(map[string]interface{})
 paramMap["appid"] = appId
 paramMap["mchid"] = mchId
 paramMap["description"] = ("WeChat Recharge:¥%d", amount)
 paramMap["out_trade_no"] = ("test%s%s", ().Format("20060102150405"), randNumber())
 paramMap["notify_url"] = "/notify"
 paramMap["amount"] = map[string]interface{}{"total": amount * 100, "currency": "CNY"}
 paramMap["payer"] = map[string]string{"openid": "opCO05utXkPQh3Vje13WjEdQpAZ4"}
 token, err := authorization(, paramMap, commonPayUrl)
 if err != nil {
  return payResMap, err
 }
 marshal, _ := (paramMap)
 request, err := (, commonPayUrl, (marshal))
 if err != nil {
  return payResMap, err
 }
 ("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
 ("User-Agent", "User Agent(/wiki/User_agent)")
 ("Content-type", "application/json;charset='utf-8'")
 ("Accept", "application/json")
 client := 
 response, err := (request)
 if err != nil {
  return payResMap, err
 }
 defer func() {
  ()
 }()
 bodyBytes, err := ()
 if err != nil {
  return payResMap, err
 }
 if err = (bodyBytes, &payResMap); err != nil {
  return payResMap, err
 }
 if payResMap["prepay_id"] == "" {
  return payResMap, ("code:" + payResMap["code"] + "err:" + payResMap["message"])
 }
 return payResMap, nil
}

Generate jsapi to initiate payment

JSAPI adjusts payment interface document /wik...

func jsApi(payResMap map[string]string) (payJson string, err error) {
 payMap := make(map[string]string)
 timeStamp := ().Unix()
 nonce := getRandomString(32)
 packageStr := "prepay_prepay_id"]
 payMap["appId"] = appId
 payMap["timeStamp"] = ("%v", timeStamp)
 payMap["nonceStr"] = nonce
 payMap["package"] = packageStr
 // sign message := ("%s\n%s\n%s\n%s\n", appId, ("%v", timeStamp), nonce, packageStr)
 open, err := ("/Users/apple/data/www/go/work/src/study/testwechantpay/")
 if err != nil {
  return payJson, err
 }
 defer ()
 privateKey, err := (open)
 if err != nil {
  return payJson, err
 }
 signBytes, err := signPKCS1v15(hasha256(message), privateKey, crypto.SHA256)
 if err != nil {
  return payJson, err
 }
 sign := base64EncodeStr(signBytes)
 payMap["signType"] = sign
 payMap["paySign"] = "RSA"
 payJsonBytes, err := (payMap)
 if err != nil {
  return payJson, err
 }
 payJson = string(payJsonBytes)
 return payJson, nil
}

Front desk initiates payment js

Need to load WeChat js/open/js/jweixin-1.6.

Calling WeChat js requires setting a payment directory on the WeChat payment platform

Guidance Document /wik...

<script type="text/javascript" src="__STATIC__/frontend/js/"></script>
<script type="text/javascript" src="/open/js/jweixin-1.6."></script>
<script> 
 $(function () {
  $(".am-btn").click(function () {
   var score = $(".score div input:checked").val();
   $.post("Initiate WeChat Pay Backend Interface URL", {"score": score}, function (res) {
    if ( === 500) {
     alert();
     return;
    }
    if (typeof WeixinJSBridge == "undefined") {
     if () {
      ('WeixinJSBridgeReady', onBridgeReady, false);
     } else if () {
      ('WeixinJSBridgeReady', onBridgeReady);
      ('onWeixinJSBridgeReady', onBridgeReady);
     }
    } else {
     onBridgeReady(res);
    }
   })
  })
  function onBridgeReady(param) {
   var orderId = ;
   ('getBrandWCPayRequest', {
     "appId": ,
     "timeStamp": ,
     "nonceStr": ,
     "package": ,
     "signType": ,
     "paySign": 
    },
    function (res) {
     if (res.err_msg === "get_brand_wcpay_request:ok") {
       = "{:url('index/order/successful')}?order_codeview">
//Verify digital signaturefunc VerifyRsaSign(msg []byte, sign []byte, publicStr []byte, hashType ) bool {
 //pem decoding block, _ := (publicStr)
 //x509 decoding publicKeyInterface, err := ()
 if err != nil {
  panic(err)
 }
 publicKey := .(*)
 //Verify digital signature err = rsa.VerifyPKCS1v15(publicKey, hashType, msg, sign) //crypto.SHA1
 return err == nil
}
// Verify the signaturefunc notifyValidate(timeStamp ,nonce,rawPost,signature string) (bool, error) {
 signature = base64DecodeStr(signature)
 message := ("%s\n%s\n%s\n", timeStamp, nonce, rawPost)
 publicKey, err := getPublicKey()
 if err != nil {
  return false, err
 }
 return VerifyRsaSign(hasha256(message), []byte(signature), []byte(publicKey), crypto.SHA256), nil
}

Decryption of message

type NotifyResponse struct {
 CreateTime string   `json:"create_time"`
 Resource NotifyResource `json:"resource"`
}
type NotifyResource struct {
 Ciphertext  string `json:"ciphertext"`
 AssociatedData string `json:"associated_data"`
 Nonce   string `json:"nonce"`
}
func notifyDecrypt(rawPost string) (decrypt string, err error) {
 var notifyResponse NotifyResponse
 if err = ([]byte(rawPost), &notifyResponse); err != nil {
  return decrypt, err
 }
 decryptBytes, err := decryptGCM(aesKey, , ,
  )
 if err != nil {
  return decrypt, err
 }
 decrypt = string(decryptBytes)
 return decrypt, nil
}

4. Query order

Documentation /wik...

Inquiry of orders

const searchTradeUrl = "/v3/pay/transactions/out-trade-no/%s?mchid=%s"
// Inquiry of transactionsfunc searchTrade(orderId string) (trade string, err error) {
 rawUrl := (searchTradeUrl, orderId, mchId)
 token, err := authorization(, nil, rawUrl)
 if err != nil {
  return trade, err
 }
 request, err := (, rawUrl, nil)
 if err != nil {
  return trade, err
 }
 ("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
 ("User-Agent", "User Agent(/wiki/User_agent)")
 ("Content-type", "application/json;charset='utf-8'")
 ("Accept", "application/json")
 client := 
 response, err := (request)
 if err != nil {
  return trade, err
 }
 defer ()
 bodyBytes, err := ()
 if err != nil {
  return trade, err
 }
 return string(bodyBytes), nil
}

5. Apply for a refund

Documentation /wik...

Apply for a refund

const refundUrl = "/v3/refund/domestic/refunds"
func refundTrade(orderId string, amount float64) (trade string, err error) {
 paramMap := make(map[string]interface{})
 paramMap["out_trade_no"] = orderId
 paramMap["out_refund_no"] = orderId + "-1"
 paramMap["amount"] = map[string]interface{}{"refund": amount * 100, "total": amount * 100, "currency": "CNY"}
 token, err := authorization(, paramMap, refundUrl)
 if err != nil {
  return trade, err
 }
 marshal, _ := (paramMap)
 request, err := (, refundUrl, (marshal))
 if err != nil {
  return trade, err
 }
 ("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
 ("User-Agent", "User Agent(/wiki/User_agent)")
 ("Content-type", "application/json;charset='utf-8'")
 ("Accept", "application/json")
 client := 
 response, err := (request)
 if err != nil {
  return trade, err
 }
 defer func() {
  ()
 }()
 bodyBytes, err := ()
 if err != nil {
  return trade, err
 }
 return string(bodyBytes), nil
}

This is the article about golang's method to implement WeChat payment v3 version. For more related content on golang's WeChat payment, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!