# CCPay 商户接口文档-2.2
[English Version](https://hackmd.io/BsXiQAnRQLanw8tMj-EYxg?view)
[中文版本](./)
version 2.1
> 2.0 更新多一种支付回调,原 payto 接口的名称修改 payback,老版本的依然兼容
> 2.1 修改原来的 appKey 和 appSecret 名称为 businessKey 与 businessSecret
> 2.2 增加一些校验说明以及增加 recordId 检测接口
[TOC]
### 接口域名
测试环境: https://paytest-api.cctip.io/
正式环境: https://pay-api.cctip.io/
### 公共的参数请求组成
请求体:
* header,属于接口的`公共部分`:
* reqId,每次请求的唯一 id,字母与数字的组合,不能含非法字符,字符数限制最多32个;
* platform,机器人平台,比如: `telegram`;
* businessKey,商户的唯一凭证;
* signature,签名信息,生成方式见下面的`签名方式`小节。
* body:
* 携带对应接口所要求的 json 参数
body 的例子:
```json
{
"user_bot_id": "",
"coin": {
"chain_id": 0,
"contract": ""
},
"origin_amount": "",
"backup": ""
}
```
### 公共的返回结构
数据返回结构都是 Json,字段组成如下:
* errno `整形`,是错误码,0 的时候,代表成功请求
* errmsg `字符串`,错误信息
* data 具体的数据,根据不同的接口而具体定义
### 现支持的接入应用与平台类型
商家机器人:
* 电报,platform 取值为: `telegram`
CCTip·APP:
* 电报,platform 取值为: `telegram`
* 推特,platform 取值为: `twitter`
* discord,platform 取值为: `discord`
* reddit,platform 取值为: `reddit`
* 苹果,platform 取值为: `apple`
### 签名方式
businessKey 和 businessSecret 将由 `CCPay` 提供给商户:
* businessKey 唯一标识商户;
* businessSecret 密钥
参数的签名步骤:
1. body 内 json 转 string 得 A,如果没有 body,那么不需要拼接它。这里还要注意一点,json 的参数顺序以具体接口中给出的为准,如果顺序改变了会导致验证签名失败;
2. `务必使用` "_" 按照格式下面例子格式进行拼接:
`businessKey + _ + platform + _ + reqId + _ + A` ,拼接后得 Str;
3. 用 businessSecret 对 Str 进行生成签名 `signature`,这个`不要转大小写`,需要原样保存:
```golang
signature = base64_encode(hash_hmac('sha1', Str, businessSecret));
```
> 注:PHP 语言的开发者,hash_hmac 函数的第四个参数,raw_output 请设置为 true
线上签名调试工具网址:https://1024tools.com/hmac
下面是个例子 / Demo:
```txt
原文:74yfkb7q8rwfss6r1oo8u74s8t_{"attach":{"action":"income","amount":"1","chat_id":894669391,"message_id":"1627080","timestamp":1591670552,"token_name":"DOGE","user_id":"894669391","username":"qweazxcdddd"},"coin":{"chain_id":7,"contract":"D00000003"},"record_id":"b4a3a0d7a405174ebb290717907f2b829992ac9af9da43b3fbc0d579594a9f9f","tx_hash":"","type":3}
```
```key
私钥:zhr9uexm6mnrxu1eukypgght64RSWFKPDTR8C1H1ZINMW5WAB8KO
```
```result
结果:jf/sXfQccE0wDxE0hWCF88vtETE=
```
### 接口列表
#### 1. 获取代币信息
> 该接口内所返回的 Token 信息,是当前 CCPay 所支持的,没在返回列表中的,证明不被支持,其它相关接口中的 `chain_id` 与 `contract` 的取值,请以该接口的为准。包含用户充值代币的合法性,也要以这里的为准,防止重复 Symbol 的代币充值攻击。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - |-
/v1/ccpay/token/list | GET | 是 | 5s/次 | 是 |
参数:
* body 无
返回
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
```json
{
"data": [{
"chain_id": 2,
"name": "Bitcoin",
"symbol": "BTC",
"contract": "100000001",
"decimals": 8,
"platform": "twitter,wechat,telegram",
"logo": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579",
"max": "0",
"min": "1",
"fee": "10000",
"confirmed": 2,
"web_site": ""
}, {
"chain_id": 4,
"name": "Bitcoin Cash",
"symbol": "BCH",
"contract": "100000002",
"decimals": 8,
"platform": "twitter,wechat,telegram",
"logo": "https://assets.coingecko.com/coins/images/780/large/bitcoin_cash.png?1547034541",
"max": "0",
"min": "1",
"fee": "10000",
"confirmed": 2,
"web_site": ""
}],
"errmsg": "",
"errno": 0
}
```
#### 2. 创建充值订单 -- `已废弃`
该接口,允许商户帮用户生成要进行`链上充值`的相关信息。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/order/deposit/submit | POST | 是 | 20ms/次 | 是 |
参数:
除 `backup` 外,都是必填;
* user_bot_id 字符串,订单中,充值用户的基于机器人平台的 id
* `chain_id` 整形,Token 所在链的id
* `contract` 字符串,Token 的合约值
* `origin_amount` 字符串,订单中要充值的数值,`非精度放大值`
```json
{
"user_bot_id": "",
"coin": {
"chain_id": 0,
"contract": ""
},
"origin_amount": "2.3",
"backup": ""
}
```
返回:
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
* address 字符串,用户要充值 Token 到的地址;
* fixed_amount_normal 字符串,要充值的数值,非精度放大值;
* fixed_amount_big_val 字符串,要充值的数值,精度放大值;
* confirmation 整形,该 Token 的充值后,在 CCTip 系统中所认定的区块确认数,达到该区块确认数,充值才会真实生效;
* expire_at 字符串,订单的过期时间,单位毫秒,代表这个时间点后将过期;
* record_id 字符串,订单的流水id,总是64位字符,每个被成功接收的订单,流水id 唯一;
* memo 字符串,备注信息
```json
{
"data": {
"address": "0xA2d7Ef1C65d679c2D6770A4a356ad9A32d753396",
"coin": {
"amount": {
"fixed_amount_normal": "2.7421",
"fixed_amount_big_val": "2742100000000000000"
},
"confirmation": 12,
"chain_id": 1,
"contract": "0x0000000000000000000000000000000000000000",
"decimal": 18
},
"expire_at": "1586925482324",
"record_id": "2eed99a4ed077ec4f0f3d93a8f8b95c05f962e90fff557dc7e692b2a1be3d686",
"memo": ""
},
"errmsg": "",
"errno": 0
}
```
#### 3. 申请提现 --- 链上 --- `已废弃`
该接口允许商户将自己 CCTip 系统的 Token 资产从链上提现到用户指定的`链地址`。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/withdraw | POST | 是 | 20ms/次 | 是 |
参数:
所有参数,都是必填
* to_bot_id 字符串,商户要将对应的 Token 提现到的用户,用户基于机器人平台的 id
* `chain_id` 整形,要提现的 Token 所在链的id
* `contract` 字符串,要提现的 Token 的合约值
* `amount` 字符串,提现的数值,`非精度放大值`
* `address` 用户指定的提现地址
```json
{
"coin": {
"chain_id": 0,
"contract": ""
},
"to_bot_id": "",
"amount": "2.43232"
}
```
返回:
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
* record_id 提现的流水 id
```json
{
"data": {
"record_id": "A2d7Ef1C65d679c2D6770A4a356ad9A32d753396"
},
"errmsg": "",
"errno": 0
}
```
#### 4. 申请转账 --- 非链上
该接口允许商户将自己的 Token 资产转账给指定的用户。请注意,这里的 payback 接口和用户在 `cctip_bot` 机器人私聊界面使用 `payto` 命令充值不是同一个。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/payback | POST | 是 | 20ms/次 | 是 |
参数:
所有参数,除特殊说明,都是必填
> 下面的 `to_bot_id` 和 `to_sys_id` 填其一即可,如果都填了,以 to_sys_id 为主。
* to_bot_id 字符串,商户要将对应的 Token 提现到的用户,用户基于机器人平台的 id
* to_sys_id 字符串,商户要将对应的 Token 提现到的用户,用户基于 cctip 系统的id
* `chain_id` 整形,要提现的 Token 所在链的id
* `contract` 字符串,要提现的 Token 的合约值
* `amount` 字符串,提现的数值,`非精度放大值`
* `order_id` 可选参数,如果携带了,ccpay 会记录,当该接口处理请求成功了,那么本次的 order_id 不再可用。会返回重放错误。
```json
{
"coin": {
"chain_id": 0,
"contract": ""
},
"to_bot_id": "",
"amount": "2.43232",
"order_id": "xxxxxx" // 可选
}
```
或
```json
{
"coin": {
"chain_id": 0,
"contract": ""
},
"to_sys_id": "",
"amount": "2.43232",
"order_id": "xxxxxx" // 可选
}
```
返回:
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
* record_id 提现的流水 id
```json
{
"data": {
"record_id": "A2d7Ef1C65d679c2D6770A4a356ad9A32d753396"
},
"errmsg": "",
"errno": 0
}
```
#### 5. 查询商户余额
该接⼝允许商户查询⾃⼰的`商家账号`在 CCTip 系统中的 Token 余额。
> `注`:如果商户自己有链上提现系统,当用户在走自己的链上提现时,请务必使用该进行一次余额检查。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/business/balance | POST | 是 | 20ms/次 | 是 |
参数:
所有参数,都是必填
* chain_id 整型,要查询的 Token 所在链的id
* 如果 chain_id 为 0,那么会返回所有币种的余额
* contract 字符串,要查询的 Token 的合约值
```json
{
"chain_id": 0,
"contract": ""
}
```
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
* confirmed 代表的是已 确认的 余额;
* balance 是精度放⼤值
* price 是当前代币的市场价格,单位 `枚/美⾦`
* unconfirmed 是待确认的
* locks 是被锁定的
```json
{
"data": {
"confirmed": [
{
"balance": "279006620705932878",
"chain_id": 1,
"contract": "0x0000000000000000000000000000000000000000",
"decimals": 18,
"logo": "https://raw.githubusercontent.com/BlockABC/eth-tokens/master/tokens/0x000000000000000000 0000000000000000000000/token.png",
"name": "Ethereum",
"price": "187.57",
"symbol": "ETH"
}],
"unconfirmed": [
{
"balance": "9006620705932878",
"chain_id": 1,
"contract": "0x0000000000000000000000000000000000000000",
"decimals": 18,
"logo": "https://raw.githubusercontent.com/BlockABC/eth-tokens/master/tokens/0x000000000000000000 0000000000000000000000/token.png",
"name": "Ethereum",
"price": "187.57",
"symbol": "ETH"
}],
"locks": [
{
"balance": "69006620705932878",
"chain_id": 1,
"contract": "0x0000000000000000000000000000000000000000",
"decimals": 18,
"logo": "https://raw.githubusercontent.com/BlockABC/eth-tokens/master/tokens/0x000000000000000000 0000000000000000000000/token.png",
"name": "Ethereum",
"price": "187.57",
"symbol": "ETH"
}]
},
"errmsg": "",
"errno": 0
}
```
#### 6. 商户查询自己账户的链上充值地址
该接⼝允许商户查询⾃⼰的`商家账号`在 CCTip 系统中的所有支持链的钱包地址。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/business/address | POST | 是 | 20ms/次 | 是 |
参数:
* 无参数
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
* address 就是地址
* chain_id 对应 token/list 接口中返回的链 id,标示了该地址是什么链的
> 注意:波场链的地址,暂时不能直接充值
```json
{
"errno": 0,
"errmsg": "",
"data": [
{
"address": "0x1234...",
"chain_id": 1
},
{
"address": "0x1234...",
"chain_id": 1
}
]
}
```
#### 7. 校验由 CCTip·APP 下发的 AccessToken
该接口允许商户在后台服务发送请求校验由 `CCTip·APP` 下发的 `AccessToken` 是否有效。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/accesstoken/check | POST | 是 | 10ms/次 | 是 |
参数:
所有参数,除特殊说明,都是必填
* access_token
```json
{
"access_token": "xxxx"
}
```
返回:
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
```json
{
"data": {
"sys_id": "xx", // 当前用户在 cctip 系统中的用户id
"nickname": "", // 昵称
"avatar": "", // 头像
"sns_platform":"", // 社交平台
"sns_id":"" // 社交平台账户ID
},
"errmsg": "",
"errno": 0
}
```
#### 8. 检测充值订单是否已经到账 ---- 重要
该接口允许商户在后台服务发送请求检查 `RecordId` 对应的充值订单是否已经入账了。自2020-10-8下午开始,虽然说 `充/提回调` 通知处已经能保证充值是必然到账了才会回调,但是还是建议商户在收到回调或其它需要的时机,使用该接口对 RecordId 进行校验!
> 注:调用该接口的时机最好是在接收到回调后几分钟内进行调用,如果时间过久才调用,可能会得到订单过期的结果。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/record/check | POST | 是 | 20ms/次 | 是 |
参数:
所有参数,除特殊说明,都是必填
* record_id
```json
{
"record_id": "由回调返回的订单 record_id 参数"
}
```
返回:
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100 // 相关的其它错误码,见错误码表的 145 和 146
}
```
成功:
```json
{
"data": {
"record_id": "xx" // 原样返回
},
"errmsg": "",
"errno": 0
}
```
#### 9. 根据用户的CCPay系统id获取用户信息
该接口允许商户在后台服务发送请求,根据CCPay的sys_id 来获取一些用户信息。
路由 | 方式 | 是否鉴权 | 是否限频 | 是否需要 header 参数
-|-|- | - | -
/v1/ccpay/userinfo | POST | 是 | 20ms/次 | 是 |
参数:
所有参数,除特殊说明,都是必填
* sys_id
```json
{
"sys_id": "用户基于CCPay系统的id"
}
```
返回:
失败:
```json
{
"data": null,
"errmsg": "param invalid",
"errno": 100
}
```
成功:
```json
{
"data": {
"sys_id": "xx", // 原样返回
"nickname": "", // 昵称
"avatar": "", // 头像
"sns_platform":"", // 社交平台
"sns_id":"" // 社交平台账户ID
},
"errmsg": "",
"errno": 0
}
```
#### 10. 充 / 提回调
充 / 提回调,包含下面四种场景:
1. 当用户通过订单信息,在链上充值 Token 给商家成功了,CCPay 通知给商家;
2. 当商家给用户进行`链上提现`,到账的时候;
3. 平台用户在 CCTip 的 `telegram 机器人` 里使用 payto 命令给商家的 telegram 机器人充值时;
4. 用户在 `CCTip APP` 里面打开了商家的应用,进行了充值 Token 给商家;
5. 商户在 CCTip 中的某币种授信额度不足的时候,CCTip 通知商家。
---
为了接收 CCPay 系统的回调,商户要提供一个满足下面要求的接口。
1. 链接:`https://example.com/some/path/ccpay/notify`,**注意以 /ccpay/notify 结尾**:
1. 合法例子:`https://ccpay.cctip.io/v1/ccpay/notify`
2. 非法例子:`http://47.23.44.252:81/ccpay/notify`
2. 方式:`POST`
3. 请求的参数:
1. header:
1. reqId,解析见`公共的参数请求组成` 小节的描述;
2. signature,签名信息,生成方式见第4点。
2. body 的 json:
1. type 回调类型:
1. 值为 1,对应上文的场景 1;
2. 值为 2,对应上文的场景 2;
3. 值为 3,对应上文的场景 3;
4. 值为 4,对应上文的场景 4;
5. 值为 5,对应上文的场景 5。
2. record_id 对应的操作流水id,用于对账;
3. tx_hash 对应充值交易的链上 hash 值,非链上操作的时候,它就会是空字符串;
4. attach 的数据体:
* 类型 `3` 和 `4` 的时候,其内部的数据就是见下⾯解析:
* action 动作,⽬前都是 income 字符串
* amount 数值,精度放小值
* app_id 当 type = 4 的时候,这个是商家应用对应的应用id
* backup ,目前只有在 type = 4 的时候,才可能有值,对应商家在接入 `CCTip·APP` 接口时候的 nonce 字段值,它是透传的。
* chat_id 聊天的id,由第三⽅平台产⽣,可能为空
* platform 用户所处的第三方平台,仅仅当 type = 4 才会出现该字段,注意是出现,不是空字符串
* message_id 消息id,由第三⽅平台产⽣,,可能为空
* sys_id 触发相关操作的用户基于 CCTip 平台的用户id
* timestamp 对应操作发⽣的时间戳,秒级别
* token_name 代币名称
* user_id ,触发相关操作的⽤户基于第三⽅平台的⽤户id,如果是 `CCTip APP` 的用户,则它对应 `CCTip APP` 提供的 `sns_id`
* username ⽤户基于第三⽅平台的⽤户名
* 类型为 `5` 的时候,其内部的数据见下解析:
* left_value 剩下的授信额度;
* debt_value 负债的授信额度;
* timestamp 授信提示的时间戳,单位秒。
5. 提示,如果要根据 `user_id` 来唯一确定一个用户,最好结合 `platfrom` 参数。
```json
{
"attach": {
"action": "income",
"amount": "1",
"app_id": "xxxx",
"backup": "yyyy",
"chat_id": -12334,
"message_id": "2324",
"platform": "telegram", // 只有当 type = 4 时才有该字段
"sys_id": "xxxx",
"timestamp": 123242343,
"token_name": "ETH",
"user_id": "14214",
"username": "lin_m"
},
"coin": {
"chain_id": 1,
"contract": "0x000..."
},
"record_id": "abc234...",
"tx_hash": "0x123456...",
"type": 3,
}
```
```json
{
"attach": {
"debt_value": "0.02",
"left_value": "0.005",
"timestamp": 123242343
},
"coin": {
"chain_id": 1,
"contract": "0x000..."
},
"record_id": "",
"tx_hash": "",
"type": 5,
}
```
4. 签名方式:
1. body 内 json 序列化为 bytes;
2. bytes 转 string 得 A,A 里面所对应的 json object 的顺序请按照上面例子进行解析,比如 attach 才到 coin,而不能是 coin 才到 attach;
3. 使用 "_" 拼接:`reqId + _ + A` 得 Str;
4. 用商户的 businessSecret 对 Str 进行生成签名 `signature` :
5. 商户要做的:
1. 对 reqId 的唯一性检测,避免被`重放攻击`;
2. 验证签名,在接收到请求参数后,对参数按照步骤4 使用自己的 businessSecret 进行生成签名 `signature_2`,然后比较 signature == signature_2,一致,那么证明请求信息没被篡改。
6. 签名代码例子:
```golang
signature = base64_encode(hash_hmac('sha1', Str, businessSecret));
```
7. 收到回调请求后,CCPay 不关心商户的处理结果,无论处理成功还是失败,务必返回 `httpCode` 为 `200` 的响应给 CCPay
### 订单回调重试机制
CCPay 在回调商户的 `notify` 接口的时候,如果返回的响应,其 httpCode 不是 200,那么CCPay 将会认为该次请求不可达,它会将该次的请求信息存储,然后进行`策略性`的下次进行调用,所有的请求信息将会一样,包含 reqId 。在特定的重试次数后,同一回调依然失败,那么 CCPay 将`会记录`并`丢弃`该回调。
### 错误码列表
数值 | 解析
-|-|-
20 | CCTip·APP 下发的 AccessToken 过期 或 非法
数值 | 解析
-|-|-
100 | 参数错误
101 | CCPay 内部服务错误
102 | 验证签名失败
103 | 接口访问次数限制
104 | 请求重放
105 | 不支持的平台
106 | 参数错误
110 | 转账余额不足
112 | 商户链下提现给用户,orderId 重放
120 | 充值订单不存在
数值 | 解析
-|-|-
145 | recordId 对应的订单,已经过期
146 | recordId 对应的订单,不存在
数值 | 解析
-|-|-
157 | 提现参数错误
158 | 提现系统内部错误
159 | 提现余额不足
160 | 提现的币种不支持
161 | 提现手续费不足
162 | TRON 链,提现地址不能是一个还没被创建过的地址
163 | TRON 链,提现地址不能是自己
164 | 申请提现记录不存在
165 | 链上提现功能维护
数值 | 解析
-|-|-
200X | 数据库操作错误