package yunda import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" "strings" "time" ) type Config struct { BaseURL string AppKey string AppSecret string Timeout time.Duration } type Client struct { baseURL string appKey string appSecret string httpClient *http.Client } type Response struct { Status json.RawMessage `json:"status"` Message string `json:"message"` Result json.RawMessage `json:"result"` } func NewClient(cfg Config) *Client { timeout := cfg.Timeout if timeout <= 0 { timeout = 10 * time.Second } return &Client{baseURL: strings.TrimRight(strings.TrimSpace(cfg.BaseURL), "/"), appKey: strings.TrimSpace(cfg.AppKey), appSecret: cfg.AppSecret, httpClient: &http.Client{Timeout: timeout}} } func (c *Client) Post(ctx context.Context, path string, params map[string]string, out any) (Response, error) { if c.baseURL == "" { return Response{}, errors.New("yunda base URL is required") } if c.appKey == "" || c.appSecret == "" { return Response{}, errors.New("yunda appKey and appSecret are required") } signed := make(map[string]string, len(params)+3) for key, value := range params { if strings.TrimSpace(value) != "" { signed[key] = value } } signed["appKey"] = c.appKey signed["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10) signed["sign"] = BuildSign(c.appKey, c.appSecret, signed) form := url.Values{} for key, value := range signed { form.Set(key, value) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/"+strings.TrimLeft(path, "/"), strings.NewReader(form.Encode())) if err != nil { return Response{}, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := c.httpClient.Do(req) if err != nil { return Response{}, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return Response{}, err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return Response{}, fmt.Errorf("yunda HTTP status %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) } var response Response if err = json.Unmarshal(body, &response); err != nil { return Response{}, fmt.Errorf("decode yunda response: %w", err) } if status := response.StatusString(); status != "0" && status != "" { return response, fmt.Errorf("yunda business status %s: %s", status, response.Message) } if out != nil && len(response.Result) > 0 && string(response.Result) != "null" { if err = json.Unmarshal(response.Result, out); err != nil { return response, fmt.Errorf("decode yunda result: %w", err) } } return response, nil } func (r Response) StatusString() string { var value string if json.Unmarshal(r.Status, &value) == nil { return value } var number json.Number if json.Unmarshal(r.Status, &number) == nil { return number.String() } return "" }