---
# System prepended metadata

title: Cowpay api 说明

---

# Cowpay api 说明
1. 目标读者  

 本文的主要目标读者是支付平台合作方的技术对接人员
 
2. 名词定义

  * 合作方：指对接支付平台的机构
  
  * 商户：指委托收单的营业机构，如超市，便利店等

# 数据交互与通讯安全协议
> 本章介绍支付平台对接交互模式一级通讯安全协议，描述组成支付平台的各个组件以及他们之间的协作方式，以及在通讯过程中保证数据的保密性和完整性。

## 1.通讯安全协议
### 1.1 通讯过程

1. 合作方请求报文上送时，采用HTTP/HTTPS协议，POST推送方式，编码格式统一为UTF-8。
2. 请求报文：
>统一请求报文格式：

    {
      "signtype": "MD5",
      "sign": urlencode($sign, 'utf8'),
      "transdata": urlencode($data, "utf8")
    }
* signtype 为签名算法，默认为MD5算法
* sign 为业务请求签名后数据，并URLEncoder.encode(sign, “UTF-8”)
* transdata 为JSON格式的业务请求报文，并URLEncoder.encode(transdata, “UTF-8”)
3. 报文结构及样例：
> 所有请求报文统一采用JSON格

    {
    "transdata":"%7B%22pay_type%22%3A%2210072%22%2C%22user_no%22%3A%22Neo%22%2C%22product  _name%22%3A%22pidai%22%2C%22product_code%22%3A%22product-123%22%2C%22order_no%22%3A%221507704879000%22%2C%22order_time%22%3A%222017-10-12T12%3A22%3A05.452Z%22%2C%22order_amount%22%3A0.1%2C%22payment%22%3A%22%E6%94%AF%E4%BB%98%E6%88%90%E5%8A%9F%22%7D",
    "sign":"RLiujUr8AHm7V%2BNfPmdzkZgFuwiluyxJJNkso9nep3YY2wCO4lCh444Nk%2Fr1SxN2CxmpJ333DuaZfNPsBd647Q%2FYpH89fIYz3A07H7NE8EWN008FNBwDhBr6N3hyisJMNdwsJof7D3tCTtc28adOlC5k1naToseOP3x38H%2Fe5Vg%3D",
    "signtype": "MD5"
    }   
## 2. 签名
>当前签名仅支持MD5方式，待签名字段（transdata是json格式，不用排序）无需排序。以transdata的json内容进行签名：将所有非空字段按ASCII码排序，以 key=value 形式用&（与符号）连接起来，最后拼上&key=${key}，进行md5签名，并把签名转大写。先签名后urlencode
### 2.1 
2.1 拼装必要参数

	   { 
        merchant_code: '1001',    
        pay_type: 'india-upi',     
        user_no: '1',    
        notify_url: 'http://www.cowpay.com',    
        return_url: 'http://www.cowpay.com/',    
        product_name: 'ttt001',    
        product_code: 'test001',    
        order_no: '541445444144414',       
        order_time: '20201102081725',    
        order_amount: 11.00,    
        merchant_ip: '127.0.0.1'
       }

2.2 转成JSON
>将对象转成json字符串

2.4 拼装请求报文

    {
      "signtype": "MD5",
      "sign": urlencode($sign, 'utf8'),
      "transdata": urlencode($data, "utf8")
    }
2.5 对transdata和sign进行urlencode

2.6 MD5签名源样例（代收）

```merchant_code=test1111&notify_url=1&order_amount=50.00&order_no=541445444144414&order_time=20201102081725&pay_type=india-upi&product_code=test001&product_name=ttt001&return_url=1&user_no=51070173&key=F1C14F3DEFF6A38450BA97161A3DF3C```

2.7 MD5签名源串样例（代付）

```bank_branch=KKBK0000888&bank_card=624144124411xxxx&merchant_code=test1111&notify_url=1&order_amount=3&order_no=541445444144414&&pay_type=india-bank-repay&user_name=Michael Taylor&key=F1C14F3DEFF6A38450BA97161A3DF3CF```
## 3. 请求接口
>POST Application/json 请求接口
## 4. 测试账号
>测试账号只能调试支付，调试完支付之后，请联系商务下正式号
测试账户请跟商户申请

# 接口参数符号约定
>在接口文档出现的具体报文格式描述中， “出现要求”列包含的值的含义如下表表示：

| 符号 | 请求方约束 |服务方约束|
| ------ | ------ |------|
| M | 必须包含该域 |必须校验该域是否存在和内容的合法性|
| C | 如果条件符合必须包含该域 |当条件满足时，必须校验该域是否存在 当该域存在时，必须检查其内容的合法性|
| O | 该域可选 |当该域存在时，必须检查其内容的合法性|

# 常见错误解决方案
## 1. 提示不是JSON类型，JSON解析出错
>原因：

1.1 报文格式错误，请查参考章节‘数据交互与通讯安全协议

1.2 头部字段未设置好Content-type:application/json

1.3 传递的不是JSON类型字符串
## 2.	MD5加密提示验签失败
>原因

2.1 输入字段中间带有空格

2.2 Md5加密串是否正确

2.3 signtype的值是否为MD5

2.4 signtype字段拼写错误

3.3 加密串不是transdata 未urlencode前的数据
## 4.	提示商户号验证失败，请检查商户号和merchant_code是否填写正确
4.1 检查请求的host是否正确

4.2 向商务咨询是否开户
## 5.	提示交易功能未开通,请联系商务开通
>联系运营人员开户
## 6. 向商务咨询是否开户
6.1 确认运营人员是否配置好通道

6.2 确认输入金额是否传入限额内的金额或是固定金额指定的金额（如果是固额）
## 7.	提示通道维护中，请联系客服。
7.1 请联系运营开启通道

## 8.	接口返回 This library (validator.js) validates strings only
8.1 请检查除order_amount字段以外的值是否都为String类型
## 9.	获取支付地址后，打开无法获取支付页面
9.1 (yl,jd,zhifubao-h5,weixin)类型支付地址需要转成二维码，用相应的app扫码
9.2 咨询运维人员通道是否正常

## 10. 请先联系商务设置IP白名单
检查merchantcode是否填写正确
## 11	异步用什么加密方式

异步加密方式看下单(代付/支付)的加密方式，下单如果用MD5异步加来就是MD5，如果是RSA异步加来也是RSA
## 12.	请求方只需要返回Response status code 200即表示确认收到通知
此处不是指返回内容，而是指http状态码(如果完全不知道如何设置，麻烦直接响应一个空请求或者返回字符串200均可，默认情况下请求成功响应码即为200)

# 对接说明
* 支付对接文档： 
* 网关服务器地址：`https://pay365.cowpay.co`
* 商户管理后台：`https://m138.cowpay.co` 
* 支付下单地址：`/pay`
* 支付订单查询地址: `/queryPayOrder`
* 发起提现: `/v2/withdraw`
* 提现订单查询：`/v2/queryWithdrawOrder`
* 余额查询：`/v2/queryBalance`
* 接口请求中需要的账户信息 商户号(merchant_code)、密钥（MD5）、签名私钥（RSA） 使用后台网址登录，找到个人配置获取
* 回调IP：`平台获取`
* 接口地址 = 网关（host） +  接口路由（path 看文档对应接口 请求URL项目）

# 1.支付下单接口
>简要描述：
*	统一支付下单接口
> 请求URL：
•	`${host}`/ pay
> 请求方式：

* POST
* application/ json
> 参数：

| 参数名 | 必选 | 类型 | 最大长度 | 中文描述 | 说明 |
| ------ | ------ |------|------|------|------|
| merchant_code | M |string|20|商户编号|平台分配的唯一编号|
| order_no | M |string|30|商户订单号|平商户订单号，不可重复，最长30位|
| order_amount | M |string||交易金额|单位：inr（只支持整数）|
| order_time | M |string|15|交易时间|时间戳，纯数字|
| product_name | M |string|60|产品名称|请尽量不要传固定值，否则会影响成功率；请尽量不要带空格。|
| notify_url | M |string|254|异步通知地址|异步回调通知地址，不支持参数传递|
| pay_type | M |string|30|支付类型|指定支付方式，详见 `[支付类型]`|
| return_url | C |string|30|成功回跳地址|提交成功后跳转的地址，非必填，但建议商户也传递该字段|
| payer_info | C |string|30|付款人姓名|付款人姓名|

>返回示例
```
{
  "code":0,
  "content":
            {
              "accountName":"Dhanendra prakash singh ",
              "upi":"Dhanendra973@federal"
            },
  "html":"",
  "msg":"操作成功",
  "orderNo":"C1731191630813200384",
  "payUrl":"https://mm.cowpay.cc/pay125605ef1927312c77995d4ac133696ee0fab741e8baa6ac554","qrcode":"image/gif;base64, /9j/4AAQS"
}
 
 ```
>返回参数说明

| 参数名 | 类型 | 说明 |
| ------ | ------ |------|
| code | int |请求结果，0代表成功，其他为失败|
| msg | string |失败原因|
| html | string |部分通道会以 html 字符串的形式输出，商户优先判断html字段是否存在，在则输出到页面中，页面会自动跳转，否则使用payUrl|
| orderNo | string |订单号|
| payUrl | string |支付收营台地址链接|
| qrcode | string |收款二维码|
| content | json |{"accountName":"Dhanendra","upi":"Dhanendra973@gmil.com"}|
| accountName | string |upi账号收款人名称|
| upi | string |upi账号|



# 2.支付订单查询
## 简要描述：
*	统一支付下单接口
## 请求URL：
•	`${host}`/queryPayOrder
## 请求方式：
* POST
* application/ json
## 参数：
|参数名|必填|类型|长度|中文描述|说明
|------|------|------|------|------|------
|merchant_code|M|string|商户编号|平台分配的唯一编号
|order_no|M|sting|商户订单号|商户订单号
|signtype|M|sting||签名类型 `MD5` 或 `RSA`
|sign|M|sting||生成签名串`A7F0F0EB66562B3AC105F719E5E56960`

## 请求参数
```
{
  "sign":"A7F0F0EB66562B3AC105F719E5E56960",
  "signtype":"MD5","transdata":"%7B%22order_no%22%3A%22I0543064507662789%22%2C%22merchant_code%22%3A%221698133934504%22%7D"
}

```
## 查询成功返回数据
|参数名|类型|说明
| ------ |------|------
|order_no|String|订单号
|merchant_code|String|商户号
|sign|String|验证签名
|pay_type|String|支付类型
|payment|booloean|true：支付成功 false:支付失败
|order_time|long|Timestamp
|order_amount|String|订单金额
|status|booloean|true：订单存在 false:订单不存在
```
{
  "order_no":"I0543064507662789",
  "merchant_code":"1698133934504",
  "order_amount":"500.00",
  "sign":"0F39BB8C132071B96BBC1737AF0FFBC2",
  "pay_type":"india-upi-h5",
  "payment":true,
  "order_time":1701585044000,
  "status":true
  }
```
## 查询失败返回数据
```
{"message":"查询不到该笔订单信息","status":false}
```


# 3.支付异步通知
>服务端发送的验签与支付验签相同
支付成功后会将以下字段通过JSON的方式POST到支付时填写参数地址，请求方只需要返回Response status code 200即表示确认收到通知，否则我们会每隔5分钟再次请求你们提交的异步接口，直达请求10次后，就不在发起请求
* POST字段如下：
* 异步回调报文示例：
```
无utr示例：
{
  "transdata":"%7B%22pay_type%22%3A%2210072%22%2C%22user_no%22%3A%22Neo%22%2C%22product_name%22%3A%22pidai%22%2C%22product_code%22%3A%22product-123%22%2C%22order_no%22%3A%221507704879000%22%2C%22order_time%22%3A%222017-10-12T12%3A22%3A05.452Z%22%2C%22order_amount%22%3A0.1%2C%22payment%22%3A%22%E6%94%AF%E4%BB%98%E6%88%90%E5%8A%9F%22%7D",
  "sign":"RLiujUr8AHm7V%2BNfPmdzkZgFuwiluyxJJNkso9nep3YY2wCO4lCh444Nk%2Fr1SxN2CxmpJ333DuaZfNPsBd647Q%2FYpH89fIYz3A07H7NE8EWN008FNBwDhBr6N3hyisJMNdwsJof7D3tCTtc28adOlC5k1naToseOP3x38H%2Fe5Vg%3D"
}

有utr示例：
{
"sign":"DA7046288CA8199B2ECC1D41D29834AE",
"transdata":"%7B%22order_no%22%3A%22I6060301291056389%22%2C%22order_time%22%3A1717655449000%2C%22product_code%22%3A%22%E5%95%86%E5%93%81Code%22%2C%22product_name%22%3A%22%E5%95%86%E5%93%81%E5%90%8D%22%2C%22user_no%22%3A%221%22%2C%22order_amount%22%3A%22100.000%22%2C%22utr_code%22%3A%2211111%22%2C%22pay_type%22%3A%22india-upi-h5%22%2C%22payment%22%3A%22%E6%94%AF%E4%BB%98%E6%88%90%E5%8A%9F%22%7D"
}
```
> 验签字段包含在”sign”字段，验签规则与发起支付相同，将transdata urldecode后获取到的JSON数据与私钥一起加密后urlencode。只有支付成功才会通知。请收到通知后校验签名和金额，并做好重复通知的处理。

> `transdata` 字段decode后为JSON字段如下：

|参数名|类型|说明
| ------ |------|------
|order_no|String|商户订单号|
|order_amount|String|订单金额（卢比为单位）
|order_time|String|系统订单创建时间戳（毫秒）
|pay_type|String|订单支付通道码
|product_name|String|支付时上传的商品名称
|product_code|String|支付时上传的商品CODE
|user_no|String|支付时上传的用户ID
|payment|String|支付成功，目前该字段值一定为支付成功四个字
|utr_code|String|交易utr，不为空时才返回，并参与签名；为空时不返回，不参与签名


# 4.代付下单
> 简要描述：
* 代付下单接口
* 后台代付需要绑定谷歌验证码，请联系商务
* API代付需要报备IP，请联系商务
## 请求URL：
* `${host}`/v2/withdraw
## 请求方式
*	POST
*	application/json
>参数

|参数名|必填|类型|长度|中文描述|说明
|------|------|------|------|------|------
|merchant_code|M|string|20|商户编号|平台分配的唯一编号
|order_no|M|string|30|商户订单号|商户订单号，不可重复
|order_amount|M|string|30|交易金额|单位：inr（100-50000，只支持整数）
|pay_type|M|string|100|代付类型|代付类型：`india-bank-repay`或`india-upi-repay`
|bank_name|C|string|100|银行名称|银行名称，不限制，随意填,upi代付不用填
|bank_card|M|string|32|银行卡号或upi账号|`e.g: 3339997788`，`xxx@sbi`
|bank_branch|OM|string|50|IFSC Code，upi代付不用填|`e.g: KKBK0000888`
|user_name|M|string|50|持卡人姓名|`e.g: Michael Taylor`
|notify_url|M|string|50|持异步通知地址|`e.g: https://callback.xxxxx/`

>返回示例
```
{    "status": true,    "message": "代付申请提交成功"  }
```
>返回参数说明

|参数名|类型|说明
|------|------|------
|status|boolean|请求结果，true代表代付发起成功，其他为失败
|message|string|失败原因

# 5.代付异步通知
## 服务端发送的验签与支付验签相同
>支付成功后会将以下字段通过JSON的方式POST到支付时填写参数地址，请求方只需要返回Response status code 200即表示确认收到通知，否则我们会每隔5分钟再次请求你们提交的异步接口，直达请求10次后，就不在发起请求验签字段包含在”sign”字段，验签规则与发起支付相同，将transdata urldecode后获取到的JSON数据与私钥一起加密后urlencode。
* `transdata` 字段decode后为JSON字段如下：

|参数名|类型|说明
|------|------|------
|order_no|boolean|商户订单号
|order_amount|string|订单金额（inr为单位）
|message|string|返回：提现成功，提现失败
|resp_code|string|以该字段判断代付结果：`S` 代付成功，`F` 代付失败，`P` 代付中
|utr_code|String|该字段有值时才返回，并参与签名；无值时不返回，不参与签名。

# 6.代付订单查询
## 简要描述
* 代付订单查询
## 请求URL
* `${host}`/v2/queryWithdrawOrder
## 请求方式：
*	POST
*	application/json

|参数名|必填|类型|长度|中文描述|说明
|------|------|------|------|------|------
|merchant_code|M|string|商户编号|平台分配的唯一编号
|order_no|M|sting|商户订单号|商户订单号
|signtype|M|sting||签名类型 `MD5` 或 `RSA`
|sign|M|sting||生成签名串`A7F0F0EB66562B3AC105F719E5E56960`
## 返回参数
|参数名|类型|说明
| ------ |------|------
|order_no|String|订单号
|merchant_code|String|商户号
|sign|String|验证签名
|order_amount|String|代付金额
|message|String|提示消息
|order_time|long|Timestamp
|status|booloean|true：订单存在 false:订单不存在
|resp_code|String|S:成功 F:失败  P:代付中
* 支付成功
```
{ 
  "status": true,    
  "merchant_code": "test001",        
  "order_no": "15621201145410",    
  "message": "提现成功",    
  "order_time": "20201223181928",    
  "order_amount": "5.01",    
  "resp_code": "S"
  }
```
* 订单不存在
```
{    "status": false,    "message": "查询不到该笔订单信息"}
```
## 返回参数说明
|参数名|类型|说明
|------|------|------
|status|Boolean|查询状态：true 代表查询成功

## status为true时，会返回以下内容：

|参数名|类型|说明
|------|------|------
|merchant_code|string|商户编号
|order_no|string|商户订单号
|order_amount|string|订单金额（inr为单位）
|order_time|string|系统下单时间
|resp_code|string|查以该字段判断代付状态。`S` 代付成功，`F` 代付失败，`P` 代付中
|message|string|订单状态描述
|sign|string|urlencode后的加密字段。待加密内容为：除sign字段之外的其他字段的json
## status为false时，会返回以下内容：
|参数名|类型|说明
|------|------|------
|message|Boolean|查询请求失败时，此字段将会返回具体错误信息

# 7.查询余额
## 简要描述：
* 查询余额
## 请求URL：
* `${host}`/v2/queryBalance
## 请求方式：
* POST
* application/json
## 参数：

|参数名|必填|类型|长度|中文描述|说明
|------|------|------|------|------|------
|merchant_code|M|string|商户编号|平台分配的唯一编号
## 返回示例
* 成功返回
```
{    
  "status": true,    
  "balance": {"merchant_code": "test001001",        
              "total_order_amount": 1000.45,        
              "cashing_balance": 1000.23,        
              "total_withdraw_amount": 120.00,        
              "frozen_balance": 10.00,        
              "balance": 154.23,        
              "withdraw_balance": 2541.23,        
              "unsettled_balance": 0,        
              "freeze_balance": 0    
              }
}
```
* •	请求失败
```
{    "status": false,    "message": "失败原因"}
```
## 返回参数说明
|参数名|类型|说明
|------|------|------
|status|Boolean|请求成功与否标志
|message|string|失败原因，status为false返回
|balance|json Object|余额明细，status为true返回
## balance参数 status为true返回
|参数名|类型|说明
|------|------|------
|merchant_code|string|平台分配的唯一编号
|total_order_amount|Number|交易总金额
|cashing_balance|Number|代付中金额
|total_withdraw_amount|Number|已代付总金额
|frozen_balance|Number|冻结中金额
|balance|Number|总余额
|withdraw_balance|Number|可提现余额
|unsettled_balance|Number|未结算余额

# 支付类型
> 目前支持的支付类型。注：平台当前仅开放 india-upi和india-upi-h5

|支付类型|中文描述
|------|------
|india-upi|upi扫码
|india-upi-h5|upi自动唤醒
|india-bank|印度银行卡代收

# 签名和验签示例：Java代码
```
package com.xxx;

import com.alibaba.fastjson.JSONObject;


import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class SignatureDemo {

    static String md5Key = "xxxxxxxxx";
    public static void main(String[] args) throws Exception {

        //request json string
        String request = signatureAndRequest();

        //verify signature
        decrypt(request);
    }

    private static String signatureAndRequest() throws UnsupportedEncodingException {
        //pay params
        Pay pay = new Pay();
        pay.setMerchant_code("111");
        pay.setPay_type("222");
        pay.setUser_no("333");
        pay.setNotify_url("444");
        pay.setReturn_url("555");
        pay.setProduct_name("666");
        pay.setProduct_code("777");
        pay.setOrder_no("888");
        pay.setOrder_time("999");
        pay.setOrder_amount("1");
        pay.setMerchant_ip("2");

        //params order by ascll
        JSONObject jsonObject = (JSONObject) JSONObject.toJSON(pay);
        String waitSignStr = mapToStringByASCll(jsonObject) + "&key=" +  md5Key;

        //signature
        String sign = hash(waitSignStr).toUpperCase();

        //request params
        HashMap<String, String> requestMap = new HashMap<>();
        requestMap.put("signtype",  "MD5");
        requestMap.put("sign", URLEncoder.encode(sign, "UTF-8"));
        requestMap.put("transdata", URLEncoder.encode(jsonObject.toJSONString(), "UTF-8"));
        String request = JSONObject.toJSONString(requestMap);
        return request;
    }


    public static void decrypt(String json) throws UnsupportedEncodingException {
        JSONObject receiveJson = JSONObject.parseObject(json);
        String transdata = URLDecoder.decode(receiveJson.getString("transdata"), "UTF-8");
        String waitSignStr = mapToStringByASCll(JSONObject.parseObject(transdata)) + "&key=" +  md5Key;
        if (hash(waitSignStr).toUpperCase().equals(URLDecoder.decode(receiveJson.getString("sign"), "UTF-8"))) {
            System.out.println("signature verify success");
        } else {
            System.out.println("signature verify failed");
        }
    }


    @Data
    public static class Pay {
        private String merchant_code;
        private String pay_type;
        private String user_no;
        private String notify_url;
        private String return_url;
        private String product_name;
        private String product_code;
        private String order_no;
        private String order_time;
        private String order_amount;
        private String merchant_ip;
    }

    public static String mapToStringByASCll(JSONObject object) {
        Map<String, Object> map = (Map<String, Object>) object.toJavaObject(Map.class);
        String[] sortedKeys = (String[]) map.keySet().toArray((Object[]) new String[0]);
        Arrays.sort((Object[]) sortedKeys);
        StringBuilder sb = new StringBuilder();
        byte b;
        int i;
        String[] arrayOfString1;
        for (i = (arrayOfString1 = sortedKeys).length, b = 0; b < i;) {
            String key = arrayOfString1[b];
            if (!"sign".equals(key)) {

                if (map.get(key) != null && !StringUtils.isBlank(map.get(key).toString()))
                    sb.append(key).append("=").append(map.get(key).toString()).append("&");
            }
            b++;
        }

        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    private static byte[] md5(String s) {
        try {
            MessageDigest algorithm = MessageDigest.getInstance("MD5");
            algorithm.reset();
            algorithm.update(s.getBytes("UTF-8"));
            byte[] messageDigest = algorithm.digest();
            return messageDigest;
        } catch (Exception e) {


            return null;
        }
    }

    private static final String toHex(byte[] hash) {
        if (hash == null) {
            return null;
        }
        StringBuffer buf = new StringBuffer(hash.length * 2);

        for (int i = 0; i < hash.length; i++) {
            if ((hash[i] & 0xFF) < 16) {
                buf.append("0");
            }
            buf.append(Long.toString((hash[i] & 0xFF), 16));
        }
        return buf.toString();
    }

    public static String hash(String s) {
        try {
            return new String(toHex(md5(s)).getBytes("UTF-8"), "UTF-8");
        } catch (Exception e) {
            return s;
        }
    }
}

```