hb-20260724155322/hb/valid.md

816 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: hb/go.mod
```go
module hb
go 1.21
```
// File: hb/types.go
```go
package hb
// RefundRequest 退款接口请求参数
type RefundRequest struct {
MerchantId string `json:"merchantId"` // 商户编号
RequestId string `json:"requestId"` // 商户请求号
SignType string `json:"signType"` // 签名方式MD5 或 RSA
Type string `json:"type"` // 接口类型OrderRefund
Version string `json:"version"` // 版本号2.0.0
OrderId string `json:"orderId"` // 商户订单号
Amount string `json:"amount"` // 退款金额,以分为单位
MerchantCert string `json:"merchantCert,omitempty"` // 商户证书公钥(不参与签名)
Hmac string `json:"hmac,omitempty"` // 签名数据
}
// RefundResponse 退款接口响应参数
type RefundResponse struct {
MerchantId string `json:"merchantId"` // 商户编号
PayNo string `json:"payNo"` // 手机支付平台退款交易流水号
ReturnCode string `json:"returnCode"` // 返回码
Message string `json:"message"` // 返回码描述信息
SignType string `json:"signType"` // 签名方式MD5 或 RSA
Type string `json:"type"` // 接口类型OrderRefund
Version string `json:"version"` // 版本号2.0.0
Amount string `json:"amount"` // 退款金额,单位为分
OrderId string `json:"orderId"` // 商户订单号
Status string `json:"status"` // 退款结果SUCCESS / FAILED
ServerCert string `json:"serverCert,omitempty"` // 服务器证书公钥(不参与签名)
Hmac string `json:"hmac,omitempty"` // 签名数据
}
// RefundStatus 退款状态常量
const (
RefundStatusSuccess = "SUCCESS"
RefundStatusFailed = "FAILED"
)
// SignType 签名方式常量
const (
SignTypeMD5 = "MD5"
SignTypeRSA = "RSA"
)
// 接口类型常量
const (
InterfaceTypeOrderRefund = "OrderRefund"
)
// 版本号常量
const (
Version = "2.0.0"
)
// 返回码常量
const (
ReturnCodeSuccess = "000000"
ReturnCodeSuccessAlt = "MCG00000"
)
```
// File: hb/errors.go
```go
package hb
import "fmt"
// Error 自定义错误类型
type Error struct {
Code string `json:"code"`
Message string `json:"message"`
Err error `json:"-"`
}
func (e *Error) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
func (e *Error) Unwrap() error {
return e.Err
}
// NewError 创建新的错误
func NewError(code, message string, err error) *Error {
return &Error{
Code: code,
Message: message,
Err: err,
}
}
// 预定义错误
var (
ErrSignTypeInvalid = NewError("SIGN_TYPE_INVALID", "不支持的签名方式,仅支持 MD5 或 RSA", nil)
ErrSignVerifyFailed = NewError("SIGN_VERIFY_FAILED", "签名验证失败", nil)
ErrPrivateKeyInvalid = NewError("PRIVATE_KEY_INVALID", "私钥格式无效", nil)
ErrPublicKeyInvalid = NewError("PUBLIC_KEY_INVALID", "公钥格式无效", nil)
ErrNetwork = NewError("NETWORK_ERROR", "网络请求失败", nil)
ErrResponseParse = NewError("RESPONSE_PARSE_ERROR", "响应解析失败", nil)
ErrMerchantIdEmpty = NewError("MERCHANT_ID_EMPTY", "商户编号不能为空", nil)
ErrSignKeyEmpty = NewError("SIGN_KEY_EMPTY", "商户密钥不能为空", nil)
ErrRequestIdEmpty = NewError("REQUEST_ID_EMPTY", "商户请求号不能为空", nil)
ErrOrderIdEmpty = NewError("ORDER_ID_EMPTY", "商户订单号不能为空", nil)
ErrAmountEmpty = NewError("AMOUNT_EMPTY", "退款金额不能为空", nil)
)
// 错误码映射
var ErrorCodeMap = map[string]string{
"C01088": "退货金额域非法",
"C01081": "产品名称域越界",
"C01033": "产品名称域为空",
"C01030": "有效期单位域不合法",
"C01028": "有效期数量域为0",
"C01027": "有效期数量域为空",
"C01025": "订单日期域不合法",
"C01024": "订单日期域为空",
"C01019": "币种域取值不合法",
"C01018": "币种域为空",
"C01014": "订单金额域为空",
"D99981": "订单已过期",
"D23190": "退款日期超过最大有效期",
"D22407": "退款金额大于可退款金额",
"D22401": "退货金额大于可退货金额",
"D22208": "订单状态异常",
"D22201": "订单不存在",
"D22224": "账户支付方式不正确",
"D77040": "商户没有开通此类交易的权限",
"IPS0008": "签名不符,一般都是由于中文编码引起",
"IPS0001": "一般都是因为商户编号或者密钥不对引起",
}
// GetErrorDescription 根据错误码获取错误描述
func GetErrorDescription(code string) string {
if desc, ok := ErrorCodeMap[code]; ok {
return desc
}
return "未知错误码"
}
```
// File: hb/crypto.go
```go
package hb
import (
"crypto"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"strings"
)
// buildSignString 按文档指定顺序拼接签名串(请求)
// 参数顺序merchantId, requestId, signType, type, version, orderId, amount
func buildSignString(params map[string]string, orderedKeys []string) string {
var parts []string
for _, k := range orderedKeys {
if v, ok := params[k]; ok {
parts = append(parts, fmt.Sprintf("%s=%s", k, v))
}
}
return strings.Join(parts, "&")
}
// getRequestSignKeys 获取请求签名参数顺序
func getRequestSignKeys() []string {
return []string{"merchantId", "requestId", "signType", "type", "version", "orderId", "amount"}
}
// getResponseSignKeys 获取响应签名参数顺序
func getResponseSignKeys() []string {
return []string{"merchantId", "payNo", "returnCode", "message", "signType", "type", "version", "amount", "orderId", "status"}
}
// SignMD5 MD5签名
// 在待签名数据之后加上商户密钥signKey生成MD5摘要
func SignMD5(signStr, signKey string) string {
data := signStr + signKey
hash := md5.Sum([]byte(data))
return hex.EncodeToString(hash[:])
}
// VerifyMD5 验证MD5签名
func VerifyMD5(signStr, signKey, hmac string) bool {
return SignMD5(signStr, signKey) == hmac
}
// SignRSA RSA签名配合SHA-1
// 使用商户私钥对签名串进行RSA-SHA1签名
func SignRSA(signStr string, privateKeyPEM string) (string, error) {
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil {
return "", fmt.Errorf("failed to decode PEM private key")
}
var privateKey *rsa.PrivateKey
// 尝试PKCS8格式
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err == nil {
var ok bool
privateKey, ok = key.(*rsa.PrivateKey)
if !ok {
return "", fmt.Errorf("not a RSA private key")
}
} else {
// 尝试PKCS1格式
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("failed to parse private key (PKCS1/PKCS8): %v", err)
}
}
// 计算SHA-1哈希
hash := sha1.Sum([]byte(signStr))
// RSA签名
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA1, hash[:])
if err != nil {
return "", fmt.Errorf("RSA sign failed: %v", err)
}
return hex.EncodeToString(signature), nil
}
// VerifyRSA 验证RSA签名
// 使用手机支付公钥验证响应签名
func VerifyRSA(signStr, signatureHex, publicKeyPEM string) error {
block, _ := pem.Decode([]byte(publicKeyPEM))
if block == nil {
return fmt.Errorf("failed to decode PEM public key")
}
// 尝试解析公钥
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
// 尝试解析证书
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse public key: %v", err)
}
pub = cert.PublicKey
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("not a RSA public key")
}
// 解码签名
signature, err := hex.DecodeString(signatureHex)
if err != nil {
return fmt.Errorf("failed to decode signature hex: %v", err)
}
// 计算SHA-1哈希
hash := sha1.Sum([]byte(signStr))
// 验证签名
err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA1, hash[:], signature)
if err != nil {
return fmt.Errorf("RSA verify failed: %v", err)
}
return nil
}
// extractCertFromPEM 从PEM格式的证书中提取公钥PEM
// 有些场景下serverCert返回的是证书需要提取公钥
func extractPublicKeyFromCert(certPEM string) (string, error) {
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
return "", fmt.Errorf("failed to decode PEM certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", fmt.Errorf("failed to parse certificate: %v", err)
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
return "", fmt.Errorf("failed to marshal public key: %v", err)
}
pubKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
})
return string(pubKeyPEM), nil
}
```
// File: hb/client.go
```go
package hb
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// Client 手机支付平台客户端
type Client struct {
merchantId string // 商户编号
signKey string // 商户密钥MD5签名使用
privateKeyPEM string // 商户RSA私钥RSA签名使用
baseURL string // 接口基础URL
httpClient *http.Client
}
// ClientOption 客户端配置选项
type ClientOption func(*Client)
// WithSignKey 设置MD5签名密钥
func WithSignKey(signKey string) ClientOption {
return func(c *Client) {
c.signKey = signKey
}
}
// WithPrivateKey 设置RSA私钥PEM格式
func WithPrivateKey(privateKeyPEM string) ClientOption {
return func(c *Client) {
c.privateKeyPEM = privateKeyPEM
}
}
// WithBaseURL 设置接口基础URL
func WithBaseURL(baseURL string) ClientOption {
return func(c *Client) {
c.baseURL = baseURL
}
}
// WithHTTPClient 设置自定义HTTP客户端
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(c *Client) {
c.httpClient = httpClient
}
}
// NewClient 创建新的手机支付平台客户端
// merchantId: 商户编号
// opts: 配置选项
func NewClient(merchantId string, opts ...ClientOption) (*Client, error) {
if merchantId == "" {
return nil, ErrMerchantIdEmpty
}
c := &Client{
merchantId: merchantId,
baseURL: "https://ipos.10086.cn",
httpClient: &http.Client{},
}
for _, opt := range opts {
opt(c)
}
return c, nil
}
// Refund 退款接口
// 通过中国移动手机支付渠道,将已成功交易的款项退还给用户
func (c *Client) Refund(ctx context.Context, req *RefundRequest) (*RefundResponse, error) {
if req == nil {
return nil, fmt.Errorf("request cannot be nil")
}
// 设置固定参数
req.MerchantId = c.merchantId
req.Type = InterfaceTypeOrderRefund
req.Version = Version
// 参数校验
if req.RequestId == "" {
return nil, ErrRequestIdEmpty
}
if req.OrderId == "" {
return nil, ErrOrderIdEmpty
}
if req.Amount == "" {
return nil, ErrAmountEmpty
}
if req.SignType == "" {
return nil, ErrSignTypeInvalid
}
if req.SignType != SignTypeMD5 && req.SignType != SignTypeRSA {
return nil, ErrSignTypeInvalid
}
// 生成签名
hmac, err := c.generateRequestSign(req)
if err != nil {
return nil, fmt.Errorf("generate sign failed: %w", err)
}
req.Hmac = hmac
// 构建请求参数
params := c.buildRequestParams(req)
// 发送请求
resp, err := c.doPost(ctx, params)
if err != nil {
return nil, err
}
// 验证响应签名
if err := c.verifyResponseSign(resp); err != nil {
return nil, err
}
return resp, nil
}
// generateRequestSign 生成请求签名
func (c *Client) generateRequestSign(req *RefundRequest) (string, error) {
// 构建参数字典
params := map[string]string{
"merchantId": req.MerchantId,
"requestId": req.RequestId,
"signType": req.SignType,
"type": req.Type,
"version": req.Version,
"orderId": req.OrderId,
"amount": req.Amount,
}
// 按文档指定顺序拼接签名串
signStr := buildSignString(params, getRequestSignKeys())
switch req.SignType {
case SignTypeMD5:
if c.signKey == "" {
return "", ErrSignKeyEmpty
}
return SignMD5(signStr, c.signKey), nil
case SignTypeRSA:
if c.privateKeyPEM == "" {
return "", ErrPrivateKeyInvalid
}
return SignRSA(signStr, c.privateKeyPEM)
default:
return "", ErrSignTypeInvalid
}
}
// buildRequestParams 构建请求参数用于HTTP请求
func (c *Client) buildRequestParams(req *RefundRequest) url.Values {
params := url.Values{}
params.Set("merchantId", req.MerchantId)
params.Set("requestId", req.RequestId)
params.Set("signType", req.SignType)
params.Set("type", req.Type)
params.Set("version", req.Version)
params.Set("orderId", req.OrderId)
params.Set("amount", req.Amount)
params.Set("hmac", req.Hmac)
return params
}
// doPost 发送POST请求
func (c *Client) doPost(ctx context.Context, params url.Values) (*RefundResponse, error) {
apiURL := c.baseURL + "/ips/cmpayService"
bodyStr := params.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, strings.NewReader(bodyStr))
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("do request failed: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response body failed: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
}
return parseResponse(string(body))
}
// parseResponse 解析响应字符串为结构体
func parseResponse(raw string) (*RefundResponse, error) {
resp := &RefundResponse{}
pairs := strings.Split(raw, "&")
for _, pair := range pairs {
eqIdx := strings.IndexByte(pair, '=')
if eqIdx < 0 {
continue
}
key := pair[:eqIdx]
value := pair[eqIdx+1:]
switch key {
case "merchantId":
resp.MerchantId = value
case "payNo":
resp.PayNo = value
case "returnCode":
resp.ReturnCode = value
case "message":
resp.Message = value
case "signType":
resp.SignType = value
case "type":
resp.Type = value
case "version":
resp.Version = value
case "amount":
resp.Amount = value
case "orderId":
resp.OrderId = value
case "status":
resp.Status = value
case "serverCert":
resp.ServerCert = value
case "hmac":
resp.Hmac = value
}
}
return resp, nil
}
// verifyResponseSign 验证响应签名
func (c *Client) verifyResponseSign(resp *RefundResponse) error {
// 构建参数字典
params := map[string]string{
"merchantId": resp.MerchantId,
"payNo": resp.PayNo,
"returnCode": resp.ReturnCode,
"message": resp.Message,
"signType": resp.SignType,
"type": resp.Type,
"version": resp.Version,
"amount": resp.Amount,
"orderId": resp.OrderId,
"status": resp.Status,
}
// 按文档指定顺序拼接签名串
signStr := buildSignString(params, getResponseSignKeys())
switch resp.SignType {
case SignTypeMD5:
if c.signKey == "" {
return ErrSignKeyEmpty
}
if !VerifyMD5(signStr, c.signKey, resp.Hmac) {
return ErrSignVerifyFailed
}
return nil
case SignTypeRSA:
if resp.ServerCert == "" {
return fmt.Errorf("response missing serverCert for RSA verification")
}
// 从证书中提取公钥
pubKeyPEM, err := extractPublicKeyFromCert(resp.ServerCert)
if err != nil {
return fmt.Errorf("extract public key from cert failed: %w", err)
}
return VerifyRSA(signStr, resp.Hmac, pubKeyPEM)
default:
return ErrSignTypeInvalid
}
}
// IsRefundSuccess 判断退款是否成功
// 返回码为 000000 或 MCG00000 表示退款请求成功
// 退款最终结果以 status 字段为准
func IsRefundRequestSuccess(returnCode string) bool {
return returnCode == ReturnCodeSuccess || returnCode == ReturnCodeSuccessAlt
}
// IsRefundSuccess 判断退款最终结果是否成功
func IsRefundSuccess(status string) bool {
return status == RefundStatusSuccess
}
```
// File: hb/example_test.go
```go
package hb
import (
"context"
"fmt"
"log"
"testing"
)
// ExampleClient_Refund_MD5 使用MD5签名的退款示例
func ExampleClient_Refund_MD5() {
// 创建客户端使用MD5签名
client, err := NewClient(
"888009941110054",
WithSignKey("your-64-digit-sign-key-here"),
WithBaseURL("https://uatipos.10086.cn"), // 测试环境
)
if err != nil {
log.Fatalf("创建客户端失败: %v", err)
}
// 构建退款请求
req := &RefundRequest{
RequestId: "20211105053044",
SignType: SignTypeMD5,
OrderId: "20211105045315",
Amount: "1",
}
// 发送退款请求
resp, err := client.Refund(context.Background(), req)
if err != nil {
log.Fatalf("退款请求失败: %v", err)
}
fmt.Printf("返回码: %s\n", resp.ReturnCode)
fmt.Printf("退款结果: %s\n", resp.Status)
fmt.Printf("流水号: %s\n", resp.PayNo)
// 判断退款请求是否成功
if IsRefundRequestSuccess(resp.ReturnCode) {
fmt.Println("退款请求提交成功")
}
// 判断退款最终结果
if IsRefundSuccess(resp.Status) {
fmt.Println("退款最终成功")
}
// Output:
// 返回码: 000000
// 退款结果: SUCCESS
// 流水号: 205206151912517707
// 退款请求提交成功
// 退款最终成功
}
// ExampleClient_Refund_RSA 使用RSA签名的退款示例
func ExampleClient_Refund_RSA() {
// 商户RSA私钥PEM格式
privateKey := `-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJ...(你的私钥)
-----END PRIVATE KEY-----`
// 创建客户端使用RSA签名
client, err := NewClient(
"888009941110054",
WithPrivateKey(privateKey),
WithBaseURL("https://ipos.10086.cn"), // 生产环境
)
if err != nil {
log.Fatalf("创建客户端失败: %v", err)
}
// 构建退款请求
req := &RefundRequest{
RequestId: "20211105053044",
SignType: SignTypeRSA,
OrderId: "20211105045315",
Amount: "1",
}
// 发送退款请求
resp, err := client.Refund(context.Background(), req)
if err != nil {
log.Fatalf("退款请求失败: %v", err)
}
fmt.Printf("返回码: %s\n", resp.ReturnCode)
fmt.Printf("退款结果: %s\n", resp.Status)
// Output:
// 返回码: 000000
// 退款结果: SUCCESS
}
// TestSignMD5 测试MD5签名
func TestSignMD5(t *testing.T) {
signStr := "merchantId=888009941110054&requestId=20211105053044&signType=MD5&type=OrderRefund&version=2.0.0&orderId=20211105045315&amount=1"
signKey := "test-sign-key-64-chars-length-string-for-testing"
hmac := SignMD5(signStr, signKey)
if hmac == "" {
t.Error("MD5签名结果为空")
}
t.Logf("MD5签名结果: %s", hmac)
// 验证签名
if !VerifyMD5(signStr, signKey, hmac) {
t.Error("MD5签名验证失败")
}
}
// TestBuildSignString 测试签名串构建
func TestBuildSignString(t *testing.T) {
params := map[string]string{
"merchantId": "888009941110054",
"requestId": "20211105053044",
"signType": "MD5",
"type": "OrderRefund",
"version": "2.0.0",
"orderId": "20211105045315",
"amount": "1",
}
keys := getRequestSignKeys()
signStr := buildSignString(params, keys)
expected := "merchantId=888009941110054&requestId=20211105053044&signType=MD5&type=OrderRefund&version=2.0.0&orderId=20211105045315&amount=1"
if signStr != expected {
t.Errorf("签名串不匹配\n期望: %s\n实际: %s", expected, signStr)
}
}
// TestParseResponse 测试响应解析
func TestParseResponse(t *testing.T) {
rawResponse := `merchantId=888009941110054&payNo=205206151912517707&returnCode=000000&message=SUCCESS&signType=RSA&type=OrderRefund&version=2.0.0&amount=1&orderId=20211105045315&status=SUCCESS&serverCert=3082042030820308A003020102020420B82E72300D06092A864886F70D0101050500302F310B300906035504061302434E3120301E06035504030C17434D434120456E74657270726973652043415F32303438301E170D3230303830363031343834395A170D3232303830363031343834395A3076310B300906035504061302434E310F300D06035504080C06E6B996E58D97310F300D06035504070C06E995BFE6B2993145304306035504030C3CE4B8ADE59BBDE7A7BBE58AA8E9809AE4BFA1E99B86E59BA2E6B996E58D97E69C89E99990E585ACE58FB8E794B5E5AD90E59586E58AA1E4B8ADE5BF8330820122300D06092A864886F70D01010105000382010F003082010A028201010092FCBC26F1107D35B966C0C098B1F31F8313103A29DC5C78A8056D1AFAC1E5BAA89F5AEE6D3E737D3554AE815040AD5A5D9BAF5BB5B4B45AA5622335E3B9F48D9C05DF29248DF944544D414513F37DFE7F618D0C62B24E1E90CFD1790791861E20FDEFC4288EA4BF906EE1A6B576D9FDE6108CF5A1ED01D1931F2C72AED5393AE9CB668BBF629B25A239E09C54D45B565FBA040963B011C7B80F7C93D4695F4568F6358D591FBCB1073C2583630EC858B7C21F5B7CBB8CC05431D93F4AC358F78A06D6E40D1C033083F403697C12A0A99FF6363D792F624B66FCA655E7057686BA5093A8F68597DB8F40865081A3EB43C1C52E91D35F83F9FF3BF40C9901D08B0203010001A381FC3081F930370603551D250101FF042D302B06082B0601050507030206082B0601050507030106096086480186F8420401060A2B0601040182370A0303301F0603551D23041830168014972A89BEE6A2AA3FA666133AE36A3FBA5D86697130710603551D1F046A30683066A02FA02D862B687474703A2F2F7777772E636D63612E6E65742F646F776E6C6F61642F63726C2F43524C3134322E63726CA233A431302F3120301E06035504030C17434D434120456E74657270726973652043415F32303438310B300906035504061302434E300B0603551D0F0404030200B1301D0603551D0E04160414F713DA66CF8A1F30440D83455C2AFC6560B040B6300D06092A864886F70D010105050003820101002AD8B85ABEEAE53C6002381120F7D6B23620464197FBBDB093D4BB98AF256E335F55A67E32BFDF165BB1E2DDA997A0727086D26F8427FE6B77F6F00C6168CE715E8D298D5EB47B7227CDB8094CB1047CBDEA3D911903DCAA57131DEE1CD0069BBF8C7940A3E68438CCCA1949FF045F7349AA865F7FAC7170B7D7339DEBC46930FF9B241200440EAFD280F868C13364D13ECEB04EA702D0479862F9590813AB94BE38DC84E366B884174731464111A1AFEB199E1D7D518651BB5F430926286CA2B5619CD54689F5851A231C1605146D4ACD6CCC54DB79CC49F992083D5DEACAFDDC2B8777B13B7D63FF7E35D1098BEFD6B550EC3328ECE0224F5AAD6C2B83FB85&hmac=00AAA308D1AA66B2E534D9177B408B55A0CC14F8B27ACE59F08150E66E51858F18070A0DD629FCBD47BB1A98A632DB3F492213A92F14885043EC68BD8180E58ED742D748B708B281693226A7D3BF326B812BA45E7E7D13B4D52498B3C6C83380827CD741A9EF0D99B1C96588C2BE56C79BDFC3813CA4E627142BA7717FFFB43BE53F9B3F2A6D3112D83A90C3D3859A8456BC892E6E960FE56F36F829FAFA7CAEF19D01D10C0BD2F2538827CFF4C642BBAA6423AED0CB8E0D980D7D0BDEB4B1A04B9DAC8867B724527423038D953603A07018BEC1980AD31EDD3E88977F72795DCFB0B3B674FA91A6443F9F0DB67B485B3F3AD5E48AA26F3641E44B64FC7B0920`
resp, err := parseResponse(rawResponse)
if err != nil {
t.Fatalf("解析响应失败: %v", err)
}
if resp.MerchantId != "888009941110054" {
t.Errorf("MerchantId不匹配: %s", resp.MerchantId)
}
if resp.PayNo != "205206151912517707" {
t.Errorf("PayNo不匹配: %s", resp.PayNo)
}
if resp.ReturnCode != "000000" {
t.Errorf("ReturnCode不匹配: %s", resp.ReturnCode)
}
if resp.Status != "SUCCESS" {
t.Errorf("Status不匹配: %s", resp.Status)
}
if resp.SignType != "RSA" {
t.Errorf("SignType不匹配: %s", resp.SignType)
}
}
// TestErrorCode 测试错误码映射
func TestErrorCode(t *testing.T) {
tests := []struct {
code string
expected string
}{
{"000000", "未知错误码"},
{"C01088", "退货金额域非法"},
{"D22201", "订单不存在"},
{"IPS0008", "签名不符,一般都是由于中文编码引起"},
{"UNKNOWN", "未知错误码"},
}
for _, tt := range tests {
desc := GetErrorDescription(tt.code)
if desc != tt.expected {
t.Errorf("错误码 %s: 期望 %s, 实际 %s", tt.code, tt.expected, desc)
}
}
}
// TestIsRefundSuccess 测试退款成功判断
func TestIsRefundSuccess(t *testing.T) {
if !IsRefundRequestSuccess("000000") {
t.Error("000000 应该表示退款请求成功")
}
if !IsRefundRequestSuccess("MCG00000") {
t.Error("MCG00000 应该表示退款请求成功")
}
if IsRefundRequestSuccess("C01088") {
t.Error("C01088 不应该表示退款请求成功")
}
if !IsRefundSuccess("SUCCESS") {
t.Error("SUCCESS 应该表示退款最终成功")
}
if IsRefundSuccess("FAILED") {
t.Error("FAILED 不应该表示退款最终成功")
}
}
```