在 Laravel 中接收加密货币付款教程
手把手教程:如何在 Laravel 项目中一步步构建一个通过 Chaingateway API 接收 TRX 与 USDT(TRC20)加密货币付款的完整支付网关系统,涵盖生成钱包、处理 Webhook 通知与校验交易状态等关键环节。
本教程将指导您使用 Laravel 构建一个支持 Tron(TRC)和 JST(TRC20)付款的支付网关。它包含以下功能:为支付会话生成钱包、处理用于交易通知的 Webhook,以及在处理前验证交易状态。
学完本教程后,您将拥有一个可用的支付网关,该网关使用 Chaingateway 的 API 来完成区块链交互。
本教程仅描述该实现方式的基本流程。经过一些小的调整,它同样适用于 Bitcoin、Ethereum、Binance Smart Chain 和 Polygon。
简介
在深入实现细节之前,让我们先了解一下我们将使用的工具:
什么是 Chaingateway?
Chaingateway 是一项区块链 API 服务,可简化与 Tron 等区块链网络的交互。它允许您:
- 生成钱包地址。
- 监控交易。
- 以编程方式执行交易。
访问官方文档以了解更多详情:
- Developer Portal:了解如何使用 Chaingateway 的各项功能。
- API 文档:详细了解各个 API 端点。
- 创建 API 密钥:生成身份验证所需的 API 密钥。
创建 API 密钥的步骤
要与 Chaingateway API 交互,您需要一个 API 密钥。请按照以下步骤操作:
- 登录 Chaingateway。
- 导航至 User Settings > API Tokens。
- 点击 Create Token,为您的令牌命名(例如”Payment Gateway”),然后复制它。您将在 Laravel 应用中使用这个密钥。
前置条件
在继续之前,请确保您已具备:
- 一个 Laravel 安装环境:一个全新搭建的 Laravel 项目。如有需要,请参照 Laravel 安装指南。
- 数据库配置:使用您的数据库凭据更新
.env文件。 - Laravel 基础知识:熟悉模型(model)、迁移(migration)、控制器(controller)和路由(route)会很有帮助。
本教程的功能
本教程将构建一个具有以下功能的支付网关:
- 一个用于启动支付会话的动态表单:
- 用户可以输入付款金额。
- 用户可以选择币种(TRX 或 USDT)。
- 每个会话都会关联一个生成的钱包地址。
- 一个显示以下内容的会话页面:
- 钱包地址。
- 会话状态。
- 应发送的金额、已收到的金额以及币种。
- 一个用于处理以下事务的 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/v2Chaingateway_API_KEY=your_api_key_hereChaingateway_NETWORK=testnetCOLD_WALLET=your_cold_wallet_address说明
api_url:Chaingateway API 的基础 URL。api_key:用于对请求进行身份验证的个人 API 密钥。network:指定您使用的是测试网(用于开发)还是主网(用于生产)。cold_wallet:验证后资金将被转移到的安全钱包。
第 2 步:定义路由
路由定义了用户如何与您的应用交互。我们将为以下功能设置路由:
- 显示付款页面。
- 启动新的支付会话。
- 查看支付会话。
- 处理 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 步:创建模型与迁移
我们需要两个数据库表:
- Wallets:存储钱包地址和私钥。
- Payment Sessions:追踪每个会话的状态(例如 Pending、Completed 或 Failed)。
生成模型与迁移
运行以下命令:
php artisan make:model Wallet -mphp 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(); });运行迁移:
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 处理我们应用中的所有逻辑:
- 生成钱包。
- 创建并显示支付会话。
- 处理 Webhook 通知。
生成控制器:
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 开发服务器:
```bashphp artisan serve测试应用
- 访问
/payment以启动新的支付会话。 - 记下钱包地址并向其发送资金(如果是在测试网测试)。
- 通过向
/webhook发送 POST 请求来模拟 Webhook 通知。 - 验证会话状态是否正确更新。
我们希望本教程能向您展示,实现我们的 API 来接收加密货币付款是多么简单。如果您有任何进一步的问题,或在实现过程中需要帮助,我们始终乐于提供支持!您可以联系我们非常热心的社区,或给我们发送电子邮件。请点击这里了解如何与我们保持联系:https://Chaingateway.io/support
准备好自己动手构建了吗? 获取你的 API key — 7 天试用,无需信用卡 — 或查看 Tron API 获取完整的 endpoint 参考。