Initial 007Pay Go SDK

This commit is contained in:
shiran
2026-07-10 17:18:58 +08:00
commit c9d007497b
10 changed files with 1075 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
/bin/
/dist/
*.exe
*.test
coverage.out
.env
+147
View File
@@ -0,0 +1,147 @@
# 007Pay Go Merchant SDK / CLI
这是 007Pay 商户应用端 Go SDK,包含:
- `pay007` SDK 包:创建订单、查询订单、退款、订单列表、手动入账、收益统计
- `007pay` CLI:可在其他项目或脚本中快速调试接入
- 与平台一致的 `pid + key + MD5` 签名工具
## 安装
作为独立子项目使用:
```bash
cd merchant-sdk-go
go test ./...
go build -o bin/007pay ./cmd/007pay
```
发布到自己的 Git 仓库后,其他项目可直接引入:
```bash
go get gitea.s1f.ren/shiran/007pay-sdk.git
```
## SDK 用法
```go
package main
import (
"context"
"fmt"
"log"
pay007 "gitea.s1f.ren/shiran/007pay-sdk.git"
)
func main() {
client, err := pay007.NewClient(
"http://127.0.0.1:8081",
"your-pid",
"your-secret-key",
)
if err != nil {
log.Fatal(err)
}
order, err := client.Submit(context.Background(), pay007.SubmitRequest{
OutTradeNo: "M202607100001",
Name: "测试订单",
Money: "0.01",
Type: "alipay",
ClientType: "pc",
NotifyURL: "https://merchant.example.com/pay/notify",
ReturnURL: "https://merchant.example.com/pay/return",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(order.CashierURL)
}
```
查询订单会调用平台商户查询接口;平台侧会在待支付时主动通过通道查询订单状态,例如支付宝会走 `alipay.trade.query`,并返回 `completed``channel_checked`
```go
result, err := client.Query(context.Background(), pay007.QueryRequest{
OutTradeNo: "M202607100001",
})
if err != nil {
log.Fatal(err)
}
if result.Completed {
fmt.Println("paid")
}
```
## CLI 用法
CLI 支持环境变量:
```bash
export PAY007_BASE_URL=http://127.0.0.1:8081
export PAY007_PID=your-pid
export PAY007_KEY=your-secret-key
```
创建订单:
```bash
007pay submit \
--out-trade-no M202607100001 \
--name "测试订单" \
--money 0.01 \
--type alipay \
--clienttype pc \
--notify-url https://merchant.example.com/pay/notify \
--return-url https://merchant.example.com/pay/return
```
查询订单:
```bash
007pay query --out-trade-no M202607100001
```
退款:
```bash
007pay refund --out-trade-no M202607100001 --money 0.01 --reason "用户退款"
```
订单列表:
```bash
007pay orders --status 1 --page 1 --page-size 20
```
收益统计:
```bash
007pay stats --begin "2026-07-01 00:00:00" --end "2026-07-31 23:59:59"
```
签名调试:
```bash
007pay sign out_trade_no=M202607100001 name=测试订单 money=0.01 type=alipay
```
## 签名规则
与平台 `utils/sign` 保持一致:
1. 过滤空值、`sign``sign_type`
2. 参数按 key 升序排序
3. 拼接为 `a=1&b=2`
4. 末尾追加应用密钥
5. MD5 后转大写
SDK 中可直接使用:
```go
sign := pay007.BuildSign(params, secret)
ok := pay007.VerifySign(params, secret)
```
+243
View File
@@ -0,0 +1,243 @@
package pay007
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
const defaultUserAgent = "007pay-go-sdk/1.0"
type Client struct {
baseURL string
pid string
secret string
httpClient *http.Client
userAgent string
}
type Option func(*Client)
func WithHTTPClient(httpClient *http.Client) Option {
return func(c *Client) {
if httpClient != nil {
c.httpClient = httpClient
}
}
}
func WithTimeout(timeout time.Duration) Option {
return func(c *Client) {
if timeout > 0 {
c.httpClient.Timeout = timeout
}
}
}
func WithUserAgent(userAgent string) Option {
return func(c *Client) {
if strings.TrimSpace(userAgent) != "" {
c.userAgent = userAgent
}
}
}
func NewClient(baseURL, pid, secret string, opts ...Option) (*Client, error) {
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
if baseURL == "" {
return nil, errors.New("baseURL is required")
}
if _, err := url.ParseRequestURI(baseURL); err != nil {
return nil, fmt.Errorf("invalid baseURL: %w", err)
}
if strings.TrimSpace(pid) == "" {
return nil, errors.New("pid is required")
}
if strings.TrimSpace(secret) == "" {
return nil, errors.New("secret is required")
}
c := &Client{
baseURL: baseURL,
pid: pid,
secret: secret,
httpClient: &http.Client{
Timeout: 15 * time.Second,
},
userAgent: defaultUserAgent,
}
for _, opt := range opts {
opt(c)
}
return c, nil
}
func (c *Client) Sign(params map[string]string) string {
return BuildSign(params, c.secret)
}
func (c *Client) Verify(params map[string]string) bool {
return VerifySign(params, c.secret)
}
func (c *Client) Submit(ctx context.Context, req SubmitRequest) (*SubmitResponse, error) {
var out SubmitResponse
if err := c.doSigned(ctx, http.MethodPost, "/api/pay/submit", req.params(), &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) Query(ctx context.Context, req QueryRequest) (*QueryResponse, error) {
var out QueryResponse
if err := c.doSigned(ctx, http.MethodGet, "/api/pay/query", req.params(), &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) Refund(ctx context.Context, req RefundRequest) (*RefundResponse, error) {
var out RefundResponse
if err := c.doSigned(ctx, http.MethodPost, "/api/pay/refund", req.params(), &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) Orders(ctx context.Context, req OrdersRequest) (*OrdersResponse, error) {
var out OrdersResponse
if err := c.doSigned(ctx, http.MethodGet, "/api/pay/orders", req.params(), &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) Manual(ctx context.Context, req ManualRequest) (*ManualResponse, error) {
var out ManualResponse
if err := c.doSigned(ctx, http.MethodPost, "/api/pay/manual", req.params(), &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) Stats(ctx context.Context, req StatsRequest) (*StatsResponse, error) {
var out StatsResponse
if err := c.doSigned(ctx, http.MethodGet, "/api/pay/stats", req.params(), &out); err != nil {
return nil, err
}
return &out, nil
}
func (c *Client) doSigned(ctx context.Context, method, path string, params map[string]string, out any) error {
signed := compactParams(params)
signed["pid"] = c.pid
signed["sign_type"] = "MD5"
signed["sign"] = BuildSign(signed, c.secret)
values := url.Values{}
for k, v := range signed {
values.Set(k, v)
}
endpoint := c.baseURL + path
var body io.Reader
if method == http.MethodGet {
endpoint += "?" + values.Encode()
} else {
body = bytes.NewBufferString(values.Encode())
}
httpReq, err := http.NewRequestWithContext(ctx, method, endpoint, body)
if err != nil {
return err
}
httpReq.Header.Set("Accept", "application/json")
httpReq.Header.Set("User-Agent", c.userAgent)
if method != http.MethodGet {
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var envelope struct {
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(respBody, &envelope); err != nil {
return &APIError{
HTTPStatus: resp.StatusCode,
Code: 0,
Message: "invalid json response: " + err.Error(),
Body: string(respBody),
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 || envelope.Code != 200 {
return &APIError{
HTTPStatus: resp.StatusCode,
Code: envelope.Code,
Message: envelope.Message,
Body: string(respBody),
}
}
if out == nil || len(envelope.Data) == 0 || string(envelope.Data) == "null" {
return nil
}
if err := json.Unmarshal(envelope.Data, out); err != nil {
return &APIError{
HTTPStatus: resp.StatusCode,
Code: envelope.Code,
Message: "invalid data response: " + err.Error(),
Body: string(respBody),
}
}
return nil
}
type APIError struct {
HTTPStatus int
Code int
Message string
Body string
}
func (e *APIError) Error() string {
if e == nil {
return ""
}
return fmt.Sprintf("007pay api error: http=%d code=%d message=%s", e.HTTPStatus, e.Code, e.Message)
}
func compactParams(params map[string]string) map[string]string {
out := make(map[string]string, len(params))
for k, v := range params {
if strings.TrimSpace(k) == "" || v == "" {
continue
}
out[k] = v
}
return out
}
func itoa(v int) string {
return strconv.Itoa(v)
}
+69
View File
@@ -0,0 +1,69 @@
package pay007
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestSubmitPostsSignedForm(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/pay/submit" {
t.Fatalf("path = %s", r.URL.Path)
}
if r.Method != http.MethodPost {
t.Fatalf("method = %s", r.Method)
}
if err := r.ParseForm(); err != nil {
t.Fatal(err)
}
params := map[string]string{}
for k, v := range r.PostForm {
if len(v) > 0 {
params[k] = v[0]
}
}
if params["pid"] != "pid-1" {
t.Fatalf("pid = %s", params["pid"])
}
if params["sign_type"] != "MD5" {
t.Fatalf("sign_type = %s", params["sign_type"])
}
if !VerifySign(params, "secret") {
t.Fatalf("invalid sign: %s", params["sign"])
}
_ = json.NewEncoder(w).Encode(map[string]any{
"code": 200,
"message": "Success",
"data": map[string]any{
"order_no": "P123",
"out_trade_no": params["out_trade_no"],
"amount": 100,
"cashier_url": "http://pay.test/cashier/?orderNo=P123",
},
})
}))
defer server.Close()
client, err := NewClient(server.URL, "pid-1", "secret")
if err != nil {
t.Fatal(err)
}
resp, err := client.Submit(context.Background(), SubmitRequest{
OutTradeNo: "A100",
Name: "test",
Money: "1.00",
Type: "alipay",
})
if err != nil {
t.Fatal(err)
}
if resp.OrderNo != "P123" || resp.Amount != 100 {
t.Fatalf("unexpected response: %+v", resp)
}
}
+303
View File
@@ -0,0 +1,303 @@
package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"strings"
"time"
pay007 "gitea.s1f.ren/shiran/007pay-sdk.git"
)
type globalConfig struct {
BaseURL string
PID string
Key string
Timeout time.Duration
}
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
func run(args []string) error {
global := flag.NewFlagSet("007pay", flag.ContinueOnError)
global.SetOutput(os.Stderr)
cfg := globalConfig{}
global.StringVar(&cfg.BaseURL, "base-url", envAny("PAY007_BASE_URL", "007PAY_BASE_URL"), "007Pay service base URL")
global.StringVar(&cfg.PID, "pid", envAny("PAY007_PID", "007PAY_PID"), "merchant app pid/token")
global.StringVar(&cfg.Key, "key", envAny("PAY007_KEY", "007PAY_KEY"), "merchant app secret key")
global.DurationVar(&cfg.Timeout, "timeout", 15*time.Second, "HTTP request timeout")
global.Usage = usage
if err := global.Parse(args); errors.Is(err, flag.ErrHelp) {
return nil
} else if err != nil {
return err
}
rest := global.Args()
if len(rest) == 0 {
usage()
return errors.New("missing command")
}
cmd, cmdArgs := rest[0], rest[1:]
switch cmd {
case "submit":
return submit(cfg, cmdArgs)
case "query":
return query(cfg, cmdArgs)
case "refund":
return refund(cfg, cmdArgs)
case "orders":
return orders(cfg, cmdArgs)
case "manual":
return manual(cfg, cmdArgs)
case "stats":
return stats(cfg, cmdArgs)
case "sign":
return signParams(cfg, cmdArgs)
case "verify":
return verifyParams(cfg, cmdArgs)
case "help", "-h", "--help":
usage()
return nil
default:
usage()
return fmt.Errorf("unknown command %q", cmd)
}
}
func newClient(cfg globalConfig) (*pay007.Client, error) {
return pay007.NewClient(cfg.BaseURL, cfg.PID, cfg.Key, pay007.WithTimeout(cfg.Timeout))
}
func submit(cfg globalConfig, args []string) error {
fs := flag.NewFlagSet("submit", flag.ContinueOnError)
req := pay007.SubmitRequest{}
fs.StringVar(&req.OutTradeNo, "out-trade-no", "", "merchant order number")
fs.StringVar(&req.Name, "name", "", "order subject")
fs.StringVar(&req.Money, "money", "", "amount in yuan, for example 9.90")
fs.StringVar(&req.Type, "type", "", "channel code, for example alipay or wechat")
fs.StringVar(&req.ClientType, "clienttype", "", "client type: pc or mobile")
fs.StringVar(&req.NotifyURL, "notify-url", "", "merchant async notify URL")
fs.StringVar(&req.ReturnURL, "return-url", "", "merchant sync return URL")
fs.StringVar(&req.Body, "body", "", "order description")
fs.StringVar(&req.Attach, "attach", "", "extra data")
if err := fs.Parse(args); err != nil {
return err
}
client, err := newClient(cfg)
if err != nil {
return err
}
resp, err := client.Submit(context.Background(), req)
if err != nil {
return err
}
return printJSON(resp)
}
func query(cfg globalConfig, args []string) error {
fs := flag.NewFlagSet("query", flag.ContinueOnError)
req := pay007.QueryRequest{}
fs.StringVar(&req.TradeNo, "trade-no", "", "007Pay order number")
fs.StringVar(&req.OutTradeNo, "out-trade-no", "", "merchant order number")
if err := fs.Parse(args); err != nil {
return err
}
client, err := newClient(cfg)
if err != nil {
return err
}
resp, err := client.Query(context.Background(), req)
if err != nil {
return err
}
return printJSON(resp)
}
func refund(cfg globalConfig, args []string) error {
fs := flag.NewFlagSet("refund", flag.ContinueOnError)
req := pay007.RefundRequest{}
fs.StringVar(&req.TradeNo, "trade-no", "", "007Pay order number")
fs.StringVar(&req.OutTradeNo, "out-trade-no", "", "merchant order number")
fs.StringVar(&req.Money, "money", "", "refund amount in yuan; omit to refund full order")
fs.StringVar(&req.Reason, "reason", "", "refund reason")
if err := fs.Parse(args); err != nil {
return err
}
client, err := newClient(cfg)
if err != nil {
return err
}
resp, err := client.Refund(context.Background(), req)
if err != nil {
return err
}
return printJSON(resp)
}
func orders(cfg globalConfig, args []string) error {
fs := flag.NewFlagSet("orders", flag.ContinueOnError)
req := pay007.OrdersRequest{}
status := fs.Int("status", -1, "order status: 0 pending, 1 paid, 2 closed, 3 refunded")
fs.StringVar(&req.OrderNo, "order-no", "", "007Pay or merchant order number")
fs.IntVar(&req.Page, "page", 1, "page number")
fs.IntVar(&req.PageSize, "page-size", 20, "page size")
if err := fs.Parse(args); err != nil {
return err
}
if *status >= 0 {
req.Status = status
}
client, err := newClient(cfg)
if err != nil {
return err
}
resp, err := client.Orders(context.Background(), req)
if err != nil {
return err
}
return printJSON(resp)
}
func manual(cfg globalConfig, args []string) error {
fs := flag.NewFlagSet("manual", flag.ContinueOnError)
req := pay007.ManualRequest{}
fs.StringVar(&req.OutTradeNo, "out-trade-no", "", "merchant order number")
fs.StringVar(&req.Name, "name", "", "order subject")
fs.StringVar(&req.Money, "money", "", "amount in yuan")
fs.StringVar(&req.Type, "type", "", "channel code")
fs.StringVar(&req.Attach, "attach", "", "extra data")
if err := fs.Parse(args); err != nil {
return err
}
client, err := newClient(cfg)
if err != nil {
return err
}
resp, err := client.Manual(context.Background(), req)
if err != nil {
return err
}
return printJSON(resp)
}
func stats(cfg globalConfig, args []string) error {
fs := flag.NewFlagSet("stats", flag.ContinueOnError)
req := pay007.StatsRequest{}
fs.StringVar(&req.Begin, "begin", "", "begin time: 2006-01-02 15:04:05")
fs.StringVar(&req.End, "end", "", "end time: 2006-01-02 15:04:05")
if err := fs.Parse(args); err != nil {
return err
}
client, err := newClient(cfg)
if err != nil {
return err
}
resp, err := client.Stats(context.Background(), req)
if err != nil {
return err
}
return printJSON(resp)
}
func signParams(cfg globalConfig, args []string) error {
if strings.TrimSpace(cfg.Key) == "" {
return errors.New("--key or PAY007_KEY is required")
}
params, err := parseKV(args)
if err != nil {
return err
}
if cfg.PID != "" {
params["pid"] = cfg.PID
}
params["sign_type"] = "MD5"
params["sign"] = pay007.BuildSign(params, cfg.Key)
return printJSON(params)
}
func verifyParams(cfg globalConfig, args []string) error {
if strings.TrimSpace(cfg.Key) == "" {
return errors.New("--key or PAY007_KEY is required")
}
params, err := parseKV(args)
if err != nil {
return err
}
return printJSON(map[string]any{
"ok": pay007.VerifySign(params, cfg.Key),
"sign": pay007.BuildSign(params, cfg.Key),
})
}
func parseKV(args []string) (map[string]string, error) {
params := map[string]string{}
for _, arg := range args {
k, v, ok := strings.Cut(arg, "=")
if !ok || strings.TrimSpace(k) == "" {
return nil, fmt.Errorf("invalid param %q, expected key=value", arg)
}
params[k] = v
}
return params, nil
}
func printJSON(v any) error {
data, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}
func envAny(keys ...string) string {
for _, key := range keys {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
}
return ""
}
func usage() {
fmt.Fprintf(os.Stderr, `007Pay merchant SDK CLI
Global flags:
--base-url 007Pay service base URL, or PAY007_BASE_URL
--pid merchant app pid/token, or PAY007_PID
--key merchant app secret key, or PAY007_KEY
--timeout HTTP timeout, default 15s
Commands:
submit create a payment order
query query and channel-check an order
refund refund an order
orders list merchant orders
manual create a manual paid order
stats query merchant stats
sign sign key=value params
verify verify key=value params with sign=...
Examples:
007pay --base-url http://127.0.0.1:8081 --pid app-token --key secret submit --out-trade-no T1001 --name test --money 0.01 --type alipay
007pay query --out-trade-no T1001
007pay sign out_trade_no=T1001 name=test money=0.01 type=alipay
`)
}
+37
View File
@@ -0,0 +1,37 @@
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"time"
pay007 "gitea.s1f.ren/shiran/007pay-sdk.git"
)
func main() {
client, err := pay007.NewClient(
os.Getenv("PAY007_BASE_URL"),
os.Getenv("PAY007_PID"),
os.Getenv("PAY007_KEY"),
)
if err != nil {
log.Fatal(err)
}
resp, err := client.Submit(context.Background(), pay007.SubmitRequest{
OutTradeNo: "demo-" + time.Now().Format("20060102150405"),
Name: "SDK demo order",
Money: "0.01",
Type: "alipay",
ClientType: "pc",
})
if err != nil {
log.Fatal(err)
}
data, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(data))
}
+3
View File
@@ -0,0 +1,3 @@
module gitea.s1f.ren/shiran/007pay-sdk.git
go 1.20
+55
View File
@@ -0,0 +1,55 @@
package pay007
import (
"crypto/md5"
"encoding/hex"
"sort"
"strings"
)
var ignoredSignKeys = map[string]struct{}{
"sign": {},
"sign_type": {},
}
// BuildSign creates the 007Pay merchant MD5 signature.
//
// Empty values, sign, and sign_type are ignored. The remaining params are
// sorted by key, joined as "a=1&b=2", appended with the app secret, then MD5
// hashed and upper-cased.
func BuildSign(params map[string]string, secret string) string {
keys := make([]string, 0, len(params))
for k, v := range params {
if v == "" {
continue
}
if _, ok := ignoredSignKeys[k]; ok {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
for i, k := range keys {
if i > 0 {
b.WriteByte('&')
}
b.WriteString(k)
b.WriteByte('=')
b.WriteString(params[k])
}
b.WriteString(secret)
sum := md5.Sum([]byte(b.String()))
return strings.ToUpper(hex.EncodeToString(sum[:]))
}
// VerifySign checks params["sign"] with the 007Pay signing rule.
func VerifySign(params map[string]string, secret string) bool {
sign := params["sign"]
if sign == "" {
return false
}
return strings.EqualFold(BuildSign(params, secret), sign)
}
+37
View File
@@ -0,0 +1,37 @@
package pay007
import "testing"
func TestBuildSign(t *testing.T) {
params := map[string]string{
"pid": "1001",
"out_trade_no": "A100",
"name": "test",
"money": "1.00",
"type": "alipay",
"empty": "",
"sign_type": "MD5",
"sign": "ignored",
}
got := BuildSign(params, "secret")
const want = "59DD6586A5050050EB08A9806C6F501B"
if got != want {
t.Fatalf("BuildSign() = %s, want %s", got, want)
}
}
func TestVerifySign(t *testing.T) {
params := map[string]string{
"pid": "1001",
"out_trade_no": "A100",
"name": "test",
"money": "1.00",
"type": "alipay",
}
params["sign"] = BuildSign(params, "secret")
if !VerifySign(params, "secret") {
t.Fatal("VerifySign() = false, want true")
}
}
+175
View File
@@ -0,0 +1,175 @@
package pay007
import "time"
const (
StatusPending = 0
StatusPaid = 1
StatusClosed = 2
StatusRefund = 3
)
// SubmitRequest creates a platform order and returns a cashier URL.
type SubmitRequest struct {
OutTradeNo string
Name string
Money string
Type string
ClientType string
NotifyURL string
ReturnURL string
Body string
Attach string
}
func (r SubmitRequest) params() map[string]string {
return compactParams(map[string]string{
"out_trade_no": r.OutTradeNo,
"name": r.Name,
"money": r.Money,
"type": r.Type,
"clienttype": r.ClientType,
"notify_url": r.NotifyURL,
"return_url": r.ReturnURL,
"body": r.Body,
"attach": r.Attach,
})
}
type SubmitResponse struct {
OrderNo string `json:"order_no"`
OutTradeNo string `json:"out_trade_no"`
Amount int64 `json:"amount"`
CashierURL string `json:"cashier_url"`
}
type QueryRequest struct {
TradeNo string
OutTradeNo string
}
func (r QueryRequest) params() map[string]string {
return compactParams(map[string]string{
"trade_no": r.TradeNo,
"out_trade_no": r.OutTradeNo,
})
}
type QueryResponse struct {
TradeNo string `json:"trade_no"`
OutTradeNo string `json:"out_trade_no"`
ChannelTradeNo string `json:"channel_trade_no"`
Amount int64 `json:"amount"`
Status int `json:"status"`
Completed bool `json:"completed"`
ChannelChecked bool `json:"channel_checked"`
PaidAt *time.Time `json:"paid_at"`
}
type RefundRequest struct {
TradeNo string
OutTradeNo string
Money string
Reason string
}
func (r RefundRequest) params() map[string]string {
return compactParams(map[string]string{
"trade_no": r.TradeNo,
"out_trade_no": r.OutTradeNo,
"money": r.Money,
"reason": r.Reason,
})
}
type RefundResponse struct {
RefundNo string `json:"refund_no"`
Status int `json:"status"`
}
type OrdersRequest struct {
OrderNo string
Status *int
Page int
PageSize int
}
func (r OrdersRequest) params() map[string]string {
params := compactParams(map[string]string{
"order_no": r.OrderNo,
})
if r.Status != nil {
params["status"] = itoa(*r.Status)
}
if r.Page > 0 {
params["page"] = itoa(r.Page)
}
if r.PageSize > 0 {
params["page_size"] = itoa(r.PageSize)
}
return params
}
type OrdersResponse struct {
Total int64 `json:"total"`
List []Order `json:"list"`
}
type Order struct {
TradeNo string `json:"trade_no"`
OutTradeNo string `json:"out_trade_no"`
Subject string `json:"subject"`
Amount int64 `json:"amount"`
Fee int64 `json:"fee"`
Status int `json:"status"`
ChannelID uint `json:"channel_id"`
ChannelTradeNo string `json:"channel_trade_no"`
ClientType string `json:"client_type"`
IsManual bool `json:"is_manual"`
PaidAt *time.Time `json:"paid_at"`
CreatedAt *time.Time `json:"created_at"`
}
type ManualRequest struct {
OutTradeNo string
Name string
Money string
Type string
Attach string
}
func (r ManualRequest) params() map[string]string {
return compactParams(map[string]string{
"out_trade_no": r.OutTradeNo,
"name": r.Name,
"money": r.Money,
"type": r.Type,
"attach": r.Attach,
})
}
type ManualResponse struct {
TradeNo string `json:"trade_no"`
OutTradeNo string `json:"out_trade_no"`
Amount int64 `json:"amount"`
Status int `json:"status"`
}
type StatsRequest struct {
Begin string
End string
}
func (r StatsRequest) params() map[string]string {
return compactParams(map[string]string{
"begin": r.Begin,
"end": r.End,
})
}
type StatsResponse struct {
OrderCount int64 `json:"order_count"`
TotalAmount int64 `json:"total_amount"`
TotalFee int64 `json:"total_fee"`
NetAmount int64 `json:"net_amount"`
}