Blog Post

Read our latest blog posts and stay updated with the latest trends and insights in the blockchain industry.
Explore a wide range of topics and discover valuable information that can help you enhance your knowledge and skills.

How to Receive Crypto Payments in Laravel with Chaingateway API

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:

  1. Developer Portal: Learn how to use Chaingateway features.
  2. API Documentation: Explore the API endpoints in detail.
  3. 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:

  1. Log in to Chaingateway.
  2. Navigate to User Settings > API Tokens.
  3. 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:

  1. A Laravel Installation: A fresh Laravel project setup. Follow the Laravel installation guide if needed.
  2. Database Configuration: Update your .env file with your database credentials.
  3. 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:

  1. A dynamic form to start payment sessions:
    • Users can input the payment amount.
    • Users can select the currency (TRX or USDT).
  2. A generated wallet address is tied to each session.
  3. A session page showing:
    • The wallet address.
    • The session status.
    • The amount to be sent, the amount received, and the currency.
  4. 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://beta.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/v2
Chaingateway_API_KEY=your_api_key_here
Chaingateway_NETWORK=testnet
COLD_WALLET=your_cold_wallet_address

Explanation

  • 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:

  1. Showing the payment page.
  2. Starting a new payment session.
  3. Viewing a payment session.
  4. 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:

  1. Wallets: Stores wallet addresses and private keys.
  2. 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 -m
php artisan make:model PaymentSession -m

Define 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 migrate

We 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:

  1. Generating wallets.
  2. Creating and displaying payment sessions.
  3. Handling webhook notifications.

Generate the controller:

php artisan make:controller PaymentController

Add 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>
<body>
    <h1>Payment Session</h1>
    <p>Send <strong> </strong> to the address below:</p>
    <p><strong></strong></p>
    <p>Status: <strong></strong></p>
    <p>Received: <strong> </strong></p>
</body>
</html>


Step 6: Testing

Start the Server

Run the Laravel development server:

php artisan serve

Test the Application

  1. Visit /payment to start a new payment session.
  2. Note the wallet address and send funds to it (if testing on testnet).
  3. Simulate a webhook notification by sending a POST request to /webhook.
  4. 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

Share This Post

Related Posts

Nov 20, 2024

Solana vs Tron: Which Blockchain is Right for You?

Solana vs Tron: Which blockchain stands out for DeFi, gaming, or content creation? Explore use cases, pros, and cons to decide for yourself.

Apr 25, 2023

The Importance of Blockchain APIs in the Digital Landscape

Importance of Blockchain APIs in the digital landscape, their benefits, and how they can be used to enhance the overall user experience.

Sep 18, 2024

Ultimate Tron Fee Guide + Free Tron Fee Calculator Tool

Calculate Tron fees easily with our free TRX Calculator. Optimize costs for transactions and smart contracts, and save up to 60% with our Tron Paymaster API.

Cookie Policy

We use cookies to enhance your experience. By continuing to visit this site, you agree to our use of cookies.