版本: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(Mapparams, 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);