以 商户单号查询转账单 为例演示
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)
}

tim_xiao
144 声望2 粉丝

后端程序员