博客
10 分钟阅读
|
2025年1月13日

在 Laravel 中接收加密货币付款教程

手把手教程:如何在 Laravel 项目中一步步构建一个通过 Chaingateway API 接收 TRX 与 USDT(TRC20)加密货币付款的完整支付网关系统,涵盖生成钱包、处理 Webhook 通知与校验交易状态等关键环节。

章节
C
Chaingateway Team
区块链专家

本教程将指导您使用 Laravel 构建一个支持 Tron(TRC)和 JST(TRC20)付款的支付网关。它包含以下功能:为支付会话生成钱包、处理用于交易通知的 Webhook,以及在处理前验证交易状态。

学完本教程后,您将拥有一个可用的支付网关,该网关使用 Chaingateway 的 API 来完成区块链交互。

本教程仅描述该实现方式的基本流程。经过一些小的调整,它同样适用于 Bitcoin、Ethereum、Binance Smart Chain 和 Polygon。


简介

在深入实现细节之前,让我们先了解一下我们将使用的工具:

什么是 Chaingateway?

Chaingateway 是一项区块链 API 服务,可简化与 Tron 等区块链网络的交互。它允许您:

  • 生成钱包地址。
  • 监控交易。
  • 以编程方式执行交易。

访问官方文档以了解更多详情:

  1. Developer Portal:了解如何使用 Chaingateway 的各项功能。
  2. API 文档:详细了解各个 API 端点。
  3. 创建 API 密钥:生成身份验证所需的 API 密钥。

创建 API 密钥的步骤

要与 Chaingateway API 交互,您需要一个 API 密钥。请按照以下步骤操作:

  1. 登录 Chaingateway
  2. 导航至 User Settings > API Tokens
  3. 点击 Create Token,为您的令牌命名(例如”Payment Gateway”),然后复制它。您将在 Laravel 应用中使用这个密钥。

前置条件

在继续之前,请确保您已具备:

  1. 一个 Laravel 安装环境:一个全新搭建的 Laravel 项目。如有需要,请参照 Laravel 安装指南
  2. 数据库配置:使用您的数据库凭据更新 .env 文件。
  3. Laravel 基础知识:熟悉模型(model)、迁移(migration)、控制器(controller)和路由(route)会很有帮助。

本教程的功能

本教程将构建一个具有以下功能的支付网关:

  1. 一个用于启动支付会话的动态表单:
    • 用户可以输入付款金额。
    • 用户可以选择币种(TRX 或 USDT)。
  2. 每个会话都会关联一个生成的钱包地址。
  3. 一个显示以下内容的会话页面:
    • 钱包地址。
    • 会话状态。
    • 应发送的金额、已收到的金额以及币种。
  4. 一个用于处理以下事务的 Webhook:
    • 验证传入的交易。
    • 更新会话状态。
    • 在付款成功后将资金转移到冷钱包(可选)。

第 1 步:配置 Chaingateway API

首先,我们将配置 Laravel 以使用 Chaingateway API。

为什么这一步很重要?

要与 Chaingateway 交互,您需要使用 API 密钥对每个请求进行身份验证,并指定您所使用的区块链网络(例如测试网或主网)。这一步可以确保您的应用能够与 Chaingateway 顺畅通信。

更新配置

将 Chaingateway 配置添加到 config/app.php:

'Chaingateway' => [
'api_url' => env('Chaingateway_API_URL', 'https://app.chaingateway.io/api/v2'),
'api_key' => env('Chaingateway_API_KEY'),
'network' => env('Chaingateway_NETWORK', 'testnet'), // Use 'mainnet' for production
'cold_wallet' => env('COLD_WALLET'),
],

接下来,打开您的 .env 文件并添加以下内容:

Chaingateway_API_URL=https://api.Chaingateway.io/api/v2
Chaingateway_API_KEY=your_api_key_here
Chaingateway_NETWORK=testnet
COLD_WALLET=your_cold_wallet_address

说明

  • api_url:Chaingateway API 的基础 URL。
  • api_key:用于对请求进行身份验证的个人 API 密钥。
  • network:指定您使用的是测试网(用于开发)还是主网(用于生产)。
  • cold_wallet:验证后资金将被转移到的安全钱包。

第 2 步:定义路由

路由定义了用户如何与您的应用交互。我们将为以下功能设置路由:

  1. 显示付款页面。
  2. 启动新的支付会话。
  3. 查看支付会话。
  4. 处理 Webhook。

将路由添加到 routes/web.php

use App\Http\Controllers\PaymentController;
Route::get('/payment', [PaymentController::class, 'showPaymentPage']);
Route::post('/start-payment-session', [PaymentController::class, 'startPaymentSession']);
Route::get('/payment-session/{id}', [PaymentController::class, 'showPaymentSession'])->name('showPaymentSession');
Route::post('/webhook', [PaymentController::class, 'handleWebhook']);

说明

  • /payment:显示一个带有按钮的页面,用于启动新的支付会话。
  • /start-payment-session:创建新会话并生成钱包地址。
  • /payment-session/{id}:显示钱包地址和会话状态。
  • /webhook:接收来自 Chaingateway 的传入交易通知。

要在 Webhook 端点上禁用 CSRF 保护,我们需要在 bootstrap/app.php 中将其排除。如果您使用的是较旧版本的 Laravel,请前往 https://laravel.com/docs/11.x/csrf#csrf-excluding-uris 查看适用于您版本的操作方式

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// 将 webhook 路由排除在 CSRF 保护之外
$middleware->validateCsrfTokens(except: [
'webhook',
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();

第 3 步:创建模型与迁移

我们需要两个数据库表:

  1. Wallets:存储钱包地址和私钥。
  2. Payment Sessions:追踪每个会话的状态(例如 Pending、Completed 或 Failed)。

生成模型与迁移

运行以下命令:

Terminal window
php artisan make:model Wallet -m
php artisan make:model PaymentSession -m

定义迁移

Wallet 迁移

database/migrations/<timestamp>_create_wallets_table.php 中:

Schema::create('wallets', function (Blueprint $table) {
$table->id();
$table->string('address')->unique(); // 钱包地址
$table->string('private_key'); // 用于交易的私钥
$table->timestamps();
});

PaymentSession 迁移

database/migrations/<timestamp>_create_payment_sessions_table.php 中:

Schema::create('payment_sessions', function (Blueprint $table) {
$table->id();
$table->string('status')->default('Pending'); // Pending、Completed 或 Failed
$table->foreignId('wallet_id')->constrained()->onDelete('cascade'); // 关联到 Wallet
$table->decimal('amount', 18, 8)->nullable(); // 发送到此会话的金额
$table->string('currency')->default('TRX'); // 金额的币种
$table->decimal('received_amount', 18, 8)->nullable(); // 发送到此会话的金额
$table->string('webhook_id')->nullable();
$table->timestamps();
});

运行迁移:

Terminal window
php artisan migrate

我们还应确保这些字段是 fillable 的,并且关联关系构建正确。为此,我们需要调整模型。

Wallet 模型

在 app\Models\Wallet.php 中

namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Wallet extends Model
{
protected $fillable = ['address', 'private_key'];
public function paymentSessions()
{
return $this->hasMany(PaymentSession::class);
}
}

PaymentSession

在 app\Models\PaymentSession.php 中

namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PaymentSession extends Model
{
protected $fillable = ['wallet_id', 'status', 'amount', 'currency', 'received_amount', 'webhook_id'];
public function wallet()
{
return $this->belongsTo(Wallet::class);
}
}

第 4 步:实现 PaymentController

PaymentController 处理我们应用中的所有逻辑:

  1. 生成钱包。
  2. 创建并显示支付会话。
  3. 处理 Webhook 通知。

生成控制器:

Terminal window
php artisan make:controller PaymentController

添加控制器逻辑

以下是 PaymentController 的完整实现:

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use App\Models\PaymentSession;
use App\Models\Transaction;
use App\Models\Wallet;
class PaymentController extends Controller
{
private $apiUrl;
private $apiKey;
private $network;
private $coldWallet;
public function __construct()
{
$this->apiUrl = config('app.Chaingateway.api_url');
$this->apiKey = config('app.Chaingateway.api_key');
$this->network = config('app.Chaingateway.network');
$this->coldWallet = config('app.Chaingateway.cold_wallet');
}
public function showPaymentPage()
{
return view('payment');
}
/**
* 启动支付会话
*
* 此函数将在 Chaingateway 中创建一个新的钱包地址和一个 webhook。
*
*/
public function startPaymentSession(Request $request)
{
$response = Http::withHeaders([
'Authorization' => "Bearer {$this->apiKey}",
'Content-Type' => 'application/json',
'X-Network' => $this->network,
])->post("{$this->apiUrl}/tron/addresses");
if ($response->successful()) {
$walletData = $response->json()['data'];
$wallet = Wallet::create([
'address' => $walletData['address'],
'private_key' => $walletData['privateKey'],
]);
$wbhookUrl = route('handleWebhook');
$response = Http::withHeaders([
'Authorization' => "Bearer {$this->apiKey}",
'Content-Type' => 'application/json',
'X-Network' => $this->network,
])->post("{$this->apiUrl}/tron/webhooks", [
'to' => $wallet->address,
'url' => $wbhookUrl,
]);
$webhookData = $response->json()['data'];
$paymentSession = PaymentSession::create([
'wallet_id' => $wallet->id,
'webhook_id' => $webhookData['id'],
'status' => 'Pending',
]);
return redirect()->route('showPaymentSession', ['id' => $paymentSession->id]);
}
return back()->withErrors(['error' => 'Failed to create payment session.']);
}
public function showPaymentSession($id)
{
$paymentSession = PaymentSession::with('wallet')->findOrFail($id);
return view('payment-session', compact('paymentSession'));
}
public function handleWebhook(Request $request)
{
$transactionData = $request->all();
$wallet = Wallet::where('address', $transactionData['to'])->first();
if (!$wallet) {
return response()->json(['error' => 'Wallet not found'], 404);
}
$paymentSession = PaymentSession::where('wallet_id', $wallet->id)->first();
if (!$paymentSession) {
return response()->json(['error' => 'Payment session not found'], 404);
}
/**
* 我们应该始终检查交易回执,确认该交易是否真的成功
*/
$receiptResponse = Http::withHeaders([
'Authorization' => "Bearer {$this->apiKey}",
'Content-Type' => 'application/json',
'X-Network' => $this->network,
])->get("{$this->apiUrl}/tron/transactions/{$transactionData['txid']}/receipt/decoded");
if ($receiptResponse->successful() && $receiptResponse->json()['data']['status'] == 'SUCCESS') {
$paymentSession->status = 'Completed';
$paymentSession->received_amount = $transactionData['amount'];
/**
* 您应该检查收到的金额是否与请求的金额一致
* 如果不一致,您应该为用户退款或采取其他措施。
* 金额可能会有所出入,例如由于交易手续费。您应该通过添加一定的容差范围来考虑这一点。
* 您还应该检查该交易是否为 TRC20 代币交易,以及合约地址是否与预期一致。
*
*/
$amountDifference = abs($paymentSession->amount - $transactionData['amount']);
$allowedDifference = $paymentSession->amount * 0.10; // 支付会话金额的 10%
if ($amountDifference >= $allowedDifference) {
// 允许最多 10% 的差异,更新合约地址
// 如果金额多付或少付超过 10%
if ($transactionData['amount'] > $paymentSession->amount) {
$paymentSession->status = 'overpaid';
} else {
$paymentSession->status = 'underpaid';
}
}
if($paymentSession->currency == 'JST' && $transactionData['contractaddress'] != 'TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3'){
$paymentSession->status = 'Wrong currency received';
}
/**
* 如果您只想将资金转移到冷钱包,可以这样做。
* 您也可以将资金发送到另一个钱包,或者什么都不做。
* 对于 TRC20 代币,您需要确保有足够的 TRX 来支付交易手续费。
* Chaingateway 的 Tron Paymaster 功能已被弃用(deprecated)。如果不想自己处理手续费,请使用 TronFuel(https://tronfuel.dev)。
$endpoint = $transactionData['contractaddress']
? "{$this->apiUrl}/tron/transactions/trc20"
: "{$this->apiUrl}/tron/transactions";
Http::withHeaders([
'Authorization' => "Bearer {$this->apiKey}",
'Content-Type' => 'application/json',
'X-Network' => $this->network,
])->post($endpoint, [
'amount' => $transactionData['amount'],
'privatekey' => $wallet->private_key,
'to' => $this->coldWallet,
'from' => $transactionData['to'],
'contractaddress' => $transactionData['contractaddress'],
]);
*/
} else {
$paymentSession->status = 'Failed';
}
/**
* 删除 Chaingateway 中的 webhook
* 仅当您不会再次使用该地址时才这样做
*/
$response = Http::withHeaders([
'Authorization' => "Bearer {$this->apiKey}",
'Content-Type' => 'application/json',
'X-Network' => $this->network,
])->delete("{$this->apiUrl}/tron/webhooks/{$paymentSession->webhook_id}");
$paymentSession->save();
return response()->json(['status' => 'success']);
}
}

每个方法的作用是什么?

  • showPaymentPage:显示带有表单的主付款页面,用于启动新会话。
  • startPaymentSession:生成钱包地址,创建新的支付会话,并将用户重定向到会话页面。
  • showPaymentSession:显示钱包地址和会话状态。
  • handleWebhook:处理来自 Chaingateway 的通知,验证交易是否成功,更新会话状态,并将资金转移到冷钱包(可选)。

第 5 步:创建视图

为了创建新的支付会话,本教程使用一个简单的表单,让您输入金额和币种。在实际项目中,这通常应由您的结账流程来完成。

付款页面

resources/views/payment.blade.php 中:

<!DOCTYPE html>
<html>
<head>
<title>Start Payment Session</title>
</head>
<body>
<h1>Start a New Payment Session</h1>
<form action="/start-payment-session" method="POST">
@csrf
<label for="amount">Amount:</label>
<input type="number" step="0.01" name="amount" id="amount" required />
<br />
<label for="currency">Currency:</label>
<select name="currency" id="currency" required>
<option value="TRX">TRX</option>
<option value="JST">JST (TRC20)</option>
</select>
<br />
<button type="submit">Start Payment Session</button>
</form>
</body>
</html>

在支付会话页面上,用户可以查看自己的付款状态。这同样是一个非常基础的示例。在真实场景中,您会使用更具交互性的轮询(polling)方式或 WebSocket 来刷新付款状态。

支付会话页面

resources/views/payment-session.blade.php 中:

<!DOCTYPE html>
<html>
<head>
<title>Payment Session</title>
</head>
```html
<body>
<h1>Payment Session</h1>
<p>
Send
<strong
>{{ '{{' }} $paymentSession->amount }} {{ '{{' }}
$paymentSession->currency }}</strong
>
to the address below:
</p>
<p><strong>{{ '{{' }} $paymentSession->wallet->address }}</strong></p>
<p>Status: <strong>{{ '{{' }} $paymentSession->status }}</strong></p>
<p>
Received:
<strong
>{{ '{{' }} $paymentSession->received_amount }} {{ '{{' }}
$paymentSession->currency }}</strong
>
</p>
</body>
</html>
---
## **第 6 步:测试**
### **启动服务器**
运行 Laravel 开发服务器:
```bash
php artisan serve

测试应用

  1. 访问 /payment 以启动新的支付会话。
  2. 记下钱包地址并向其发送资金(如果是在测试网测试)。
  3. 通过向 /webhook 发送 POST 请求来模拟 Webhook 通知。
  4. 验证会话状态是否正确更新。

我们希望本教程能向您展示,实现我们的 API 来接收加密货币付款是多么简单。如果您有任何进一步的问题,或在实现过程中需要帮助,我们始终乐于提供支持!您可以联系我们非常热心的社区,或给我们发送电子邮件。请点击这里了解如何与我们保持联系:https://Chaingateway.io/support

准备好自己动手构建了吗? 获取你的 API key — 7 天试用,无需信用卡 — 或查看 Tron API 获取完整的 endpoint 参考。

C
Chaingateway Team
区块链专家

Chaingateway 团队致力于为全球开发者简化区块链集成。