How to Receive Crypto Payments in Laravel with Chaingateway API
Learn how to receive crypto payments in Laravel using Chaingateway API. This tutorial covers generating wallets, handling webhooks, and verifying transactions for Tron and TRC20 USDT payments. Perfect for developers looking to integrate blockchain payments into their Laravel applications.
This tutorial will guide you through building a payment gateway in Laravel that supports Tron (TRC) and JST (TRC20) payments. It includes features like generating wallets for payment sessions, handling webhooks for transaction notifications, and verifying transaction status before processing.
By the end of this tutorial, you will have a functional payment gateway that uses Chaingateway’s API for blockchain interactions.
This tutorial should only describe the basic process of how the implementation works. This will also work for Bitcoin, Ethereum, Binance Smart Chain, and Polygon with some small adaptations.
Introduction
Before diving into the implementation, let’s understand the tools we’ll use:
What is Chaingateway?
Chaingateway is a blockchain API service that simplifies interaction with Blockchain networks like Tron. It allows you to:
- Generate wallet addresses.
- Monitor transactions.
- Execute transactions programmatically.
Visit the official documentation for more details:
- Developer Portal: Learn how to use Chaingateway features.
- API Documentation: Explore the API endpoints in detail.
- API Key Creation: Generate API keys required for authentication.
Steps to Create an API Key
To interact with the Chaingateway API, you’ll need an API key. Follow these steps:
- Log in to Chaingateway.
- Navigate to User Settings > API Tokens.
- Click Create Token, give your token a name (e.g., “Payment Gateway”), and copy it. You’ll use this key in your Laravel application.
Prerequisites
Before proceeding, ensure you have:
- A Laravel Installation: A fresh Laravel project setup. Follow the Laravel installation guide if needed.
- Database Configuration: Update your
.envfile with your database credentials. - Basic Knowledge of Laravel: Familiarity with models, migrations, controllers, and routes is helpful.
Features of the Tutorial
This tutorial builds a payment gateway with the following features:
- A dynamic form to start payment sessions:
- Users can input the payment amount.
- Users can select the currency (TRX or USDT).
- A generated wallet address is tied to each session.
- A session page showing:
- The wallet address.
- The session status.
- The amount to be sent, the amount received, and the currency.
- A webhook to handle:
- Verifying incoming transactions.
- Updating the session’s status.
- Forwarding funds to a cold wallet upon successful payment (optional).
Step 1: Configure Chaingateway API
First, we’ll configure Laravel to use the Chaingateway API.
Why is this important?
To interact with Chaingateway, you need to authenticate every request using an API key and specify the blockchain network you’re working with (e.g., testnet or mainnet). This step ensures your application can communicate with Chaingateway seamlessly.
Update the Configuration
Add Chaingateway configuration to 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'),],Next, open your .env file and add the following:
Chaingateway_API_URL=https://api.Chaingateway.io/api/v2Chaingateway_API_KEY=your_api_key_hereChaingateway_NETWORK=testnetCOLD_WALLET=your_cold_wallet_addressExplanation
api_url: The base URL for the Chaingateway API.api_key: Your personal API key for authenticating requests.network: Specify whether you’re using the testnet (for development) or mainnet (for production).cold_wallet: The secure wallet where funds will be forwarded after verification.
Step 2: Define Routes
Routes define how users interact with your application. We’ll set up routes for:
- Showing the payment page.
- Starting a new payment session.
- Viewing a payment session.
- Handling webhooks.
Add Routes to 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']);Explanation
/payment: Displays a page with a button to start a new payment session./start-payment-session: Creates a new session and generates a wallet address./payment-session/{id}: Displays the wallet address and session status./webhook: Receives notifications about incoming transactions from Chaingateway.
To disable csrf protection on the webhooks endpoint, we need to exclude it in bootstrap/app.php. If you use older versions of Laravel, head over to https://laravel.com/docs/11.x/csrf#csrf-excluding-uris to see how it works for your version
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) { // Exclude webhook route from scrf protection $middleware->validateCsrfTokens(except: [ 'webhook', ]); }) ->withExceptions(function (Exceptions $exceptions) { // })->create();Step 3: Create Models and Migrations
We need two database tables:
- Wallets: Stores wallet addresses and private keys.
- Payment Sessions: Tracks the status of each session (e.g., Pending, Completed, or Failed).
Generate Models and Migrations
Run the following commands:
php artisan make:model Wallet -mphp artisan make:model PaymentSession -mDefine Migrations
Wallet Migration
In database/migrations/<timestamp>_create_wallets_table.php:
Schema::create('wallets', function (Blueprint $table) { $table->id(); $table->string('address')->unique(); // Wallet address $table->string('private_key'); // Private key for transactions $table->timestamps();});PaymentSession Migration
In database/migrations/<timestamp>_create_payment_sessions_table.php:
Schema::create('payment_sessions', function (Blueprint $table) { $table->id(); $table->string('status')->default('Pending'); // Pending, Completed, or Failed $table->foreignId('wallet_id')->constrained()->onDelete('cascade'); // Links to Wallet $table->decimal('amount', 18, 8)->nullable(); // Amount sent to this session $table->string('currency')->default('TRX'); // Currency of the amount $table->decimal('received_amount', 18, 8)->nullable(); // Amount sent to this session $table->string('webhook_id')->nullable(); $table->timestamps(); });Run the migrations:
php artisan migrateWe should also ensure that the fields are fillable and relations are built correctly. To do so, we will adapt the models.
Wallet Model
in 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
in 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); }}Step 4: Implement PaymentController
The PaymentController handles all the logic for our application:
- Generating wallets.
- Creating and displaying payment sessions.
- Handling webhook notifications.
Generate the controller:
php artisan make:controller PaymentControllerAdd the Controller Logic
Here’s the complete implementation of 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'); }
/** * Start payment session * * This Function will create a new wallet address and webhook in 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); }
/** * We always should check the transaction receipt if the transaction really was successful */ $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'];
/** * you should check if the amount received is the same as the amount requested * if not, you should refund the user or do something else. * Amounts could vary, for example, due to the transaction fee. You should consider that by adding a margin. * You should also check if the transaction is a TRC20 token transaction and the contract is the same as the one you are expecting. * */ $amountDifference = abs($paymentSession->amount - $transactionData['amount']); $allowedDifference = $paymentSession->amount * 0.10; // 10% of the payment session amount
if ($amountDifference >= $allowedDifference) { // Allow a difference of up to 10%, update the contract address // If the amount is overpaid or underpaid by more than 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'; }
/** * Do this if you want to move your funds to an cold wallet only. * You can also send the funds to another wallet or do nothing. * In case of TRC20 tokens, you need to ensure you have enough TRX to pay for the transaction fee. * You can also use the Chaingateway Tron Paymaster feature so you dont need to handle the fees.
$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'; }
/** * Delete Webhook in Chaingateway * Only do that if you will not use the address again */ $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']); }}What does each method do?
showPaymentPage: Displays the main payment page with a form to start a new session.startPaymentSession: Generates a wallet address, creates a new payment session, and redirects the user to the session page.showPaymentSession: Displays the wallet address and the session status.handleWebhook: Processes notifications from Chaingateway, verifies transaction success, updates the session’s status, and forwards funds to the cold wallet (optional).
Step 5: Create Views
To create a new payment session, this tutorial uses a basic form where you type in amount and currency. This should normally be done by your checkout process.
Payment Page
In 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>On the Payment Session page, users can check their payment status. This is also a very basic example. In a real-life scenario, you would use more interactive polling or websockets to refresh the payment status.
Payment Session Page
In 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>---
## **Step 6: Testing**
### **Start the Server**
Run the Laravel development server:
```bashphp artisan serveTest the Application
- Visit
/paymentto start a new payment session. - Note the wallet address and send funds to it (if testing on testnet).
- Simulate a webhook notification by sending a POST request to
/webhook. - Verify the session’s status updates correctly.
We hope this tutorial shows you how easy it is to implement our API to receive crypto payments. If you have any further questions or need help during implementation, we are always here to help! You can reach out to our very supportive community or write us an email. See here how to stay in touch: https://Chaingateway.io/support