跳转到内容

Go 示例

此示例仅使用 Go 标准库,包含签名、POST JSONGET Query,以及代收下单、代付下单、商户信息查询三个接口。

go
package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Client struct {
	BaseURL    string
	MchID      int
	APIToken   string
	HTTPClient *http.Client
}

func (c *Client) CreateCollectOrder() (map[string]any, error) {
	return c.Post("/api/v1/mch/pmt-orders", map[string]any{
		"trans_id":     "ORDER-" + strconv.FormatInt(time.Now().Unix(), 10),
		"currency":     "VND",
		"amount":       "100.00",
		"channel":      "bank",
		"callback_url": "https://merchant.example.com/callback/collect",
		"return_url":   "https://merchant.example.com/payment/result",
		"remarks":      "collect demo",
	})
}

func (c *Client) CreatePayoutOrder() (map[string]any, error) {
	return c.Post("/api/v1/mch/wdl-orders", map[string]any{
		"trans_id":         "WDL-" + strconv.FormatInt(time.Now().Unix(), 10),
		"channel":          "bank",
		"amount":           "100.00",
		"currency":         "VND",
		"account_no":       "2333667799212341",
		"account_name":     "NGUYEN XUAN HUNG",
		"account_org":      "PVCOMBANK",
		"account_org_code": "970412",
		"callback_url":     "https://merchant.example.com/callback/payout",
		"remarks":          "payout demo",
	})
}

func (c *Client) GetMerchantInfo() (map[string]any, error) {
	return c.Get("/api/v1/mch/info", map[string]any{})
}

func (c *Client) Post(path string, params map[string]any) (map[string]any, error) {
	params = c.WithSignature(params)
	body, err := json.Marshal(params)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(http.MethodPost, c.BaseURL+path, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")

	return c.Do(req)
}

func (c *Client) Get(path string, params map[string]any) (map[string]any, error) {
	params = c.WithSignature(params)
	query := url.Values{}
	for key, value := range params {
		query.Set(key, fmt.Sprint(value))
	}

	req, err := http.NewRequest(http.MethodGet, c.BaseURL+path+"?"+query.Encode(), nil)
	if err != nil {
		return nil, err
	}

	return c.Do(req)
}

func (c *Client) WithSignature(params map[string]any) map[string]any {
	signed := make(map[string]any, len(params)+4)
	for key, value := range params {
		signed[key] = value
	}

	signed["mch_id"] = c.MchID
	signed["nonce"] = RandString(12)
	signed["timestamp"] = time.Now().Unix()
	signed["sign"] = c.Sign(signed)

	return signed
}

func (c *Client) Sign(params map[string]any) string {
	keys := make([]string, 0, len(params))
	for key, value := range params {
		if key == "sign" || value == nil || fmt.Sprint(value) == "" {
			continue
		}
		keys = append(keys, key)
	}
	sort.Strings(keys)

	var builder strings.Builder
	builder.WriteString(c.APIToken)
	for _, key := range keys {
		builder.WriteString("&")
		builder.WriteString(key)
		builder.WriteString("=")
		builder.WriteString(fmt.Sprint(params[key]))
	}

	hash := md5.Sum([]byte(builder.String()))
	return hex.EncodeToString(hash[:])
}

func (c *Client) Do(req *http.Request) (map[string]any, error) {
	httpClient := c.HTTPClient
	if httpClient == nil {
		httpClient = &http.Client{Timeout: 10 * time.Second}
	}

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("http request failed: %s", resp.Status)
	}

	var result map[string]any
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, err
	}
	if code, ok := result["code"].(float64); !ok || int(code) != 200 {
		return nil, fmt.Errorf("api request failed: %v", result)
	}

	payload, _ := result["payload"].(map[string]any)
	return payload, nil
}

func RandString(n int) string {
	const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	source := rand.New(rand.NewSource(time.Now().UnixNano()))
	result := make([]byte, n)
	for i := range result {
		result[i] = letters[source.Intn(len(letters))]
	}
	return string(result)
}

func main() {
	client := &Client{
		BaseURL:  "https://api.example.com",
		MchID:    10001,
		APIToken: "demo_key_123456",
	}

	collectOrder, _ := client.CreateCollectOrder()
	payoutOrder, _ := client.CreatePayoutOrder()
	merchantInfo, _ := client.GetMerchantInfo()

	fmt.Println(collectOrder, payoutOrder, merchantInfo)
}

Released under the MIT License.