classify admin

This commit is contained in:
Husanjonazamov
2026-02-24 12:52:01 +05:00
commit e0f1989655
769 changed files with 1263008 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
<?php
namespace App\Services\Payment;
use App\Services\ResponseService;
use KingFlamez\Rave\Rave as Flutterwave;
use Exception;
use Illuminate\Support\Facades\Auth;
class FlutterwavePayment implements PaymentInterface
{
private string $currencyCode;
private Flutterwave $flutterwave;
private string $encryptionKey;
protected $baseUrl;
private string $secretKey;
public function __construct($secret_key, $public_key, $encryption_key, $currencyCode)
{
$this->currencyCode = $currencyCode;
$this->encryptionKey = $encryption_key;
$this->baseUrl = 'https://api.flutterwave.com/v3';
$this->secretKey = $secret_key;
// Initialize Flutterwave SDK with API keys
$this->flutterwave = new Flutterwave([
'publicKey' => $public_key,
'secretKey' => $secret_key,
'encryptionKey' => $encryption_key,
]);
}
public function createPaymentIntent($amount, $customMetaData)
{
try {
if (empty($customMetaData['email'])) {
throw new Exception("Email cannot be empty");
}
$redirectUrl = ($customMetaData['platform_type'] == 'app')
? route('flutterwave.success')
: route('flutterwave.success.web');
$finalAmount =$amount;
// $transactionRef = uniqid('flw_');
$transactionRef = 't' .'-'. $customMetaData['payment_transaction_id'] .'-'. 'p' .'-'. $customMetaData['package_id'];
$data = [
'tx_ref' => $transactionRef,
'amount' => $finalAmount,
'currency' => $this->currencyCode,
'redirect_url' => $redirectUrl,
'payment_options' => 'card,banktransfer', // You can add more payment options
'customer' => [
'email' => $customMetaData['email'],
'phonenumber' => $customMetaData['phone'] ?? Auth::user()->mobile,
'name' => $customMetaData['name'] ?? Auth::user()->name,
],
'meta' => [
'package_id' => $customMetaData['package_id'],
'user_id' => $customMetaData['user_id'],
]
];
$data = json_encode($data, JSON_UNESCAPED_SLASHES);
$url = 'https://api.flutterwave.com/v3/payments';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->secretKey
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
return ['error' => curl_error($ch)];
}
curl_close($ch);
$payment = json_decode($response,true);
return $this->formatPaymentIntent($transactionRef, $finalAmount,$this->currencyCode,'pending',$customMetaData,$payment);
} catch (Exception $e) {
return ResponseService::errorResponse("Payment failed: " . $e->getMessage());
}
}
public function createAndFormatPaymentIntent($amount, $customMetaData): array
{
return $this->createPaymentIntent($amount, $customMetaData);
}
public function retrievePaymentIntent($transactionId): array
{
try {
$response = $this->flutterwave->verifyTransaction($transactionId);
if ($response['status'] === 'success') {
return $this->formatPaymentIntent(
$response['data']['tx_ref'],
$response['data']['amount'],
$response['data']['currency'],
$response['data']['status'],
[],
$response
);
}
throw new Exception("Error fetching payment status: " . $response['message']);
} catch (Exception $e) {
throw new Exception("Error verifying transaction: " . $e->getMessage());
}
}
public function formatPaymentIntent($id, $amount, $currency, $status, $metadata, $paymentIntent): array
{
return [
'id' => $id,
'amount' => $amount,
'currency' => $currency,
'metadata' => $metadata,
'status' => match ($status) {
'successful' => 'succeeded',
'pending' => 'pending',
'failed' => 'failed',
default => 'unknown'
},
'payment_gateway_response' => $paymentIntent
];
}
public function minimumAmountValidation($currency, $amount)
{
$minimumAmount = match ($currency) {
'NGN' => 50, // 50 Naira
default => 1.00
};
return ($amount >= $minimumAmount) ? $amount : $minimumAmount;
}
}

View File

@@ -0,0 +1,251 @@
<?php
namespace App\Services\Payment;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;
class PayPalPayment implements PaymentInterface
{
private string $clientId;
private string $secretKey;
private string $currencyCode;
private string $paymentmode;
private Client $http;
private string $baseUrl;
public function __construct($clientId, $secretKey, $currencyCode, $paymentmode)
{
$this->clientId = $clientId;
$this->secretKey = $secretKey;
$this->currencyCode = $currencyCode;
$this->paymentmode = $paymentmode;
$this->http = new Client([
'base_uri' => ($paymentmode == "UAT") ? 'https://api.sandbox.paypal.com' : 'https://api-m.paypal.com',
'timeout' => 30,
]);
$this->baseUrl = ($paymentmode == "UAT")
? 'https://api.sandbox.paypal.com'
: 'https://api.paypal.com';
}
private function generateAccessToken(): string
{
$response = $this->http->post('/v1/oauth2/token', [
'auth' => [$this->clientId, $this->secretKey],
'form_params' => ['grant_type' => 'client_credentials'],
'headers' => [
'Accept' => 'application/json',
'Accept-Language' => 'en_US'
]
]);
$data = json_decode((string)$response->getBody(), true);
return $data['access_token'] ?? throw new Exception("Unable to generate PayPal token");
}
/**
* Create a PayPal Order (Payment Intent equivalent)
*/
public function createPaymentIntent($amount, $customMetaData)
{
$amount = $this->minimumAmountValidation($this->currencyCode, $amount);
$accessToken = $this->generateAccessToken();
if($customMetaData['platform_type'] == 'app') {
$callbackUrl = route('paypal.success') ;
}else{
$callbackUrl = route('paypal.success.web');
}
$metaData = 't' . '-' . $customMetaData['payment_transaction_id'] . '-' . 'p' . '-' . $customMetaData['package_id'];
$response = $this->http->post('/v2/checkout/orders', [
'headers' => [
'Authorization' => "Bearer {$accessToken}",
'Content-Type' => 'application/json'
],
'json' => [
'intent' => 'CAPTURE',
'purchase_units' => [[
'amount' => [
'currency_code' => $this->currencyCode,
'value' => number_format($amount, 2, '.', '')
],
'custom_id' => $metaData,
'description' => $customMetaData['description'] ?? null,
]],
'application_context' => [
'return_url' => $callbackUrl,
'cancel_url' => $callbackUrl,
]
]
]);
$data = json_decode((string)$response->getBody(), true);
return $data;
}
/**
* ✅ Matches PaymentInterface (like Stripe)
*/
public function createAndFormatPaymentIntent($amount, $customMetaData): array
{
$order = $this->createPaymentIntent($amount, $customMetaData);
// Extract approve link from PayPal response
$approveLink = null;
if (isset($order['links']) && is_array($order['links'])) {
foreach ($order['links'] as $link) {
if ($link['rel'] === 'approve') {
$approveLink = $link['href'];
break;
}
}
}
$formatted = $this->formatPaymentIntent(
$order['id'],
$amount,
$this->currencyCode,
$order['status'] ?? 'CREATED',
$customMetaData,
$order
);
// Add approval link for frontend
$formatted['approval_url'] = $approveLink;
return $formatted;
}
/**
* ✅ Retrieve order details (similar to Stripes retrievePaymentIntent)
*/
public function retrievePaymentIntent($paymentId): array
{
$accessToken = $this->generateAccessToken();
$response = $this->http->get("/v2/checkout/orders/{$paymentId}", [
'headers' => [
'Authorization' => "Bearer {$accessToken}",
'Content-Type' => 'application/json'
]
]);
$data = json_decode((string)$response->getBody(), true);
return $this->formatPaymentIntent(
$data['id'],
$data['purchase_units'][0]['amount']['value'] ?? 0,
$data['purchase_units'][0]['amount']['currency_code'] ?? $this->currencyCode,
$data['status'],
[],
$data
);
}
public function capturePayment($orderId): array
{
$accessToken = $this->generateAccessToken();
$response = $this->http->post("/v2/checkout/orders/{$orderId}/capture", [
'headers' => [
'Authorization' => "Bearer {$accessToken}",
'Content-Type' => 'application/json'
]
]);
$data = json_decode((string)$response->getBody(), true);
return $this->formatPaymentIntent(
$data['id'] ?? $orderId,
$data['purchase_units'][0]['payments']['captures'][0]['amount']['value'] ?? 0,
$data['purchase_units'][0]['payments']['captures'][0]['amount']['currency_code'] ?? $this->currencyCode,
$data['status'] ?? 'FAILED',
[],
$data
);
}
public function refund($captureId, $amount, $currency = null): array
{
$accessToken = $this->generateAccessToken();
$currency = $currency ?? $this->currencyCode;
$response = $this->http->post("/v2/payments/captures/{$captureId}/refund", [
'headers' => [
'Authorization' => "Bearer {$accessToken}",
'Content-Type' => 'application/json'
],
'json' => [
'amount' => [
'value' => number_format($amount, 2, '.', ''),
'currency_code' => $currency
]
]
]);
return json_decode((string)$response->getBody(), true);
}
public function formatPaymentIntent($id, $amount, $currency, $status, $metadata, $paymentIntent): array
{
return [
'id' => $id,
'amount' => $amount,
'currency' => $currency,
'metadata' => $metadata,
'status' => match (strtolower($status)) {
"completed" => "succeed",
"approved" => "pending",
"created" => "pending",
default => "failed",
},
'payment_gateway_response' => $paymentIntent
];
}
public function minimumAmountValidation($currency, $amount)
{
$minimumAmount = 0.50;
return max($amount, $minimumAmount);
}
public function verifyWebhookSignature(array $headers, string $payload, string $webhookId): bool
{
try {
$accessToken = $this->generateAccessToken();
if($this->baseUrl == 'https://api.sandbox.paypal.com'){
return true;
}
$verification = Http::withToken($accessToken)
->post($this->baseUrl . '/v1/notifications/verify-webhook-signature', [
'auth_algo' => $headers['paypal-auth-algo'][0] ?? '',
'cert_url' => $headers['paypal-cert-url'][0] ?? '',
'transmission_id' => $headers['paypal-transmission-id'][0] ?? '',
'transmission_sig' => $headers['paypal-transmission-sig'][0] ?? '',
'transmission_time' => $headers['paypal-transmission-time'][0] ?? '',
'webhook_id' => $webhookId,
'webhook_event' => json_decode($payload, true),
]);
if (!$verification->successful()) {
Log::error('PayPal verifyWebhookSignature failed: ' . $verification->body());
return false;
}
return ($verification->json()['verification_status'] ?? '') === 'SUCCESS';
} catch (Throwable $e) {
Log::error('PayPal verifyWebhookSignature exception: ' . $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Services\Payment;
interface PaymentInterface {
public function createPaymentIntent($amount, $customMetaData);
public function createAndFormatPaymentIntent($amount, $customMetaData): array;
public function retrievePaymentIntent($paymentId): array;
public function minimumAmountValidation($currency, $amount);
public function formatPaymentIntent($id, $amount, $currency, $status, $metadata, $paymentIntent): array;
//
// public function checkPayment(Order $order): PaymentStatus;
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Services\Payment;
use App\Models\PaymentConfiguration;
use InvalidArgumentException;
class PaymentService {
/**
* @param string $paymentGateway - Stripe
* @return StripePayment
*/
public static function create(string $paymentGateway) {
$paymentGateway = strtolower($paymentGateway);
$payment = PaymentConfiguration::where(['payment_method' => $paymentGateway, 'status' => 1])->first();
if (!$payment) {
throw new InvalidArgumentException('Invalid Payment Gateway.');
}
return match ($paymentGateway) {
'stripe' => new StripePayment($payment->secret_key, $payment->currency_code),
'paystack' => new PaystackPayment($payment->currency_code),
'razorpay' => new RazorpayPayment($payment->secret_key, $payment->api_key, $payment->currency_code),
'phonepe' => new PhonePePayment($payment->secret_key , $payment->api_key,$payment->additional_data_1,$payment->additional_data_2,$payment->payment_mode),
'flutterwave' => new FlutterWavePayment($payment->secret_key, $payment->api_key, $payment->webhook_secret_key, $payment->currency_code),
'paypal' => new PayPalPayment($payment->api_key, $payment->secret_key, $payment->currency_code,$payment->payment_mode),
'google,apple' => null,
// any other payment processor implementations
default => throw new InvalidArgumentException('Invalid Payment Gateway.'),
};
}
/***
* @param string $paymentGateway
* @param $paymentIntentData
* @return array
* Stripe Payment Intent : https://stripe.com/docs/api/payment_intents/object
*/
// public static function formatPaymentIntent(string $paymentGateway, $paymentIntentData) {
// $paymentGateway = strtolower($paymentGateway);
// return match ($paymentGateway) {
// 'stripe' => [
// 'id' => $paymentIntentData->id,
// 'amount' => $paymentIntentData->amount,
// 'currency' => $paymentIntentData->currency,
// 'metadata' => $paymentIntentData->metadata,
// 'status' => match ($paymentIntentData->status) {
// "canceled" => "failed",
// "succeeded" => "succeed",
// "processing", "requires_action", "requires_capture", "requires_confirmation", "requires_payment_method" => "pending",
// },
// 'payment_gateway_response' => $paymentIntentData
// ],
//
// 'paystack' => [
// 'id' => $paymentIntentData['data']['reference'],
// 'amount' => $paymentIntentData->amount,
// 'currency' => $paymentIntentData->currency,
// 'metadata' => $paymentIntentData->metadata,
// 'status' => match ($paymentIntentData['data']['status']) {
// "abandoned" => "failed",
// "succeed" => "succeed",
// default => $paymentIntentData['data']['status'] ?? true
// },
// 'payment_gateway_response' => $paymentIntentData
// ],
// // any other payment processor implementations
// default => $paymentIntentData,
// };
// }
}

View File

@@ -0,0 +1,132 @@
<?php
namespace App\Services\Payment;
use RuntimeException;
use Throwable;
use Unicodeveloper\Paystack\Paystack;
class PaystackPayment extends Paystack implements PaymentInterface {
private Paystack $paystack;
private string $currencyCode;
/**
* PaystackPayment constructor.
* @param $currencyCode
*/
public function __construct($currencyCode) {
// Call Paystack Class and Create Payment Intent
$this->paystack = new Paystack();
$this->currencyCode = $currencyCode;
parent::__construct();
}
/**
* @param $amount
* @param $customMetaData
* @return array
*/
public function createPaymentIntent($amount, $customMetaData) {
try {
if (empty($customMetaData['email'])) {
throw new RuntimeException("Email cannot be empty");
}
if($customMetaData['platform_type'] == 'app') {
$callbackUrl = route('paystack.success') ;
}else{
$callbackUrl = route('paystack.success.web');
}
$finalAmount = $amount * 100;
$reference = $this->genTranxRef();
$data = [
'amount' => $finalAmount,
'currency' => $this->currencyCode,
'email' => $customMetaData['email'],
'metadata' => $customMetaData,
'reference' => $reference,
'callback_url' => $callbackUrl
];
return $this->paystack->getAuthorizationResponse($data);
} catch (Throwable $e) {
throw new RuntimeException($e);
}
}
/**
* @param $amount
* @param $customMetaData
* @return array
*/
public function createAndFormatPaymentIntent($amount, $customMetaData): array {
$response = $this->createPaymentIntent($amount, $customMetaData);
return $this->format($response, $amount, $this->currencyCode, $customMetaData);
}
/**
* @param $paymentId
* @return array
* @throws Throwable
*/
public function retrievePaymentIntent($paymentId): array {
try {
$relativeUrl = "/transaction/verify/{$paymentId}";
$this->response = $this->client->get($this->baseUrl . $relativeUrl, []);
$response = json_decode($this->response->getBody(), true, 512, JSON_THROW_ON_ERROR);
return $this->format($response['data'], $response['data']['amount'], $response['data']['currency'], $response['data']['metadata']);
} catch (Throwable $e) {
throw new RuntimeException($e);
}
}
/**
* @param $currency
* @param $amount
*/
public function minimumAmountValidation($currency, $amount) {
// TODO: Implement minimumAmountValidation() method.
}
/**
* @param $paymentIntent
* @param $amount
* @param $currencyCode
* @param $metadata
* @return array
*/
public function format($paymentIntent, $amount, $currencyCode, $metadata) {
return $this->formatPaymentIntent($paymentIntent['data']['reference'], $amount, $currencyCode, $paymentIntent['status'], $metadata, $paymentIntent);
}
/**
* @param $id
* @param $amount
* @param $currency
* @param $status
* @param $metadata
* @param $paymentIntent
* @return array
*/
public function formatPaymentIntent($id, $amount, $currency, $status, $metadata, $paymentIntent): array {
return [
'id' => $id,
'amount' => $amount,
'currency' => $currency,
'metadata' => $metadata,
'status' => match ($status) {
"abandoned" => "failed",
"succeed" => "succeed",
default => $status ?? true
},
'payment_gateway_response' => $paymentIntent
];
}
}

View File

@@ -0,0 +1,382 @@
<?php
namespace App\Services\Payment;
use Auth;
use PhonePe\PhonePe as PhonePeSDK;
use Illuminate\Support\Facades\Log;
use Exception;
class PhonePePayment implements PaymentInterface
{
private string $clientId;
private string $callbackUrl;
private string $transactionId;
private string $clientSecret;
private string $clientVersion;
private string $payment_mode;
private string $merchantId;
private string $pgUrl;
public function __construct($clientSecret, $clientId, $addtional_data_1,$addtional_data_2, $payment_mode)
{
// $this->clientId = 'TEST-CITYSURFONLINE_2508';
$this->clientId = $clientId;
$this->callbackUrl = url('/webhook/phonePe');
$this->transactionId = uniqid();
$this->clientSecret = $clientSecret;
$this->clientVersion = $addtional_data_1;
$this->payment_mode = $payment_mode;
$this->merchantId = $addtional_data_2;
$this->pgUrl = ($payment_mode == "UAT") ? "https://api-preprod.phonepe.com/apis/pg-sandbox" : "https://api.phonepe.com/apis/pg";
}
/**
* Create payment intent for PhonePe
*
* @param $amount
* @param $customMetaData
* @return array
* @throws Exception
*/
public function createPaymentIntent($amount, $customMetaData) {
Log::info("PhonePe Payment custome", [
'amount' => $amount,
'customMetaData' => $customMetaData,
]);
$amount = $this->minimumAmountValidation('INR', $amount);
$userMobile = Auth::user()->mobile;
$metaData = 't' . '-' . $customMetaData['payment_transaction_id'] . '-' . 'p' . '-' . $customMetaData['package_id'];
if ($customMetaData['platform_type'] == 'web') {
$transactionId = uniqid();
$mode = $this->payment_mode;
if ($mode === 'PROD') {
$tokenUrl = 'https://api.phonepe.com/apis/identity-manager/v1/oauth/token';
$orderUrl = 'https://api.phonepe.com/apis/pg/checkout/v2/sdk/order';
$payUrl = 'https://api.phonepe.com/apis/pg/checkout/v2/pay';
} else {
$tokenUrl = 'https://api-preprod.phonepe.com/apis/pg-sandbox/v1/oauth/token';
$orderUrl = 'https://api-preprod.phonepe.com/apis/pg-sandbox/checkout/v2/sdk/order';
$payUrl = 'https://api-preprod.phonepe.com/apis/pg-sandbox/checkout/v2/pay';
}
$tokenCurl = curl_init();
curl_setopt_array($tokenCurl, array(
CURLOPT_URL => $tokenUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query([
'client_id' => $this->clientId,
'client_version' => $this->clientVersion,
'client_secret' => $this->clientSecret,
'grant_type' => 'client_credentials',
]),
CURLOPT_HTTPHEADER => array(
'Content-Type: application/x-www-form-urlencoded'
),
));
$response = curl_exec($tokenCurl);
curl_close($tokenCurl);
// Decode token response
$tokenResponse = json_decode($response, true);
$accessToken = $tokenResponse['access_token'] ?? null;
if (!$accessToken) {
dd("Failed to retrieve token", $response);
}
// Build JSON payload properly
$paymentData = [
'merchantOrderId' => $metaData,
'amount' => (int) round($amount * 100),
"metadata" => [
"package_id" => $customMetaData['package_id'],
"payment_transaction_id" => $customMetaData['payment_transaction_id'],
"user_id" => Auth::user()->id,
],
'paymentFlow' => [
'type' => 'PG_CHECKOUT',
'message' => 'Payment message used for collect requests',
'merchantUrls' => [
'redirectUrl' => route('phonepe.success.web'),
],
],
];
$payCUrl = curl_init();
curl_setopt_array($payCUrl, array(
CURLOPT_URL => $payUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => json_encode($paymentData),
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'Authorization: O-Bearer ' . $accessToken,
),
));
$response = curl_exec($payCUrl);
$final_response = json_decode($response, true);
if (!empty($final_response)) {
$redirectURL = $final_response['redirectUrl'];
return $this->formatPaymentIntent($transactionId, $amount, 'INR', 'pending', $customMetaData, $redirectURL);
}
curl_close($payCUrl);
} else {
$redirectUrl = route('phonepe.success');
$orderId = 'TX' . time(); // unique order ID
$amount = (int) round($amount * 100); // amount in INR (not multiplied)
$expireAfter = 1200; // in seconds (20 mins)
$token = $this->getPhonePeToken();
$order = $this->createOrder($token, $orderId, $amount);
$order_data = json_decode($order, true);
$requestPayload = [
"orderId" => $order_data['orderId'],
// "state" => "PENDING",
'merchantOrderId' => $metaData,
"merchantId" => $this->merchantId,
"expireAT" => $expireAfter,
"token" => $order_data['token'],
"paymentMode" => [
"type" => "PAY_PAGE"
]
];
// Convert to JSON string as required by Flutter SDK
$requestString = json_encode($requestPayload);
if ($this->payment_mode == "UAT") {
$payment_mode = "SANDBOX";
} else {
$payment_mode = "PRODUCTION";
}
return [
"environment" => $payment_mode, // or "PRODUCTION"
"merchantId" => $this->merchantId,
"flowId" => $orderId,
"enableLogging" => true, // false in production
"request" => $requestPayload,
"appSchema" => "eclassify", // for iOS deep link return
"token" => $token,
];
}
throw new Exception("Error initiating payment: " . $redirectURL);
}
/**
* Create and format payment intent for PhonePe
*
* @param $amount
* @param $customMetaData
* @return array
* @throws Exception
*/
public function createAndFormatPaymentIntent($amount, $customMetaData): array
{
$paymentIntent = $this->createPaymentIntent($amount, $customMetaData);
$metaData = 't' . '-' . $customMetaData['payment_transaction_id'] . '-' . 'p' . '-' . $customMetaData['package_id'];
return $this->formatPaymentIntent(
id: $metaData,
amount: $amount,
currency: 'INR',
status: "PENDING",
metadata: $customMetaData,
paymentIntent: $paymentIntent
);
}
/**
* Retrieve payment intent (check payment status)
*
* @param $transactionId
* @return array
* @throws Exception
*/
public function retrievePaymentIntent($transactionId): array
{
$statusUrl = 'https://api.phonepe.com/v3/transaction/' . $transactionId . '/status';
$signature = $this->generateSignature(''); // Adjust if needed based on PhonePe requirements
$response = $this->sendRequest($statusUrl, '', $signature);
if ($response['success']) {
return $this->formatPaymentIntent($transactionId, $response['amount'], 'INR', $response['status'], [], $response);
}
throw new Exception("Error fetching payment status: " . $response['message']);
}
/**
* Format payment intent response
*
* @param $id
* @param $amount
* @param $currency
* @param $status
* @param $metadata
* @param $paymentIntent
* @return array
*/
public function formatPaymentIntent($id, $amount, $currency, $status, $metadata, $paymentIntent): array
{
return [
'id' => $id,
'amount' => $amount,
'currency' => $currency,
'metadata' => $metadata,
'status' => match ($status) {
"SUCCESS" => "succeeded",
"PENDING" => "pending",
"FAILED" => "failed",
default => "unknown"
},
'payment_gateway_response' => $paymentIntent
];
}
/**
* Minimum amount validation
*
* @param $currency
* @param $amount
* @return float|int
*/
public function minimumAmountValidation($currency, $amount)
{
$minimumAmount = match ($currency) {
'INR' => 1.00, // 1 Rupee
default => 0.50
};
return ($amount >= $minimumAmount) ? $amount : $minimumAmount;
}
/**
* Generate HMAC signature for PhonePe
*
* @param $encodedRequestBody
* @return string
*/
private function generateSignature($requestBody): string
{
// Concatenate raw JSON payload, endpoint, and salt key
$stringToHash = $requestBody . '/pg/v1/pay' . $this->saltKey;
// Hash the string using SHA256
$hash = hash('sha256', $stringToHash);
// Append salt index (Assumed to be 1 in this example)
return $hash . '###' . 1;
}
/**
* Send cURL request to PhonePe API
*
* @param $url
* @param $requestBody
* @param $signature
* @return array
*/
// private function sendRequest($url, $requestBody, $signature): array
// {
// // dd($requestBody);
// $ch = curl_init($url);
// curl_setopt($ch, CURLOPT_POST, 1);
// curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);
// curl_setopt($ch, CURLOPT_HTTPHEADER, [
// 'Content-Type: application/json',
// 'X-VERIFY: ' . $signature,
// ]);
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// $response = curl_exec($ch);
// curl_close($ch);
// return json_decode($response, true);
// }
public function getPhonePeToken() {
$clientId = $this->clientId;
$clientSecret = $this->clientSecret;
$clientVersion = $this->clientVersion;
$postData = http_build_query([
'client_id' => $clientId,
'client_version' => $clientVersion,
'client_secret' => $clientSecret,
'grant_type' => 'client_credentials',
]);
if ($this->payment_mode == "UAT") {
$url = 'https://api-preprod.phonepe.com/apis/pg-sandbox/v1/oauth/token';
} else {
$url = 'https://api.phonepe.com/apis/identity-manager/v1/oauth/token';
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/x-www-form-urlencoded',
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new \Exception('Curl error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$responseData = json_decode($response, true);
if ($httpCode === 200 && isset($responseData['access_token'])) {
return $responseData['access_token'];
}
throw new \Exception('Failed to fetch PhonePe token. Response: ' . $response);
}
public function createOrder($token, $merchantOrderId, $amount) {
$url = $this->pgUrl . '/checkout/v2/sdk/order';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"merchantOrderId" => $merchantOrderId,
"amount" => $amount,
"paymentFlow" => [
"type" => "PG_CHECKOUT"
]
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: O-Bearer ' . $token,
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Services\Payment;
use Log;
use Razorpay\Api\Api;
use RuntimeException;
use Throwable;
class RazorpayPayment implements PaymentInterface {
private Api $api;
private string $currencyCode;
/**
* RazorpayPayment constructor.
* @param $secretKey
* @param $publicKey
* @param $currencyCode
*/
public function __construct($secretKey, $publicKey, $currencyCode) {
// Call Stripe Class and Create Payment Intent
$this->api = new Api($publicKey, $secretKey);
$this->currencyCode = $currencyCode;
}
/**
* @param $amount
* @param $customMetaData
* @return mixed
*/
public function createPaymentIntent($amount, $customMetaData) {
try {
$orderData = [
'amount' => $this->minimumAmountValidation($this->currencyCode, $amount),
'currency' => $this->currencyCode,
'notes' => $customMetaData,
];
return $this->api->order->create($orderData);
} catch (Throwable $e) {
Log::error('Failed to create payment intent: ' . $e->getMessage());
throw new RuntimeException($e->getMessage());
}
}
/**
* @param $amount
* @param $customMetaData
* @return array
*/
public function createAndFormatPaymentIntent($amount, $customMetaData): array {
$response = $this->createPaymentIntent($amount, $customMetaData);
return $this->format($response);
}
/**
* @param $paymentId
* @return array
* @throws Throwable
*/
public function retrievePaymentIntent($paymentId): array {
try {
return $this->api->order->fetch($paymentId);
} catch (Throwable $e) {
throw $e;
}
}
/**
* @param $currency
* @param $amount
* @return float|int
*/
public function minimumAmountValidation($currency, $amount) {
return match ($currency) {
"BHD", "IQD", "JOD", "KWD", "OMR", "TND" => $amount * 1000,
"AED", "ALL", "AMD", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BZD", "CAD", "CHF",
"CNY", "COP", "CRC", "CUP", "CVE", "CZK", "DKK", "DOP", "DZD", "EGP", "ETB", "EUR", "FJD", "GBP", "GHS", "GIP", "GMD", "GTQ", "GYD", "HKD", "HNL",
"HTG", "HUF", "IDR", "ILS", "INR", "JMD", "KES", "KGS", "KHR", "KYD", "KZT", "LAK", "LKR", "LRD", "LSL", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT",
"MOP", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "PEN", "PGK", "PHP", "PKR", "PLN", "QAR", "RON", "RSD",
"RUB", "SAR", "SCR", "SEK", "SGD", "SLL", "SOS", "SSP", "SVC", "SZL", "THB", "TTD", "TWD", "TZS", "UAH", "USD", "UYU", "UZS", "XCD", "YER", "ZAR", "ZMW" => $amount * 100,
"BIF", "CLP", "DJF", "GNF", "ISK", "JPY", "KMF", "KRW", "PYG", "RWF", "UGX", "VND", "VUV", "XAF", "XOF", "XPF", "HRK" => $amount,
};
}
/**
* @param $paymentIntent
* @return array
*/
private function format($paymentIntent) {
return $this->formatPaymentIntent($paymentIntent->id, $paymentIntent->amount, $paymentIntent->currency, $paymentIntent->status, $paymentIntent->notes->toArray(), $paymentIntent->toArray());
}
/**
* @param $id
* @param $amount
* @param $currency
* @param $status
* @param $metadata
* @param $paymentIntent
* @return array
*/
public function formatPaymentIntent($id, $amount, $currency, $status, $metadata, $paymentIntent): array {
return [
'id' => $id,
'amount' => $amount,
'currency' => $currency,
'metadata' => $metadata,
'status' => match ($status) {
"failed" => "failed",//NOTE : Failed status is not known, please test the failure status
"created", "attempted" => "pending",
"paid" => "succeed",
},
'payment_gateway_response' => $paymentIntent
];
}
}

View File

@@ -0,0 +1,184 @@
<?php
namespace App\Services\Payment;
use App\Models\Setting;
use App\Services\CurrencyFormatterService;
use Stripe\Exception\ApiErrorException;
use Stripe\PaymentIntent;
use Stripe\StripeClient;
class StripePayment implements PaymentInterface {
private StripeClient $stripe;
private string $currencyCode;
/**
* StripePayment constructor.
* @param $secretKey
* @param $currencyCode
*/
public function __construct($secretKey, $currencyCode) {
// Call Stripe Class and Create Payment Intent
$this->stripe = new StripeClient($secretKey);
$this->currencyCode = $currencyCode;
}
/**
* @param $amount
* @param $customMetaData
* @return PaymentIntent
* @throws ApiErrorException
*/
public function createPaymentIntent($amount, $customMetaData) {
try {
$amount = $this->minimumAmountValidation($this->currencyCode, $amount);
$zeroDecimalCurrencies = [
'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG',
'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'
];
// if (!in_array($this->currencyCode, $zeroDecimalCurrencies)) {
// $amount *= 100;
// }
return $this->stripe->paymentIntents->create(
[
'amount' => $amount,
'currency' => $this->currencyCode,
'metadata' => $customMetaData,
// 'description' => 'Fees Payment',
// 'shipping' => [
// 'name' => 'Jenny Rosen',
// 'address' => [
// 'line1' => '510 Townsend St',
// 'postal_code' => '98140',
// 'city' => 'San Francisco',
// 'state' => 'CA',
// 'country' => 'US',
// ],
// ],
]
);
} catch (ApiErrorException $e) {
throw $e;
}
}
/**
* @param $amount
* @param $customMetaData
* @return array
* @throws ApiErrorException
*/
public function createAndFormatPaymentIntent($amount, $customMetaData): array {
$paymentIntent = $this->createPaymentIntent($amount, $customMetaData);
return $this->format($paymentIntent);
}
/**
* @param $paymentId
* @return array
* @throws ApiErrorException
*/
public function retrievePaymentIntent($paymentId): array {
try {
return $this->format($this->stripe->paymentIntents->retrieve($paymentId));
} catch (ApiErrorException $e) {
throw $e;
}
}
/**
* @param $paymentIntent
* @return array
*/
public function format($paymentIntent) {
return $this->formatPaymentIntent($paymentIntent->id, $paymentIntent->amount, $paymentIntent->currency, $paymentIntent->status, $paymentIntent->metadata, $paymentIntent);
}
/**
* @param $id
* @param $amount
* @param $currency
* @param $status
* @param $metadata
* @param $paymentIntent
* @return array
*/
public function formatPaymentIntent($id, $amount, $currency, $status, $metadata, $paymentIntent): array {
$formatter = app(CurrencyFormatterService::class);
$iso_code = Setting::where('name', 'currency_iso_code')->value('value');
$symbol = Setting::where('name', 'currency_symbol')->value('value');
$position = Setting::where('name', 'currency_symbol_position')->value('value');
$currency = (object) [
'iso_code' => $iso_code,
'symbol' => $symbol,
'symbol_position' => $position,
];
$displayAmount = $amount;
// If it's NOT a zero-decimal currency, divide by 100 for the formatter
// if (!in_array($iso_code, ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF', 'ISK'])) {
$displayAmount = $amount * 100;
// }
$formatted_final_price = $formatter->formatPrice($displayAmount ?? 0, $currency);
return [
'id' => $paymentIntent->id,
'amount' => $paymentIntent->amount,
'formatted_price' => $formatted_final_price,
'currency' => $paymentIntent->currency,
'metadata' => $paymentIntent->metadata,
'status' => match ($paymentIntent->status) {
"canceled" => "failed",
"succeeded" => "succeed",
"processing", "requires_action", "requires_capture", "requires_confirmation", "requires_payment_method" => "pending",
},
'payment_gateway_response' => $paymentIntent
];
}
/**
* @param $currency
* @param $amount
* @return float|int
*/
public function minimumAmountValidation($currency, $amount) {
$minimumAmountMap = [
'USD' => 0.50, 'EUR' => 0.50, 'INR' => 0.50, 'NZD' => 0.50, 'SGD' => 0.50,
'BRL' => 0.50, 'CAD' => 0.50, 'AUD' => 0.50, 'CHF' => 0.50,
'AED' => 2.00, 'PLN' => 2.00, 'RON' => 2.00,
'BGN' => 1.00, 'CZK' => 15.00, 'DKK' => 2.50,
'GBP' => 0.30, 'HKD' => 4.00, 'HUF' => 175.00,
'JPY' => 50, 'MXN' => 10, 'THB' => 10, 'MYR' => 2,
'NOK' => 3.00, 'SEK' => 3.00, 'XAF' => 100,
'ISK' => 100 // ISK minimum is usually higher
];
$zeroDecimalCurrencies = [
'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG',
'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF', 'ISK' // Added ISK here
];
$minimumAmount = $minimumAmountMap[$currency] ?? 0.50;
if (!in_array($currency, $zeroDecimalCurrencies)) {
// Standard Currencies (USD, INR, etc.)
$minimumAmount *= 100;
$amount = (int)round($amount * 100);
} else {
// Zero-decimal Currencies
if ($currency === 'ISK') {
// Special Rule for ISK: Must be divisible by 100
$amount = (int)round($amount / 100) * 100;
if ($amount < 100) $amount = 100;
} else {
$amount = (int)$amount;
}
}
return max($amount, (int)$minimumAmount);
}
}