阅读对象
本文阅读对象:商户系统集成品信支付涉及的技术架构师,研发工程师,测试工程师,系统运维工程师。
协议规则
提交方式:Get / Post
字符编码:UTF-8
签名算法:MD5
金额:分
签名算法
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
- ◆ 参数名ASCII码从小到大排序(字典序);
- ◆ 如果参数的值为空不参与签名;
- ◆ 参数名区分大小写;
- ◆ sign参数不参与签名,将生成的签名与该sign值作校验。
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
◆ key设置路径:亿付商户平台-->密钥管理-->商户密钥
举例(假设传送参数如下):
mch_id : 1002
mch_order_id : 20181130A001
money : 100
notify_url : http://www.example.com/notify
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:
stringA="mch_id=117&mch_order_id=l20191212161294208320481100&money=10000¬ify_url=http://baidu.com&pay_type=alipay&trade_type=native_wap&user_tag=test_";
第二步:拼接API密钥:
//key为商户平台设置的密钥key
stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d"
//MD5签名方式
sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7"
签名算法java代码示例:
/**
* 实体类转map
*
* @param object 参数封装类
* @return 待签名数据
*/
public static Map objectToMap(Object object) {
Map map = new LinkedHashMap<>();
Field[] fields = object.getClass().getDeclaredFields();
for (Field entityField : fields) {
Field field = null;
try {
field = object.getClass().getDeclaredField(entityField.getName());
map.put(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName()), field.get(object) == null ? "" : field.get(object).toString());
} catch (NoSuchFieldException exception) {
exception.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
/**
* 签名处理
*
* @param data 待签名数据
* @param key 私钥
* @return 签名
*/
public static String generateSignature(final Map data, String key) throws Exception {
Set keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals("sign")) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
//System.out.println(MD5(sb.toString()).toUpperCase());
return MD5(sb.toString()).toUpperCase();
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
java.security.MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 判断签名是否正确
*
* @param data 待签名数据
* @param key 私钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(
Map data, String key) throws Exception {
if (!data.containsKey("sign")) {
return false;
}
String sign = data.get("sign");
return generateSignature(data, key).equals(sign);
}
统一下单
应用场景:商户系统调用统一下单接口发起支付渠道支付订单
接口地址:http://121.37.16.158:8080/pay/order
提交方式:post提交
注意:
- 金额 money 存在限制,不同通道情况不同(通常 100-9999元)
- 默认支付宝, 对应参数 pay_type = alipay
- 快捷支付(quick-pay) card_no 必传
请求参数
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
mch_id | long | 是 | 商户id | 1002 |
mch_order_id | string(32) | 是 | 商户订单id | 20181202A001 |
money | int | 是 | 订单金额/分 | 1000 |
user_tag | string(32) | 是 | 用户唯一标示 | 用户id,用户电话,用户IP。。。 |
pay_type | string(32) | 是 | 支付类型:附录 | alipay |
trade_type | string(32) | 是 | 支付方式(重要):附录 | native_wap |
notify_url | string(128) | 是 | 商户系统接受异步通知地址, 不可携带参数,请求方式为Post |
http://www.example.com/notify |
redirect_url | string(128) | 否 | 用于商户系统在支付完毕后跳转至自己业务系统 | |
sign | string(64) | 是 | 签名:参见签名算法 | F545F8C26B3811126417E |
card_no | string(32) | ? | only快捷支付(pay_type = quick-pay)必传,其他支付类型忽略此字段。 | 6217920274920374 |
参数示例:
"mch_id" : "1002",
"money" : 100,
"mch_order_id" : "20181129A001",
"notify_url" : "http://www.example.com/pay/notify",
"redirect_url" : "",
"sign" : "F545F8C26B3811126417E3F26F34258E",
"pay_type" : "alipay"
返回结果
参数 | 类型 | 描述 |
---|---|---|
code | string | 网关返回码:参加附录 |
message | string | 网关返回码描述:参加附录 |
type | string | 返回类型:form 与 url |
data | object | 额外信息,部分请求失败会原样返回商户系统请求参数,请求成功data见示例 |
sign | string | 签名,当网关返回码为成功时,会额外返回签名值 |
type 为data 数据的返回结果类型,设为 form 与 url。
商户系统需注意,返回结果:code,message,type,data均参与签名对于返回form,部分通道会返回form。商户需要根据返回形式 type || form 进行相应处理,
系统默认返回url . (特殊通道不能处理除外)
type = url 请求成功示例
{
"code": "A000",
"message": "成功",
"sign": "C67DA60C3775867093B92F9438E39A43",
"type": "url",
"sys_order_id": "20181202035005B000100200000007",
"data": "http://gateway.iexindex.com/ydpay/PayH5New.aspx?price=500&istype=1&return_url=http://gateway.iexindex.com:80/return/AliPay/AlipayThree_return.aspx&payurl=https%3a%2f%2fnwww.kexpay.com%2falipay%3fM2481wNJDW1oehjLTO-2088432126584038-500.00&id=19010612302481020760"
}
请求失败示例:data可能不回返回
{
"code": "A001",
"message": "签名校验失败",
"data": {
"mch_id": 1002,
"money": 100,
"mch_order_id": "20181129A001",
"notify_url": "http://127.0.0.1:8080/pay/notify",
"redirect_url": "",
"sign": "0CA41B17DC8840D56D5B09076D5FC583",
"pay_type": "alipay"
}
}
查询订单
应用场景:商户系统可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
接口地址:http://121.37.16.158:8080/pay/order/query
请求参数
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
mch_id | long | 是 | 商户id | 1002 |
mch_order_id | string(32) | 是 | 商户订单id | 20181202A001 |
sign | string(64) | 是 | 签名:参见签名算法 | F545F8C26B381112641 |
参数示例:
"mch_id" : "1002",
"mch_order_id" : "20181129A001",
"sign" : "F545F8C26B3811126417E3F26F34258E"
返回结果
参数 | 类型 | 描述 |
---|---|---|
code | string | 网关返回码:参加附录 |
message | string | 网关返回码描述:参加附录 |
以下字段在 code 为 A000 (成功) 的时候有返回
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
sys_order_id | string(32) | 是 | 系统订单id | 20181202035005B000100200000007 |
mch_order_id | string(32) | 是 | 商户订单id | 20181202A001 |
money | int | 是 | 订单金额/分 | 1000 |
status | string | 是 | 状态:S - 成功 F - 失败 I - 进行中 |
S 商户系统以此状态为订单判断标准 |
trade_time | string | 否 | 交易时间:yyyy-MM-dd HH:mm:ss | 2018-11-28 00:00:00 |
sign | string(64) | 是 | 签名:参见签名算法 | F545F8C26B3811126417E |
请求成功示例:
{
"code": "A000",
"message": "成功",
"sign": "8789F10F8C45E217E432995438D497C2",
"sys_order_id": "20181202035005B000100200000007",
"mch_order_id": "20181129A001",
"money": 100,
"status": "I"
}
请求失败示例:
{
"code": "A006",
"message": "订单不存在"
}
异步通知notify
应用场景:对应下单接口中的notify_url地址,请求方式POST,支付完成后,系统会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答;商户系统应答非成功时,系统会通过一定策略定期重发通知,时间依此增长 ;
注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
商户系统在成功接收请求后,需返回字符串 success 则表示成功。
返回参数
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
sys_order_id | string(32) | 是 | 系统订单id | 20181202035005B000100200000007 |
mch_order_id | string(32) | 是 | 商户订单id | 20181202A001 |
money | int | 是 | 订单金额/分 | 1000 |
status | string | 是 | 状态:S - 成功 F - 失败 I - 进行中 |
S 商户系统以此状态为订单判断标准 |
trade_time | string | 否 | 交易时间:yyyy-MM-dd HH:mm:ss | 2018-11-28 00:00:00 |
sign | string(64) | 是 | 签名:参见签名算法 | F545F8C26B3811126417E |
//通知结果示例:
"mch_order_id":"1011Alian41255360",
"sys_order_id":"2018121402472700000009",
"money":"11100",
"trade_time":"2018-12-1402:48:21",
"sign":"94064B807C5985CBBF612C62798DF3C1",
"status":"S"
商户系统接受异步请求示例代码java:
public void paynotify(HttpServletRequest request, HttpServletResponse response) {
InputStream inputStream;
OutputStream outputStream = null;
try {
//读取参数
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
outputStream = response.getOutputStream();
//System.out.println(sb.toString());
if (sb.length() == 0) {
outputStream.write("empty".getBytes("UTF-8"));
return;
}
//verify sign
//boolean flag = signVerify(sb.toString(), key);
if (flag) {
//业务处理
outputStream.write("success".getBytes("UTF-8"));
} else {
outputStream.write("sign error".getBytes("UTF-8"));
}
} catch (Exception ex) {
LOGGER.error(ex.getMessage());
} finally {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
单笔代发
应用场景:商户系统按照本接口数据规范将订单信息发往alianpay平台,完成单笔提现功能。
接口地址:http://121.37.16.158:8080/pay/bankPay/order
注意:
- 金额 money 存在限制, 1 - 49999 元 注意金额单位为分
- notify_url 商户传入回调地址。系统将采用post方式给商户发回调,30分钟内通知6次。url不可携带参数。若不传值,请商户调取查询接口获取订单终态,一般系统处理时间 3分钟。
请求参数
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
mch_id | long | 是 | 商户id | 1002 |
mch_order_id | string(32) | 是 | 商户订单id | 20181202A001 |
money | long | 是 | 订单金额/分 | 1000 |
acc_no | string(64) | 是 | 收款卡号 | 621792027492xxxx |
acc_name | string(64) | 是 | 姓名 | 李玉龙 |
bank_address | string | 否 | 银行支行 | 龙华支行 |
mobile | string(32) | 否 | 手机 | 1575537XXXX |
notify_url | string(128) | 否 | 系统采用post方式 | http://www.example.com/notify 处理完返回 ”succes“字符串 就可以了 |
sign | string(64) | 是 | 签名:参见签名算法 | F545F8C26B3811126417E |
参数示例:
"mch_id" : "1002",
"money" : 100,
"mch_order_id" : "20181129A001",
"notify_url" : "http://www.example.com/bankpay/notify",
"acc_no" : "62179202749203XX",
"acc_name" : "李煜",
"pay_type" : 1,
"acc_type" : 1,
"sign" : "F545F8C26B3811126417E3F26F34258E",
返回结果
参数 | 类型 | 描述 |
---|---|---|
code | string | 网关返回码:参加附录 |
message | string | 网关返回码描述:参加附录 |
data | object | 额外信息,失败会原样返回商户系统请求参数,请求成功data见示例 |
data数据结构
参数 | 类型 | 描述 |
---|---|---|
status | string | S - 成功 F - 失败 I - 进行中 商户系统以此来标识订单状态 |
{
"code": "A000",
"message": "success",
"sign": "729C1273CCC9B5F062CB973CE64E73A9",
"sys_order_id": "2190426131128e563b25067421579",
"mch_order_id": "b20190427170483558406199",
"money": 10,
"status": "S"
}
请求失败示例:
{
"code": "A001",
"message": "签名校验失败"
}
异步通知示例代码
form 方式通知 普通post通知
通知参数
{
"code": "A000",
"message": "成功",
"sign": "8789F10F8C45E217E432995438D497C2",
"sys_order_id": "20181202035005B000100200000007",
"mch_order_id": "20181129A001",
"money": 100,
"status": "I"
}
public void paynotify(HttpServletRequest request, HttpServletResponse response) {
InputStream inputStream;
OutputStream outputStream = null;
try {
//读取参数
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
outputStream = response.getOutputStream();
//System.out.println(sb.toString());
if (sb.length() == 0) {
outputStream.write("empty".getBytes("UTF-8"));
return;
}
//verify sign
//boolean flag = signVerify(sb.toString(), key);
if (flag) {
//业务处理
outputStream.write("success".getBytes("UTF-8"));
} else {
outputStream.write("sign error".getBytes("UTF-8"));
}
} catch (Exception ex) {
LOGGER.error(ex.getMessage());
} finally {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
查询订单
应用场景:商户系统可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
接口地址:http://121.37.16.158:8080/pay/bankPay/order/query
请求参数
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
mch_id | long | 是 | 商户id | 1002 |
mch_order_id | string(32) | 是 | 商户订单id | 20181202A001 |
sign | string(64) | 是 | 签名:参见签名算法 | F545F8C26B381112641 |
参数示例:
"mch_id" : "1002",
"mch_order_id" : "20181129A001",
"sign" : "F545F8C26B3811126417E3F26F34258E"
返回结果
参数 | 类型 | 描述 |
---|---|---|
code | string | 网关返回码:参加附录 |
message | string | 网关返回码描述:参加附录 |
以下字段在 code 为 A000 (成功) 的时候有返回
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
sys_order_id | string(32) | 是 | 系统订单id | 20181202035005B000100200000007 |
mch_order_id | string(32) | 是 | 商户订单id | 20181202A001 |
money | int | 是 | 订单金额/分 | 1000 |
status | string | 是 | 状态:S - 成功 F - 失败 I - 进行中 |
S 商户系统以此状态为订单判断标准 |
trade_time | string | 否 | 交易时间:yyyy-MM-dd HH:mm:ss | 2018-11-28 00:00:00 |
sign | string(64) | 是 | 签名:参见签名算法 | F545F8C26B3811126417E |
请求成功示例:
{
"code": "A000",
"message": "成功",
"sign": "8789F10F8C45E217E432995438D497C2",
"sys_order_id": "20181202035005B000100200000007",
"mch_order_id": "20181129A001",
"money": 100,
"status": "I"
}
请求失败示例:
{
"code": "A006",
"message": "订单不存在"
}
附录:
网关返回码
code | message |
---|---|
A000 | 成功 |
A001 | 签名校验失败 |
A002 | 请先上传商户私钥 |
A003 | 非法的请求参数:%s |
A004 | 订单号重复 |
A005 | 金额限制:%s |
A006 | 订单不存在 |
A099 | 下单失败:%s |
A100 | 商户被冻结,请联系管理员 |
A999 | 系统维护 |
支付类型
支付类型 | 参数值 |
---|---|
支付宝 | alipay |
支付宝H5 | aliwap |
微信 | wx |
云闪付 | union |
综合支付 | coin |
拼多多微信 | pdd |
拼多多支付宝 | pdd_zfb |
支付方式
支付类型 | 参数值 |
---|---|
没有自己系统的方式 | native |
自定义扫码界面 | native_api |
显示我们web页面 | native_wap |