為了保證支付接口使用的安全,微信支付平臺在支付API中使用了一些用于接口安全調(diào)用的技術。在調(diào)用時接口需要使用商戶私鑰進行接口調(diào)用的簽名,獲取到微信支付平臺的應答之后也需要對應答進行簽名驗證。微信的應答簽名使用平臺證書來進行簽名驗證,因此在調(diào)用支付接口前還需要實現(xiàn)平臺證書的下載以及管理。另外微信支付在回調(diào)通知和平臺證書下載接口中,對關鍵信息進行了AES-256-GCM加密,因此開發(fā)者還需要了解如何使用APIv3密鑰進行數(shù)據(jù)解密。在調(diào)用具體接口之前需要了解這是邏輯,并實現(xiàn)接口調(diào)用的一些基礎代碼。
11.1基本規(guī)則
商戶接入微信支付,調(diào)用API必須遵循以下規(guī)則:
1)微信支付API v3使用 JSON?作為消息體的數(shù)據(jù)交換格式。請求須設置HTTP頭部:
- Content-Type: application/json
- Accept: application/json
2)請求的唯一標識
微信支付給每個接收到的請求分配了一個唯一標識。請求的唯一標識包含在應答的HTTP頭Request-ID中。
3)錯誤信息
微信支付API v3使用HTTP狀態(tài)碼來表示請求處理的結(jié)果。 - 處理成功的請求,如果有應答的消息體將返回200,若沒有應答的消息體將返回204。
- 已經(jīng)被成功接受待處理的請求,將返回202。
- 請求處理失敗時,如缺少必要的入?yún)?、支付時余額不足,將會返回4xx范圍內(nèi)的錯誤碼。
- 請求處理時發(fā)生了微信支付側(cè)的服務系統(tǒng)錯誤,將返回500/501/503的狀態(tài)碼。這種情況比較少見。
4)User Agent
HTTP協(xié)議要求發(fā)起請求的客戶端在每一次請求中都使用HTTP頭 User-Agent來標識自己。微信支付API v3很可能會拒絕處理無User-Agent 的請求。
11.2請求簽名
微信支付使用APIv3密鑰對請求進行簽名。微信支付會在收到請求后進行簽名的驗證。如果簽名驗證不通過,微信支付將會拒絕處理請求,并返回401 Unauthorized。
開發(fā)人員調(diào)用支付接口時需要按照以下的規(guī)則構造簽名串。簽名串一共有五行,每一行為一個參數(shù),行尾以 \n結(jié)束,包括最后一行。
HTTP請求方法\n
URL\n
請求時間戳\n
請求隨機串\n
請求報文主體\n
然后使用商戶私鑰對待簽名串進行SHA256 with RSA簽名,并對簽名結(jié)果進行Base64編碼得到簽名值。
微信支付要求請求使用HTTP Authorization頭來傳遞簽名。Authorization由認證類型和簽名信息兩個部分組成。具體內(nèi)容為:
- 認證類型,目前為WECHATPAY2-SHA256-RSA2048
- 簽名信息:包括發(fā)起請求的商戶的商戶號mchid,商戶API證書的serial_no,請求隨機串nonce_str,時間戳timestamp,簽名值signature。
Authorization 頭的示例如下:
Authorization:WECHATPAY2-SHA256-RSA2048 mchid=“1900009191”,nonce_str=“593BEC0C930BF1AFEB40B4A08C8FB242”,signature=“uOVRnA4qG…”,timestamp=“1554208460”,serial_no=“1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C”’
下面我們一步步來實現(xiàn)向微信支付服務器發(fā)送一個POST請求,首先來看看如何生成向請求頭中的Authorization信息。
接下來首先給出商戶數(shù)據(jù)結(jié)構的定義,定義商戶對象是需要指定商戶的類型(直連商戶、服務商商戶),以及商戶的參數(shù)(商戶號、商戶關聯(lián)的APPID), 以及為商戶對象加載商戶密鑰以及商戶證書。以下是商戶結(jié)構的定義代碼:
type MchWxapp struct {
//商戶類型 0直連商戶 1服務商商戶
MchType int
//商戶對應的appid
Appid string
//商戶號
Mchid string
//商戶的API v3密鑰
MchAPIKey string
//商戶API私鑰
MchPrivateKey *rsa.PrivateKey
//商戶 API 證書
MchCertificate *x509.Certificate
}
商戶的API私鑰用于生成調(diào)用簽名,接下來給出商戶密鑰的加載代碼:
func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) {
privateKeyBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
block, _ := pem.Decode([]byte(privateKeyStr))
if block == nil {
return nil, fmt.Errorf("decode private key err")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
privateKey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("%s is not rsa private key", privateKeyStr)
}
return privateKey, nil
}
生成請求頭Authorization信息時需要用到商戶證書的SerialNumber,以下是商戶證書的加載代碼:
func LoadCertificateWithPath(path string) (certificate *x509.Certificate, err error) {
certificateBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
block, _ := pem.Decode([]byte(certificateStr))
if block == nil {
return nil, fmt.Errorf("decode certificate err")
}
certificate, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
}
接下來要進行簽名串的構造以及對簽名串進行簽名,具體代碼如下:
func GenerateWxPayReqHeader(ctx *MchParam, method string, rawUrl string, signBody string) (authorization string, err error){
timestamp := time.Now().Unix()
url, err := url.Parse(rawUrl)
if err != nil {
return "", err
}
nonce, err := GenerateNonce()
if err != nil {
return "", err
}
SignatureMessageFormat := "%s\n%s\n%d\n%s\n%s\n"
message := fmt.Sprintf(SignatureMessageFormat, method, url.RequestURI(), timestamp, nonce, signBody)
signatureResult, err := SignSHA256WithRSA(ctx.MchPrivateKey, message)
if err != nil {
return "", err
}
certSerialNo := fmt.Sprintf("%X", ctx.MchCertificate.SerialNumber)
HeaderAuthorizationFormat := "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""
authorization = fmt.Sprintf(HeaderAuthorizationFormat, ctx.Mchid, nonce, timestamp, certSerialNo, signatureResult)
return authorization, nil
}
代碼中使用GenerateNonce()生成一個32個字節(jié)的請求隨機串,并調(diào)用SignSHA256WithRSA對待簽名串進行SHA256 with RSA簽名。下面是函數(shù)SignSHA256WithRSA的實現(xiàn):文章來源:http://www.zghlxwxcb.cn/news/detail-500159.html
func SignSHA256WithRSA(privateKey *rsa.PrivateKey, source string)
(signature string, err error) {
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
}
最后來我們通過代碼來看看如何通過HTTP的POST方法來調(diào)用支付接口。以下代碼中去掉了響應數(shù)據(jù)簽名驗證的邏輯,響應數(shù)據(jù)簽名驗證稍后再來分析:文章來源地址http://www.zghlxwxcb.cn/news/detail-500159.html
func WxPayPostV3(ctx *MchParam, url string, data []byte) (string, error) {
token, err := GenerateWxPayReqHeader(ctx, http.MethodPost, url, string(data))
if err != nil {
log.Println(err)
return "", err
}
request, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
if err != nil {
return "", err
}
request.Header.Add("Authorization", token)
request.Header.Add("User-Agent", "go pay sdk")
request.Header.Add("Content-type", "application/json;charset='utf-8'")
request.Header.Add("Accept", "application/json")
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Do(request)
if err != nil {
log.Println(err)
return "", err
}
defer resp.Body.Close()
result, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 && resp.StatusCode != 204 {
err := fmt.Errorf("status:%d;msg=%s", resp.StatusCode, string(result))
log.Println(err)
return string(result), err
}
return string(result), nil
}
到了這里,關于微信小程序開發(fā)實戰(zhàn)10_2 小程序支付請求簽名的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!