# 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 | 数据库操作错误