package xy_sh import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" ) // Client 供应商接口客户端 type Client struct { httpClient *http.Client sm4Key []byte // SM4加密密钥,16字节 sm3Salt []byte // SM3签名盐值 orderURL string // 下单接口地址 queryURL string // 查询接口地址 rechargeURL string // 微信立减金充值接口地址 } // NewClient 创建新的客户端 // - sm4Key: SM4加密密钥,必须为16字节 // - sm3Salt: SM3签名盐值 // - orderURL: 下单接口地址 // - queryURL: 查询接口地址 // - rechargeURL: 微信立减金充值接口地址 func NewClient(sm4Key []byte, sm3Salt []byte, orderURL, queryURL, rechargeURL string) (*Client, error) { if len(sm4Key) != 16 { return nil, fmt.Errorf("SM4密钥长度必须为16字节") } if len(sm3Salt) == 0 { return nil, fmt.Errorf("SM3盐值不能为空") } return &Client{ httpClient: &http.Client{}, sm4Key: sm4Key, sm3Salt: sm3Salt, orderURL: orderURL, queryURL: queryURL, rechargeURL: rechargeURL, }, nil } // SetHTTPClient 设置自定义HTTP客户端 func (c *Client) SetHTTPClient(httpClient *http.Client) { c.httpClient = httpClient } // doRequest 执行加密请求并解密响应 func (c *Client) doRequest(ctx context.Context, url string, bizReq interface{}) (*RawResponse, error) { // 1. 序列化业务参数为JSON bizBody, err := json.Marshal(bizReq) if err != nil { return nil, fmt.Errorf("序列化业务参数失败: %v", err) } // 2. SM4加密业务参数 encryptedData, err := SM4Encrypt(bizBody, c.sm4Key) if err != nil { return nil, fmt.Errorf("SM4加密失败: %v", err) } // 3. 生成时间戳和签名 timestamp := GenerateTimestampMillis() sign := GenerateSign(timestamp, encryptedData, c.sm3Salt) // 4. 构建加密请求体 reqBody := EncryptedRequest{EncryptedData: encryptedData} reqBytes, err := json.Marshal(reqBody) if err != nil { return nil, fmt.Errorf("序列化请求体失败: %v", err) } // 5. 创建HTTP请求 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBytes)) if err != nil { return nil, fmt.Errorf("创建HTTP请求失败: %v", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("timestamp", timestamp) req.Header.Set("sign", sign) // 6. 发送请求 resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("发送HTTP请求失败: %v", err) } defer resp.Body.Close() // 7. 读取响应体 respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应体失败: %v", err) } // 8. 解析原始响应 var rawResp RawResponse if err := json.Unmarshal(respBytes, &rawResp); err != nil { return nil, fmt.Errorf("解析响应JSON失败: %v", err) } return &rawResp, nil } // decryptData 解密响应中的data字段 func (c *Client) decryptData(encryptedData string, target interface{}) error { if encryptedData == "" { return nil } plaintext, err := SM4Decrypt(encryptedData, c.sm4Key) if err != nil { return fmt.Errorf("SM4解密失败: %v", err) } if err := json.Unmarshal(plaintext, target); err != nil { return fmt.Errorf("解析解密后数据失败: %v", err) } return nil } // Order 卡券/直充权益下单 // 适用于卡密、直充商品下单 func (c *Client) Order(ctx context.Context, req *OrderRequest) (*OrderResponse, error) { rawResp, err := c.doRequest(ctx, c.orderURL, req) if err != nil { return nil, err } resp := &OrderResponse{ Code: rawResp.Code, Msg: rawResp.Msg, } if rawResp.Data != "" { var data OrderData if err := c.decryptData(rawResp.Data, &data); err != nil { return nil, err } resp.Data = &data } return resp, nil } // QueryOrder 卡券/直充/微信立减金订单查询 // 通过供应商订单号查询订单状态 func (c *Client) QueryOrder(ctx context.Context, req *QueryRequest) (*QueryResponse, error) { rawResp, err := c.doRequest(ctx, c.queryURL, req) if err != nil { return nil, err } resp := &QueryResponse{ Code: rawResp.Code, Msg: rawResp.Msg, } if rawResp.Data != "" { var data QueryData if err := c.decryptData(rawResp.Data, &data); err != nil { return nil, err } resp.Data = &data } return resp, nil } // WechatRecharge 微信立减金订单充值 func (c *Client) WechatRecharge(ctx context.Context, req *WechatRechargeRequest) (*WechatRechargeResponse, error) { rawResp, err := c.doRequest(ctx, c.rechargeURL, req) if err != nil { return nil, err } resp := &WechatRechargeResponse{ Code: rawResp.Code, Msg: rawResp.Msg, } if rawResp.Data != "" { var data WechatRechargeData if err := c.decryptData(rawResp.Data, &data); err != nil { return nil, err } resp.Data = &data } return resp, nil } // SendCallback 发送回调通知(供应商调用行方回调接口) // 返回的 CallbackResponse 中 StatusCode 为 HTTP 状态码,Body 为响应体内容 func (c *Client) SendCallback(ctx context.Context, callbackURL string, req *CallbackRequest) (*CallbackResponse, error) { // 1. 序列化业务参数 bizBody, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("序列化回调参数失败: %v", err) } // 2. SM4加密 encryptedData, err := SM4Encrypt(bizBody, c.sm4Key) if err != nil { return nil, fmt.Errorf("SM4加密失败: %v", err) } // 3. 生成时间戳和签名 timestamp := GenerateTimestampMillis() sign := GenerateSign(timestamp, encryptedData, c.sm3Salt) // 4. 构建请求体 reqBody := EncryptedRequest{EncryptedData: encryptedData} reqBytes, err := json.Marshal(reqBody) if err != nil { return nil, fmt.Errorf("序列化请求体失败: %v", err) } // 5. 创建HTTP请求 httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, callbackURL, bytes.NewReader(reqBytes)) if err != nil { return nil, fmt.Errorf("创建HTTP请求失败: %v", err) } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("timestamp", timestamp) httpReq.Header.Set("sign", sign) // 6. 发送请求 resp, err := c.httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("发送回调请求失败: %v", err) } defer resp.Body.Close() // 7. 读取响应体 respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取回调响应失败: %v", err) } return &CallbackResponse{ StatusCode: resp.StatusCode, Body: string(respBytes), }, nil }