xy-shanghai-20260721-181118/client.go

247 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"))
})
}