classify admin
This commit is contained in:
382
app/Services/Payment/PhonePePayment.php
Normal file
382
app/Services/Payment/PhonePePayment.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user