// File: go.mod ```go module xy-shanghai go 1.21 require github.com/tjfoc/gmsm v1.4.1 ``` // File: errors.go ```go package xyshanghai import "fmt" // ErrorCode 定义错误码类型 type ErrorCode int const ( // ErrCodeSuccess 成功 ErrCodeSuccess ErrorCode = 0 // ErrCodeFailure 失败 ErrCodeFailure ErrorCode = -1 ) // SDKError 自定义SDK错误 type SDKError struct { Code ErrorCode Message string Err error } func (e *SDKError) Error() string { if e.Err != nil { return fmt.Sprintf("code=%d, message=%s, err=%v", e.Code, e.Message, e.Err) } return fmt.Sprintf("code=%d, message=%s", e.Code, e.Message) } // Unwrap 返回内部错误 func (e *SDKError) Unwrap() error { return e.Err } // NewSDKError 创建SDK错误 func NewSDKError(code ErrorCode, message string, err error) *SDKError { return &SDKError{ Code: code, Message: message, Err: err, } } ``` // File: types.go ```go package xyshanghai // CommonRequestHeader 通用请求头 type CommonRequestHeader struct { Timestamp string `json:"timestamp"` Sign string `json:"sign"` } // CommonRequestBody 通用请求体 type CommonRequestBody struct { EncryptedData string `json:"encryptedData"` } // CommonResponse 公共响应 type CommonResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data *string `json:"data,omitempty"` // SM4加密后的JSON字符串 } // CreateOrderRequest 卡券/直充权益下单业务参数(加密前) type CreateOrderRequest struct { ActCode string `json:"actCode"` GoodsCode string `json:"goodsCode"` ActOrderNum string `json:"actOrderNum"` Account string `json:"account,omitempty"` CallbackURL string `json:"callbackUrl,omitempty"` } // CreateOrderResponse 下单响应业务数据(解密后) type CreateOrderResponse struct { OrderNo string `json:"orderNo"` CouponNo string `json:"couponNo,omitempty"` CouponCode string `json:"couponCode,omitempty"` Status int `json:"status"` ExpireTime string `json:"expireTime,omitempty"` } // QueryOrderRequest 订单查询业务参数(加密前) type QueryOrderRequest struct { ActCode string `json:"actCode"` OrderNo string `json:"orderNo"` } // QueryOrderResponse 订单查询响应业务数据(解密后) type QueryOrderResponse struct { OrderNo string `json:"orderNo"` Status int `json:"status"` Account string `json:"account,omitempty"` CardInfo *CardInfo `json:"cardInfo,omitempty"` CouponID string `json:"couponId,omitempty"` } // CardInfo 卡券信息 type CardInfo struct { CouponNo string `json:"couponNo"` CouponCode string `json:"couponCode"` ExpireTime string `json:"expireTime"` } // RechargeOrderRequest 微信立减金充值业务参数(加密前) type RechargeOrderRequest struct { ActCode string `json:"actCode"` OrderNo string `json:"orderNo"` GoodsCode string `json:"goodsCode"` ActOrderNum string `json:"actOrderNum"` AppID string `json:"appId"` OpenID string `json:"openId"` CallbackURL string `json:"callbackUrl,omitempty"` } // RechargeOrderResponse 充值响应业务数据(解密后) type RechargeOrderResponse struct { OrderNo string `json:"orderNo"` Status int `json:"status"` CouponID string `json:"couponId"` } // CallbackRequest 回调通知业务参数(加密前) type CallbackRequest struct { OrderNo string `json:"orderNo"` ActOrderNum string `json:"actOrderNum"` Status int `json:"status"` Account string `json:"account,omitempty"` CardInfo *CardInfo `json:"cardInfo,omitempty"` CouponID string `json:"couponId,omitempty"` } ``` // File: crypto.go ```go package xyshanghai import ( "crypto/cipher" "encoding/base64" "encoding/hex" "fmt" "github.com/tjfoc/gmsm/sm3" "github.com/tjfoc/gmsm/sm4" ) // SM4Encrypt 使用SM4 ECB模式加密数据,返回base64编码字符串 func SM4Encrypt(key, plaintext []byte) (string, error) { block, err := sm4.NewCipher(key) if err != nil { return "", fmt.Errorf("sm4 new cipher: %w", err) } // PKCS7填充 plaintext = pkcs7Padding(plaintext, block.BlockSize()) // ECB模式 mode := newECBEncrypter(block) ciphertext := make([]byte, len(plaintext)) mode.CryptBlocks(ciphertext, plaintext) return base64.StdEncoding.EncodeToString(ciphertext), nil } // SM4Decrypt 解密base64编码的SM4 ECB密文,返回明文 func SM4Decrypt(key []byte, ciphertextBase64 string) ([]byte, error) { ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64) if err != nil { return nil, fmt.Errorf("base64 decode: %w", err) } block, err := sm4.NewCipher(key) if err != nil { return nil, fmt.Errorf("sm4 new cipher: %w", err) } mode := newECBDecrypter(block) plaintext := make([]byte, len(ciphertext)) mode.CryptBlocks(plaintext, ciphertext) // 去除PKCS7填充 plaintext, err = pkcs7Unpadding(plaintext, block.BlockSize()) if err != nil { return nil, fmt.Errorf("pkcs7 unpadding: %w", err) } return plaintext, nil } // SM3Sign 使用SM3加盐签名,返回十六进制字符串 func SM3Sign(salt []byte, data string) string { h := sm3.Sm3WithSalt(salt) h.Write([]byte(data)) return hex.EncodeToString(h.Sum(nil)) } // pkcs7Padding PKCS7填充 func pkcs7Padding(src []byte, blockSize int) []byte { padding := blockSize - len(src)%blockSize padtext := make([]byte, padding) for i := range padtext { padtext[i] = byte(padding) } return append(src, padtext...) } // pkcs7Unpadding 去除PKCS7填充 func pkcs7Unpadding(src []byte, blockSize int) ([]byte, error) { length := len(src) if length == 0 { return nil, fmt.Errorf("empty data") } unpadding := int(src[length-1]) if unpadding > blockSize || unpadding == 0 { return nil, fmt.Errorf("invalid padding") } for i := length - unpadding; i < length; i++ { if src[i] != byte(unpadding) { return nil, fmt.Errorf("invalid padding") } } return src[:length-unpadding], nil } // ecb 实现ECB模式(标准库未提供) type ecb struct { b cipher.Block blockSize int } func newECB(b cipher.Block) *ecb { return &ecb{ b: b, blockSize: b.BlockSize(), } } type ecbEncrypter ecb func newECBEncrypter(b cipher.Block) cipher.BlockMode { return (*ecbEncrypter)(newECB(b)) } func (x *ecbEncrypter) BlockSize() int { return x.blockSize } func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { if len(src)%x.blockSize != 0 { panic("crypto/cipher: input not full blocks") } if len(dst) < len(src) { panic("crypto/cipher: output smaller than input") } for i := 0; i < len(src); i += x.blockSize { x.b.Encrypt(dst[i:i+x.blockSize], src[i:i+x.blockSize]) } } type ecbDecrypter ecb func newECBDecrypter(b cipher.Block) cipher.BlockMode { return (*ecbDecrypter)(newECB(b)) } func (x *ecbDecrypter) BlockSize() int { return x.blockSize } func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { if len(src)%x.blockSize != 0 { panic("crypto/cipher: input not full blocks") } if len(dst) < len(src) { panic("crypto/cipher: output smaller than input") } for i := 0; i < len(src); i += x.blockSize { x.b.Decrypt(dst[i:i+x.blockSize], src[i:i+x.blockSize]) } } ``` // File: api_key.go ```go package xyshanghai // APIKey 存储SM4密钥和SM3盐值 type APIKey struct { SM4Key []byte SM3Salt []byte } // NewAPIKey 创建APIKey实例 func NewAPIKey(sm4Key, sm3Salt []byte) *APIKey { return &APIKey{ SM4Key: sm4Key, SM3Salt: sm3Salt, } } ``` // File: client.go ```go 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")) }) } ``` // File: example_test.go ```go package xyshanghai import ( "context" "encoding/hex" "encoding/json" "testing" "time" ) // 测试用的密钥和盐值(实际使用时需替换为双方约定的值) var ( testSM4Key, _ = hex.DecodeString("0123456789abcdef0123456789abcdef") // 16字节 testSM3Salt = []byte("test-salt") ) func TestCreateOrder(t *testing.T) { apiKey := NewAPIKey(testSM4Key, testSM3Salt) client := NewClient(apiKey, WithBaseURL("https://api.example.com"), WithTimeout(10*time.Second)) req := &CreateOrderRequest{ ActCode: "ACT001", GoodsCode: "123456", ActOrderNum: "00001", Account: "19912345678", CallbackURL: "https://xxx/notice", } resp, err := client.CreateOrder(context.Background(), req) if err != nil { t.Logf("CreateOrder error: %v", err) // 实际测试中可能因为网络原因失败,这里仅验证逻辑 return } t.Logf("CreateOrder response: %+v", resp) } func TestQueryOrder(t *testing.T) { apiKey := NewAPIKey(testSM4Key, testSM3Salt) client := NewClient(apiKey, WithBaseURL("https://api.example.com")) req := &QueryOrderRequest{ ActCode: "FBG6vdYqGE4mGX7EH/woEg==", OrderNo: "HM1757046717684000102707339840b23222", } resp, err := client.QueryOrder(context.Background(), req) if err != nil { t.Logf("QueryOrder error: %v", err) return } t.Logf("QueryOrder response: %+v", resp) } func TestRechargeOrder(t *testing.T) { apiKey := NewAPIKey(testSM4Key, testSM3Salt) client := NewClient(apiKey, WithBaseURL("https://api.example.com")) req := &RechargeOrderRequest{ ActCode: "FBG6vdYqGE4mGX7EH/woEg==", GoodsCode: "0001", ActOrderNum: "0001", OpenID: "0001", AppID: "00001", } resp, err := client.RechargeOrder(context.Background(), req) if err != nil { t.Logf("RechargeOrder error: %v", err) return } t.Logf("RechargeOrder response: %+v", resp) } func TestParseCallbackRequest(t *testing.T) { // 模拟回调请求的构造和解析 apiKey := NewAPIKey(testSM4Key, testSM3Salt) client := NewClient(apiKey) // 构造回调业务数据 callbackReq := &CallbackRequest{ OrderNo: "HM202509291010001", ActOrderNum: "XY2025092910100001", Status: 3, Account: "19912345678", } // 加密 plaintext, _ := json.Marshal(callbackReq) encryptedData, _ := SM4Encrypt(apiKey.SM4Key, plaintext) timestamp := "1700000000000" sign := SM3Sign(apiKey.SM3Salt, timestamp+encryptedData) // 构造HTTP请求(简化,实际测试需使用httptest) // 这里仅验证加密解密和签名逻辑 t.Logf("Encrypted data: %s", encryptedData) t.Logf("Sign: %s", sign) // 验证解密 decrypted, err := SM4Decrypt(apiKey.SM4Key, encryptedData) if err != nil { t.Fatalf("decrypt error: %v", err) } t.Logf("Decrypted: %s", string(decrypted)) } ```