简介
FlyPay聚合支付系统
快速接入
- 1. 请联系flypay 运营或者商务人员 获取商户ID和安全秘钥
- 2. 沙盒环境测试 (聚合页面测试工具)
- 3. 联系flypay运营人员开通线上支付
- 4. 具体费率和账期联系flypay商务人员
支付流程图

聚合页面示例图

支付方式(pay_type)
- 对应支付主页url参数show_types 中的取值,及回调通知中pay_type值
- 目前 momo,zalopay,人头卡 都是个人转账方式
- 金额单位:越南盾VND
| 支付方式 | pay_type值 | code | 是否固定金额列表 | 是否开通 | 额度限制 |
|---|---|---|---|---|---|
| 电话卡 | 1 | phonecard | 是 | 是 | 按实际面额 |
| MoMo | 4 | momo | 否 | 是 | 上限2000万,下限1000 |
| 网银聚合 | 36 | Banking | 否 | 是 | 上限5000万,下限10万 |
| ZALO | 39 | ViettelPayQR | 否 | 是 | 上限500万,下限10万 |
| VIETTELPAY | 40 | ZaloPayQR | 否 | 是 | 上限500万,下限10万 |
API签名规则
为了确保交易的安全性,本系统对所有交互API采用了一套加密机制,所有API交互均需要使用此套加密规则,以确保合作伙伴资金财产的安全性。加密规则具体如下:
- 从《支付系统》获取到secret_key;
- 设所有发送或者接收到的数据为集合M,将集合M内参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串String1;
- 在String1字符串后面追加您的secret_key,形成String2;
- 对String2进行MD5运算,再将得到的字符串所有字符转换为大写,得到值hash;
- 发送请求按上述规则设置签名
- 接收通过上述规则校验签名
- 上述规则中hash值的计算方法代码,见右:
# hash 计算方法
def gen_http_request_sign(req_params, secret_key):
# req_params (kv 字典)
keys = req_params.keys()
keys.remove("hash")
s_keys = sorted(keys)
prestr_list = []
for key in s_keys:
value = req_params.get(key)
if value is None:
value = ""
s = str(key) + "=" + str(value)
prestr_list.append(s)
prestr = "&".join(prestr_list)
prestr += secret_key
sign = gen_md5(prestr)
sign = sign.upper()
return sign
// hash 计算方法
function gen_http_request_sign($req_params, $secret_key){
// req_params (kv array)
unset($req_params['hash']);
ksort($req_params);
$signstr = '';
foreach ($req_params as $key => $value) {
$signstr .= $key . '=' . $value . '&';
}
$signstr = rtrim($signstr, '&');
$signstr .= $secret_key;
$signstr = md5($signstr);
$signstr = strtoupper($signstr);
return $signstr;
}
// hash 计算方法
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.SortedMap;
import java.security.MessageDigest;
import java.util.Iterator;
import java.math.BigInteger;
public String gen_http_request_sign(Map<String,Object> params, String secret)
{
params.remove("hash");
// 先将参数以其参数名的字典序升序进行排序
Map<String, Object> sortedTree = new TreeMap<String, Object>(params);
// 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起
StringBuilder basestring = new StringBuilder();
int count = 0;
Iterator<String> it = sortedTree.keySet().iterator();
while(it.hasNext()){
String key = it.next();
String value = sortedTree.get(key).toString();
if(count > 0){
basestring.append("&");
}
basestring.append(key).append("=").append(value);
count++;
}
basestring.append(secret);
try {
// 使用MD5对待签名串求签
byte[] bytes = null;
MessageDigest md5 = MessageDigest.getInstance("MD5");
bytes = md5.digest(basestring.toString().getBytes("UTF-8"));
//16是表示转换为16进制数
String sign = new BigInteger(1, bytes).toString(16).toUpperCase();
return sign;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// hash 计算方法
func gen_http_request_sign(sourceMap map[string]interface{}, bizKey string) string {
orderedString := orderParam(sourceMap, bizKey)
md5Ctx := md5.New()
md5Ctx.Write([]byte(orderedString))
signString := md5Ctx.Sum(nil)
return strings.ToUpper(hex.EncodeToString(signString))
}
func orderParam(source map[string]interface{}, bizKey string) string {
var tempArr []string
i := 0
for k, v := range source {
tempArr = append(tempArr, fmt.Sprintf("%v=%v", k, v))
i++
}
sort.Strings(tempArr)
temString := ""
for n, v := range tempArr {
if n+1 < len(tempArr) {
temString = temString + v + "&"
} else {
temString = temString + v + bizKey
}
}
return temString
}
支付页面param参数生成规则
- 设所有发送或者接收到的数据为集合M,计算集合M的hash (方法见API签名规则)
- 将集合hash添加到集合M 生成新的集合M1;
- 集合M1 json 序列化 生成新的字符串string1;
- 集合string1计算base64 url encode 得到param;
- 示例代码,见右:
# 支付页面param参数生成方法
def gen_param(req_params,secret_key):
signstr = gen_http_request_sign(req_params,secret_key)
req_params['hash'] = signstr
param_str=json.dumps(req_params)
param=base64.urlsafe_b64encode(param_str)
return param
# 支付页面param参数生成方法
function gen_param($req_params,$secret_key){
$signstr = gen_http_request_sign($req_params,$secret_key);
$req_params['hash'] = $signstr;
$param_str=json_encode($req_params);
$param=base64_encode($param_str);
return $param;
}
// 支付页面param参数生成方法
public String gen_param(Map<String, Object> params, String secret_key) {
String signstr = gen_http_request_sign(params, secret_key);
params.put("hash", signstr);
String param_str = JSON.toJSONString(params);
try {
return Base64.getEncoder().encodeToString(param_str.getBytes("utf-8"));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
// 支付页面param参数生成方法
func gen_param(params map[string]interface{}, secret_key string) string {
sign := gen_http_request_sign(params, secret_key)
params["hash"] = sign
paramsStr, _ := json.Marshal(params)
param := base64.URLEncoding.EncodeToString([]byte(paramsStr))
return param
}
mch_url & 通知callback param参数解析规则
- 将param base64 url decone 得到字符串string1;
- 示例代码,见右:
# mch_url & 通知callback param参数解析方法
def parse_url_param(param):
params_str = base64.urlsafe_b64decode(param)
params={}
for key_value in params_str.split( '&' ):
kv = key_value.split('=')
params[kv[0]] = kv[1]
return params
# mch_url & 通知callback param参数解析方法
function parse_url_param($param){
$param_str=base64_decode($param);
parse_str($param_str,$params);
return $params
}
// mch_url & 通知callback param参数解析方法
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public Map<String, Object> parse_url_param(String param) {
String paramstr = new String(Base64.getDecoder().decode(param));
Map<String, Object> map = new HashMap<String, Object>();
String[] keyValues = paramstr.split("&");
for (int i = 0; i < keyValues.length; i++) {
String key = keyValues[i].substring(0, keyValues[i].indexOf("="));
String value = keyValues[i].substring(keyValues[i].indexOf("=") + 1);
map.put(key, value);
}
return map;
}
// mch_url & 通知callback param参数解析方法
func parse_url_param(param string) map[string]string{
base64.URLEncoding.DecodeString(param)
decodeBytes, err := base64.StdEncoding.DecodeString(encodeString)
if err != nil {
//process err
return nil
}
params:=map[string]string{}
for _,v :=range strings.Split(string(decodeBytes),"&") {
kv:=strings.Split(v,"=")
params[kv[0]]=kv[1]
}
return params
}
API endpoint
Production environment(Live):
- https://apipay.flypay.vn + API_URI
Sandbox environment (Test):
- https://test-apipay.flypay.vn + API_URI
每个API的详细文档中都描述了其中的API_URI
接口
1. 创建flypay支付页面(创建支付订单)
- API_URL: /public/comm/index.html?param={param}
- Method: GET
- param: 见param参数生成规则
- 业务参数:
| key | 类型 | 长度说明 | 含义 | 说明 | 取值 |
|---|---|---|---|---|---|
| mch_id | string | 商户ID,平台分配 | 支付平台为商户分配mch_id, 与商户一一对应 | 请联系支付平台工作人员获取 | - |
| mch_uid | string | max 48 bytes | 商户自己的用户uid,由商户平台自己决定赋值内容 | - | - |
| mch_order_id | string | max 48 bytes | 商户订单ID | ||
| equipment_type | int | - | 访问类型 | - | 0: 未知类型; 1: PC浏览器; 2: 移动设备浏览器 |
| expected_amount | int | - | 支付金额 | - | - |
| mch_url | string | max 1024 bytes | 商户的结果页(订单支付有结果后自动跳转) | 必须url safe encode (padding 模式) | |
| show_types | string 格式化字符串,"+"分割支付类型 | - | 定制聚合支付页面中的支付类型 | eg: show_types=1+4 表示支付主页显示电话卡支付及momo支付 | 取值见 支付方式(pay_type),不填则使用平台默认支持的支付方式 |
| attach | string | 最大512 | 商户附加参数 | 只支持ascii 字符,不支持中文,越南文等 | - |
| hash | string | - | API校验规则生成的hash值 | 详见 API签名规则 | - |
mch_url说明:
- 1. 用户在flyapy支付平台支付,得到支付结果后,自动跳转到商户的结果页(mch_url)
- 2. 跳转到mch_url时flypay增加了param参数
- 3. param 为 mch_id, mch_order_id, svr_transaction_id, status, error, error_descr, pay_type 等这些参数url base64 生成 (见mch_url & 通知callback param参数解析规则)
- 例如:
- 商户提供的mch_url="http://host/v1/status?a=1&b=2"
- param = base64.urlsafe_b64encode("mch_id=xxxx&mch_order_id=xxxx")
- 返回举例: http://host/v1/status?a=1&b=2¶m=xxxxxxxx
聚合页面param 生成示例:
# 聚合页面param 生成示例
req_params={
"mch_id":'100000',
"mch_uid":"xxxxxx",
"mch_order_id":'02202008101010000020081034811088',
"equipment_type": 1,
"expected_mount":50000,
"mch_url":"http://xxxxx",
"show_types":"3"
}
secret_key = 'N111110dQfPLTxc'
param = gen_param(req_params,secret_key)
url = endpoint + "/public/comm/index.html?param=" + param
# 聚合页面param 生成示例
$req_params =array("mch_id"=> '100000',
"mch_uid"=> "xxxxxx",
"mch_order_id"=> '02202008101010000020081034811088',
"equipment_type"=> 1,
"expected_mount"=> 50000,
"mch_url"=> "http://xxxxx",
"show_types"=>"3");
$secret_key = 'N111110dQfPLTxc';
$param = gen_param($req_params,$secret_key);
$url = $endpoint . "/public/comm/index.html?param=" . $param
- 沙盒测试,支付页面地址举例:
https://test-apipay.flypay.vn/public/comm/index.html?param=bWNoX2lkPTEwMjIwMiZtY2hfdWlkPTk5MDAmc3ZyX3BsYXRmb3JtPXZudG9wbmV3cyZtY2hfb3JkZXJfaWQ9MDIyMDIwMDYyMzEwMjIwMjRlMjhlNWI2NmI1OTExZWFiNWQwYjA5M2ZlZDk5YjQ5JmVxdWlwbWVudF90eXBlPTImcGF5X3R5cGU9MCZleHBlY3RlZF9tb3VudD0yMDAwMCZyZXR1cm5fdXJsPWh0dHBzJTNBJTJGJTJGYmFpZHUuY29tJTNGYSUzRDEyJTI2YiUzRDIzJm1jaF91cmw9aHR0cHM0ElMkYlMkZiYWlkdS5jb20lM0ZhJTNEMTIlMjZiJTNEMjMmaGFzaD04ZGFkMzMwMzcyMDMzZTg4ZGRiMzc1MTk3NDcyNzE5Zg==
2. 支付订单查询接口
- API_URL: /comm/v1/query_order
- Method: POST
- Content-Type: 'application/json'
- 业务参数:
| key | 类型 | 长度说明 | 含义 | 说明 | 取值 |
|---|---|---|---|---|---|
| mch_id | string | 商户ID,平台分配 | 支付平台为商户分配的mch_id | 请联系支付平台工作人员获取 | - |
| mch_order_id | string | 商户订单号 | |||
| hash | string | - | API校验规则生成的hash值 | 详见API签名规则 | - |
- 请求举例:
请求举例:
{"mch_id":"000100",
"mch_order_id":"0220200328000100f66c126e70ce11eab7f1595321913560",
"hash":"EF962FBF690F1DDB1D62BE61B6633AC9"
}
- 返回:
- Content-Type: 'application/json'
- 返回参数说明:
| key | 类型 | 长度说明 | 含义 | 说明 | 取值 |
|---|---|---|---|---|---|
| ret | int | 0:成功 其他失败 | |||
| msg | string | ret!=0时错误描述 | |||
| data | object | 订单信息 | |||
| data.mch_id | string | 商户ID | 请联系支付平台工作人员获取 | ||
| data.mch_order_id | string | 商户订单号 | |||
| data.attach | string | 商户附加参数 | - | ||
| data.pay_type | int | 支付方式 | |||
| data.amount | int | 支付金额(越南盾) | |||
| data.svr_transaction_id | string | flypay订单号 | |||
| data.status | int | 支付状态详见 支付订单状态 |
- 返回举例:
返回举例:
{
"data": {
"mch_id": "000100",
"mch_order_id": "0220200328000100f66c126e70ce11eab7f1595321913560",
"pay_type": 4,
"amount": 10000,
"svr_transaction_id": "V220200721010001003556434114268020992",
"status": 1,
"attach":"xxxx"
},
"ret": 0
}
3.支付订单通知回调
- 要求商户提供回调HOST及路径, 假设为: https://www.domain.com/uri
- HTTP 请求方式: GET(param加密)
- 回调格式: http://www.domain.com/uri?param={param}
- param 解析后参数说明 (见mch_url & 通知callback param参数解析规则):
| key | 含义 | 说明 | 取值 |
|---|---|---|---|
| mch_id | 商户ID | - | - |
| mch_order_id | 商户订单号 | - | - |
| svr_transaction_id | 支付平台的交易ID | 唯一、字符串、max 64 | |
| amount | 交易越南盾 | - | |
| ts | 回调时间戳 | 秒 | - |
| status | 订单状态 | 支付状态详见 支付订单状态 | |
| error | 失败错误码 | - | - |
| error_descr | 失败描述 | - | - |
| pay_type | 支付类型 | - | - |
| attach | 商户附加参数 | - | - |
| hash | API校验规则生成的hash值 | 详见 API签名规则 | - |
校验
- 校验hash
- 务必校验 amount,mch_id等参数
回调响应:
- 1. Content-Type: 'application/json'
- 2.
响应示例:{"ret": 0} - 3. 如果回调响应中,ret!=0,表示失败,如果回调通知失败,1分钟后通知第二次,若第二次通知失败,则再过1分钟通知第三次,结束通知。
示例:
https://test-apipay.flypay.vn/comm/v1/test_cp_callback?param=YW1vdW50PTEwMDAwJmF0dGFjaD1hdHRhY2gmaGFzaD0wRDBDQzg0NTk1MDVDOUFBQjE0NTg3MkQ3RDZBMzdFNiZtY2hfaWQ9MTAwJm1jaF9vcmRlcl9pZD0wMjIwMjAwMzI4MTAwZjY2YzEyNmU3MGNlMTFlYWI3ZjE1OTk3MzAwOTUyODEmcGF5X3R5cGU9NCZzdGF0dXM9MSZzdnJfdHJhbnNhY3Rpb25faWQ9VjIyMDIwMDkxMDAxMTAwMzY3NDk2ODA1MjQ2NDI5MjA5NiZ0cz0xNTk5NzQxNjc5
4. 代付订单创建
- API_URL: /comm/v1/transfer
- Method: POST
- Content-Type: 'application/json'
- 业务参数:
| key | 类型 | 长度说明 | 含义 | 说明 | 取值 |
|---|---|---|---|---|---|
| mch_id | string | 支付平台为商户分配mch_id, 与商户一一对应 | 请联系支付平台工作人员获取 | - | |
| mch_uid | string | max 48 bytes | 商户自己的用户uid,由商户平台自己决定赋值内容 | - | - |
| mch_order_id | string | max 48 bytes | 商户订单ID | ||
| pay_type | int | - | 支付类型 | - | 取值见 支付方式(pay_type) |
| amount | int | - | 支付金额 | - | - |
| notify_url | string | 通知商户代付结果地址 | |||
| account | string | 收款人账号 | - | ||
| bank_name | string | 收款银行 | pay_type=13 才有此字段 | ||
| payee_name | string | 收款人姓名 | pay_type=13 才有此字段 | ||
| attach | string | 代付备注 | - | ||
| hash | string | - | API校验规则生成的hash值 | 详见 API签名规则 | - |
- 请求举例:
请求举例:
{
"pay_type":13,
"account":"收款人账号",
"hash":"xxxxxxxxxxxxxxxxxx",
"bank_name":"收款银行",
"payee_name":"收款人姓名",
"amount":10000,
"mch_id":"100000",
"notify_url":"https://www.baidu.com",
"mch_order_id":"xxxxxxxxxxxxxxx",
"attach":"funx"
}
- 返回:
- Content-Type: 'application/json'
- 返回参数说明:
| key | 类型 | 长度说明 | 含义 | 说明 | 取值 |
|---|---|---|---|---|---|
| ret | int | 0:成功 其他失败 | |||
| msg | string | ret!=0时错误描述 | |||
| data | object | 订单信息 | |||
| data.error_no | int | 错误码 | |||
| data.error_user_msg | string | 错误描述 | |||
| data.mch_order_id | string | 商户订单号 | |||
| data.svr_transaction_id | string | flypay订单号 | |||
| data.status | int | 代付状态详见 代付订单状态 |
- 返回举例:
返回举例:
{
"data": {
"mch_order_id": "0220200328000100f66c126e70ce11eab7f1595321913560",
"svr_transaction_id": "V220200721010001003556434114268020992",
"status": 0
},
"ret": 0
}
5. 代付订单查询
- API_URL: /comm/v1/query_transfer
- Method: POST
- Content-Type: 'application/json'
- 业务参数:
| key | 类型 | 长度说明 | 含义 | 说明 | 取值 |
|---|---|---|---|---|---|
| mch_id | string | 商户ID,平台分配 | 支付平台为商户分配mch_id, 与商户一一对应 | 请联系支付平台工作人员获取 | - |
| mch_order_id | string | max 48 bytes | 商户订单ID | ||
| hash | string | - | API校验规则生成的hash值 | 详见 API签名规则 | - |
- 请求举例:
请求举例:
{
"mch_id":"100",
"hash":"099881BB4FA17280F0764735FCE32164",
"mch_order_id":"02202008140001009d4eed9ede2411eaa786784f439badb1"
}
- 返回:
- Content-Type: 'application/json'
- 返回参数说明:
| key | 类型 | 长度说明 | 含义 | 说明 | 取值 |
|---|---|---|---|---|---|
| ret | int | 0:成功 其他失败 | |||
| msg | string | ret!=0时错误描述 | |||
| data | object | 订单信息 | |||
| data.mch_id | string | 支付平台为商户分配mch_id, 与商户一一对应 | 请联系支付平台工作人员获取 | - | |
| data.mch_order_id | string | 商户订单ID | |||
| data.amount | int | - | 支付金额 | - | - |
| data.pay_type | int | 支付方式 | 取值见 支付方式(pay_type) | ||
| data.account | string | - | 收款人账号 | - | |
| data.status | int | 代付状态详见 代付订单状态 | |||
| data.error_no | int | 错误码 | |||
| data.error_user_msg | string | 错误描述 |
- 返回举例:
返回举例:
{
"data": {
"account":"xxx",
"amount":10000,
"pay_type":13,
"mch_id":"100000",
"mch_order_id": "02202008140001009d4eed9ede2411eaa786784f439badb1",
"svr_transaction_id": "V220200721010001003556434114268020992",
"status": 0
},
"ret": 0
}
6. 代付订单通知回调
- 要求商户提供回调HOST及路径, 假设为: https://www.domain.com/uri
- HTTP 请求方式: GET(param加密)
- 回调格式: http://www.domain.com/uri?param={param}
- param 解析后参数说明 (见mch_url & 通知callback param参数解析规则):
| key | 含义 | 说明 | 取值 |
|---|---|---|---|
| mch_id | - | - | - |
| mch_order_id | - | - | - |
| svr_transaction_id | 支付平台的交易ID | 唯一、字符串、max 64 | |
| amount | 交易越南盾 | - | |
| ts | 回调时间戳 | 秒 | - |
| status | 订单状态 | 代付状态详见 代付订单状态 | |
| pay_type | 支付类型 | - | - |
| error_no | 错误码 | 代付失败时值有效 | 详见 代付订单通知错误码 |
| hash | API校验规则生成的hash值 | 详见 API签名规则 | - |
校验
- 校验hash
- 务必校验 amount,mch_id等参数
回调响应:
- 1. Content-Type: 'application/json'
- 2.
响应示例:{"ret": 0} - 3. 如果回调响应中,ret!=0,表示失败,如果回调通知失败,1分钟后通知第二次,若第二次通知失败,则再过1分钟通知第三次,结束通知。
示例:
http://www.domain.com/uri?param=YW1vdW50PTEwMDAwJmhhc2g9M0YzNEM2MjkwRkI2OUFDN0NBMEI2ODMzREFERkQxODUmbWNoX2lkPTEwMjIxNyZtY2hfb3JkZXJfaWQ9MjAwOTEwMDg1NDIxMzIwNjQ2JnBheV90eXBlPTEzJnN0YXR1cz0xJnN2cl90cmFuc2FjdGlvbl9pZD1WMjIwMjAwOTEwMDIxMDIyMTczNjc0MDcyMzcwODU3NTc5MDA4JnRzPTE1OTk3Mzg4Nzc=
沙盒测试
电话卡沙盒测试
- 支付接口测试时,填写下述沙盒测试卡信息为沙盒测试,可以得到正确的回调,用于接口测试
- Serial thẻ <=======> "card_id": "345677853005567"
- Mã thẻ <=======> "card_num": "1634908098790345"
baokim 网银沙盒测试
- 沙盒测试环境,直接测试不需要真实支付,自动回调
momo zalopay 银行卡转账
- 测试时,无需真实扫码支付,5秒之后自动回调
错误码 error
TODO
支付订单状态 status
| status | 含义 |
|---|---|
| 0 | 未支付 |
| 1 | 支付成功 |
| 2 | 支付失败 |
代付订单状态 status
| status | 含义 |
|---|---|
| 0 | 未代付 |
| 1 | 代付成功 |
| 2 | 代付失败 |
代付订单通知错误码 error_no
| error_no | 含义 |
|---|---|
| 0 | 未知 |
| 1 | 没有绑定银行卡 |
| 2 | 未注册 |
| 3 | 无效账号 |