Recibir pagos cripto en Laravel: guía práctica
Tutorial paso a paso: cómo construir una pasarela de pago en Laravel que recibe TRX y USDT (TRC20) a través de la API de Chaingateway.
Este tutorial le guiará en la construcción de una pasarela de pago en Laravel compatible con pagos en Tron (TRC) y JST (TRC20). Incluye funciones como generar wallets para sesiones de pago, gestionar webhooks para notificaciones de transacciones y verificar el estado de la transacción antes de procesarla.
Al final de este tutorial, tendrá una pasarela de pago funcional que usa la API de Chaingateway para las interacciones con la blockchain.
Este tutorial solo pretende describir el proceso básico de cómo funciona la implementación. También funcionará para Bitcoin, Ethereum, Binance Smart Chain y Polygon con algunas pequeñas adaptaciones.
Introducción
Antes de entrar en la implementación, entendamos las herramientas que vamos a usar:
¿Qué es Chaingateway?
Chaingateway es un servicio de API de blockchain que simplifica la interacción con redes blockchain como Tron. Le permite:
- Generar direcciones de wallet.
- Monitorizar transacciones.
- Ejecutar transacciones de forma programática.
Visite la documentación oficial para más detalles:
- Developer Portal: aprenda a usar las funciones de Chaingateway.
- Documentación de la API: explore los endpoints de la API en detalle.
- Creación de clave API: genere las claves API necesarias para la autenticación.
Pasos para crear una clave API
Para interactuar con la API de Chaingateway, necesitará una clave API. Siga estos pasos:
- Inicie sesión en Chaingateway.
- Vaya a User Settings > API Tokens.
- Haga clic en Create Token, dele un nombre a su token (por ejemplo, “Payment Gateway”) y cópielo. Usará esta clave en su aplicación Laravel.
Requisitos previos
Antes de continuar, asegúrese de tener:
- Una instalación de Laravel: un proyecto Laravel recién configurado. Siga la guía de instalación de Laravel si es necesario.
- Configuración de la base de datos: actualice su archivo
.envcon sus credenciales de base de datos. - Conocimientos básicos de Laravel: es útil estar familiarizado con models, migrations, controladores y rutas.
Funciones del tutorial
Este tutorial construye una pasarela de pago con las siguientes funciones:
- Un formulario dinámico para iniciar sesiones de pago:
- Los usuarios pueden introducir el importe del pago.
- Los usuarios pueden seleccionar la divisa (TRX o USDT).
- Se asocia una dirección de wallet generada a cada sesión.
- Una página de sesión que muestra:
- La dirección del wallet.
- El estado de la sesión.
- El importe a enviar, el importe recibido y la divisa.
- Un webhook que se encarga de:
- Verificar las transacciones entrantes.
- Actualizar el estado de la sesión.
- Reenviar los fondos a un cold wallet tras un pago exitoso (opcional).
Paso 1: Configurar la API de Chaingateway
Primero configuraremos Laravel para usar la API de Chaingateway.
¿Por qué es importante?
Para interactuar con Chaingateway, debe autenticar cada solicitud con una clave API y especificar la red blockchain con la que está trabajando (por ejemplo, testnet o mainnet). Este paso garantiza que su aplicación pueda comunicarse con Chaingateway sin problemas.
Actualizar la configuración
Añada la configuración de Chaingateway en 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'),],A continuación, abra su archivo .env y añada lo siguiente:
Chaingateway_API_URL=https://api.Chaingateway.io/api/v2Chaingateway_API_KEY=your_api_key_hereChaingateway_NETWORK=testnetCOLD_WALLET=your_cold_wallet_addressExplicación
api_url: la URL base de la API de Chaingateway.api_key: su clave API personal para autenticar solicitudes.network: especifique si está usando la testnet (para desarrollo) o la mainnet (para producción).cold_wallet: el wallet seguro al que se reenviarán los fondos tras la verificación.
Paso 2: Definir rutas
Las rutas definen cómo interactúan los usuarios con su aplicación. Configuraremos rutas para:
- Mostrar la página de pago.
- Iniciar una nueva sesión de pago.
- Ver una sesión de pago.
- Gestionar webhooks.
Añadir rutas a 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']);Explicación
/payment: muestra una página con un botón para iniciar una nueva sesión de pago./start-payment-session: crea una nueva sesión y genera una dirección de wallet./payment-session/{id}: muestra la dirección del wallet y el estado de la sesión./webhook: recibe notificaciones de transacciones entrantes desde Chaingateway.
Para desactivar la protección CSRF en el endpoint de webhooks, tenemos que excluirlo en bootstrap/app.php. Si usa versiones anteriores de Laravel, consulte https://laravel.com/docs/11.x/csrf#csrf-excluding-uris para ver cómo funciona en su versión
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) { // Excluir la ruta del webhook de la protección CSRF $middleware->validateCsrfTokens(except: [ 'webhook', ]); }) ->withExceptions(function (Exceptions $exceptions) { // })->create();Paso 3: Crear models y migrations
Necesitamos dos tablas de base de datos:
- Wallets: almacena direcciones de wallet y claves privadas.
- Payment Sessions: registra el estado de cada sesión (por ejemplo, Pending, Completed o Failed).
Generar models y migrations
Ejecute los siguientes comandos:
php artisan make:model Wallet -mphp artisan make:model PaymentSession -mDefinir las migrations
Migration de Wallet
En database/migrations/<timestamp>_create_wallets_table.php:
Schema::create('wallets', function (Blueprint $table) { $table->id(); $table->string('address')->unique(); // Dirección del wallet $table->string('private_key'); // Clave privada para transacciones $table->timestamps();});Migration de PaymentSession
En database/migrations/<timestamp>_create_payment_sessions_table.php:
Schema::create('payment_sessions', function (Blueprint $table) { $table->id(); $table->string('status')->default('Pending'); // Pending, Completed o Failed $table->foreignId('wallet_id')->constrained()->onDelete('cascade'); // Enlace al Wallet $table->decimal('amount', 18, 8)->nullable(); // Importe enviado a esta sesión $table->string('currency')->default('TRX'); // Divisa del importe $table->decimal('received_amount', 18, 8)->nullable(); // Importe enviado a esta sesión $table->string('webhook_id')->nullable(); $table->timestamps(); });Ejecute las migrations:
php artisan migrateTambién debemos asegurarnos de que los campos sean fillable y de que las relaciones estén correctamente construidas. Para ello, adaptaremos los models.
Model Wallet
en 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
en 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); }}Paso 4: Implementar el PaymentController
El PaymentController gestiona toda la lógica de nuestra aplicación:
- Generar wallets.
- Crear y mostrar sesiones de pago.
- Gestionar notificaciones de webhooks.
Genere el controlador:
php artisan make:controller PaymentControllerAñadir la lógica del controlador
Aquí tiene la implementación completa de 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'); }
/** * Iniciar sesión de pago * * Esta función creará una nueva dirección de wallet y un webhook en 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); }
/** * Siempre deberíamos comprobar el recibo de la transacción para confirmar que realmente fue exitosa */ $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'];
/** * debería comprobar si el importe recibido coincide con el importe solicitado * si no es así, debería reembolsar al usuario o actuar de otra forma. * Los importes pueden variar, por ejemplo debido a la comisión de transacción. Debería tenerlo en cuenta añadiendo un margen. * También debería comprobar si la transacción es de un token TRC20 y si el contrato coincide con el esperado. * */ $amountDifference = abs($paymentSession->amount - $transactionData['amount']); $allowedDifference = $paymentSession->amount * 0.10; // 10% del importe de la sesión de pago
if ($amountDifference >= $allowedDifference) { // Permitir una diferencia de hasta un 10%, actualizar la dirección del contrato // Si el importe está sobrepagado o infrapagado en más de un 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'; }
/** * Haga esto si quiere mover sus fondos únicamente a un cold wallet. * También puede enviar los fondos a otro wallet o no hacer nada. * En el caso de tokens TRC20, debe asegurarse de tener suficiente TRX para pagar la comisión de transacción. * La función Tron Paymaster de Chaingateway es deprecated. Use TronFuel (https://tronfuel.dev) si no quiere gestionar las comisiones usted mismo.
$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'; }
/** * Eliminar el webhook en Chaingateway * Haga esto solo si no va a reutilizar la dirección */ $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']); }}¿Qué hace cada método?
showPaymentPage: muestra la página de pago principal con un formulario para iniciar una nueva sesión.startPaymentSession: genera una dirección de wallet, crea una nueva sesión de pago y redirige al usuario a la página de sesión.showPaymentSession: muestra la dirección del wallet y el estado de la sesión.handleWebhook: procesa las notificaciones de Chaingateway, verifica el éxito de la transacción, actualiza el estado de la sesión y reenvía los fondos al cold wallet (opcional).
Paso 5: Crear vistas
Para crear una nueva sesión de pago, este tutorial usa un formulario básico donde se introduce el importe y la divisa. Normalmente, esto lo haría su proceso de checkout.
Página de pago
En 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>En la página de la sesión de pago, los usuarios pueden comprobar el estado de su pago. Este también es un ejemplo muy básico. En un escenario real, usaría un polling más interactivo o websockets para actualizar el estado del pago.
Página de sesión de pago
En 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>---
## **Paso 6: Pruebas**
### **Iniciar el servidor**
Ejecute el servidor de desarrollo de Laravel:
```bashphp artisan serveProbar la aplicación
- Visite
/paymentpara iniciar una nueva sesión de pago. - Anote la dirección del wallet y envíele fondos (si está probando en testnet).
- Simule una notificación de webhook enviando una solicitud POST a
/webhook. - Verifique que el estado de la sesión se actualiza correctamente.
Esperamos que este tutorial le muestre lo fácil que es implementar nuestra API para recibir pagos cripto. Si tiene más preguntas o necesita ayuda durante la implementación, ¡siempre estamos aquí para ayudar! Puede contactar con nuestra comunidad, siempre dispuesta a ayudar, o escribirnos un correo electrónico. Vea aquí cómo mantenerse en contacto: https://Chaingateway.io/support
¿Listo para construirlo tú mismo? Obtenga su clave de API — prueba de 7 días, sin tarjeta — o consulte API de Tron para la referencia completa de los endpoints.