接口域名:pay.yilise.com | 版本:v1 | 编码:UTF-8 | 请求格式:JSON
所有POST业务接口同步返回固定结构,code=0000 代表请求成功,其余code均为业务失败
{
"code": "0000",
"msg": "操作成功",
"data": {}
}
请求地址:POST https://pay.yilise.com/api/v1/payout/create
接口功能:接收商户付款请求,验签后创建付款订单并调用渠道代付
| 参数名 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| merchantId | String | 是 | 商户编号,平台分配唯一标识 |
| merchantOrderNo | String | 是 | 商户唯一订单号,不可重复 |
| amount | BigDecimal | 是 | 付款金额(单位:元),必须大于0 |
| currency | String | 是 | 币种编码 |
| ipAddress | String | 是 | 用户IP |
| notifyUrl | String | 是 | 付款结果异步通知URL |
| bankCode | String | 是 | 银行IFSC编码 |
| accountNo | String | 是 | 收款账户号码 |
| accountName | String | 是 | 收款账户持有者姓名 |
| String | 是 | 收款人邮箱 | |
| phone | String | 是 | 收款人电话号码 |
| bankName | String | 是 | 银行名称 |
| subBranch | String | 是 | 支行名称 |
| region | String | 否 | 地区 |
| province | String | 是 | 省份 |
| city | String | 是 | 城市 |
| postalCode | String | 否 | 邮编 |
| address | String | 否 | 地址 |
| bankType | String | 是 | 银行类型枚举:IMPS / UPI |
| remark | String | 否 | 付款备注 |
| sign | String | 是 | 请求参数签名,生成规则参考签名模块 |
| 参数名 | 子参数 | 类型 | 场景 | 说明 |
|---|---|---|---|---|
| code | - | String | 全部返回 | 响应码,0000=下单成功 |
| msg | - | String | 全部返回 | 响应描述 |
| data | - | Object | 成功返回 | 付款订单业务子对象 |
| payoutOrderNo | String | 成功返回 | 平台生成的付款订单号 | |
| status | String | 成功返回 | 付款状态,参考6.1 付款状态枚举 |
POST https://pay.yilise.com/api/v1/payout/create
Content-Type: application/json
{
"merchantId": "M100001",
"merchantOrderNo": "PO20260719001",
"amount": 500.00,
"currency": "INR",
"ipAddress": "192.168.1.100",
"notifyUrl": "https://merchant.com/notify",
"bankCode": "HDFC0001234",
"accountNo": "1234567890",
"accountName": "John Doe",
"email": "john@example.com",
"phone": "+91-9876543210",
"bankName": "HDFC Bank",
"subBranch": "Mumbai Main",
"province": "Maharashtra",
"city": "Mumbai",
"bankType": "IMPS",
"remark": "Salary payment",
"sign": "xxxxxxxxxxxxxxxxxxxx"
}
{
"code": "0000",
"msg": "下单成功",
"data": {
"payoutOrderNo": "PW20260719123456",
"status": "PAYOUT_ING"
}
}
请求地址:POST https://pay.yilise.com/api/v1/payout/query
接口功能:主动查询付款订单实时状态,若订单未到终态会主动向渠道同步最新状态
| 参数名 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| merchantId | String | 是 | 商户编号,平台分配唯一标识 |
| merchantOrderNo | String | 否 | 平台付款订单号和商户订单号二选一 |
| payoutOrderNo | String | 否 | 平台付款订单号和商户订单号二选一 |
| sign | String | 是 | 请求参数签名,生成规则参考签名模块 |
| 参数名 | 子参数 | 类型 | 场景 | 说明 |
|---|---|---|---|---|
| code | - | String | 全部返回 | 0000=查询成功 |
| msg | - | String | 全部返回 | 响应描述 |
| data | - | Object | 成功返回 | 付款订单详情子对象 |
| respCode | String | 成功返回 | 业务响应码 | |
| respDesc | String | 成功返回 | 业务响应描述 | |
| payoutOrderNo | String | 成功返回 | 平台付款订单号 | |
| merchantOrderNo | String | 成功返回 | 商户订单号 | |
| status | String | 成功返回 | 付款状态,参考6.1 付款状态枚举 | |
| payoutSuccessTime | String | 付款成功订单 | 付款完成时间 yyyy-MM-dd HH:mm:ss |
POST https://pay.yilise.com/api/v1/payout/query
Content-Type: application/json
{
"merchantId": "M100001",
"payoutOrderNo": "PW20260719123456",
"sign": "xxxxxxxxxxxxxxxxxxxx"
}
{
"code": "0000",
"msg": "查询成功",
"data": {
"respCode": "0000",
"respDesc": "查询成功",
"payoutOrderNo": "PW20260719123456",
"merchantOrderNo": "PO20260719001",
"status": "PAYOUT_SUCCESS",
"payoutSuccessTime": "2026-07-19 14:30:00"
}
}
请求地址:POST https://pay.yilise.com/api/v1/payout/balance
接口功能:查询商户可用余额,可用余额 = 收入结算金额汇总 - 支出结算金额汇总
| 参数名 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| merchantId | String | 是 | 商户编号,平台分配唯一标识 |
| sign | String | 是 | 请求参数签名,生成规则参考签名模块 |
| 参数名 | 子参数 | 类型 | 场景 | 说明 |
|---|---|---|---|---|
| code | - | String | 全部返回 | 0000=查询成功 |
| msg | - | String | 全部返回 | 响应描述 |
| data | - | Object | 成功返回 | 余额详情子对象 |
| respCode | String | 成功返回 | 业务响应码 | |
| respDesc | String | 成功返回 | 业务响应描述 | |
| availableBalance | BigDecimal | 成功返回 | 可用余额 |
POST https://pay.yilise.com/api/v1/payout/balance
Content-Type: application/json
{
"merchantId": "M100001",
"sign": "xxxxxxxxxxxxxxxxxxxx"
}
{
"code": "0000",
"msg": "查询成功",
"data": {
"respCode": "0000",
"respDesc": "查询成功",
"availableBalance": 125680.50
}
}
重要说明: 1、异步通知为平台主动POST推送,商户收到通知校验签名后,返回纯文本 success 表示接收成功,否则平台会重试推送。 2、只在付款成功时推送
{
"respCode": "0000",
"respDesc": "付款成功",
"merchantOrderNo": "PO20260719001",
"payoutOrderNo": "PW20260719123456",
"status": "PAYOUT_SUCCESS",
"payoutSuccessTime": "2026-07-19 14:30:00",
"sign": "xxxxxxxxxxxxxxxxxxxx"
}
| 字段名 | 说明 |
|---|---|
| respCode | 响应码,不用作业务判断的依据 |
| respDesc | 响应描述 |
| merchantOrderNo | 商户订单号 |
| payoutOrderNo | 平台付款订单号 |
| status | 付款状态,仅PAYOUT_SUCCESS为付款成功 |
| payoutSuccessTime | 付款完成时间 |
| sign | 回调参数签名,校验规则参考签名模块 |
| 状态码 | 中文名称 | 业务说明 |
|---|---|---|
| PAYOUT_ING | 付款中 | 已提交渠道,等待渠道返回结果 |
| PAYOUT_SUCCESS | 付款成功 | 渠道返回付款成功,资金已转出 |
| PAYOUT_FAILED | 付款失败 | 渠道返回付款失败,资金未转出 |
| 枚举值 | 说明 |
|---|---|
| IMPS | 即时支付结算(Immediate Payment Service) |
| UPI | 统一支付接口(Unified Payments Interface) |
1. 业务判断标准:同步接口仅 code=0000 视为调用成功;付款订单仅 status=PAYOUT_SUCCESS 视为付款完成。
2. 商户订单号全局唯一,重复提交会返回错误码,需做好幂等控制。
3. ipAddress 必须传入用户真实外网IP,内网IP会校验失败。
4. notifyUrl 必须为公网可访问地址,不能携带临时鉴权Token。
5. 所有请求、异步回调必须校验签名,未校验签名直接拒绝业务处理,防止恶意伪造请求(签名规则参考签名模块)。
6. 业务逻辑优先以异步通知结果为准;未收到回调时,使用付款查询接口轮询补单。
7. 异步通知需做好幂等处理,避免多次回调导致重复业务操作。
sign字段key1=value1&key2=value2格式的字符串sign参数值package com.xpay.common.util;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class RSAUtil {
private static final String RSA_KEY_ALGORITHM = "RSA";
private static final String RSA_SIGNATURE_ALGORITHM = "SHA1withRSA";
private static final String RSA2_SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final int KEY_SIZE = 1024;
public static Map<String, String> generateKey() {
KeyPairGenerator keygen;
try {
keygen = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("RSA初始化密钥出现错误,算法异常");
}
SecureRandom secrand = new SecureRandom();
secrand.setSeed(String.valueOf(System.currentTimeMillis()).getBytes());
keygen.initialize(KEY_SIZE, secrand);
KeyPair keyPair = keygen.genKeyPair();
byte[] pub_key = keyPair.getPublic().getEncoded();
String publicKeyStr = Base64.getEncoder().encodeToString(pub_key);
byte[] pri_key = keyPair.getPrivate().getEncoded();
String privateKeyStr = Base64.getEncoder().encodeToString(pri_key);
Map<String, String> keyPairMap = new HashMap<>();
keyPairMap.put("publicKeyStr", publicKeyStr);
keyPairMap.put("privateKeyStr", privateKeyStr);
return keyPairMap;
}
public static String encryptByPublicKey(String data, String publicKeyStr) {
try {
byte[] decodePublicKeyByte = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodePublicKeyByte);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytesContent = data.getBytes();
int inputLen = bytesContent.length;
int offLen = 0;
int i = 0;
ByteArrayOutputStream bops = new ByteArrayOutputStream();
while (inputLen - offLen > 0) {
byte[] cache;
if (inputLen - offLen > 117) {
cache = cipher.doFinal(bytesContent, offLen, 117);
} else {
cache = cipher.doFinal(bytesContent, offLen, inputLen - offLen);
}
bops.write(cache);
i++;
offLen = 117 * i;
}
bops.close();
byte[] encryptedData = bops.toByteArray();
String encode = Base64.getEncoder().encodeToString(encryptedData);
return encode;
} catch (Exception e) {
log.error("加密失败:", e);
return "";
}
}
public static String decryptByPrivateKey(String data, String privateKeyStr) throws Exception {
try {
byte[] priKey = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytesContent = Base64.getDecoder().decode(data.getBytes());
int inputLen = bytesContent.length;
int offLen = 0;
int i = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while (inputLen - offLen > 0) {
byte[] cache;
if (inputLen - offLen > 128) {
cache = cipher.doFinal(bytesContent, offLen, 128);
} else {
cache = cipher.doFinal(bytesContent, offLen, inputLen - offLen);
}
byteArrayOutputStream.write(cache);
i++;
offLen = 128 * i;
}
byteArrayOutputStream.close();
byte[] byteArray = byteArrayOutputStream.toByteArray();
return new String(byteArray);
} catch (Exception e) {
log.error("解密失败,待加密内容:{}", data, e);
return "";
}
}
public static String sign(byte[] data, byte[] priKey, String signType) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;
Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(data);
byte[] sign = signature.sign();
return Base64.getEncoder().encodeToString(sign);
}
public static boolean verify(byte[] data, byte[] sign, byte[] pubKey, String signType) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;
Signature signature = Signature.getInstance(algorithm);
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(sign);
}
}
package com.xpay.common.util;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
@Slf4j
public class SignUtil {
private static final String SIGN_KEY = "sign";
private static final String CONNECT = "&";
private static final String CONNECT_EQUAL = "=";
public static String sign(Object signContent, String privateKey) {
try {
String signMsg = wrapSignData(signContent);
String sign = RSAUtil.sign(signMsg.getBytes(), Base64.getDecoder().decode(privateKey), "RSA");
log.info("待签名字符串:{},计算所得签名:{}", signMsg, sign);
return sign;
} catch (Exception e) {
log.error("请求信息:{},签名处理失败:", JSON.toJSONString(signContent), e);
return "";
}
}
private static String wrapSignData(Object signContent) {
Map<String, Object> signContentMap;
if (signContent instanceof Map) {
signContentMap = (Map<String, Object>) signContent;
} else {
signContentMap = BeanUtil.beanToMap(signContent, false, true);
}
return buildSignData(signContentMap);
}
private static String buildSignData(Map<String, Object> signContentMap) {
signContentMap.remove(SIGN_KEY);
List<String> keys = new ArrayList<>(signContentMap.keySet());
Collections.sort(keys);
StringBuffer content = new StringBuffer();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
Object value = signContentMap.get(key);
if (Objects.isNull(value)) {
continue;
}
if (value instanceof List) {
value = JSON.toJSONString(value);
}
if (i != keys.size() - 1) {
content.append(key).append(CONNECT_EQUAL).append(value).append(CONNECT);
} else {
content.append(key).append(CONNECT_EQUAL).append(value);
}
}
return content.toString();
}
public static Boolean verifySign(Object signContent, String sign, String publicKey) {
try {
String signData = wrapSignData(signContent);
log.info("待签名字符串:{},签名:{}", signData, sign);
return RSAUtil.verify(signData.getBytes(), Base64.getDecoder().decode(sign), Base64.getDecoder().decode(publicKey), "RSA");
} catch (Exception e) {
log.error("请求信息:{},验证处理异常:", JSON.toJSONString(signContent), e);
return false;
}
}
}