xy_sh-20260724180223/xy_sh/valid.md

828 lines
24 KiB
Markdown
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.

// File: xy_sh/go.mod
```go
module xy_sh
go 1.21
require github.com/tjfoc/gmsm v1.4.1
```
// File: xy_sh/errors.go
```go
package xy_sh
import "fmt"
// ErrorCode 定义错误码类型
type ErrorCode int
const (
// ErrCodeSuccess 请求成功
ErrCodeSuccess ErrorCode = 0
// ErrCodeFail 请求失败
ErrCodeFail 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("[%d] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
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,
}
}
var (
// ErrInvalidConfig 无效配置
ErrInvalidConfig = NewSDKError(ErrCodeFail, "无效配置", nil)
// ErrEncryptFailed 加密失败
ErrEncryptFailed = NewSDKError(ErrCodeFail, "加密失败", nil)
// ErrDecryptFailed 解密失败
ErrDecryptFailed = NewSDKError(ErrCodeFail, "解密失败", nil)
// ErrSignFailed 签名失败
ErrSignFailed = NewSDKError(ErrCodeFail, "签名失败", nil)
// ErrRequestFailed 请求失败
ErrRequestFailed = NewSDKError(ErrCodeFail, "请求失败", nil)
// ErrResponseParseFailed 响应解析失败
ErrResponseParseFailed = NewSDKError(ErrCodeFail, "响应解析失败", nil)
)
```
// File: xy_sh/types.go
```go
package xy_sh
// ============================================================
// 通用请求/响应结构体
// ============================================================
// BaseRequest 基础请求头参数
type BaseRequest struct {
Timestamp string `json:"-"` // 毫秒级时间戳由SDK自动填充
Sign string `json:"-"` // 签名由SDK自动生成
}
// EncryptedRequest 加密后的请求体
type EncryptedRequest struct {
EncryptedData string `json:"encryptedData"` // SM4加密后的业务数据
}
// EncryptedResponse 加密后的响应体
type EncryptedResponse struct {
Code int `json:"code"` // 返回状态编码0成功 -1失败
Msg string `json:"msg"` // 返回错误信息
Data *string `json:"data"` // JSON格式业务数据进行SM4加密后的字符串可能为null
}
// ============================================================
// 接口1卡券/直充权益下单接口
// ============================================================
// PlaceOrderRequest 下单请求业务参数
type PlaceOrderRequest struct {
ActCode string `json:"actCode"` // 活动code
GoodsCode string `json:"goodsCode"` // 供应商商品编号
ActOrderNum string `json:"actOrderNum"` // 活动方订单号,唯一
Account string `json:"account,omitempty"` // 充值账号
CallbackUrl string `json:"callbackUrl,omitempty"` // 回调地址
}
// PlaceOrderResponseData 下单响应解密后的业务数据
type PlaceOrderResponseData struct {
OrderNo string `json:"orderNo"` // 权益订单号
CouponNo string `json:"couponNo,omitempty"` // 卡号
CouponCode string `json:"couponCode,omitempty"` // 卡密
Status int `json:"status"` // 状态:-1发放中 0成功 1失败 2已核销 3已过期
ExpireTime string `json:"expireTime,omitempty"` // 有效期
}
// ============================================================
// 接口2卡券/直充/微信立减金订单查询接口
// ============================================================
// QueryOrderRequest 查询订单请求业务参数
type QueryOrderRequest struct {
ActCode string `json:"actCode"` // 活动code
OrderNo string `json:"orderNo"` // 供应商订单号
}
// CardInfo 卡券信息
type CardInfo struct {
CouponNo string `json:"couponNo"` // 卡号/短链
CouponCode string `json:"couponCode"` // 卡密
ExpireTime string `json:"expireTime"` // 过期时间
}
// QueryOrderResponseData 查询订单响应解密后的业务数据
type QueryOrderResponseData struct {
OrderNo string `json:"orderNo"` // 权益订单号
Status int `json:"status"` // 订单状态
Account string `json:"account,omitempty"` // 充值账号
CardInfo *CardInfo `json:"cardInfo,omitempty"` // 卡券信息
CouponId string `json:"couponId,omitempty"` // 优惠id
}
// ============================================================
// 接口3微信立减金订单充值接口
// ============================================================
// WechatRechargeRequest 微信立减金充值请求业务参数
type WechatRechargeRequest struct {
ActCode string `json:"actCode"` // 活动code
OrderNo string `json:"orderNo"` // 供应商订单号
GoodsCode string `json:"goodsCode"` // 供应商商品编号
ActOrderNum string `json:"actOrderNum"` // 活动方订单号
AppId string `json:"appId"` // 公众账号ID
OpenId string `json:"openId"` // 微信用户标识
CallbackUrl string `json:"callbackUrl,omitempty"` // 回调地址
}
// WechatRechargeResponseData 微信立减金充值响应解密后的业务数据
type WechatRechargeResponseData struct {
OrderNo string `json:"orderNo"` // 权益订单号
Status int `json:"status"` // 订单状态
CouponId string `json:"couponId,omitempty"` // 微信优惠id
}
// ============================================================
// 接口4卡券/直充/微信立减金充值结果通知接口
// ============================================================
// NotifyRequest 充值结果通知请求业务参数
type NotifyRequest 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"` // 微信优惠id
}
// NotifyResponse 回调通知响应
type NotifyResponse struct {
StatusCode int // HTTP状态码
Body string // 响应体内容
}
```
// File: xy_sh/crypto.go
```go
package xy_sh
import (
"bytes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"time"
"github.com/tjfoc/gmsm/sm3"
"github.com/tjfoc/gmsm/sm4"
)
// ============================================================
// 时间戳生成
// ============================================================
// GenerateTimestampMillis 生成毫秒级时间戳字符串
func GenerateTimestampMillis() string {
return fmt.Sprintf("%d", time.Now().UnixMilli())
}
// ============================================================
// SM3 哈希(含盐值)
// ============================================================
// SM3WithSalt 使用SM3算法结合盐值进行哈希
// 对应Java Hutool的 SmUtil.sm3WithSalt(salt).digestHex(data)
// 实现方式将salt作为HMAC的key对数据进行HMAC-SM3计算
func SM3WithSalt(data []byte, salt []byte) string {
h := hmac.New(sm3.New, salt)
h.Write(data)
return hex.EncodeToString(h.Sum(nil))
}
// SM3WithSaltString 便捷方法对字符串进行SM3加盐哈希
func SM3WithSaltString(data string, salt string) string {
return SM3WithSalt([]byte(data), []byte(salt))
}
// ============================================================
// SM4-CBC 加密/解密
// ============================================================
// SM4Encrypt SM4-CBC模式加密返回base64编码字符串
// 使用 github.com/tjfoc/gmsm/sm4 实现
func SM4Encrypt(plaintext []byte, key []byte) (string, error) {
if len(key) != 16 {
return "", fmt.Errorf("SM4密钥长度必须为16字节")
}
block, err := sm4.NewCipher(key)
if err != nil {
return "", fmt.Errorf("创建SM4 cipher失败: %v", err)
}
padded := pkcs7Padding(plaintext, block.BlockSize())
iv := make([]byte, block.BlockSize())
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", fmt.Errorf("生成IV失败: %v", err)
}
mode := cipher.NewCBCEncrypter(block, iv)
ciphertext := make([]byte, len(padded))
mode.CryptBlocks(ciphertext, padded)
result := append(iv, ciphertext...)
return base64.StdEncoding.EncodeToString(result), nil
}
// SM4Decrypt SM4-CBC模式解密输入base64编码字符串返回明文
func SM4Decrypt(encryptedData string, key []byte) ([]byte, error) {
if len(key) != 16 {
return nil, fmt.Errorf("SM4密钥长度必须为16字节")
}
data, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return nil, fmt.Errorf("base64解码失败: %v", err)
}
block, err := sm4.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
}
blockSize := block.BlockSize()
if len(data) < blockSize {
return nil, fmt.Errorf("数据长度不足")
}
iv := data[:blockSize]
ciphertext := data[blockSize:]
mode := cipher.NewCBCDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
plaintext, err = pkcs7UnPadding(plaintext)
if err != nil {
return nil, fmt.Errorf("去除填充失败: %v", err)
}
return plaintext, nil
}
// pkcs7Padding PKCS7填充
func pkcs7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
// pkcs7UnPadding 去除PKCS7填充
func pkcs7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, fmt.Errorf("数据为空")
}
padding := int(data[length-1])
if padding > length || padding == 0 {
return nil, fmt.Errorf("无效的填充")
}
for i := length - padding; i < length; i++ {
if data[i] != byte(padding) {
return nil, fmt.Errorf("无效的填充")
}
}
return data[:length-padding], nil
}
```
// File: xy_sh/client.go
```go
package xy_sh
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)
// Client 供应商API客户端
type Client struct {
httpClient *http.Client
sm4Key []byte // SM4加密密钥16字节
sm3Salt string // SM3加盐哈希的盐值
baseURL string // 供应商接口基础地址
}
// ClientOption 客户端配置选项
type ClientOption func(*Client)
// WithHTTPClient 设置自定义HTTP客户端
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(c *Client) {
c.httpClient = httpClient
}
}
// NewClient 创建新的供应商API客户端
// sm4Key: SM4加密密钥必须是16字节
// sm3Salt: SM3加盐哈希的盐值
// baseURL: 供应商接口基础地址
func NewClient(sm4Key []byte, sm3Salt string, baseURL string, opts ...ClientOption) (*Client, error) {
if len(sm4Key) != 16 {
return nil, fmt.Errorf("%w: SM4密钥长度必须为16字节", ErrInvalidConfig)
}
if sm3Salt == "" {
return nil, fmt.Errorf("%w: SM3盐值不能为空", ErrInvalidConfig)
}
if baseURL == "" {
return nil, fmt.Errorf("%w: 接口地址不能为空", ErrInvalidConfig)
}
c := &Client{
httpClient: &http.Client{},
sm4Key: sm4Key,
sm3Salt: sm3Salt,
baseURL: baseURL,
}
for _, opt := range opts {
opt(c)
}
return c, nil
}
// ============================================================
// 内部方法:加密、签名、请求发送
// ============================================================
// encryptAndSign 对业务参数进行SM4加密并生成SM3签名
// 返回: encryptedData, timestamp, sign, error
func (c *Client) encryptAndSign(bizParams interface{}) (string, string, string, error) {
// 1. 将业务参数序列化为JSON
bizJSON, err := json.Marshal(bizParams)
if err != nil {
return "", "", "", fmt.Errorf("%w: 序列化业务参数失败: %v", ErrEncryptFailed, err)
}
// 2. SM4加密
encryptedData, err := SM4Encrypt(bizJSON, c.sm4Key)
if err != nil {
return "", "", "", fmt.Errorf("%w: %v", ErrEncryptFailed, err)
}
// 3. 生成毫秒级时间戳
timestamp := GenerateTimestampMillis()
// 4. 生成签名SM3(salt, timestamp + encryptedData)
signStr := timestamp + encryptedData
sign := SM3WithSaltString(signStr, c.sm3Salt)
return encryptedData, timestamp, sign, nil
}
// decryptResponseData 解密响应中的data字段
func (c *Client) decryptResponseData(encryptedData string, target interface{}) error {
// 1. SM4解密
plaintext, err := SM4Decrypt(encryptedData, c.sm4Key)
if err != nil {
return fmt.Errorf("%w: %v", ErrDecryptFailed, err)
}
// 2. JSON反序列化
if err := json.Unmarshal(plaintext, target); err != nil {
return fmt.Errorf("%w: 反序列化业务数据失败: %v", ErrResponseParseFailed, err)
}
return nil
}
// doRequest 发送加密请求并解密响应
func (c *Client) doRequest(ctx context.Context, url string, bizParams interface{}, responseData interface{}) (*EncryptedResponse, error) {
// 1. 加密业务参数并生成签名
encryptedData, timestamp, sign, err := c.encryptAndSign(bizParams)
if err != nil {
return nil, err
}
// 2. 构建请求体
reqBody := EncryptedRequest{
EncryptedData: encryptedData,
}
reqBodyJSON, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("%w: 序列化请求体失败: %v", ErrRequestFailed, err)
}
// 3. 创建HTTP请求
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBodyJSON))
if err != nil {
return nil, fmt.Errorf("%w: 创建请求失败: %v", ErrRequestFailed, err)
}
// 4. 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("timestamp", timestamp)
req.Header.Set("sign", sign)
// 5. 发送请求
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("%w: 发送请求失败: %v", ErrRequestFailed, err)
}
defer resp.Body.Close()
// 6. 读取响应体
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("%w: 读取响应体失败: %v", ErrResponseParseFailed, err)
}
// 7. 解析响应
var encryptedResp EncryptedResponse
if err := json.Unmarshal(respBody, &encryptedResp); err != nil {
return nil, fmt.Errorf("%w: 解析响应JSON失败: %v", ErrResponseParseFailed, err)
}
// 8. 如果业务失败,直接返回
if encryptedResp.Code != 0 {
return &encryptedResp, nil
}
// 9. 如果data不为空解密data
if encryptedResp.Data != nil && *encryptedResp.Data != "" && responseData != nil {
if err := c.decryptResponseData(*encryptedResp.Data, responseData); err != nil {
return nil, err
}
}
return &encryptedResp, nil
}
// ============================================================
// 接口1卡券/直充权益下单接口
// ============================================================
// PlaceOrder 卡券/直充权益下单
// 适用于卡密,直充商品下单
func (c *Client) PlaceOrder(ctx context.Context, req *PlaceOrderRequest) (*EncryptedResponse, *PlaceOrderResponseData, error) {
url := c.baseURL + "/placeOrder"
var data PlaceOrderResponseData
resp, err := c.doRequest(ctx, url, req, &data)
if err != nil {
return nil, nil, err
}
if resp.Code != 0 {
return resp, nil, nil
}
return resp, &data, nil
}
// ============================================================
// 接口2卡券/直充/微信立减金订单查询接口
// ============================================================
// QueryOrder 查询订单状态
// 通过供应商订单号查询订单状态
func (c *Client) QueryOrder(ctx context.Context, req *QueryOrderRequest) (*EncryptedResponse, *QueryOrderResponseData, error) {
url := c.baseURL + "/queryOrder"
var data QueryOrderResponseData
resp, err := c.doRequest(ctx, url, req, &data)
if err != nil {
return nil, nil, err
}
if resp.Code != 0 {
return resp, nil, nil
}
return resp, &data, nil
}
// ============================================================
// 接口3微信立减金订单充值接口
// ============================================================
// WechatRecharge 微信立减金订单充值
func (c *Client) WechatRecharge(ctx context.Context, req *WechatRechargeRequest) (*EncryptedResponse, *WechatRechargeResponseData, error) {
url := c.baseURL + "/wechatRecharge"
var data WechatRechargeResponseData
resp, err := c.doRequest(ctx, url, req, &data)
if err != nil {
return nil, nil, err
}
if resp.Code != 0 {
return resp, nil, nil
}
return resp, &data, nil
}
// ============================================================
// 接口4卡券/直充/微信立减金充值结果通知接口(服务端处理)
// ============================================================
// ParseNotifyRequest 解析回调通知请求
// 供应商主动推送充值结果通知时,行方使用此方法解析请求
// 返回: 解密后的通知请求参数, error
func (c *Client) ParseNotifyRequest(r *http.Request) (*NotifyRequest, error) {
// 1. 读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
return nil, fmt.Errorf("读取请求体失败: %v", err)
}
defer r.Body.Close()
// 2. 解析请求体
var encryptedReq EncryptedRequest
if err := json.Unmarshal(body, &encryptedReq); err != nil {
return nil, fmt.Errorf("解析请求体JSON失败: %v", err)
}
// 3. 获取请求头中的timestamp和sign
timestamp := r.Header.Get("timestamp")
sign := r.Header.Get("sign")
// 4. 验证签名
expectedSign := SM3WithSaltString(timestamp+encryptedReq.EncryptedData, c.sm3Salt)
if sign != expectedSign {
return nil, fmt.Errorf("签名验证失败")
}
// 5. 解密业务数据
var notifyReq NotifyRequest
if err := c.decryptResponseData(encryptedReq.EncryptedData, &notifyReq); err != nil {
return nil, err
}
return &notifyReq, nil
}
// NotifyResponseOK 生成回调通知成功响应
// 行方处理成功后返回HTTP 200且内容为"ok"
func NotifyResponseOK() (int, string) {
return http.StatusOK, "ok"
}
// NotifyResponseFail 生成回调通知失败响应
func NotifyResponseFail() (int, string) {
return http.StatusInternalServerError, "fail"
}
```
// File: xy_sh/example_test.go
```go
package xy_sh
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httptest"
"strings"
)
// ExampleClient_PlaceOrder 演示卡券/直充权益下单
func ExampleClient_PlaceOrder() {
// 模拟供应商服务端
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 读取请求体
var req EncryptedRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
// 验证签名(实际生产环境需要验证)
timestamp := r.Header.Get("timestamp")
sign := r.Header.Get("sign")
_ = timestamp
_ = sign
// 模拟响应
resp := EncryptedResponse{
Code: 0,
Msg: "请求成功",
Data: strPtr(`{"orderNo":"HM17575805323790000127073398f3772c00","expireTime":"2025-09-12 00:00:00","couponNo":"27073398f3772c00","couponCode":"27073398f3772c00","status":1}`),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
// 初始化客户端
// 注意SM4密钥必须为16字节
sm4Key := []byte("1234567890abcdef") // 16字节密钥
sm3Salt := "my_sm3_salt_value"
baseURL := server.URL
client, err := NewClient(sm4Key, sm3Salt, baseURL)
if err != nil {
log.Fatalf("创建客户端失败: %v", err)
}
// 调用下单接口
req := &PlaceOrderRequest{
ActCode: "ACT001",
GoodsCode: "123456",
ActOrderNum: "00001",
Account: "19912345678",
CallbackUrl: "https://xxx/notice",
}
resp, data, err := client.PlaceOrder(context.Background(), req)
if err != nil {
log.Fatalf("下单失败: %v", err)
}
if resp.Code != 0 {
fmt.Printf("下单失败: %s\n", resp.Msg)
return
}
fmt.Printf("订单号: %s\n", data.OrderNo)
fmt.Printf("卡号: %s\n", data.CouponNo)
fmt.Printf("卡密: %s\n", data.CouponCode)
fmt.Printf("状态: %d\n", data.Status)
fmt.Printf("有效期: %s\n", data.ExpireTime)
// Output:
// 订单号: HM17575805323790000127073398f3772c00
// 卡号: 27073398f3772c00
// 卡密: 27073398f3772c00
// 状态: 1
// 有效期: 2025-09-12 00:00:00
}
// ExampleClient_QueryOrder 演示订单查询
func ExampleClient_QueryOrder() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := EncryptedResponse{
Code: 0,
Msg: "请求成功",
Data: strPtr(`{"orderNo":"HM1757046717684000102707339840b23222","cardInfo":"{\"couponNo\":\"11111\",\"couponCode\":\"12334\",\"expireTime\":\"2029-03-09 00:00:00\"}","status":3}`),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
client, _ := NewClient([]byte("1234567890abcdef"), "my_sm3_salt", server.URL)
req := &QueryOrderRequest{
ActCode: "FBG6vdYqGE4mGX7EH/woEg==",
OrderNo: "HM1757046717684000102707339840b23222",
}
resp, data, err := client.QueryOrder(context.Background(), req)
if err != nil {
log.Fatalf("查询失败: %v", err)
}
if resp.Code != 0 {
fmt.Printf("查询失败: %s\n", resp.Msg)
return
}
fmt.Printf("订单号: %s\n", data.OrderNo)
fmt.Printf("状态: %d\n", data.Status)
if data.CardInfo != nil {
fmt.Printf("卡号: %s\n", data.CardInfo.CouponNo)
fmt.Printf("卡密: %s\n", data.CardInfo.CouponCode)
fmt.Printf("过期时间: %s\n", data.CardInfo.ExpireTime)
}
// Output:
// 订单号: HM1757046717684000102707339840b23222
// 状态: 3
// 卡号: 11111
// 卡密: 12334
// 过期时间: 2029-03-09 00:00:00
}
// ExampleClient_WechatRecharge 演示微信立减金充值
func ExampleClient_WechatRecharge() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := EncryptedResponse{
Code: 0,
Msg: "请求成功",
Data: strPtr(`{"orderNo":"0001","couponId":"123456","status":1}`),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
client, _ := NewClient([]byte("1234567890abcdef"), "my_sm3_salt", server.URL)
req := &WechatRechargeRequest{
ActCode: "FBG6vdYqGE4mGX7EH/woEg==",
GoodsCode: "0001",
ActOrderNum: "0001",
OpenId: "0001",
AppId: "00001",
}
resp, data, err := client.WechatRecharge(context.Background(), req)
if err != nil {
log.Fatalf("充值失败: %v", err)
}
if resp.Code != 0 {
fmt.Printf("充值失败: %s\n", resp.Msg)
return
}
fmt.Printf("订单号: %s\n", data.OrderNo)
fmt.Printf("优惠ID: %s\n", data.CouponId)
fmt.Printf("状态: %d\n", data.Status)
// Output:
// 订单号: 0001
// 优惠ID: 123456
// 状态: 1
}
// ExampleClient_ParseNotifyRequest 演示解析回调通知
func ExampleClient_ParseNotifyRequest() {
client, _ := NewClient([]byte("1234567890abcdef"), "my_sm3_salt", "http://example.com")
// 模拟供应商发来的回调通知
notifyData := NotifyRequest{
OrderNo: "HM202509291010001",
ActOrderNum: "XY2025092910100001",
Status: 3,
Account: "19912345678",
}
// 加密并签名(模拟供应商端操作)
encryptedData, timestamp, sign, err := client.encryptAndSign(notifyData)
if err != nil {
log.Fatalf("加密失败: %v", err)
}
// 构建HTTP请求
reqBody := fmt.Sprintf(`{"encryptedData":"%s"}`, encryptedData)
httpReq := httptest.NewRequest(http.MethodPost, "/notify", strings.NewReader(reqBody))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("timestamp", timestamp)
httpReq.Header.Set("sign", sign)
// 解析回调通知
notifyReq, err := client.ParseNotifyRequest(httpReq)
if err != nil {
log.Fatalf("解析回调通知失败: %v", err)
}
fmt.Printf("订单号: %s\n", notifyReq.OrderNo)
fmt.Printf("活动方订单号: %s\n", notifyReq.ActOrderNum)
fmt.Printf("状态: %d\n", notifyReq.Status)
fmt.Printf("账号: %s\n", notifyReq.Account)
// 生成成功响应
statusCode, body := NotifyResponseOK()
fmt.Printf("响应状态码: %d\n", statusCode)
fmt.Printf("响应内容: %s\n", body)
// Output:
// 订单号: HM202509291010001
// 活动方订单号: XY2025092910100001
// 状态: 3
// 账号: 19912345678
// 响应状态码: 200
// 响应内容: ok
}
// strPtr 辅助函数:返回字符串指针
func strPtr(s string) *string {
return &s
}
```