305 lines
8.0 KiB
Go
305 lines
8.0 KiB
Go
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")
|
|
fs.StringVar(&req.ExpireTime, "expire-time", "", "order lifetime in minutes or an absolute expiration time (default 10 minutes)")
|
|
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
|
|
`)
|
|
}
|