Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for the latest version of Pasargad Bank API #298

Merged
merged 1 commit into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions config/payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,16 +201,13 @@
'currency' => 'T', // Can be R, T (Rial, Toman)
],
'pasargad' => [
'apiPaymentUrl' => 'https://pep.shaparak.ir/payment.aspx',
'apiGetToken' => 'https://pep.shaparak.ir/Api/v1/Payment/GetToken',
'apiCheckTransactionUrl' => 'https://pep.shaparak.ir/Api/v1/Payment/CheckTransactionResult',
'apiVerificationUrl' => 'https://pep.shaparak.ir/Api/v1/Payment/VerifyPayment',
'baseUrl' => 'https://pep.shaparak.ir/dorsa1',
'userName' => '',
'password' => '',
'merchantId' => '',
'terminalCode' => '',
'certificate' => '', // can be string (and set certificateType to xml_string) or an xml file path (and set cetificateType to xml_file)
'certificateType' => 'xml_file', // can be: xml_file, xml_string
'callbackUrl' => 'http://yoursite.com/path/to',
'currency' => 'R', //Can be R, T (Rial, Toman)
'currency' => 'T', //Can be R, T (Rial, Toman)
],
'payir' => [
'apiPurchaseUrl' => 'https://pay.ir/pg/send',
Expand Down
214 changes: 142 additions & 72 deletions src/Drivers/Pasargad/Pasargad.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
namespace Shetabit\Multipay\Drivers\Pasargad;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Shetabit\Multipay\Invoice;
use Shetabit\Multipay\Receipt;
use Shetabit\Multipay\Abstracts\Driver;
use Shetabit\Multipay\Contracts\ReceiptInterface;
use Shetabit\Multipay\Exceptions\InvalidPaymentException;
use Shetabit\Multipay\Drivers\Pasargad\Utils\RSAProcessor;
use Shetabit\Multipay\RedirectionForm;
use Shetabit\Multipay\Request;
use DateTimeZone;
Expand All @@ -19,9 +19,9 @@ class Pasargad extends Driver
/**
* Guzzle client
*
* @var object
* @var Client
*/
protected \GuzzleHttp\Client $client;
protected Client $client;

/**
* Invoice
Expand All @@ -42,12 +42,13 @@ class Pasargad extends Driver
*
* @var array
*/
protected $preparedData = [];
protected array $preparedData = array();

/**
* Pasargad(PEP) constructor.
* Construct the class with the relevant settings.
*
* @param Invoice $invoice
* @param $settings
*/
public function __construct(Invoice $invoice, $settings)
Expand All @@ -61,118 +62,152 @@ public function __construct(Invoice $invoice, $settings)
* Purchase Invoice.
*
* @return string
* @throws \DateMalformedStringException
*/
public function purchase()
public function purchase(): string
{
$invoiceData = $this->getPreparedInvoiceData();

$this->invoice->transactionId($invoiceData['InvoiceNumber']);
$this->invoice->transactionId($invoiceData['invoice']);

// return the transaction's id
return $this->invoice->getTransactionId();
}

/**
* Pay the Invoice
*
* @return RedirectionForm
* @throws \DateMalformedStringException
* @throws InvalidPaymentException|GuzzleException
*/
public function pay() : RedirectionForm
{
$paymentUrl = $this->settings->apiPaymentUrl;
$getTokenUrl = $this->settings->apiGetToken;
$tokenData = $this->request($getTokenUrl, $this->getPreparedInvoiceData());
$baseUrl = $this->settings->baseUrl;

$paymentResult = $this->request($baseUrl . '/api/payment/purchase', $this->getPreparedInvoiceData());

if ($paymentResult['resultCode'] !== 0 || empty($paymentResult['data']) || empty($paymentResult['data']['url'])) {
throw new InvalidPaymentException($result['resultMsg'] ?? $this->getDefaultExceptionMessage());
}

$paymentUrl = $paymentResult['data']['url'];

// redirect using HTML form
return $this->redirectWithForm($paymentUrl, $tokenData, 'POST');
// redirect using the HTML form
return $this->redirectWithForm($paymentUrl);
}

/**
* Verify payment
*
* @return ReceiptInterface
*
* @throws InvalidPaymentException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws GuzzleException
*/
public function verify() : ReceiptInterface
{
$invoiceDetails = $this->request(
$this->settings->apiCheckTransactionUrl,
$baseUrl = $this->settings->baseUrl;

$invoiceInquiry = $this->request(
$baseUrl . '/api/payment/payment-inquiry',
[
'TransactionReferenceID' => Request::input('tref')
'invoiceId' => Request::input('invoiceId')
]
);

if ($invoiceInquiry['resultCode'] !== 0 || empty($invoiceInquiry['data'])) {
throw new InvalidPaymentException($result['resultMsg'] ?? $this->getDefaultExceptionMessage());
}

$invoiceDetails = $invoiceInquiry['data'];
$invoiceInquiryStatus = $invoiceDetails['status'];

$responseErrorStatusMessage = $this->getResponseErrorStatusMessage($invoiceInquiryStatus);
if (!empty($responseErrorStatusMessage)) {
throw new InvalidPaymentException($responseErrorStatusMessage);
}

$amount = $this->invoice->getAmount() * ($this->settings->currency == 'T' ? 10 : 1); // convert to rial
if ($amount != $invoiceDetails['Amount']) {
if ($amount != $invoiceDetails['amount']) {
throw new InvalidPaymentException('Invalid amount');
}
$iranTime = new DateTime('now', new DateTimeZone('Asia/Tehran'));
$fields = [
'MerchantCode' => $invoiceDetails['MerchantCode'],
'TerminalCode' => $invoiceDetails['TerminalCode'],
'InvoiceNumber' => $invoiceDetails['InvoiceNumber'],
'InvoiceDate' => $invoiceDetails['InvoiceDate'],
'Amount' => $invoiceDetails['Amount'],
'Timestamp' => $iranTime->format("Y/m/d H:i:s"),
];

$verifyResult = $this->request($this->settings->apiVerificationUrl, $fields);
$paymentUrlId = $invoiceDetails['url'];

$verifyResult = $this->request(
$baseUrl . '/api/payment/confirm-transactions',
[
'invoice' => Request::input('invoiceId'),
'urlId' => $paymentUrlId
]
);

return $this->createReceipt($verifyResult, $invoiceDetails);
}

/**
* Generate the payment's receipt
*
* @param $referenceId
* @param $verifyResult
* @param $invoiceDetails
* @return Receipt
*/
protected function createReceipt(array $verifyResult, array $invoiceDetails): \Shetabit\Multipay\Receipt
protected function createReceipt($verifyResult, $invoiceDetails): Receipt
{
$referenceId = $invoiceDetails['TransactionReferenceID'];
$traceNumber = $invoiceDetails['TraceNumber'];
$referenceNumber = $invoiceDetails['ReferenceNumber'];
$verifyResultData = $verifyResult['data'];
$referenceId = $invoiceDetails['transactionId'];
$trackId = $invoiceDetails['trackId'];
$referenceNumber = $invoiceDetails['referenceNumber'];

$reciept = new Receipt('Pasargad', $referenceId);
$receipt = new Receipt('Pasargad', $referenceId);

$reciept->detail('TraceNumber', $traceNumber);
$reciept->detail('ReferenceNumber', $referenceNumber);
$reciept->detail('MaskedCardNumber', $verifyResult['MaskedCardNumber']);
$reciept->detail('ShaparakRefNumber', $verifyResult['ShaparakRefNumber']);
$receipt->detail('TraceNumber', $trackId);
$receipt->detail('ReferenceNumber', $referenceNumber);
$receipt->detail('urlId', $invoiceDetails['url']);
$receipt->detail('MaskedCardNumber', $verifyResultData['maskedCardNumber']);

return $reciept;
return $receipt;
}

/**
* A default message for exceptions
*
* @return string
*/
protected function getDefaultExceptionMessage(): string
{
return 'مشکلی در دریافت اطلاعات از بانک به وجود آمده است';
return 'مشکلی در دریافت اطلاعات از بانک به وجود آمدهاست.';
}

/**
* Sign given data.
*
* @param string $data
* Return response status message
*
* @param $status
* @return string
*/
public function sign($data)
protected function getResponseErrorStatusMessage($status): string
{
$certificate = $this->settings->certificate;
$certificateType = $this->settings->certificateType;

$processor = new RSAProcessor($certificate, $certificateType);

return $processor->sign($data);
return match ($status) {
13005 => 'عدم امکان دسترسی موقت به سرور پرداخت بانک',
13016, 13018 => 'تراکنشی یافت نشد!',
13021 => 'پرداخت از پیش لغو شده است!',
13022 => 'تراکنش پرداخت نشده‌است!',
13025 => 'تراکنش ناموفق است!',
13030 => 'تراکنش تایید شده و ناموفق است!',
default => ''
};
}

/**
* Retrieve prepared invoice's data
*
* @return array
* @throws \DateMalformedStringException
*/
protected function getPreparedInvoiceData()
protected function getPreparedInvoiceData(): array
{
if ($this->preparedData === []) {
if (empty($this->preparedData)) {
$this->preparedData = $this->prepareInvoiceData();
}

Expand All @@ -181,53 +216,83 @@ protected function getPreparedInvoiceData()

/**
* Prepare invoice data
*
* @return array
* @throws \DateMalformedStringException
*/
protected function prepareInvoiceData(): array
{
$action = 1003; // 1003 : for buy request (bank standard)
$merchantCode = $this->settings->merchantId;
$serviceCode = 8; // 8 : for PURCHASE request
$terminalCode = $this->settings->terminalCode;
$amount = $this->invoice->getAmount() * ($this->settings->currency == 'T' ? 10 : 1); // convert to rial
$redirectAddress = $this->settings->callbackUrl;
$invoiceNumber = crc32($this->invoice->getUuid()) . random_int(0, time());
$invoiceNumber = crc32($this->invoice->getUuid()) . rand(0, time());

$iranTime = new DateTime('now', new DateTimeZone('Asia/Tehran'));
$timeStamp = $iranTime->format("Y/m/d H:i:s");
$invoiceDate = $iranTime->format("Y/m/d H:i:s");

if (!empty($this->invoice->getDetails()['date'])) {
$invoiceDate = $this->invoice->getDetails()['date'];
}

return [
'InvoiceNumber' => $invoiceNumber,
'InvoiceDate' => $invoiceDate,
'Amount' => $amount,
'TerminalCode' => $terminalCode,
'MerchantCode' => $merchantCode,
'RedirectAddress' => $redirectAddress,
'Timestamp' => $timeStamp,
'Action' => $action,
'invoice' => $invoiceNumber,
'invoiceDate' => $invoiceDate,
'amount' => $amount,
'serviceCode' => $serviceCode,
'serviceType' => 'PURCHASE',
'terminalNumber' => $terminalCode,
'callbackApi' => $redirectAddress,
];
}

/**
* Prepare signature based on Pasargad document
* Get action token
*
* @throws InvalidPaymentException
* @throws GuzzleException
*/
public function prepareSignature(string $data): string
protected function getToken()
{
return base64_encode($this->sign(sha1($data, true)));
$baseUrl = $this->settings->baseUrl;
$userName = $this->settings->userName;
$password = $this->settings->password;

$response = $this->client->request(
'POST',
$baseUrl . '/token/getToken',
[
'body' => json_encode(['username' => $userName, 'password' => $password]),
'headers' => [
'content-type' => 'application/json'
],
'http_errors' => false,
]
);

$result = json_decode($response->getBody(), true);

if ($result['resultCode'] !== 0 || empty($result['token'])) {
throw new InvalidPaymentException($result['resultMsg'] ?? 'Invalid Authentication');
}

return $result['token'];
}

/**
* Make request to pasargad's Api
* Make request to Pasargad's Api
*
* @param string $url
* @param array $body
* @param string $method
* @return array
* @throws InvalidPaymentException
* @throws GuzzleException
*/
protected function request(string $url, array $body, $method = 'POST'): array
protected function request(string $url, array $body, string $method = 'POST'): array
{
$body = json_encode($body);
$sign = $this->prepareSignature($body);
$token = $this->getToken();

$response = $this->client->request(
'POST',
Expand All @@ -236,16 +301,21 @@ protected function request(string $url, array $body, $method = 'POST'): array
'body' => $body,
'headers' => [
'content-type' => 'application/json',
'Sign' => $sign
'Authorization' => "Bearer {$token}"
],
"http_errors" => false,
'http_errors' => false,
]
);

$result = json_decode($response->getBody(), true);

if ($result['IsSuccess'] === false) {
throw new InvalidPaymentException($result['Message']);
if ($result['resultCode'] !== 0) {
$responseStatusMessage = $this->getResponseErrorStatusMessage($result['resultCode']);
throw new InvalidPaymentException(
!empty($responseStatusMessage)
? $responseStatusMessage
: ($result['resultMsg'] ?? $result['resultCode'])
);
}

return $result;
Expand Down
Loading