How to implement phonepe in laravel?

This guide provides a step-by-step process on how to integrate the phonepe payment gateway into a PHP or a Laravel application without using any package, I prefer to use laravel but this process will work in PHP aswell


You dont need a an account for testing phonpe payment gateway, PhonePe test credentials are public, But if you wish for live / production keys for real payments then you need to login / register on phonepe business solutions


Test Credentials : 

Merchant ID : PGTESTPAYUAT
Salt Key : 099eb0cd-02cf-4e2a-8aca-3e6c6aff0399


Test Card : 

Card : 4111 1111 1111 1111
Expiry: 12/25 (any future date)
CVV: 111
OTP : 123456

Step 1 : Create a controller.

To create a controller use the command below.

php artisan make:controller PaymentController
Step 2 : Create a payment form.

Now we will create a payment form so you can ask user for the detials and conformation you need, Then we will process the payment and then in callback we will confirm the payment and Check if its a success or not, So lets create some routes in web.php

//PAYMENT FORM
Route::get('payment-form', [\App\Http\Controllers\PaymentController::class, 'index'])->name('payment-form');
//PAYMENT ROUTE
Route::post('pay-with-phonepe', [\App\Http\Controllers\PaymentController::class, 'payWithPhonePe'])->name('pay-with-phonepe');
//CALLBACK ROUTE
Route::get('phonepe-callback', [\App\Http\Controllers\PaymentController::class, 'callback'])->name('phonepe-callback');

Now create a function named index in PaymentController.php

public function index()
{
    return view('phonepe.payment-form');
}

Now create a payment form,

resources/views/phonepe/payment-form.blade.php


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>PhonePe Payment Gateway</title>

    <!-- BOOTSTRAP -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />

    <!-- Styles -->
    <style>
        body {
            background: #f7f7f7;
        }

        .form-box {
            max-width: 500px;
            margin: auto;
            padding: 50px;
            background: #ffffff;
            border: 10px solid #f2f2f2;
            margin-top: 100px;
        }

        h1, p {
            text-align: center;
        }

        input, textarea {
            width: 100%;
        }
    </style>
</head>
<body>
<div class="form-box">
    <h1>Pay with PhonePe</h1>
    <form action="{{ route('pay-with-phonepe') }}" method="post" style="text-align: center;margin-top: 50px;">
        @csrf
        <button type="submit" class="btn btn-primary">Pay $19</button>
    </form>
</div>
</body>
</html>
Step 3 : Exclude callback URL.

Now because PhonePe payment callback will be in post method and there will be no csrf token passed from PhonePe side, So we need to exclude csrf verification for phonepe callback url, To do that we need to add callback url in a middleware located in app/Http/Middleware/VerifyCsrfToken.php 

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    protected $except = [
        'phonepe-callback'
    ];
}
Step 4 : Create payment function.

Now create a function named payWithPhonePe in PaymentController.php

public function payWithPhonePe()
{
    $redirect_url = route('phonepe-callback');

    $saltKey = '099eb0cd-02cf-4e2a-8aca-3e6c6aff0399';
    $merchantId = 'PGTESTPAYUAT';

    $amount = 19 * 100;

    $testMode = true;

    $merchantUserId = time();


    if($testMode)
    {
        $chUrl = 'https://api-preprod.phonepe.com/apis/pg-sandbox/pg/v1/pay';
    }
    else
    {
        $chUrl = 'https://api.phonepe.com/apis/hermes';
    }

    $paymentData = array(
        'merchantId' => $merchantId,
        'merchantTransactionId' => time().rand(1,1000),
        "merchantUserId"=> $merchantUserId,
        'amount' => $amount,
        'redirectUrl' => $redirect_url,
        'redirectMode' => "POST",
        'callbackUrl' => $redirect_url,
        "merchantOrderId" => time(),
        "paymentInstrument" => array(
            "type"=> "PAY_PAGE",
        ),
    );

    $jsonencode = json_encode($paymentData);
    $payloadMain = base64_encode($jsonencode);
    $salt_index = 1;
    $payload = $payloadMain . "/pg/v1/pay" . $saltKey;
    $sha256 = hash("sha256", $payload);
    $final_x_header = $sha256 . '###' . $salt_index;
    $request = json_encode(array('request'=>$payloadMain));

    $curl = curl_init();
    curl_setopt_array($curl, [
        CURLOPT_URL => $chUrl,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "POST",
        CURLOPT_POSTFIELDS => $request,
        CURLOPT_HTTPHEADER => [
            "Content-Type: application/json",
            "X-VERIFY: " . $final_x_header,
            "accept: application/json"
        ],
    ]);

    $response = curl_exec($curl);
    $err = curl_error($curl);

    curl_close($curl);

    if ($err)
    {
        //HANDLE YOUR ERROR MESSAGE HERE
        dd('ERROR : ' . $err);
    }
    else
    {
        $res = json_decode($response);

        if (isset($res->success) && $res->success == '1')
        {
            $payUrl = $res->data->instrumentResponse->redirectInfo->url;
            //REDIRECT TO PHONEPE HOSTED CHECKOUT PAGE
            return redirect($payUrl);
        }
        else
        {
            //HANDLE YOUR ERROR MESSAGE HERE
            dd('ERROR : ' . $res);
        }
    }
}

PhonePe Checkout Page


Step 5 : Callback Function.

Now create a function named callback in PaymentController.php, In this function we will receive code, merchantId, transactionId, amount, providerReferenceId, checksum and more


Note : You will receive data in POST Method

public function callback(Request $request)
{
    if($request->code == 'PAYMENT_SUCCESS')
    {
        $phonepe_transaction_id = $request->transactionId;
        //Transaction completed, You can add transaction details into database              
    }
    else
    {
        //HANDLE YOUR ERROR MESSAGE HERE
        dd('ERROR : ' .$request->code. ', Please Try Again Later.');
    }
}