接口域名:pay.yilise.com | 版本:v1 | 编码:UTF-8 | 请求格式:JSON
所有POST业务接口同步返回固定结构,code=0000 代表请求成功,其余code均为业务失败
{
"code": "0000",
"msg": "操作成功",
"data": {}
}
请求地址:POST https://pay.yilise.com/api/v1/order/create
接口功能:创建支付订单,返回H5收银台跳转链接
| 参数名 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| merchantId | String | 是 | 商户编号,平台分配唯一标识 |
| merchantOrderNo | String | 是 | 商户唯一订单号,不可重复 |
| amount | BigDecimal | 是 | 订单金额 |
| currency | String | 是 | 币种编码 |
| description | String | 是 | 订单描述 |
| clientIp | String | 是 | 客户端IP |
| deviceType | String | 是 | 终端设备类型枚举:IOS / ANDROID / WINDOWS / LINUX / MACOS |
| systemVersion | String | 是 | 终端系统版本 |
| notifyUrl | String | 是 | 支付结果异步回调地址 |
| returnUrl | String | 否 | 支付成功页面跳转地址 |
| expireMin | Integer | 否 | 订单支付超时时间,单位分钟 |
| payerFirstName | String | 否 | 付款人姓 |
| payerLastName | String | 否 | 付款人名 |
| payerPhone | String | 否 | 付款人手机号 |
| payerEmail | String | 否 | 付款人邮箱 |
| sign | String | 是 | 请求参数签名,生成规则参考签名模块 |
| 参数名 | 子参数 | 类型 | 场景 | 说明 |
|---|---|---|---|---|
| code | - | String | 全部返回 | 响应码,0000=下单成功 |
| msg | - | String | 全部返回 | 响应描述 |
| data | - | Object | 成功返回 | 订单业务子对象 |
| orderNo | String | 成功返回 | 平台生成唯一订单号 | |
| cashierForwardUrl | String | 成功返回 | H5收银台跳转链接 |
POST https://pay.yilise.com/api/v1/order/create
Content-Type: application/json
{
"merchantId": "MCH202607030001",
"merchantOrderNo": "M202607030001",
"amount": 9900,
"currency": "CNY",
"description": "月度会员充值套餐",
"clientIp": "113.20.12.34",
"deviceType": "ANDROID",
"systemVersion": "14",
"notifyUrl": "https://pay.yilise.com/notify",
"returnUrl": "https://pay.yilise.com/return",
"expireMin": 30,
"payerFirstName": "james",
"payerLastName": "lbj",
"payerPhone": "XXXXXXXXXXXX",
"payerEmail": "11@outlook.com",
"sign": "xxxxxxxxxxxxxxxxxxxx"
}
{
"code": "0000",
"msg": "下单成功",
"data": {
"orderNo": "20260703123023000001",
"cashierForwardUrl": "https://pay.yilise.com/api/v1/order/pay/20260703123023000001"
}
}
请求地址:POST https://pay.yilise.com/api/v1/order/query
接口功能:主动查询订单实时支付状态
| 参数名 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| merchantId | String | 是 | 商户编号,平台分配唯一标识 |
| orderNo | String | 否 | 支付平台订单号和商户订单号二选一 |
| merchantOrderNo | String | 否 | 支付平台订单号和商户订单号二选一 |
| sign | String | 是 | 请求参数签名,生成规则参考签名模块 |
| 参数名 | 子参数 | 类型 | 场景 | 说明 |
|---|---|---|---|---|
| code | - | String | 全部返回 | 0000=查询成功 |
| msg | - | String | 全部返回 | 响应描述 |
| data | - | Object | 成功返回 | 订单详情子对象 |
| merchantId | String | 成功返回 | 商户编号 | |
| orderNo | String | 成功返回 | 平台订单号 | |
| merchantOrderNo | String | 成功返回 | 商户订单号 | |
| status | String | 成功返回 | 订单状态,参考5.1 订单状态枚举 | |
| paidTime | String | 已支付订单 | 支付完成时间 yyyy-MM-dd HH:mm:ss |
POST https://pay.yilise.com/api/v1/query
Content-Type: application/json
{
"merchantId": "MCH202607030001",
"merchantOrderNo": "M202607030001",
"sign": "xxxxxxxxxxxxxxxxxxxx"
}
{
"code": "0000",
"msg": "查询成功",
"data": {
"respCode": "0000",
"respDesc": "支付成功",
"merchantId": "MCH202607030001",
"orderNo": "20260703123023000001",
"merchantOrderNo": "M202607030001",
"status": "PAID",
"paidTime": "2026-07-03 12:30:30"
}
}
重要说明: 1、异步通知为平台主动POST推送,商户收到通知校验签名后,返回纯文本 success 表示接收成功,否则平台会重试推送。 2、只在支付成功时推送
{
"respCode": "0000",
"respDesc": "支付成功",
"merchantOrderNo": "2026070310001",
"orderNo": "20260703123023000001",
"status": "PAID",
"paidTime": "2026-07-03 12:30:30",
"sign": "xxxxxxxxxxxxxxxxxxxx"
}
| 字段名 | 说明 |
|---|---|
| respCode | 响应码,不用作业务判断的依据 |
| respDesc | 响应描述 |
| merchantOrderNo | 商户订单号 |
| orderNo | 平台订单号 |
| status | 订单状态,仅PAID为支付成功 |
| paidTime | 支付完成时间 |
| sign | 回调参数签名,校验规则参考签名模块 |
| 状态码 | 中文名称 | 业务说明 |
|---|---|---|
| PENDING | 待支付 | 订单创建,用户未发起付款 |
| PAYING | 支付中 | 用户已提交付款,等待渠道返回结果 |
| PAID | 已支付 | 支付成功,可执行发货/充值等业务 |
| FAILED | 支付失败 | 付款失败,资金未扣款 |
| CLOSED | 已关闭 | 订单超时/手动关闭,无法再支付 |
| 枚举值 | 说明 |
|---|---|
| IOS | 苹果手机/平板设备 |
| ANDROID | 安卓手机设备 |
| WINDOWS | Windows电脑客户端 |
| LINUX | Linux客户端 |
| MACOS | Mac电脑客户端 |
1. 业务判断标准:同步接口仅 code=0000 视为调用成功;订单仅 status=PAID 视为支付完成。
2. 商户订单号全局唯一,重复提交会返回错误码,需做好幂等控制。
3. clientIp 必须传入用户真实外网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 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 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 signContentMap;
if (signContent instanceof Map) {
signContentMap = (Map) signContent;
} else {
signContentMap = BeanUtil.beanToMap(signContent, false, true);
}
return buildSignData(signContentMap);
}
private static String buildSignData(Map signContentMap) {
signContentMap.remove(SIGN_KEY);
List 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;
}
}
}