Stories

Detail Return Return

微信支付API V3 簽名認證go版本 - Stories Detail

以 商户單號查詢轉賬單 為例演示
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 Avatar yuzhoustayhungry Avatar yejianfeixue Avatar gouguoyin Avatar tyltr Avatar lixingning Avatar wilburxu Avatar headofhouchang Avatar hanhoudeniupai Avatar yaochujiadebiandou Avatar gangyidesongshu Avatar apocelipes Avatar
Favorites 13 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.