Skip to content

Commit

Permalink
Add parspal driver (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
sadegh19b authored Sep 15, 2024
1 parent 06f97d7 commit f4f2bf3
Show file tree
Hide file tree
Showing 3 changed files with 285 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ For **Laravel** integration you can use [shetabit/payment](https://github.com/sh
- [nextpay](https://nextpay.ir/) :heavy_check_mark:
- [omidpay](https://omidpayment.ir/) :heavy_check_mark:
- [parsian](https://www.pec.ir/) :heavy_check_mark:
- [parspal](https://parspal.com/) :heavy_check_mark:
- [pasargad](https://bpi.ir/) :heavy_check_mark:
- [payfa](https://payfa.com/) :heavy_check_mark:
- [payir](https://pay.ir/) :heavy_check_mark:
Expand Down
18 changes: 18 additions & 0 deletions config/payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,24 @@
'description' => 'payment using parsian',
'currency' => 'T', //Can be R, T (Rial, Toman)
],
'parspal' => [
/* Normal api */
'apiPurchaseUrl' => 'https://api.parspal.com/v1/payment/request',
'apiVerificationUrl' => 'https://api.parspal.com/v1/payment/verify',

/* Sandbox api */
'sandboxApiPurchaseUrl' => ' https://sandbox.api.parspal.com/v1/payment/request',
'sandboxApiVerificationUrl' => 'https://sandbox.api.parspal.com/v1/payment/verify',

// You can change the token storage path in Laravel like this
// 'cachePath' => function_exists('storage_path') ? storage_path('parspal/') : 'parspal/'
'cachePath' => 'parspal/',

'sandbox' => false, // Set it to true for test environments
'merchantId' => '', // Set `00000000aaaabbbbcccc000000000000` for test environments (sandbox)
'callbackUrl' => '',
'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',
Expand Down
266 changes: 266 additions & 0 deletions src/Drivers/Parspal/Parspal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<?php

namespace Shetabit\Multipay\Drivers\Parspal;

use GuzzleHttp\Client;
use Shetabit\Multipay\Abstracts\Driver;
use Shetabit\Multipay\Contracts\ReceiptInterface;
use Shetabit\Multipay\Exceptions\InvalidPaymentException;
use Shetabit\Multipay\Exceptions\PurchaseFailedException;
use Shetabit\Multipay\Invoice;
use Shetabit\Multipay\Receipt;
use Shetabit\Multipay\RedirectionForm;
use Shetabit\Multipay\Request;
use chillerlan\SimpleCache\CacheOptions;
use chillerlan\SimpleCache\FileCache;

class Parspal extends Driver
{
/**
* HTTP Client.
*
* @var object
*/
protected $client;

/**
* Invoice
*
* @var Invoice
*/
protected $invoice;

/**
* Driver settings
*
* @var object
*/
protected $settings;

/**
* Cache
*
* @var FileCache
*/
protected $cache;

protected const PAYMENT_PURCHASE_STATUS_OK = 'ACCEPTED';

protected const PAYMENT_VERIFY_STATUS_OK = 'SUCCESSFUL';

/**
* Constructor.
* Construct the class with the relevant settings.
*
* @param Invoice $invoice
* @param $settings
*/
public function __construct(Invoice $invoice, $settings)
{
$this->invoice($invoice);
$this->settings = (object) $settings;
$this->client = new Client();
$this->cache = new FileCache(
new CacheOptions([
'filestorage' => $this->settings->cachePath,
])
);
}

/**
* Retrieve data from details using its name.
*
* @return string
*/
private function extractDetails($name)
{
return empty($this->invoice->getDetails()[$name]) ? null : $this->invoice->getDetails()[$name];
}

/**
* Purchase Invoice.
*
* @return string
*
* @throws PurchaseFailedException
*/
public function purchase()
{
$data = [
'amount' => $this->getInvoiceAmount(),
'return_url' => $this->settings->callbackUrl,
'order_id' => $this->invoice->getUuid(),
'description' => $this->extractDetails('description'),
'payer' => [
'name' => $this->extractDetails('name'),
'mobile' => $this->extractDetails('mobile'),
'email' => $this->extractDetails('email'),
],
];

$response = $this->client->request(
'POST',
$this->getPurchaseUrl(),
[
'headers' => [
'ApiKey' => $this->settings->merchantId,
'Content-Type' => 'application/json',
],
'json' => $data,
'http_errors' => false,
]
);

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

if ($body['status'] !== self::PAYMENT_PURCHASE_STATUS_OK) {
throw new PurchaseFailedException($body['message'], $body['error_code'] ?? 0);
}

$this->invoice->transactionId($body['payment_id']);

$this->cache->set('payment_link_' . $body['payment_id'], $body['link'], 3600);

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

/**
* Pay the Invoice
*
* @return RedirectionForm
*/
public function pay(): RedirectionForm
{
$payUrl = $this->cache->get('payment_link_' . $this->invoice->getTransactionId());

return $this->redirectWithForm($payUrl, [], 'GET');
}

/**
* Verify payment
*
* @return ReceiptInterface
*
* @throws InvalidPaymentException
*/
public function verify(): ReceiptInterface
{
$paymentStatusCode = Request::input('status');
$paymentReceiptNumber = Request::input('receipt_number');

if ($paymentStatusCode != 100) {
throw new InvalidPaymentException($this->translateStatus($paymentStatusCode), $paymentStatusCode);
}

$data = [
'amount' => $this->getInvoiceAmount(),
'receipt_number' => $paymentReceiptNumber,
];

$response = $this->client->request(
'POST',
$this->getVerificationUrl(),
[
'headers' => [
'ApiKey' => $this->settings->merchantId,
'Content-Type' => 'application/json',
],
'json' => $data,
"http_errors" => false,
]
);

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

if ($body['status'] !== self::PAYMENT_VERIFY_STATUS_OK) {
throw new InvalidPaymentException($body['message'], $body['error_code'] ?? 0);
}

$receipt = $this->createReceipt($paymentReceiptNumber);

$receipt->detail([
'id' => $body['id'],
'paid_amount' => $body['paid_amount'],
'message' => $body['message'],
]);

return $receipt;
}

/**
* Generate the payment's receipt
*
* @param $referenceId
*
* @return Receipt
*/
public function createReceipt($referenceId)
{
return new Receipt('parspal', $referenceId);
}

/**
* Retrieve invoice amount
*
* @return int|float
*/
protected function getInvoiceAmount()
{
return $this->invoice->getAmount() * (strtolower($this->settings->currency) === 't' ? 10 : 1); // convert to rial
}

/**
* Retrieve purchase url
*
* @return string
*/
protected function getPurchaseUrl(): string
{
return $this->isSandboxMode()
? $this->settings->sandboxApiPurchaseUrl
: $this->settings->apiPurchaseUrl;
}

/**
* Retrieve verification url
*
* @return string
*/
protected function getVerificationUrl(): string
{
return $this->isSandboxMode()
? $this->settings->sandboxApiVerificationUrl
: $this->settings->apiVerificationUrl;
}

/**
* Retrieve payment in sandbox mode?
*
* @return bool
*/
protected function isSandboxMode() : bool
{
return $this->settings->sandbox;
}

/**
* Convert status to a readable message.
*
* @param $status
*
* @return mixed|string
*/
private function translateStatus($status)
{
$translations = [
'99' => 'انصراف کاربر از پرداخت',
'88' => 'پرداخت ناموفق',
'77' => 'لغو پرداخت توسط کاربر',
];

$unknownError = 'خطای ناشناخته رخ داده است. در صورت کسر مبلغ از حساب حداکثر پس از 72 ساعت به حسابتان برمیگردد';

return array_key_exists($status, $translations) ? $translations[$status] : $unknownError;
}
}

0 comments on commit f4f2bf3

Please sign in to comment.