Create an Instant Push Notification(IPN) Service in Laravel

Imran Hossain (Rubab)
3 min readOct 23, 2023

Let’s create an Instant Push Notification (IPN) service for user’s card add and delete in a payment gateway service. We will notify the IPN listener about this user action.

First, we will create a notifications table using the php artisan command

php artisan make:migration create_notifications_table --create=notifications

please add the scheme insde the migration file

Schema::create('notifications', function (Blueprint $table) {
$table->id();
$table->string('action_type');
$table->string('ipn_listener_url');
$table->text('post_data');
$table->integer('tries')->default(0);
$table->timestamp('last_tried_at')->nullable();
$table->timestamps();
$table->timestamp('deleted_at')->nullable(); //for soft delete
});
}

Now, migrate this file using the command

php artisan migrate

Let’s create a model called Notification.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Notification extends Model
{
use SoftDeletes;

protected $table = 'notifications';
}

Now, we will save a notification to this notification table when a user add/delete a card.

First, create a route for the action of a card. We will send the card index, and action type to identify which card is added/deleted.

$router->post('card/action', 'CardController@cardAction');

Create a controller CardController.php and add a method called cardAction

public function cardAction(Request $request){
// store this action to notificaion table
$notification = new Notification();
$notification->action_type = $request->action_type;
$notification->ipn_listener_url = 'http://localhost/my-ipn-listener'; // please change it to your ipn listener url who will receive the IPN data
$notification->post_data = json_encode(['action_type'=>$request->action_type, 'card_index'=>$request->card_index]);
$notification->tries = 0;
$notification->last_tried_at = null;

if(!$notification->save())
return false;
return true;
}

It’s time to create our artisan command which will send notifications to the IPN listener. Let’s create an artisan command first. In your terminal type,

php artisan make:command IpnNotification --command=ipn:notification

We will see a new file IpnNotification.php file is created inside app/console/Commands folder.

In the IpnNotification.php class, we will first add the description of the command.

/**
* The console command description.
*
* @var string
*/
protected $description = 'notify data to IPN';

Next, to send the IPN data we will add our notification send logic inside the handle method which is already provided in the class.

public function handle()
{
$tryLimit = 3; // max try limit for a failed notificaion
$intervalMinute = 5; // try a failed one after each five minutes

// send all the notifications between yesterday and today
$notifications = Notification::whereBetween('created_at', [Carbon::yesterday()->startOfDay(), Carbon::now()])
->where(function($query) use($intervalMinute){
$query->where('last_tried_at', '<', Carbon::now()->subMinutes($intervalMinute))
->orWhereNull('last_tried_at');
})
->where('tries', '<=', $tryLimit)
->get();

if(!$notifications->isEmpty()){
foreach($notifications as $notification) {
$payload = ['ipn_data'=>$notification->post_data];

// sending notification using CURL request
$isSent = $this->sendNotification($notification->ipn_listener_url, $payload);

if($isSent) {
$notification->delete(); // soft delete the notifcaion as it's successful and no need to send further
}
else{
$notification->tries = $notification->tries + 1;
$notification->save();
}
}
}
}

private function sendNotification(string $url, array $body)
{
try{
$curl = curl_init();

curl_setopt_array($curl, array(
CURLOPT_URL => $url,
CURLOPT_TIMEOUT_MS => 60000, // Set the timeout value in milliseconds
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $body,
));

$curlResponse = curl_exec($curl);
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$err = curl_error($curl);

curl_close($curl);

if (curl_errno($curl) == CURLE_OPERATION_TIMEDOUT || $err || $httpcode != 200)
return false;

return true;
} catch(\Throwable $e) {
return false;
}
}

Now, we will register our notification command inside app/console/Kernel.php file.

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
'App\Console\Commands\IpnNotification',
];

/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('ipn:notification')
->everyMinute()
->withoutOverlapping();
}
}

To Send the notification we will run our specific schedule command

php artisan ipn:notification

Or we can run the scheduler

php artisan schedule:run

If we always want to run the scheduler every minute then we can achieve it by adding the following Cron entry to our server.

* * * * * php /path-to-your-project/artisan schedule:run >> /dev/null 2>&1

Thank you for reading my article. You can join code with rubab for web development-related queries & discussions.

Also, you can find me on:

Linkedin For Regular Posts

My website

My Facebook Page

My Youtube Channel

--

--

Imran Hossain (Rubab)

I'm a Senior Software Specialist working at SSLCommerz which is the largest Payment Gateway Service in Bangladesh. Founder of Code With Rubab to teach coding.