版本:V1.0.3 - 更新时间:
本文阅读对象:使用 泰达U选 商户自服务系统的技术架构师、研发工程师、系统运维工程师。通过本文档,商户可了解 泰达U选 接入的技术、接入的产品业务、接入的流程、接入规范等信息,以便于商户顺利完成接入工作。
网关地址:/getway.html
提交方式:POST
| 参数名称 | 参数含义 | 是否必填 | 参与签名 | 参数说明 |
|---|---|---|---|---|
| merchantid | 商户号 | 是 | 是 | 平台分配商户号 |
| network | 主网 | 是 | 是 | 主网 1: mainnet, 测试 2: test [用于调试,正式环境请使用 1] |
| orderid | 订单号 | 是 | 是 | 商户订单号唯一, 字符长度20 |
| create_time | 提交时间 | 是 | 是 | unix时间戳:1626373569 |
| cashier | 是否返回Json(整数) | 否 | 是 | 1:返回json数据、2:跳转到收银台 |
| notifyurl | 异步通知地址 | 是 | 是 | 用于回调通知订单状态.(POST返回数据) |
| amount | 订单金额(USD) | 是 | 是 | 保留4小数,不四舍五入 |
| productname | 商品名称 | 否 | 是 | 商品描述信息 |
| sign | MD5签名 | 是 | 否 | 请查看签名算法 |
| 参数名称 | 参数含义 | 参数说明 |
|---|---|---|
| status | 状态码 | 1:成功,0:失败 |
| message | 返回消息 | 状态说明 |
| data | 返回数据 | 包含订单信息的JSON对象 |
| data.qrcode_url | 二维码URL | 收款二维码链接 |
| data.account_address | 收款地址 | USDT收款钱包地址 |
| data.amount | 支付金额 | 实际需要支付的USDT金额 |
| data.orderid | 商户订单号 | 商户提交的订单号 |
| data.platform_orderid | 平台订单号 | 系统生成的唯一订单号(以PYS开头) |
| data.create_time | 创建时间 | 订单创建时间 |
| data.time_out | 超时时间 | 订单有效期(秒) |
| data.pay_code | 支付类型 | 1: 以太坊 ERC20 、2:波场 TRC20 |
| data.cashier_url | 收银台地址 | 当cashier=2时返回收银台跳转链接 |
POST /getway.html HTTP/1.1 Content-Type: application/x-www-form-urlencoded merchantid=1&network=1&orderid=TEST20230101000001&create_time=1626373569&cashier=1¬ifyurl=https://您的域名/notify.php&amount=10.0000&productname=测试商品&sign=A1B2C3D4E5F6G7H8I9J0
当请求参数cashier=1时,返回JSON和二维码示例:
{
"status": 1,
"message": "订单创建成功",
"data": {
"qrcode": "data:image/png;base64",
"account_address": "TFpS9NJ4Djm29RTmax3VonXL8HumgrC4zw",
"amount": "10.69",
"platform_orderid": "PYS2025032450595",
"orderid": "202503201546059331",
"create_time": "2025-03-20 15:46:05",
"time_out": 1800,
"pay_code": "2"
}
}
当请求参数cashier=2时,返回收银台URL示例:
{
"status": 1,
"message": "订单创建成功",
"data": {
"cashier_url": "http://loc.weepay.com:8888/index/index/cashier.html?osn=202503201544321601",
"amount": "10.78",
"orderid": "202503201544321601",
"create_time": "2025-03-20 15:44:32",
"time_out": 1800,
"pay_code": "2"
}
}
返回参数说明:
提交方式:POST
| 参数名称 | 参数含义 | 是否必填 | 参与签名 | 参数说明 |
|---|---|---|---|---|
| code | 状态码 | 是 | 否 | 1:成功,0:失败 |
| msg | 消息 | 是 | 否 | 返回消息说明 |
| data | 数据对象 | 是 | 是 | 包含订单信息的JSON对象 - data非空字段参与签名 |
| data.merchantid | 商户编号 | 是 | 是 | 平台分配商户号 |
| data.orderid | 平台订单号 | 是 | 是 | 系统生成的唯一订单号(以PYS开头) |
| data.out_trade_id | 商户订单号 | 是 | 是 | 商户提交的原始订单号 |
| data.amount | 订单金额 | 是 | 是 | 支付金额,单位USDT |
| data.poundage | 手续费 | 是 | 是 | 手续费金额,单位USDT |
| data.status | 订单状态 | 是 | 是 | 0:未支付,1:支付中,2:已支付,3:订单超时 |
| sign | MD5签名 | 是 | 否 | 请查看签名算法 |
{
"code": 1,
"msg": "success",
"data": {
"merchantid": "1",
"orderid": "PYS2025032450595",
"out_trade_id": "202503241012158778",
"amount": "10.7300",
"poundage": "0.1073",
"status": 2,
"sign": "63FB0B1B2340AB0E0801FE0F249012E4"
}
}
提交方式:POST
地址:/query.html
| 参数名称 | 参数含义 | 是否必填 | 参与签名 | 参数说明 |
|---|---|---|---|---|
| merchantid | 商户编号 | 是 | 是 | 平台分配的商户ID |
| orderid | 商户订单号 | 是 | 是 | 商户系统的订单号 |
| query_type | 查询类型 | 否 | 是 | payment: 支付订单, payout: 代付订单, 空值: 自动判断 |
| sign | MD5签名 | 是 | 否 | 请查看签名算法 |
| 参数名称 | 参数含义 | 参数说明 |
|---|---|---|
| orderid | 平台订单号 | 系统生成的订单编号 |
| out_trade_id | 商户订单号 | 商户提交的订单号 |
| amount | 订单金额 | 单位:usdt |
| time_end | 支付成功时间 | 格式:yyyy-mm-dd HH:ii:ss |
| trade_state | 支付状态 | 支付订单:SUCCESS:支付成功,NOTPAY:未支付 代付订单:SUCCESS:代付成功,NOTPAY:未支付,PAYING:代付处理中,PAY_FAILED:代付失败 |
| order_type | 订单类型 | payment:支付订单,payout:代付订单 |
| status_text | 状态描述 | 订单状态的中文描述 |
POST /query.html HTTP/1.1 Content-Type: application/x-www-form-urlencoded merchantid=1&orderid=TEST20230101000001&query_type=payment&sign=A1B2C3D4E5F6G7H8I9J0
{
"code": 1,
"msg": "查询支付订单成功",
"data": {
"orderid": "PAY202301010001",
"out_trade_id": "TEST20230101000001",
"amount": "10.0000",
"time_end": "2023-01-01 12:05:30",
"trade_state": "SUCCESS",
"order_type": "payment",
"status_text": "已支付",
"sign": "B2C3D4E5F6G7H8I9J0K1"
}
}
系统会自动判断订单类型,并在查询结果中返回order_type字段标识订单类型。如需查询指定类型的订单,可以在请求参数中添加query_type参数。
提交方式:POST
地址:/payout.html
| 参数名称 | 参数含义 | 是否必填 | 参与签名 | 参数说明 |
|---|---|---|---|---|
| merchantid | 商户编号 | 是 | 是 | 平台分配商户号 |
| network | 主网 | 是 | 是 | 主网 1: mainnet, 测试 2: ropsten |
| orderid | 订单号 | 是 | 是 | 商户订单号唯一, 字符长度20 |
| create_time | 提交时间 | 是 | 是 | unix时间戳:1626373569 |
| pay_type | 币种选择(整数) | 是 | 是 | 币种选择 usdt: 1 |
| notifyurl | 服务端通知 | 是 | 是 | 服务端返回地址.(POST返回数据) |
| withdraw_query_url | 代付二次确认URL | 如果开启二次确认,则需要传入这个参数 | 是 | 商户需要是否二次确认,如果不需要则可以不传这个参数。 |
| pay_code | 支付类型(整数) | 是 | 是 | 1: 以太坊 ERC20 、2:波场 TRC20 |
| amount | 订单金额 | 是 | 是 | 保留4小数,不四舍五入 |
| address | 代付地址 | 是 | 是 | USDT收款地址(ERC20或TRC20) |
| sign | MD5签名 | 是 | 否 | 请查看签名算法 |
| version | 接口版本号 | 否 | 是 | '1.0.0' |
根据商户需要是否二次确认,如果不需要则可以不传这个参数。
如果需要二次确认,则需要传入一个回查URL地址,当商户提交代付订单API后会回查这个地址,并带入商户订单号,代付金额,代付地址。
{
"orderId": "1234567890",
"amount": "10.0000",
"address": "TYour_TRC20_Address_Here"
}
对应返回值:
{
"code": 10001,
"message": "订单被恶意修改。"
}
或者
{
"code": 10000,
"message": "订单验证通过。"
}
如果订单验证通过,则继续执行代付操作。否则返回订单被恶意修改。并且代付订单不会进入平台进行任何处理。
二次确认回调地址需要商户自己提供,并且需要保证地址能够被公网访问。
二次确认回调地址需要支持POST请求,并返回JSON数据。
二次确认回调地址需要支持跨域请求。
二次确认回调地址需要支持HTTPS请求。
| 参数名称 | 参数含义 | 参数说明 |
|---|---|---|
| code | 结果代码 | success:成功,0:失败 |
| msg | 结果消息 | 接口返回的消息说明 |
| data | 返回数据 | JSON对象,包含订单详情 |
| data.orderid | 商户订单号 | 商户提交的原始订单号 |
| data.platform_orderid | 平台订单号 | 系统生成的平台内部订单号(以DFS开头) |
| data.amount | 订单金额 | 代付金额,单位USDT |
| data.poundage | 手续费 | 代付手续费,单位USDT |
| data.address | 代付地址 | USDT收款地址 |
| data.create_time | 创建时间 | 订单创建时间,格式:yyyy-mm-dd HH:ii:ss |
| data.network | 主网 | 1: mainnet, 2: 测试网 |
| data.pay_code | 支付类型 | ERC20: 以太坊, TRC20: 波场 |
| data.pay_type | 币种 | USDT: USDT代币 |
| data.status | 订单状态 | 0:未支付, 1:处理中, 2:已支付, 3:订单超时, 4:失败, 5:拒绝 |
| data.status_desc | 状态描述 | 订单状态的文字描述 |
POST /payout.html HTTP/1.1 Content-Type: application/x-www-form-urlencoded merchantid=1&network=1&orderid=202503281035406411&create_time=1626373569&pay_type=1¬ifyurl=https://您的域名/notify.php&pay_code=2&amount=10.0000&address=TYour_TRC20_Address_Here&sign=A1B2C3D4E5F6G7H8I9J0&version=1.0.0
{
"code": "success",
"msg": "代付订单提交成功",
"data": {
"orderid": "202503281035406411",
"platform_orderid": "DFS2025032885781",
"amount": "10.0000",
"poundage": "1.00",
"address": "TYour_TRC20_Address_Here",
"create_time": "2025-03-28 10:35:40",
"network": "1",
"pay_code": "TRC20",
"pay_type": "USDT",
"status": 1,
"status_desc": "代付处理中"
}
}
提交方式:POST
| 参数名称 | 参数含义 | 是否必填 | 参与签名 | 参数说明 |
|---|---|---|---|---|
| merchantid | 商户编号 | 是 | 是 | 平台分配商户号 |
| orderid | 系统订单号 | 是 | 是 | 系统生成的唯一订单号(以DF开头) |
| out_trade_id | 商户订单号 | 是 | 是 | 商户提交的原始订单号 |
| amount | 订单金额 | 是 | 是 | 代付金额,单位USDT |
| status | 订单状态 | 是 | 是 | 0:未支付,1:支付中,2:已支付,3:订单超时,4:失败,5:拒绝 |
| time | 通知时间 | 是 | 是 | unix时间戳 |
| sign | MD5签名 | 是 | 否 | 请查看签名算法 |
{
"code": 1,
"msg": "success",
"data": {
"merchantid": 1,
"orderid": "DFS2025032846917",
"out_trade_id": "202503281034025239",
"amount": "10.0000",
"transaction_id": "",
"status": 2,
"time": 1743944210,
"remark": "已支付",
"sign": "DE987378BAC0AC03976A79AD17060310"
}
}
提交方式:POST
地址:/balance.html
| 参数名称 | 参数含义 | 是否必填 | 参与签名 | 参数说明 |
|---|---|---|---|---|
| merchantid | 商户编号 | 是 | 是 | 平台分配的商户ID |
| timestamp | 时间戳 | 是 | 是 | unix时间戳,5分钟内有效 |
| sign | MD5签名 | 是 | 否 | 请查看签名算法 |
| 参数名称 | 参数含义 | 参数说明 |
|---|---|---|
| code | 状态码 | 1:成功,0:失败 |
| msg | 返回消息 | 状态说明 |
| data | 返回数据 | 包含余额信息的JSON对象 |
| data.merchantid | 商户ID | 商户唯一标识 |
| data.username | 商户名称 | 商户账户名 |
| data.balance | 账户余额 | 商户当前可用余额,单位USDT |
| data.currency | 货币类型 | 余额的货币类型,固定为USDT |
| data.query_time | 查询时间 | 格式:yyyy-mm-dd HH:ii:ss |
POST /balance.html HTTP/1.1 Content-Type: application/x-www-form-urlencoded merchantid=1×tamp=1711603200&sign=A1B2C3D4E5F6G7H8I9J0
{
"code": 1,
"msg": "查询余额成功",
"data": {
"merchantid": "1",
"username": "merchant_demo",
"balance": "1000.0000",
"currency": "USDT",
"query_time": "2025-03-28 10:35:40"
}
}
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为数组M,将数组M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1+value1+key2+value2…)拼接成字符串stringA。
第二步,在stringA最后拼接上应用private_key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
原始参数:
{
"amount": "300.0000",
"callbackurl": "http://www.baidu.com/callbackurl.html",
"cashier": "2",
"create_time": "1626376050",
"default_currency": "cny",
"merchantid": "2",
"network": "1",
"notifyurl": "http://www.baidu.com/notifyurl.html",
"orderid": "out2021071587481",
"pay_code": "1",
"pay_type": "1",
"productname": "测试商品",
"version": "1.0.0"
}
私钥:
K0d8F434vUjVc88vxkDxmC0z8cj0UZ
第一步:按照参数名ASCII码从小到大排序
amount300.0000callbackurlhttp://www.baidu.com/callbackurl.htmlcashier2create_time1626376050default_currencycnymerchantid2network1notifyurlhttp://www.baidu.com/notifyurl.htmlorderidout2021071587481pay_code1pay_type1productname测试商品version1.0.0
第二步:拼接私钥
stringSignTemp="amount300.0000callbackurlhttp://www.baidu.com/callbackurl.htmlcashier2create_time1626376050default_currencycnymerchantid2network1notifyurlhttp://www.baidu.com/notifyurl.htmlorderidout2021071587481pay_code1pay_type1productname测试商品version1.0.0K0d8F434vUjVc88vxkDxmC0z8cj0UZ"
第三步:生成MD5并转大写
sign=MD5(stringSignTemp).toUpperCase()
import java.security.MessageDigest;
import java.util.*;
public class WeePaySignUtil {
private static final String CHARSET = "UTF-8";
/**
* 生成签名
* @param params 请求参数
* @param privateKey 商户私钥
* @return 签名结果
*/
public static String generateSign(Map params, String privateKey) {
try {
// 1. 过滤空值和sign参数
Map filteredParams = new TreeMap<>();
for (Map.Entry entry : params.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isEmpty()
&& !"sign".equals(entry.getKey())) {
filteredParams.put(entry.getKey(), entry.getValue());
}
}
// 2. 按照参数名ASCII码从小到大排序
StringBuilder stringA = new StringBuilder();
for (Map.Entry entry : filteredParams.entrySet()) {
stringA.append(entry.getKey()).append(entry.getValue());
}
// 3. 拼接私钥
String stringSignTemp = stringA.toString() + privateKey;
// 4. MD5加密并转大写
return md5(stringSignTemp).toUpperCase();
} catch (Exception e) {
throw new RuntimeException("生成签名失败", e);
}
}
/**
* MD5加密
* @param text 待加密文本
* @return 加密结果
*/
private static String md5(String text) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(text.getBytes(CHARSET));
return bytesToHex(bytes);
}
/**
* 字节数组转16进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* 使用示例
*/
public static void main(String[] args) {
// 配置信息
String merchantId = "1";
String privateKey = "K0d8F434vUjVc88vxkDxmC0z8cj0UZ";
// 构建请求参数
Map params = new HashMap<>();
params.put("merchantid", merchantId);
params.put("network", "1");
params.put("orderid", "TEST" + System.currentTimeMillis());
params.put("create_time", String.valueOf(System.currentTimeMillis() / 1000));
params.put("cashier", "1");
params.put("notifyurl", "https://您的域名/notify.php");
params.put("amount", "10.0000");
params.put("productname", "测试商品");
// 生成签名
String sign = generateSign(params, privateKey);
params.put("sign", sign);
// 打印请求参数
System.out.println("请求参数:");
for (Map.Entry entry : params.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// 发送HTTP请求示例
try {
String response = sendHttpRequest(params);
System.out.println("响应结果:" + response);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发送HTTP请求示例
*/
private static String sendHttpRequest(Map params) throws Exception {
// 这里使用HttpClient发送请求,需要添加相关依赖
// 实际项目中建议使用OkHttp或Apache HttpClient等HTTP客户端
StringBuilder urlParams = new StringBuilder();
for (Map.Entry entry : params.entrySet()) {
if (urlParams.length() > 0) {
urlParams.append("&");
}
urlParams.append(entry.getKey()).append("=")
.append(java.net.URLEncoder.encode(entry.getValue(), CHARSET));
}
// 这里仅作示例,实际项目中需要根据具体情况实现
return "HTTP请求实现代码";
}
}
我们为多种主流编程语言提供了完整的集成示例,包括详细的代码实现、框架集成和最佳实践。
/**
* 生成签名
*
* @param array $data 请求参数
* @param string $privateKey 商户私钥
* @return array 包含签名和签名字符串
*/
function makeSign($data, $privateKey)
{
// 除去数组中的空值和签名参数
$para_filter = array();
foreach ($data as $key => $val) {
if ($key == "sign" || $val === "" || $val === null) {
continue;
}
$para_filter[$key] = $val;
}
// 按照键名字典序排序
ksort($para_filter);
reset($para_filter);
// 拼接字符串
$str_sign = "";
foreach ($para_filter as $key => $val) {
$str_sign .= $key . $val;
}
// 添加私钥
$str_sign .= $privateKey;
// MD5加密并转大写
$sign = strtoupper(md5($str_sign));
return [
'sign' => $sign,
'str_sign' => $str_sign
];
}
// 配置信息
$config = [
'merchant_id' => 1,
'private_key' => 'your_private_key',
'notify_url' => 'https://您的域名/notify.php',
'gateway_url' => 'https://支付域名/getway.html',
];
// 组装下单参数
$timestamp = time();
$orderid = date('YmdHis') . mt_rand(1000, 9999);
$params = [
'merchantid' => $config['merchant_id'],
'network' => 1,
'orderid' => $orderid,
'create_time' => $timestamp,
'cashier' => 1,
'notifyurl' => $config['notify_url'],
'amount' => '10.0000',
'productname' => '测试商品',
];
// 生成签名
$sign_data = makeSign($params, $config['private_key']);
$params['sign'] = $sign_data['sign'];
// 发起HTTP请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $config['gateway_url']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
// 处理响应
$result = json_decode($response, true);
print_r($result);
// 获取所有请求参数
$data = $_POST;
// 获取商户信息(实际应用中应该从数据库获取)
$private_key = 'your_private_key';
// 验证签名
$sign = $data['sign'];
unset($data['sign']);
// 签名验证
$sign_data = makeSign($data, $private_key);
if ($sign_data['sign'] === $sign) {
// 验证成功,处理订单逻辑
// ...
// 返回成功标识
echo 'success';
} else {
// 签名验证失败
echo 'fail';
}
// 配置信息
$config = [
'merchant_id' => 1,
'private_key' => 'your_private_key',
'balance_url' => 'https://支付域名/balance.html',
];
// 组装参数
$timestamp = time();
$params = [
'merchantid' => $config['merchant_id'],
'timestamp' => $timestamp,
];
// 生成签名
$sign_data = makeSign($params, $config['private_key']);
$params['sign'] = $sign_data['sign'];
// 发起HTTP请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $config['balance_url']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
// 处理响应
$result = json_decode($response, true);
if ($result['code'] == 1) {
echo "余额查询成功:" . $result['data']['balance'] . " " . $result['data']['currency'] . "\n";
} else {
echo "查询失败:" . $result['msg'] . "\n";
}
print_r($result);