<think>我们正在构建一个支持多APP的USDT支付系统,需要生成支付链接并集成二维码。考虑到多APP支持,我们需要为每个APP配置独立的支付参数(如不同的收款地址、回调URL等)。以下是实现步骤:
1. 设计数据库存储APP配置
2. 生成支付链接(支持TRON和ERC20)
3. 生成二维码
4. 处理支付回调
我们将使用以下技术:
- PHP 7.4+
- MySQL
- Endroid QR Code 库(通过Composer安装)
步骤1:数据库设计(apps表)
```sql
CREATE TABLE `apps` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`app_name` VARCHAR(50) NOT NULL COMMENT '应用名称',
`tron_address` VARCHAR(34) DEFAULT NULL COMMENT 'TRON收款地址',
`erc20_address` VARCHAR(42) DEFAULT NULL COMMENT 'ERC20收款地址',
`callback_url` VARCHAR(255) NOT NULL COMMENT '支付回调URL',
`secret_key` VARCHAR(32) NOT NULL COMMENT '回调签名密钥',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
步骤2:生成支付链接
根据不同的链(TRON或ERC20)生成不同的支付链接。
对于TRON网络,使用标准格式:
```
tron:{address}?contractAddress=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&amount={amount}
```
对于ERC20网络,使用标准格式:
```
ethereum:{address}@1/transfer?address=0xdAC17F958D2ee523a2206206994597C13D831ec7&uint256={amountInWei}
```
注意:ERC20的金额需要转换为最小单位(Wei,1 USDT = 1000000 Wei)
步骤3:支付订单处理
创建订单表存储支付信息:
```sql
CREATE TABLE `orders` (
`order_id` VARCHAR(32) NOT NULL COMMENT '订单ID',
`app_id` INT(11) NOT NULL COMMENT '应用ID',
`amount` DECIMAL(18,6) NOT NULL COMMENT '支付金额(USDT)',
`chain_type` ENUM('TRON','ERC20') NOT NULL COMMENT '链类型',
`status` ENUM('pending','paid','expired') NOT NULL DEFAULT 'pending',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`paid_at` TIMESTAMP NULL DEFAULT NULL,
`tx_hash` VARCHAR(66) DEFAULT NULL COMMENT '交易哈希',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
步骤4:PHP代码实现
4.1 初始化项目(composer.json):
```json
{
"require": {
"endroid/qr-code": "^4.6"
}
}
```
4.2 支付链接生成(PayService.php):
```php
<?php
require_once 'vendor/autoload.php';
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\Label\Alignment\LabelAlignmentCenter;
use Endroid\QrCode\Label\Font\NotoSans;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
use Endroid\QrCode\Writer\PngWriter;
class PayService {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
/**
* 生成支付信息(包含支付链接和二维码图片)
* @param int $appId 应用ID
* @param float $amount 支付金额(USDT)
* @param string $chainType 链类型(TRON/ERC20)
* @return array 返回支付链接和二维码图片的base64编码
* @throws Exception
*/
public function generatePayment(int $appId, float $amount, string $chainType): array {
// 验证链类型
if (!in_array($chainType, ['TRON', 'ERC20'])) {
throw new InvalidArgumentException('不支持的链类型');
}
// 获取APP配置
$stmt = $this->pdo->prepare("SELECT * FROM apps WHERE id = ?");
$stmt->execute([$appId]);
$app = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$app) {
throw new RuntimeException('APP配置不存在');
}
// 生成订单ID
$orderId = bin2hex(random_bytes(16));
// 根据链类型生成支付链接
if ($chainType === 'TRON') {
$address = $app['tron_address'];
if (empty($address)) {
throw new RuntimeException('该APP未配置TRON收款地址');
}
$paymentLink = "tron:{$address}?contractAddress=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&amount={$amount}";
} else { // ERC20
$address = $app['erc20_address'];
if (empty($address)) {
throw new RuntimeException('该APP未配置ERC20收款地址');
}
// 金额转换为Wei (USDT有6位小数,所以乘以10^6)
$amountInWei = bcmul($amount, '1000000', 0);
$paymentLink = "ethereum:{$address}@1/transfer?address=0xdAC17F958D2ee523a2206206994597C13D831ec7&uint256={$amountInWei}";
}
// 生成二维码
$qrCode = Builder::create()
->writer(new PngWriter())
->data($paymentLink)
->encoding(new Encoding('UTF-8'))
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
->size(300)
->margin(10)
->roundBlockSizeMode(new RoundBlockSizeModeMargin())
->build();
// 将二维码图片转为base64
$qrCodeImage = 'data:image/png;base64,' . base64_encode($qrCode->getString());
// 保存订单
$stmt = $this->pdo->prepare("INSERT INTO orders (order_id, app_id, amount, chain_type) VALUES (?, ?, ?, ?)");
$stmt->execute([$orderId, $appId, $amount, $chainType]);
return [
'order_id' => $orderId,
'payment_link' => $paymentLink,
'qr_code' => $qrCodeImage,
'expire_time' => time() + 3600 // 1小时过期
];
}
}
```
步骤5:支付回调处理
我们需要一个后台进程监控交易,当交易确认后,调用APP配置的回调URL。
5.1 回调通知格式(POST):
```json
{
"order_id": "订单ID",
"app_id": "应用ID",
"amount": "支付金额",
"chain_type": "链类型",
"tx_hash": "交易哈希",
"timestamp": "时间戳",
"sign": "签名"
}
```
签名生成方式:`sign = md5(order_id + app_id + amount + chain_type + tx_hash + timestamp + secret_key)`
5.2 回调处理示例(CallbackService.php):
```php
class CallbackService {
public function notifyApp(array $order) {
// 获取APP配置
$stmt = $this->pdo->prepare("SELECT * FROM apps WHERE id = ?");
$stmt->execute([$order['app_id']]);
$app = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$app) {
throw new RuntimeException('APP配置不存在');
}
$data = [
'order_id' => $order['order_id'],
'app_id' => $order['app_id'],
'amount' => $order['amount'],
'chain_type' => $order['chain_type'],
'tx_hash' => $order['tx_hash'],
'timestamp' => time()
];
// 生成签名
$signStr = $data['order_id'] . $data['app_id'] . $data['amount'] . $data['chain_type'] . $data['tx_hash'] . $data['timestamp'] . $app['secret_key'];
$data['sign'] = md5($signStr);
// 发送POST请求
$ch = curl_init($app['callback_url']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 记录回调结果
if ($httpCode == 200 && $response === 'success') {
// 标记回调成功
$this->updateOrderCallbackStatus($order['order_id'], 'success');
} else {
// 记录失败,后续重试
$this->logCallbackError($order['order_id'], $response);
}
}
}
```
步骤6:交易监控(后台进程)
使用定时任务(cron)每分钟执行一次交易监控脚本(Monitor.php):
```php
// Monitor.php
$monitor = new TransactionMonitor($pdo);
$monitor->checkPayments();
class TransactionMonitor {
// ... 构造函数等
public function checkPayments() {
// 获取待支付的订单(注意:实际中需要限制数量,避免一次处理太多)
$stmt = $this->pdo->query("SELECT * FROM orders WHERE status='pending' AND created_at > NOW() - INTERVAL 1 DAY");
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($orders as $order) {
try {
if ($order['chain_type'] === 'TRON') {
$this->checkTronPayment($order);
} else {
$this->checkErc20Payment($order);
}
} catch (Exception $e) {
// 记录错误日志
error_log("订单{$order['order_id']}监控失败: " . $e->getMessage());
}
}
}
private function checkTronPayment(array $order) {
// 使用TRON API查询该地址的USDT交易(需要实现)
// 如果找到匹配的交易(金额、订单ID等),更新订单状态并触发回调
}
private function checkErc20Payment(array $order) {
// 使用Etherscan API查询该地址的USDT交易
}
}
```
注意:TRON和ERC20的交易监控需要调用区块链浏览器API或节点API,这里不展开具体实现,因为涉及复杂的交易解析。
总结:
1. 通过多APP配置实现不同收款地址和回调
2. 生成符合标准的支付链接和二维码
3. 通过后台进程监控交易状态并回调
安全建议:
1. 对回调进行签名验证,防止伪造请求
2. 限制订单的支付有效期(如1小时)
3. 使用HTTPS传输支付链接和回调
相关问题:
1. 如何验证TRON网络交易的真实性?
2. 支付系统如何防止重复支付?
3. 如何处理不同链上USDT的小数位数差异?
4. 如何设计支付系统的交易监控模块?
5. 支付回调失败的重试机制如何实现?</think>### PHP实现多APP支持的USDT支付系统
以下是完整的PHP实现方案,支持多APP的USDT支付链接生成与支付流程处理:
#### 1. 系统架构设计
```mermaid
graph TD
A[客户端] --> B[支付网关]
B --> C{路由到APP配置}
C --> D[APP1 收款地址]
C --> E[APP2 收款地址]
C --> F[APP3 收款地址]
D --> G[生成支付链接]
E --> G
F --> G
G --> H[返回支付二维码]
H --> I[区块链监控]
I --> J[支付回调]
```
#### 2. 数据库设计(MySQL)
```sql
-- APP配置表
CREATE TABLE apps (
id INT AUTO_INCREMENT PRIMARY KEY,
app_key VARCHAR(32) UNIQUE NOT NULL COMMENT 'APP唯一标识',
app_name VARCHAR(100) NOT NULL COMMENT '应用名称',
tron_address VARCHAR(34) NOT NULL COMMENT 'TRON收款地址',
callback_url VARCHAR(255) NOT NULL COMMENT '支付回调URL',
secret_key VARCHAR(64) NOT NULL COMMENT '签名密钥',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 订单表
CREATE TABLE orders (
order_id VARCHAR(24) PRIMARY KEY COMMENT '订单ID',
app_id INT NOT NULL COMMENT '关联APP',
amount DECIMAL(18,6) NOT NULL COMMENT '支付金额',
status ENUM('pending','paid','expired') DEFAULT 'pending',
qr_code VARCHAR(255) COMMENT '二维码路径',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
paid_at TIMESTAMP NULL,
tx_hash VARCHAR(66) COMMENT '交易哈希',
FOREIGN KEY (app_id) REFERENCES apps(id)
);
```
#### 3. 支付链接生成核心代码
```php
<?php
require 'vendor/autoload.php';
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
class USDTGateway {
private $pdo;
private $usdtContract = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; // USDT-TRC20合约
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
/**
* 生成支付链接和二维码
* @param string $appKey APP标识
* @param float $amount 支付金额(USDT)
* @return array 支付信息
*/
public function createPayment(string $appKey, float $amount): array {
// 获取APP配置
$app = $this->getAppConfig($appKey);
// 验证金额
if ($amount <= 0) {
throw new InvalidArgumentException("金额必须大于0");
}
// 生成订单ID (格式: APP前缀+时间戳+随机数)
$orderId = substr($appKey, 0, 3) . time() . rand(1000, 9999);
// 生成TRON支付链接
$paymentLink = $this->generateTronLink($app['tron_address'], $amount);
// 生成二维码
$qrPath = $this->generateQRCode($orderId, $paymentLink);
// 保存订单
$stmt = $this->pdo->prepare("INSERT INTO orders
(order_id, app_id, amount, qr_code)
VALUES (?, ?, ?, ?)");
$stmt->execute([$orderId, $app['id'], $amount, $qrPath]);
return [
'order_id' => $orderId,
'amount' => $amount,
'payment_link' => $paymentLink,
'qr_code' => $qrPath,
'expire_time' => time() + 3600 // 1小时有效期
];
}
private function getAppConfig(string $appKey): array {
$stmt = $this->pdo->prepare("SELECT * FROM apps WHERE app_key = ?");
$stmt->execute([$appKey]);
$app = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$app) {
throw new RuntimeException("APP配置不存在: " . $appKey);
}
return $app;
}
private function generateTronLink(string $address, float $amount): string {
// 生成TRON标准支付链接
return "tron:{$address}?contractAddress={$this->usdtContract}&amount={$amount}";
}
private function generateQRCode(string $orderId, string $content): string {
$saveDir = __DIR__ . '/qrcodes/';
if (!is_dir($saveDir)) mkdir($saveDir, 0755, true);
$filename = "{$orderId}.png";
$filePath = $saveDir . $filename;
// 使用Endroid QR Code生成二维码
$result = Builder::create()
->data($content)
->encoding(new Encoding('UTF-8'))
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
->size(300)
->margin(10)
->build()
->saveToFile($filePath);
return $filePath;
}
}
```
#### 4. 支付回调处理
```php
class PaymentCallback {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
/**
* 处理支付回调
* @param string $orderId 订单ID
* @param string $txHash 交易哈希
* @param float $amount 实际支付金额
*/
public function handleCallback(string $orderId, string $txHash, float $amount) {
// 获取订单信息
$stmt = $this->pdo->prepare("SELECT o.*, a.callback_url, a.secret_key
FROM orders o
JOIN apps a ON o.app_id = a.id
WHERE o.order_id = ?");
$stmt->execute([$orderId]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$order) {
throw new RuntimeException("订单不存在: " . $orderId);
}
// 验证金额 (允许1%的误差)
$minAmount = $order['amount'] * 0.99;
if ($amount < $minAmount) {
throw new RuntimeException("支付金额不足: 需{$order['amount']} 实付{$amount}");
}
// 更新订单状态
$updateStmt = $this->pdo->prepare("UPDATE orders
SET status = 'paid', paid_at = NOW(), tx_hash = ?
WHERE order_id = ?");
$updateStmt->execute([$txHash, $orderId]);
// 通知APP回调
$this->notifyApp($order, $txHash);
}
private function notifyApp(array $order, string $txHash) {
$data = [
'order_id' => $order['order_id'],
'amount' => $order['amount'],
'tx_hash' => $txHash,
'timestamp' => time()
];
// 生成签名
$signature = hash_hmac('sha256', json_encode($data), $order['secret_key']);
$data['sign'] = $signature;
// 发送POST请求
$ch = curl_init($order['callback_url']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode !== 200 || $response !== 'success') {
// 记录失败日志,实现重试机制
error_log("回调失败: Order {$order['order_id']} - {$response}");
}
}
}
```
#### 5. 区块链交易监控(后台进程)
```php
class BlockchainMonitor {
private $pdo;
private $tronApiUrl = 'https://api.trongrid.io';
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function checkTransactions() {
// 获取所有需要监控的地址
$addresses = $this->getWatchAddresses();
foreach ($addresses as $address) {
$this->checkAddressTransactions($address);
}
}
private function getWatchAddresses(): array {
$stmt = $this->pdo->query("SELECT DISTINCT tron_address FROM apps");
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
private function checkAddressTransactions(string $address) {
// 调用TRON API获取最近交易
$url = "{$this->tronApiUrl}/v1/accounts/{$address}/transactions?limit=20";
$response = file_get_contents($url);
$transactions = json_decode($response, true)['data'] ?? [];
foreach ($transactions as $tx) {
$this->processTransaction($address, $tx);
}
}
private function processTransaction(string $address, array $tx) {
// 只处理USDT交易
if ($tx['raw_data']['contract'][0]['parameter']['value']['contract_address'] !== $this->usdtContract) {
return;
}
// 获取交易金额
$amount = $tx['raw_data']['contract'][0]['parameter']['value']['amount'] / 1000000;
$txHash = $tx['txID'];
// 检查是否已处理过
if ($this->isTransactionProcessed($txHash)) {
return;
}
// 匹配订单 (实际应用中需要更精确的匹配逻辑)
$stmt = $this->pdo->prepare("SELECT * FROM orders
WHERE status = 'pending'
AND ABS(amount - ?) < 0.01
ORDER BY created_at DESC
LIMIT 1");
$stmt->execute([$amount]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
if ($order) {
$callback = new PaymentCallback($this->pdo);
$callback->handleCallback($order['order_id'], $txHash, $amount);
}
}
private function isTransactionProcessed(string $txHash): bool {
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM orders WHERE tx_hash = ?");
$stmt->execute([$txHash]);
return $stmt->fetchColumn() > 0;
}
}
```
#### 6. 使用示例
```php
// 初始化支付网关
$pdo = new PDO('mysql:host=localhost;dbname=usdt_pay', 'user', 'pass');
$gateway = new USDTGateway($pdo);
// APP发起支付请求
$payment = $gateway->createPayment('APP123', 50.0); // 50 USDT
// 返回给客户端
header('Content-Type: application/json');
echo json_encode([
'code' => 0,
'data' => [
'order_id' => $payment['order_id'],
'qr_code_url' => 'https://yourdomain.com/qrcodes/' . basename($payment['qr_code']),
'payment_link' => $payment['payment_link'],
'expire_time' => $payment['expire_time']
]
]);
// 后台监控进程 (使用cron定时执行)
// */5 * * * * /usr/bin/php /path/to/monitor.php
$monitor = new BlockchainMonitor($pdo);
$monitor->checkTransactions();
```
### 关键技术说明
1. **多APP支持机制**:
- 每个APP有独立收款地址和回调配置
- 订单ID包含APP前缀 `(APP123_1689154235_5832)`
- 独立的签名密钥保证回调安全
2. **支付链接生成**:
- TRON标准格式:`tron:{address}?contractAddress={contract}&amount={amount}`
- 支持直接跳转TRON钱包支付
3. **二维码集成**:
- 使用`endroid/qr-code`库生成专业二维码
- 文件名包含订单ID便于管理
- 自动创建存储目录
4. **支付验证**:
- 金额验证(允许1%误差)
- 交易哈希唯一性检查
- 基于时间戳的订单有效期
5. **回调安全**:
```php
// 签名生成
$signature = hash_hmac('sha256', json_encode($data), $secretKey);
// 回调验证
$receivedSign = $_POST['sign'];
$expectedSign = hash_hmac('sha256', json_encode($_POST), $secretKey);
if (!hash_equals($expectedSign, $receivedSign)) {
throw new RuntimeException("无效签名");
}
```
### 部署建议
1. **服务器配置**:
```nginx
# Nginx配置
location /qrcodes/ {
alias /path/to/qrcodes/;
expires 1h;
access_log off;
}
```
2. **安全措施**:
- 使用HTTPS保护所有接口
- 限制区块链监控API的调用频率
- 定期清理过期二维码(24小时以上)
- 对支付金额设置最小/最大限制
3. **监控任务**:
```bash
# 每5分钟执行一次监控
*/5 * * * * /usr/bin/php /var/www/pay-system/monitor.php
```
### 相关问题
1. 如何验证TRON网络交易的真实性?
2. 支付系统如何防止重复支付攻击?
3. 如何设计多币种(USDT-TRC20/USDT-ERC20)的支付系统?
4. 支付回调失败的重试机制如何实现?
5. 如何优化大流量下的二维码生成性能?
6. 支付系统如何与现有用户账户体系集成?
> 通过这种设计,支付系统可以同时支持多个APP的USDT支付需求,每个APP有独立的配置和回调处理,同时共享核心支付基础设施[^1]。