動態

詳情 返回 返回

微信支付API V3 簽名認證go版本 - 動態 詳情

以 商户單號查詢轉賬單 為例演示
https://pay.weixin.qq.com/doc/v3/merchant/4012716437

package main

import (
    "crypto"
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "time"
)

const (
    // NonceSymbols 隨機字符串可用字符集
    NonceSymbols           = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    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\""
    MethodGet                 = "GET"
)

// LoadPrivateKeyWithPath 通過私鑰的文件路徑內容加載私鑰
func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) {
    privateKeyBytes, err := ioutil.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read private pem file err:%s", err.Error())
    }
    return LoadPrivateKey(string(privateKeyBytes))
}

// 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
}

type Client struct {
    mchID                      string //商户號
    mchCertificateSerialNumber string
    privateKeyWithPath         string
}

// GetTransferByOutBillNo 商户單號查詢轉賬單
// Api:https://pay.weixin.qq.com/doc/v3/merchant/4012716437
func (a *Client) GetTransferByOutBillNo(no string) (resBody string, err error) {
    if no == "" {
        err = fmt.Errorf("商户單號不能為空")
        return
    }
    url := fmt.Sprintf("https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/%s", no)
    authorization, err := a.Authorization(MethodGet, url, "")
    if err != nil {
        return
    }

    client := http.DefaultClient
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", authorization)
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
    req.Header.Set("Accept", "application/json")

    resp, err := client.Do(req)
    if err != nil {
        fmt.Printf("Error sending request: %v\n", err)
        return
    }
    defer resp.Body.Close() 
    res, err := ioutil.ReadAll(resp.Body)
    resBody = string(res)
    return
}

func (a *Client) Authorization(method, rawURL, signBody string) (str string, err error) {
    nonce, err := a.GenerateNonce()
    if err != nil {
        return
    }
    timestamp := time.Now().Unix()
    parsedURL, _ := url.Parse(rawURL)

    message := fmt.Sprintf(SignatureMessageFormat, method, parsedURL.Path, timestamp, nonce, signBody)
    mchPrivateKey, err := LoadPrivateKeyWithPath(a.privateKeyWithPath)
    if err != nil {
        return
    }
    signatureResult, err := a.Sign(message, mchPrivateKey)
    if err != nil {
        return
    }
    str = fmt.Sprintf(
        HeaderAuthorizationFormat, a.mchID, nonce, timestamp, a.mchCertificateSerialNumber, signatureResult,
    )
    return
}

// Sign 使用商户私鑰對字符串進行簽名
func (a *Client) Sign(source string, privateKey *rsa.PrivateKey) (string, 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
}

// SignSHA256WithRSA SHA256 with RSA簽名
func (a *Client) SignSHA256WithRSA() (string, error) {
    bytes := make([]byte, 32)
    _, err := rand.Read(bytes)
    if err != nil {
        return "", err
    }
    symbolsByteLength := byte(len(NonceSymbols))
    for i, b := range bytes {
        bytes[i] = NonceSymbols[b%symbolsByteLength]
    }
    return string(bytes), nil
}

// GenerateNonce 生成請求隨機字符串
func (a *Client) GenerateNonce() (string, error) {
    bytes := make([]byte, 32)
    _, err := rand.Read(bytes)
    if err != nil {
        return "", err
    }
    symbolsByteLength := byte(len(NonceSymbols))
    for i, b := range bytes {
        bytes[i] = NonceSymbols[b%symbolsByteLength]
    }
    return string(bytes), nil
}

func main() {
    client := &Client{
        mchID:                      "商户號idxxx",
        mchCertificateSerialNumber: "api系列號xxxx",
        privateKeyWithPath:         "API私鑰路徑xxxx",
    }
    // 以商户號查詢訂單為例説明
    // https://pay.weixin.qq.com/doc/v3/merchant/4012716437
    res, err := client.GetTransferByOutBillNo("商家單號xxx")
    fmt.Println(res, err)
}
user avatar runyubingxue 頭像 yuzhoustayhungry 頭像 yejianfeixue 頭像 gouguoyin 頭像 tyltr 頭像 lixingning 頭像 wilburxu 頭像 headofhouchang 頭像 hanhoudeniupai 頭像 yaochujiadebiandou 頭像 gangyidesongshu 頭像 apocelipes 頭像
點贊 13 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.