添加文件: client.go
This commit is contained in:
parent
18462ef94a
commit
54d4cba9e0
|
|
@ -0,0 +1,247 @@
|
||||||
|
package xyshanghai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client 供应商API客户端
|
||||||
|
type Client struct {
|
||||||
|
baseURL string
|
||||||
|
httpClient *http.Client
|
||||||
|
apiKey *APIKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option 客户端配置选项
|
||||||
|
type Option func(*Client)
|
||||||
|
|
||||||
|
// WithBaseURL 设置基础URL
|
||||||
|
func WithBaseURL(baseURL string) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.baseURL = baseURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout 设置HTTP超时时间
|
||||||
|
func WithTimeout(timeout time.Duration) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.httpClient.Timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHTTPClient 设置自定义HTTP客户端
|
||||||
|
func WithHTTPClient(httpClient *http.Client) Option {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.httpClient = httpClient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient 创建新的客户端
|
||||||
|
func NewClient(apiKey *APIKey, opts ...Option) *Client {
|
||||||
|
c := &Client{
|
||||||
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||||
|
apiKey: apiKey,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// doRequest 发送请求并处理响应
|
||||||
|
func (c *Client) doRequest(ctx context.Context, path string, reqBody interface{}) (*CommonResponse, error) {
|
||||||
|
// 序列化业务数据
|
||||||
|
plaintext, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal request body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SM4加密
|
||||||
|
encryptedData, err := SM4Encrypt(c.apiKey.SM4Key, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("encrypt data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成时间戳
|
||||||
|
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||||
|
|
||||||
|
// 生成签名
|
||||||
|
sign := SM3Sign(c.apiKey.SM3Salt, timestamp+encryptedData)
|
||||||
|
|
||||||
|
// 构建请求体
|
||||||
|
commonReq := CommonRequestBody{EncryptedData: encryptedData}
|
||||||
|
reqBytes, err := json.Marshal(commonReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal common request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP请求
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(reqBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("timestamp", timestamp)
|
||||||
|
req.Header.Set("sign", sign)
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("do request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析公共响应
|
||||||
|
var commonResp CommonResponse
|
||||||
|
if err := json.Unmarshal(respBody, &commonResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal common response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &commonResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decryptData 解密响应中的data字段
|
||||||
|
func (c *Client) decryptData(encryptedData string, target interface{}) error {
|
||||||
|
plaintext, err := SM4Decrypt(c.apiKey.SM4Key, encryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decrypt data: %w", err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(plaintext, target); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal decrypted data: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrder 卡券/直充权益下单
|
||||||
|
func (c *Client) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*CreateOrderResponse, error) {
|
||||||
|
commonResp, err := c.doRequest(ctx, "/order/create", req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if commonResp.Code != 0 {
|
||||||
|
return nil, NewSDKError(ErrorCode(commonResp.Code), commonResp.Msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if commonResp.Data == nil {
|
||||||
|
return nil, NewSDKError(ErrCodeFailure, "response data is nil", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp CreateOrderResponse
|
||||||
|
if err := c.decryptData(*commonResp.Data, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryOrder 卡券/直充/微信立减金订单查询
|
||||||
|
func (c *Client) QueryOrder(ctx context.Context, req *QueryOrderRequest) (*QueryOrderResponse, error) {
|
||||||
|
commonResp, err := c.doRequest(ctx, "/order/query", req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if commonResp.Code != 0 {
|
||||||
|
return nil, NewSDKError(ErrorCode(commonResp.Code), commonResp.Msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if commonResp.Data == nil {
|
||||||
|
return nil, NewSDKError(ErrCodeFailure, "response data is nil", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp QueryOrderResponse
|
||||||
|
if err := c.decryptData(*commonResp.Data, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RechargeOrder 微信立减金订单充值
|
||||||
|
func (c *Client) RechargeOrder(ctx context.Context, req *RechargeOrderRequest) (*RechargeOrderResponse, error) {
|
||||||
|
commonResp, err := c.doRequest(ctx, "/order/recharge", req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if commonResp.Code != 0 {
|
||||||
|
return nil, NewSDKError(ErrorCode(commonResp.Code), commonResp.Msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if commonResp.Data == nil {
|
||||||
|
return nil, NewSDKError(ErrCodeFailure, "response data is nil", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp RechargeOrderResponse
|
||||||
|
if err := c.decryptData(*commonResp.Data, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseCallbackRequest 解析回调请求,返回业务数据
|
||||||
|
func (c *Client) ParseCallbackRequest(r *http.Request) (*CallbackRequest, error) {
|
||||||
|
// 读取请求体
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read request body: %w", err)
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
// 解析通用请求体
|
||||||
|
var commonReq CommonRequestBody
|
||||||
|
if err := json.Unmarshal(body, &commonReq); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal common request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证签名(可选)
|
||||||
|
timestamp := r.Header.Get("timestamp")
|
||||||
|
sign := r.Header.Get("sign")
|
||||||
|
expectedSign := SM3Sign(c.apiKey.SM3Salt, timestamp+commonReq.EncryptedData)
|
||||||
|
if sign != expectedSign {
|
||||||
|
return nil, NewSDKError(ErrCodeFailure, "sign verification failed", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密业务数据
|
||||||
|
plaintext, err := SM4Decrypt(c.apiKey.SM4Key, commonReq.EncryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decrypt callback data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var callbackReq CallbackRequest
|
||||||
|
if err := json.Unmarshal(plaintext, &callbackReq); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal callback request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &callbackReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallbackHandler 返回一个http.Handler,用于处理回调通知
|
||||||
|
func (c *Client) CallbackHandler() http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
callbackReq, err := c.ParseCallbackRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "invalid request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户需要实现自己的处理逻辑,这里仅返回成功
|
||||||
|
// 实际使用时,用户应通过闭包或回调函数注入业务处理
|
||||||
|
_ = callbackReq
|
||||||
|
|
||||||
|
// 返回成功
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue