Приём крипто-платежей в Laravel — руководство
Пошаговое руководство: как построить платёжный шлюз на Laravel, принимающий TRX и USDT (TRC20) через API Chaingateway.
Этот туториал проведёт вас через создание платёжного шлюза на Laravel, поддерживающего платежи в Tron (TRC) и JST (TRC20). Он включает такие возможности, как генерация кошельков для платёжных сессий, обработку webhook для уведомлений о транзакциях и проверку статуса транзакции перед обработкой.
К концу этого туториала у вас будет рабочий платёжный шлюз, использующий API Chaingateway для взаимодействия с блокчейном.
Этот туториал описывает лишь базовый процесс работы реализации. С небольшими доработками это будет работать и для Bitcoin, Ethereum, Binance Smart Chain и Polygon.
Введение
Прежде чем перейти к реализации, разберёмся с инструментами, которые мы будем использовать:
Что такое Chaingateway?
Chaingateway — это сервис блокчейн-API, упрощающий взаимодействие с блокчейн-сетями вроде Tron. Он позволяет вам:
- Генерировать адреса кошельков.
- Отслеживать транзакции.
- Выполнять транзакции программно.
Посетите официальную документацию для получения дополнительных сведений:
- Developer Portal: узнайте, как использовать возможности Chaingateway.
- Документация API: подробно изучите эндпоинты API.
- Создание API-ключа: сгенерируйте API-ключи, необходимые для аутентификации.
Шаги для создания API-ключа
Чтобы взаимодействовать с API Chaingateway, вам понадобится API-ключ. Следуйте этим шагам:
- Войдите в Chaingateway.
- Перейдите в User Settings > API Tokens.
- Нажмите Create Token, дайте своему токену имя (например, «Payment Gateway») и скопируйте его. Этот ключ вы будете использовать в своём приложении Laravel.
Предварительные требования
Прежде чем продолжить, убедитесь, что у вас есть:
- Установка Laravel: свежая установка проекта Laravel. При необходимости следуйте руководству по установке Laravel.
- Настройка базы данных: обновите файл
.envс реквизитами доступа к вашей базе данных. - Базовые знания Laravel: полезно быть знакомым с моделями, миграциями, контроллерами и маршрутами.
Возможности туториала
Этот туториал строит платёжный шлюз со следующими возможностями:
- Динамическая форма для запуска платёжных сессий:
- Пользователи могут вводить сумму платежа.
- Пользователи могут выбирать валюту (TRX или USDT).
- С каждой сессией связан сгенерированный адрес кошелька.
- Страница сессии, показывающая:
- Адрес кошелька.
- Статус сессии.
- Сумму к отправке, полученную сумму и валюту.
- Webhook, обрабатывающий:
- Проверку входящих транзакций.
- Обновление статуса сессии.
- Пересылку средств на холодный кошелёк при успешной оплате (опционально).
Шаг 1: настройка API Chaingateway
Сначала мы настроим Laravel для использования API Chaingateway.
Почему это важно?
Чтобы взаимодействовать с Chaingateway, вам нужно аутентифицировать каждый запрос с помощью API-ключа и указывать блокчейн-сеть, с которой вы работаете (например, testnet или mainnet). Этот шаг гарантирует, что ваше приложение сможет беспрепятственно взаимодействовать с 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: базовый URL для API Chaingateway.api_key: ваш персональный API-ключ для аутентификации запросов.network: укажите, используете ли вы testnet (для разработки) или mainnet (для продакшена).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 уведомления о входящих транзакциях.
Чтобы отключить CSRF-защиту на эндпоинте webhook, нам нужно исключить его в 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'); }
/** * Запуск платёжной сессии * * Эта функция создаст новый адрес кошелька и webhook в Chaingateway. * */
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'; }
/** * Удаление webhook в Chaingateway * Делайте это только если больше не будете использовать этот адрес */ $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 или websockets для обновления статуса платежа.
Страница платёжной сессии
В 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, чтобы запустить новую платёжную сессию. - Запишите адрес кошелька и отправьте на него средства (если тестируете в testnet).
- Смоделируйте уведомление webhook, отправив POST-запрос на
/webhook. - Убедитесь, что статус сессии корректно обновляется.
Мы надеемся, что этот туториал показывает, насколько легко реализовать наш API для приёма крипто-платежей. Если у вас возникнут дополнительные вопросы или понадобится помощь во время реализации, мы всегда готовы помочь! Вы можете обратиться в наше очень отзывчивое сообщество или написать нам письмо. Узнайте здесь, как оставаться на связи: https://Chaingateway.io/support
Готовы собрать это сами? Получите свой API-ключ — 7-дневный пробный период, без карты — или посмотрите API Tron для полного справочника по эндпоинтам.