# 对接文档

> AppKey：**{{.payKey}}**  
> AppSecret：**{{.paySecret}}**

## 1.请求支付页面

1.DEMO文件链接[Python demo文件](/assets/demo.zip)

请求卡密支付页面，页面如下：  
![img.png](/images/pay/AB4467C3-FA86-069A-99BB-C3997694311C.png)

### 请求参数

地址：{{.shopHost}}/?sign=xxx  
方法：GET

| 参数名称  | 类型   | 是否必须 | 描述     | 示例                                                                |
| --------- | ------ | -------- | -------- | ------------------------------------------------------------------- |
| sign      | string | 是       | 签名     | 6634ddd39796e4e0a98d6f5e919ae50ddc82edbf255f15aa2e30c5c70381e7b9648 |
| returnUrl | string | 否       | 返回地址 | 订单完成（成功或失败）后的返回地址 https://baidu.com/notify         |

### sign生成算法

> 注意：生成签名时，需要提前按照参数的参数名进行排序，按照 a-z 顺序排序

| 参数名称      | 类型   | 是否必须 | 描述               | 示例                            |
| ------------- | ------ | -------- | ------------------ | ------------------------------- |
| payKey        | string | 是       | 支付 key           | 20190520123456789               |
| generatedTime | number | 是       | 支付时间           | 默认为 10 位当前时间（单位/秒） |
| duration      | number | 是       | 订单存续时间       | 默认为 24 小时                  |
| orderNo       | string | 是       | 订单号(自定义生成) | 20190520123456789               |
| notifyUrl     | string | 是       | 回调地址           | https://baidu.com/notify        |
| showMMValue   | string | 是       | 卡片面额           | 100                             |
| productCode   | string | 是       | 卡片编码           | 苹果卡                          |

### 通道编码

> 通道编码需参考后台配置，以下仅作为示例

| 通道                         | 编码 | 说明                             |
| ---------------------------- | ---- | -------------------------------- |
| 苹果充值卡                   | 8545 | 苹果充值卡面，由卡号和卡密组成   |
| 沃尔玛充值卡                 | 8645 | 沃尔玛充值卡面，有卡号和卡密组成 |
| 天猫店铺充值兑换\|确认收货版 | 8745 | 天猫店铺充值，用户秒收货秒回调   |
| 天猫店铺充值兑换\|好评版     | 8645 | 天猫店铺充值，用户秒好评秒回调   |

### 加密算法

需要将上述参数加密生成 sign 参数，sign 参数需要拼接在 url
后面，如：`http://121.37.253.228:12305/?sign=6634ddd39796e4e0a98d6f5e919ae50ddc82edbf255f15aa2e30c5c70381e7b96489f102aa574e70a4ea1b874685dfafec957b237b02d5980f0a22139d860c7d7a8d5c58e3dfbde606a0de08186a4d45228bcc1917a35768f1903bee921b78b118e8b31d25642737cb3e094c140c81f7&showMM=100&linkID=736797719398`。
sign 加密算法为 AES/CBC 加密，数据填充模式为 PKCS7 填充，加密 key 和 iv 如下：

> key = thisis32bitlongpassphraseimusing  
> iv = 1234567890123456

##### 加密算法参考

```go
package main

import (
 "bytes"
 "crypto/aes"
 "crypto/cipher"
 "encoding/hex"
 "encoding/json"
 "fmt"
)

type OrderParams struct {
 OrderNo       string  `json:"orderNo"`
 PayKey        string  `json:"payKey"`        // 支付明文秘钥
 NotifyUrl     string  `json:"notifyUrl"`     // 回调地址
 GeneratedTime int64   `json:"generatedTime"` // 过期时间
 Duration      int     `json:"duration"`      // 存续时间（小时）
 ProductCode   string  `json:"productCode"`
 ShowMMValue   float64 `json:"showMMValue"`
}

func String(p *OrderParams) string {
 r, err := json.Marshal(p)
 if err != nil {
  return ""
 }
 logs.Info(string(r))
 return string(r)
}

func Encrypt(p *OrderParams) string {
 prepareData := String(p)
 print(prepareData, "\n")
 if prepareData == "" {
  return prepareData
 }

 // 加密密码数据
 key := "thisis32bitlongpassphraseimusing"
 iv := "1234567890123456"
 result, _ := AesCBCEncrypt([]byte(prepareData), []byte(key), []byte(iv))
 return hex.EncodeToString(result)
}

// AesCBCEncrypt AES/CBC/PKCS7Padding 加密
func AesCBCEncrypt(plaintext []byte, key []byte, iv []byte) ([]byte, error) {
 // AES
 block, err := aes.NewCipher(key)
 if err != nil {
  panic(err)
 }

 // PKCS7 填充
 plaintext = paddingPKCS7(plaintext, aes.BlockSize)

 // CBC 加密
 mode := cipher.NewCBCEncrypter(block, iv)
 mode.CryptBlocks(plaintext, plaintext)

 return plaintext, nil
}

// PKCS7 填充
func paddingPKCS7(plaintext []byte, blockSize int) []byte {
 paddingSize := blockSize - len(plaintext)%blockSize
 paddingText := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize)
 return append(plaintext, paddingText...)
}

func main() {
 orderParams := OrderParams{
  OrderNo:       "20190520123456789",
  PayKey:        "20190520123456789",
  NotifyUrl:     "https://baidu.com/notify",
  GeneratedTime: 1558312000,
  Duration:      24,
  ProductCode:   "8645",
  ShowMMValue:   100,
 }
 sign := Encrypt(&orderParams)
 // 原始本文
 fmt.Println(sign)
}

// 原始文本：{"orderNo":"20190520123456789","payKey":"20190520123456789","notifyUrl":"https://baidu.com/notify","generatedTime":1558312000,"duration":24,"productCode":"8645","showMMValue":100}
// aes加密后的文本：94db297041a61fa78ac2176b9e8e1e9b34d2ae33b6d46fc8c731a34b4c2b76eea7738993c2c175e6d61648c31c00b5d6d465246aa39b5fba5d6b77599f112c791370486b9fb19ad675e228b1d36ec6ad890ac692a262a73f126dda2bffae493452ea20d8604874af1ff2ba67cf6aab5c5821d100beec4d4c4de86d4c014fc8fe0bfe61b259752007f498dbee92f0136b2cbb756ff5405f95fc7763405022c835812f58dda86c9d72f84c6d576d2e98909d0e499e3c4b4da552f478bfbce3ba63
// 签名：key=thisis32bitlongpassphraseimusing iv=1234567890123456
// 算法：CBC非对称加密/PKCS7填充
```

## 2.请求实际支付金额(可选)

> 通过面额获取实际支付金额。注意，实际支付面额可能会随时修改，建议每次拉取订单前重新获取最新面额，切勿缓存。  
> 当前接口会返回当前面额在各个平台设置的实际支付金额，以及设置各个平台的链接，根据需要获取。

接口：{{.remoteHost}}/gateway/getAllowedMM  
请求方式：GET

| 参数名称    | 类型   | 是否必须 | 描述     | 实例              |
| ----------- | ------ | -------- | -------- | ----------------- |
| showMMValue | string | 是       | 展示面额 | 充值卡上的面额    |
| payKey      | string | 是       | 支付 key | 20190520123456789 |
| productCode | string | 卡片编码 | 是       | 8456              |

#### 返回参数

| 参数名称 | 参数类型  | 描述         | 说明            |
| -------- | --------- | ------------ | --------------- |
| code     | int       | 平台参数     | 0 成功 其他失败 |
| msg      | string    | 信息提示     | 错误原因        |
| data     | interface | 具体返回数据 | 返回数据如下    |

具体成功返回数据

| 参数名称  | 参数类型 | 描述           | 说明 |
| --------- | -------- | -------------- | ---- |
| showLabel | string   | 卡面面值       |      |
| factLabel | string   | 实际面值       |      |
| linkID    | string   | 需要下单的链接 |      |

## 3.提交订单(创建订单)

> 提交订单接口，此接口用于第三方直接提交卡号/卡密，使用自有拉单页面

接口：{{.remoteHost}}/gateway/scan  
请求方式：POST/FormData

### 请求参数

| 参数名称    | 类型   | 是否必须 | 描述               | 示例                                                |
| ----------- | ------ | -------- | ------------------ | --------------------------------------------------- |
| exValue     | string | 是       | 扩展参数           | 卡密数据（具体格式如下）                            |
| orderNo     | string | 是       | 订单号             | 20190520123456789                                   |
| orderPeriod | int    | 否       | 订单有效期（小时） | 24                                                  |
| orderPrice  | float  | 是       | 订单价格           | 100.12                                              |
| notifyUrl   | string | 是       | 回调地址           | https://baidu.com/notify?sign=xxx                   |
| payKey      | string | 是       | 商户秘钥           |                                                     |
| timestamp   | int    | 是       | 时间戳             | Unix 时间戳,时区为 GMT+8 允许与服务器最大误差 30 秒 |
| sign        | string | 是       | 签名               | 签名算法如下                                        |
| productCode | string | 卡片编码 | 是                 | 8456                                                |
| ip          | string | 否       | 客户端IP           | 127.0.0.1                                           |
| deviceId    | string | 否       | 设备唯一标识       | 127.0.0.1                                           |

### 参数说明

#### exValue

内需参数

| 参数名称     | 参数类型 | 描述     | 说明                   |
| ------------ | -------- | -------- | ---------------------- |
| data         | string   | 卡密     | ---                    |
| cardNo       | string   | 面额     | ---                    |
| recoveryType | string   | 回收类型 | 2.仅有卡密 8.卡密/卡号 |
| faceType     | string   | 面额     | ---                    |

### 返回参数

返回参数

| 参数名称 | 参数类型  | 描述         | 说明            |
| -------- | --------- | ------------ | --------------- |
| code     | int       | 平台参数     | 0 成功 其他失败 |
| msg      | string    | 信息提示     |                 |
| data     | interface | 具体返回数据 | 返回数据如下    |

具体成功返回数据

| 参数名称   | 类型   | 是否必须 | 描述         | 示例              |
| ---------- | ------ | -------- | ------------ | ----------------- |
| statusCode | string | 是       | 返回状态码   | 00 成功，其他失败 |
| orderNo    | string | 是       | 订单号       | 20190520123456789 |
| orderPrice | string | 是       | 订单价格     | 100.12            |
| sign       | string | 是       | 签名         | xxx               |
| msg        | string | 是       | 附带返回信息 | 成功              |

## 4.查询订单

地址：{{.remoteHost}}/gateway/merchant/query
方法：Get

查询参数

| 参数名称  | 参数类型 | 是否必须 | 描述     | 说明                                                |
| --------- | -------- | -------- | -------- | --------------------------------------------------- |
| orderNo   | string   | 是       | 订单号   |                                                     |
| appKey    | string   | 是       | 应用 key |                                                     |
| timestamp | int      | 是       | 时间戳   | Unix 时间戳,时区为 GMT+8 允许与服务器最大误差 30 秒 |
| sign      | string   | 是       | 签名     | xxx                                                 |

### 返回参数

| 参数名称 | 参数类型  | 描述         | 说明            |
| -------- | --------- | ------------ | --------------- |
| code     | int       | 平台参数     | 0 成功 其他失败 |
| msg      | string    | 信息提示     |                 |
| data     | interface | 具体返回数据 | 返回数据如下    |

具体成功返回数据

| 参数名称       | 参数类型 | 描述         | 说明                |
| -------------- | -------- | ------------ | ------------------- |
| orderNo        | string   | 订单号       |                     |
| cardNo         | string   | 卡号         |                     |
| cardPwd        | string   | 卡密         |                     |
| status         | string   | 订单状态     | success，fail, wait |
| faceVal        | float    | 卡面面额     |                     |
| amount         | string   | 实际充值金额 |                     |
| CardReturnData | string   | 充值失败原因 |                     |

## 4.创建订单(新)

> 创建订单，并返回下单单号，地址等信息

地址：{{.remoteHost}}/gateway/createOrder
请求方式：Post/JSON

### 请求参数

| 参数名称    | 类型   | 是否必须 | 描述               | 示例                                                |
| ----------- | ------ | -------- | ------------------ | --------------------------------------------------- |
| orderNo     | string | 是       | 订单号             | 20190520123456789                                   |
| orderPeriod | int    | 是       | 订单有效期（小时） | 24                                                  |
| orderPrice  | float  | 是       | 订单价格           | 100.12                                              |
| notifyUrl   | string | 是       | 回调地址           | https://baidu.com/notify?sign=xxx                   |
| payKey      | string | 是       | 商户秘钥           |                                                     |
| timestamp   | int    | 是       | 时间戳             | Unix 时间戳,时区为 GMT+8 允许与服务器最大误差 30 秒 |
| sign        | string | 是       | 签名               | 签名算法如下                                        |
| productCode | string | 卡片编码 | 是                 | 8456                                                |

### 返回参数

| 参数名称 | 参数类型  | 描述         | 说明           |
| -------- | --------- | ------------ | -------------- |
| code     | int       | 平台参数     | 0 成功 -1失败  |
| msg      | string    | 信息提示     | 失败的具体原因 |
| data     | interface | 具体返回数据 | 返回数据如下   |

具体成功返回数据

| 参数名称        | 参数类型 | 描述       | 说明                    |
| --------------- | -------- | ---------- | ----------------------- |
| productCode     | string   | 产品编码   |                         |
| paymentName     | string   | 产品名称   |                         |
| transactionType | string   | 卡密       | 是兑换还是充值 暂时无用 |
| PayUrl          | string   | 支付链接   | 最终构建的支付链接      |
| MerchantOrderNo | string   | 商户订单号 | 来源自请求传入订单号    |
| OrderNo         | string   | 订单编号   | 系统编号                |

#### 签名算法（sign）

> 注意：  
> 1 对参数进行签名时，一定不要写死！！！，请按签名算法进行计算！！  
> 2 所有提交参数都需要参与签名计算 为空的也需要

##### 参数签名算法示例算法

请注意: 1.对参数进行签名时，一定不要写死！！！，请按签名算法进行计算！！ 2.所有提交参数都需要参与签名计算 为空的也需要3.下面的参数只是示例，具体参数要根据实际接口参数进行验签

```go
package main

import (
 "crypto/md5"
 "encoding/hex"
 "sort"
 "strconv"
 "time"
)

func main() {
 //实际获取的appKey值
 appKey := "1f2811dcd32b2c5c"
 //实际获取的appSecret值
 appSecret := "3C569A210D1260B2"

 // 如果有其它参数，依次添加 key=value
 params := map[string]string{}

 params["appKey"] = appKey
 params["phone"] = "13800138000"
 params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
 params["amount"] = "100"

 strArr := SortMap(params)
 signStr := ""
 for i := 0; i < len(strArr); i++ {
  k := strArr[i]
  if len(params[k]) == 0 {
   signStr += k
  } else {
   signStr += k + params[k]
  }
 }
 signStr += appSecret
 h := md5.New()
 h.Write([]byte(signStr))
 hex.EncodeToString(h.Sum(nil))
}

// SortMap 对map的key值进行排序
func SortMap(m map[string]string) []string {
 var arr []string
 for k := range m {
  arr = append(arr, k)
 }
 sort.Strings(arr)
 return arr
}


// 原始值
// {
//     "amount": "100",
//     "appKey": "1f2811dcd32b2c5c",
//     "phone": "13800138000",
//     "timestamp": "1737284505"
// }

// appSecret：3C569A210D1260B2

// 加密前的值
// amount100appKey1f2811dcd32b2c5cphone13800138000timestamp17372845053C569A210D1260B2
// 加密后的值：96ff597f7b89322e83e234e3ad1d42e5
```

Java 算法

```Java
String appSecret = "3C569A210D1260B2";

TreeMap<String, String> params = new TreeMap<String,String>();
params.put("timestamp", ""+(System.currentTimeMillis()/1000L));
params.put("phone", "13800138000");
params.put("amount", "100");

StringBuilder sb = new StringBuilder();
for(Entry<String, String> item : params.entrySet()){
     if(item.getKey().equals("sign"))
         continue;
     sb.append(item.getKey()).append(item.getValue());
}

sb.append(appSecret);
params.put("sign",md5(sb.toString()));
```

## 订单回调接口签名检验

### 回调接口示例

地址：http://baidu.com/notify?param1=xx&param2=xx  
方法：Get

返回参数：

| 参数名称   | 参数类型 | 含义                       | 示例                                |
| ---------- | -------- | -------------------------- | ----------------------------------- |
| orderNo    | string   | 订单号(己方账号)           | 20190520123456789                   |
| orderPrice | string   | 订单价格(面值，一般为整数) | 100                                 |
| factPrice  | string   | 实际价格                   | 98.89                               |
| orderTime  | string   | 回调时间                   | 2006010215:04:05                    |
| trxNo      | string   | 交易(系统账户)流水号       | 20190520123456789                   |
| payKey     | string   | 支付通道 key               | 20190520123456789                   |
| sign       | string   | 签名                       | xxx                                 |
| statusCode | int      | 交易状态                   | 1 成功，0 通用失败 其他状态码待添加 |
| failReason | string   | 订单状态                   | 失败的具体原因                      |

### 签名算法

签名算法参见`提交订单(创建订单)`接口 ↑
