动态

详情 返回 返回

微信支付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.