添加文件: xy-shanghai/generate.md
This commit is contained in:
parent
9d908e79f8
commit
086c36e321
|
|
@ -0,0 +1,668 @@
|
|||
// File: xy-shanghai/go.mod
|
||||
```go
|
||||
module xy-shanghai
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/tjfoc/gmsm v1.4.1
|
||||
```
|
||||
|
||||
// File: xy-shanghai/errors.go
|
||||
```go
|
||||
package xy-shanghai
|
||||
|
||||
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: xy-shanghai/types.go
|
||||
```go
|
||||
package xy-shanghai
|
||||
|
||||
// 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: xy-shanghai/crypto.go
|
||||
```go
|
||||
package xy-shanghai
|
||||
|
||||
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: xy-shanghai/api_key.go
|
||||
```go
|
||||
package xy-shanghai
|
||||
|
||||
// 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: xy-shanghai/client.go
|
||||
```go
|
||||
package xy-shanghai
|
||||
|
||||
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: xy-shanghai/example_test.go
|
||||
```go
|
||||
package xy-shanghai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"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))
|
||||
}
|
||||
```
|
||||
Loading…
Reference in New Issue