Payment Gateway Service Class
Copy php artisan payable:gateway Paypal
This will generate Paypal
class implementing PaymentGatewayInterface
located in app\PaymentGateway
implementing 5 methods :-
pay(float $amount, $return_url, $purchase_order_id, $purchase_order_name);
Accepts 4 arguments:-
amount(float): Payment amount to be processed via payment gateway
return_url : Payment gateway redirect url
Responsible for payment process requests to payment gateway
initiate(float $amount, $return_url, ?array $arguments = null)
Accepts 3 arguments:-
amount(float): Payment amount to be processed via payment gateway
return_url : Payment gateway redirect url
arguments(array) : Any additional data needed for the transaction process
Responsible for payment process requests to payment gateway
inquiry($transaction_id, ?array $arguments = null) : array
Accepts 2 arguments:-
transaction_id: Uniquely identifiable key given by payment gateway vendor
arguments(array) : Any additional data needed for the inquiry process
Responsible for lookup requests for payment transactions to verify their authenticity
isSuccess(array $inquiry, ?array $arguments = null): bool
Accepts 2 arguments:-
inquiry: Array response from inquiry
method
arguments(array) : Any additional data needed for the isSuccess process
Responsible for verifying transaction success status
requestedAmount(array $inquiry, ?array $arguments = null): float
Accepts 2 arguments:-
inquiry: Array response from inquiry
method
arguments(array) : Any additional data needed for the isSuccess process
Responsible for returning requested amount to be processed in payment gateway
Payment Gateway
This class accepts payment gateway service class and is responsible for initiating and processing payment from the gateway to registering verified payment to the database
__construct(PaymentGatewayInterface $gateway, $model)
Accepts 2 arguments:-
Payment gateway service class (Paypal instance for example)
Model instance which has HasPayable trait
pay(float $amount, $return_url, $product_id = null, $product_name = null)
Accepts 3 arguments:-
product_id(optional) if left empty takes model instance id ($model->id)
product_name (optional) if left empty takes the model instance name ($model->name)
Responsible for payment process requests to payment gateway
process($transaction_id, ?array $arguments = null): Payment
Accepts 2 arguments:-
transaction_id: Uniquely identifiable key given by payment gateway vendor
arguments(array) : Any additional data needed for the inquiry process
Responsible for verifying payment status and registering payments.
Example Walk Through
Creating payment generator
Generate Paypal payment gateway using the payment gateway generator command
Copy <?php
namespace App\PaymentGateway;
use Exception;
use Illuminate\Support\Facades\Http;
use Pratiksh\Payable\Contracts\PaymentGatewayInterface;
class Paypal implements PaymentGatewayInterface
{
/**
*
* Function to perform some logic before payment process
*
*/
public function pay(float $amount, $return_url)
{
// Some Actions
return $this->initiate($amount, $return_url);
}
/**
*
* Initiate Payment Gateway Transaction
* @param float amount : Amount requested for payment transaction
* @param return_url : Redirect url after payment transaction
* @param array arguments : Additional dataset
*
*/
public function initiate(float $amount, $return_url, ?array $arguments = null)
{
// Some Actions
}
/**
*
* Success status of payment transaction
* @param array inquiry : Payment transaction response
* @param array arguments : Additional dataset
* @return bool
*
*/
public function isSuccess(array $inquiry, ?array $arguments = null): bool
{
return true;
}
/**
*
* Requested amount to be registered
* @param array inquiry : Payment transaction response
* @param array arguments : Additional dataset
* @return float
*
*/
public function requestedAmount(array $inquiry, ?array $arguments = null): float
{
return 0;
}
/**
*
* Payment status lookup request
* @param mixed transaction_id : Code provided by payment gateway vendor to uniquely identify the payment transaction
* @param array arguments : Additional dataset
* @return array
*
*/
public function inquiry($transaction_id, ?array $arguments = null): array
{
return [];
}
}
Controller to handle checkout and verification
Copy <?php
namespace App\Http\Controllers\PaymentGateway;
use Illuminate\Http\Request;
use App\Models\Booking;
use App\Http\Controllers\Controller;
use App\PaymentGateway\Paypal;
use Pratiksh\Payable\Services\PaymentGateway;
class PaypalController extends Controller
{
// Booking Checkout
public function checkout(Booking $booking){
$paypal= new Paypal;
$return_url = route('paypal.booking.verification', ['booking' => $booking->id]);
return (new PaymentGateway($paypal, $booking))->pay($booking->fee, $return_url);
}
// Adventure Booking Verification
public function verification(Request $request,Booking $booking)
{
$decodedString = base64_decode($request->data);
$data = json_decode($decodedString, true);
$transaction_code = $data['transaction_code'] ?? null;
$status = $data['status'] ?? null;
$total_amount = $data['total_amount'] ?? null;
$transaction_uuid = $data['transaction_uuid'] ?? null;
$product_code = $data['product_code'] ?? null;
$signed_field_names = $data['signed_field_names'] ?? null;
$signature = $data['signature'] ?? null;
$payment = (new PaymentGateway(new Paypal, $booking))->process($transaction_uuid, [
'transaction_code' => $transaction_code,
'status' => $status,
'total_amount' => $total_amount,
'transaction_uuid' => $transaction_uuid,
'product_code' => $product_code,
'signed_field_names' => $signed_field_names,
'signature' => $signature,
]);
dd('Payment Success');
}
}
Routes to handle checkout and verification
Copy Route::get('paypal-booking-checkout/{booking}',[PaypalController::class,'checkout'])->name('paypal.booking.checkout');
Route::get('paypal-booking-verification/{booking}',[PaypalController::class,'verification'])->name('paypal.booking.verification');
Some of the premade payment gateways
Esewa
Copy <?php
namespace App\PaymentGateway;
use Exception;
use Illuminate\Support\Facades\Http;
use Pratiksh\Payable\Contracts\PaymentGatewayInterface;
class Esewa implements PaymentGatewayInterface
{
public $inquiry;
public $amount;
public $base_url;
public $purchase_order_id;
public $purchase_order_name;
public function __construct()
{
$this->base_url = env('APP_DEBUG') ? 'https://uat.esewa.com.np/api/epay/' : 'https://epay.esewa.com.np/api/epay/';
}
/**
*
* Function to perform some logic before payment process
*/
public function pay(float $amount, $return_url, $purchase_order_id, $purchase_order_name)
{
$this->purchase_order_id = $purchase_order_id;
$this->purchase_order_name = $purchase_order_name;
return $this->initiate($amount, $return_url);
}
/**
*
* Initiate Payment Gateway Transaction
* @param float amount : Amount requested for payment transaction
* @param return_url : Redirect url after payment transaction
* @param array arguments : Additional dataset
*
*/
public function initiate(float $amount, $return_url, ?array $arguments = null)
{
$this->amount = env('APP_DEBUG') ? 1000 : $amount;
$process_url = $this->base_url . 'main/v2/form/';
$tuid = now()->timestamp;
$merchant_id = env('ESEWA_MERCHENT_ID');
$message = "total_amount=$amount,transaction_uuid=$tuid,product_code=$merchant_id";
$s = hash_hmac('sha256', $message, env('ESEWA_SECRET_KEY'), true);
$signature = base64_encode($s);
$data = [
"amount" => $amount,
"failure_url" => url('/'),
"product_delivery_charge" => "0",
"product_service_charge" => "0",
"product_code" => "EPAYTEST",
"signature" => $signature,
"signed_field_names" => "total_amount,transaction_uuid,product_code",
"success_url" => $return_url,
"tax_amount" => "0",
"total_amount" => $amount,
"transaction_uuid" => $tuid
];
// generate form from attributes
$htmlForm = '<form method="POST" action="' . ($process_url) . '" id="esewa-form">';
foreach ($data as $name => $value) :
$htmlForm .= sprintf('<input name="%s" type="hidden" value="%s">', $name, $value);
endforeach;
$htmlForm .= '</form><script type="text/javascript">document.getElementById("esewa-form").submit();</script>';
// output the form
echo $htmlForm;
}
/**
*
* Success status of payment transaction
* @param array inquiry : Payment transaction response
* @param array arguments : Additional dataset
* @return bool
*
*/
public function isSuccess(array $inquiry, ?array $arguments = null): bool
{
return ($inquiry['status'] ?? null) == 'COMPLETE';
}
/**
*
* Requested amount to be registered
* @param array inquiry : Payment transaction response
* @param array arguments : Additional dataset
* @return float
*
*/
public function requestedAmount(array $inquiry, ?array $arguments = null): float
{
return $inquiry['total_amount'];
}
/**
*
* Payment status lookup request
* @param mixed transaction_id : Code provided by payment gateway vendor to uniquely identify payment transaction
* @param array arguments : Additional dataset
* @return array
*
*/
public function inquiry($transaction_id, ?array $arguments = null): array
{
$process_url = $this->base_url . 'transaction/status/';
$total_amount = $arguments['total_amount'] ?? null;
if (!is_null($total_amount)) {
$payload = [
'product_code' => env('ESEWA_MERCHENT_ID'),
'transaction_uuid' => $transaction_id,
'total_amount' => $total_amount
];
$response = Http::get($process_url, $payload);
$this->inquiry = json_decode($response->body(), true);
return $this->inquiry;
} else {
throw new Exception('total_amount is required');
}
}
}
Khalti
Copy <?php
namespace App\PaymentGateway;
use Exception;
use Stripe\Refund;
use Illuminate\Support\Facades\Http;
use Pratiksh\Payable\Contracts\PaymentGatewayInterface;
class Khalti implements PaymentGatewayInterface
{
public $amount;
public $base_url;
public $purchase_order_id;
public $purchase_order_name;
public $inquiry_response;
/*
|--------------------------------------------------------------------------
| Customer Detail
|--------------------------------------------------------------------------
|
*/
public $customer_name;
public $customer_phone;
public $customer_email;
public function __construct()
{
$this->base_url = env('APP_DEBUG') ? 'https://a.khalti.com/api/v2/' : 'https://khalti.com/api/v2/';
}
public function byCustomer($name,$email,$phone){
$this->customer_name = $name;
$this->customer_email = $email;
$this->customer_phone = $phone;
return $this;
}
public function pay(float $amount, $return_url, $purchase_order_id, $purchase_order_name)
{
$this->purchase_order_id = $purchase_order_id;
$this->purchase_order_name = $purchase_order_name;
return $this->initiate($amount, $return_url);
}
public function initiate(float $amount, $return_url, ?array $arguments = null)
{
$this->amount = env('APP_DEBUG') ? 1000 : ($amount * 100);
$process_url = $this->base_url . 'epayment/initiate/';
$return_url = $return_url;
$website_url = url('/');
$purchase_order_id = $this->purchase_order_id;
$purchase_order_name = $this->purchase_order_name;
$customer_name = $this->customer_name;
$customer_email = $this->customer_email;
$customer_phone = $this->customer_phone;
// Build the data array
$data = [
"return_url" => $return_url,
"website_url" => $website_url,
"amount" => $this->amount,
"purchase_order_id" => $purchase_order_id,
"purchase_order_name" => $purchase_order_name,
"customer_info" => [
"name" => $customer_name,
"email" => $customer_email,
"phone" => $customer_phone
]
];
$response = Http::withHeaders([
'Content-Type' => 'application/json',
'Authorization' => 'key ' . env('KHALTI_SECRET_KEY'), // Replace with your authorization token
])->post($process_url, $data);
if ($response->ok()) {
$body = json_decode($response->body());
return redirect()->to($body->payment_url);
} else {
throw new Exception('Khalti transaction failed');
}
}
public function isSuccess(array $inquiry, ?array $arguments = null): bool
{
return ($inquiry['status'] ?? null) == 'Completed';
}
public function requestedAmount(array $inquiry, ?array $arguments = null): float
{
return $inquiry['total_amount'];
}
public function inquiry($transaction_id, ?array $arguments = null) : array
{
$process_url = $this->base_url . 'epayment/lookup/';
$payload = [
'pidx' => $transaction_id
];
$response = Http::withHeaders([
'Content-Type' => 'application/json',
'Authorization' => 'key ' . env('KHALTI_SECRET_KEY'),
])->post($process_url, $payload);
$this->inquiry_response = json_decode($response->body(),true);
return $this->inquiry_response;
}
}
Fonepay
Copy <?php
namespace App\PaymentGateway;
use Exception;
use Carbon\Carbon;
use Illuminate\Support\Facades\Http;
use Pratiksh\Payable\Contracts\PaymentGatewayInterface;
class Fonepay implements PaymentGatewayInterface
{
public $inquiry;
public $amount;
public $base_url;
public $purchase_order_id;
public $purchase_order_name;
public function __construct()
{
$this->base_url = env('APP_DEBUG') ? 'https://dev-clientapi.fonepay.com/' : 'https://clientapi.fonepay.com/';
}
public function pay(float $amount, $return_url, $purchase_order_id, $purchase_order_name)
{
$this->purchase_order_id = $purchase_order_id;
$this->purchase_order_name = $purchase_order_name;
return $this->initiate($amount, $return_url);
}
public function initiate(float $amount, $return_url, ?array $arguments = null)
{
$this->amount = env('APP_DEBUG') ? 1000 : $amount;
$process_url = $this->base_url . 'api/merchantRequest';
$sharedSecretKey = env('FONEPAY_SECRET_KEY');
$merchant_id = env('FONEPAY_MERCHANT_ID');
$RU = $return_url; // Return URL
$PID = $merchant_id; // Merchant Code, Defined by fonepay system
$PRN = $this->purchase_order_id; //Product Reference Number, need to send by merchant
$AMT = $this->amount; // Payable Amount
$CRN = 'NPR';
$DT = date('m/d/Y');
$R1 = $this->purchase_order_name; // Additional Info 1
$R2 = title(); // Additional Info 2
$MD = 'P'; // P - Payment
$DV = hash_hmac('sha512', $PID . ',' . $MD . ',' . $PRN . ',' . $AMT . ',' . $CRN . ',' . $DT . ',' . $R1 . ',' . $R2 . ',' . $RU, $sharedSecretKey);
$data = [
'RU' => $RU,
'PID' => $PID,
'PRN' => $PRN,
'AMT' => $AMT,
'CRN' => $CRN,
'DT' => $DT,
'R1' => $R1,
'R2' => $R2,
'MD' => $MD,
'DV' => $DV,
];
// generate form from attributes
$htmlForm = '<form method="POST" action="' . ($process_url) . '" id="esewa-form">';
foreach ($data as $name => $value) :
$htmlForm .= sprintf('<input name="%s" type="hidden" value="%s">', $name, $value);
endforeach;
$htmlForm .= '</form><script type="text/javascript">document.getElementById("esewa-form").submit();</script>';
// output the form
echo $htmlForm;
}
public function isSuccess(array $inquiry, ?array $arguments = null): bool
{
return ($inquiry['success'] ?? null) == 'true';
}
public function requestedAmount(array $inquiry, ?array $arguments = null): float
{
return $inquiry['amount'];
}
public function inquiry($transaction_id, ?array $arguments = null) : array
{
$process_url = $this->base_url . 'api/merchantRequest/verificationMerchant';
$sharedSecretKey = env('FONEPAY_SECRET_KEY');
$PID = env('FONEPAY_MERCHANT_ID');
$UID = $transaction_id;
$PRN = $arguments['PRN'] ?? '';
$BID = $arguments['BID'] ?? '';
$R_AMT = $arguments['R_AMT'] ?? '';
$data =[
'PRN' => $PRN,
'PID' => $PID,
'BID' => $BID,
'AMT' => $R_AMT, // original payment amount
'UID' => $UID,
'DV' => hash_hmac('sha512', $PID . ',' . $R_AMT . ',' . $PRN . ',' . $BID . ',' . $UID, $sharedSecretKey),
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $process_url . '?' . http_build_query($data));
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$responseXML = curl_exec($ch);
// Load XML string
$response = simplexml_load_string($responseXML);
// Convert XML to JSON and then to array
return json_decode(json_encode($response), true);
}
}
Last updated 10 months ago