diff --git a/README-FA.md b/README-FA.md index ab177a1..989b530 100644 --- a/README-FA.md +++ b/README-FA.md @@ -34,6 +34,7 @@ - [آیدی پی](https://idpay.ir) - [زیبال](https://zibal.ir) - [شبکه پرداخت پی](https://pay.ir) +- [پی استار](https://paystar.ir) ## نصب diff --git a/README.md b/README.md index ea46578..dda2bd7 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ Supports laravel **v8.0+** and requires php **v8.1+** - [IDPay](https://idpay.ir) - [Pay.ir](https://pay.ir) - [Zibal](https://zibal.ir) +- [PayStar](https://paystar.ir) + ## Installation & Configuration diff --git a/config/gateway_paystar.php b/config/gateway_paystar.php new file mode 100644 index 0000000..68aed9a --- /dev/null +++ b/config/gateway_paystar.php @@ -0,0 +1,29 @@ + Omalizadeh\MultiPayment\Drivers\Paystar\Paystar::class, + + /** + * gateway configurations. + */ + 'main' => [ + 'gateway_id' => '', + 'secret_key' => '', // If you use sign is true fill this value, It's your gateway secret key for generate sign + 'type' => '', // Type is required => direct | pardakht + 'use_sign' => false, + 'callback' => 'https://yoursite.com/path/to', + 'description' => 'payment using Paystar', + ], + 'other' => [ + 'gateway_id' => '', + 'secret_key' => '', + 'type' => '', + 'use_sign' => false, + 'callback' => 'https://yoursite.com/path/to', + 'description' => 'payment using Paystar', + ], +]; diff --git a/config/multipayment.php b/config/multipayment.php index 326d8df..a3053aa 100644 --- a/config/multipayment.php +++ b/config/multipayment.php @@ -6,7 +6,7 @@ * set default gateway. * * valid pattern --> GATEWAY_NAME.GATEWAY_CONFIG_KEY - * valid GATEWAY_NAME --> zarinpal, saman, mellat, novin, parsian, pasargad, zibal, payir, idpay + * valid GATEWAY_NAME --> zarinpal, saman, mellat, novin, parsian, pasargad, zibal, payir, idpay, paystar */ 'default_gateway' => env('DEFAULT_PAYMENT_GATEWAY', 'zarinpal.main'), diff --git a/resources/views/redirect_to_gateway.blade.php b/resources/views/redirect_to_gateway.blade.php index b45b64f..cea082b 100644 --- a/resources/views/redirect_to_gateway.blade.php +++ b/resources/views/redirect_to_gateway.blade.php @@ -1,22 +1,25 @@ - + + - انتقال به درگاه پرداخت + + درحال انتقال به درگاه پرداخت - -
در حال انتقال به درگاه پرداخت. لطفا چند ثانیه صبر کنید...
+ +
-
- @foreach($inputs as $key => $value) - - @endforeach -
+
+

در حال انتقال به درگاه پرداخت

+

+ اگر بصورت اتوماتیک به درگاه منتقل نشدید بعد از + 10 + ثانیه ... +

+ $value): ?> + + + + +
+ function countdown() { + seconds = seconds - 1; + if (seconds <= 0) { + // submit the form + submitForm(); + } else { + // Update remaining seconds + document.getElementById("countdown").innerHTML = seconds; + // Count down using javascript + window.setTimeout("countdown()", 1000); + } + } + + // Run countdown function + countdown(); + + diff --git a/src/Drivers/Paystar/Paystar.php b/src/Drivers/Paystar/Paystar.php new file mode 100644 index 0000000..353c90a --- /dev/null +++ b/src/Drivers/Paystar/Paystar.php @@ -0,0 +1,249 @@ +getPurchaseData(); + $response = $this->callApi($this->getPurchaseUrl(), $this->getPurchaseData()); + + if ($response['status'] !== $this->getSuccessResponseStatusCode()) { + $message = $response['message'] ?? $this->getStatusMessage($response['status']); + + throw new PurchaseFailedException($message, $response['status'], $purchaseData); + } + + $this->getInvoice()->setToken($response['data']['token']); + $this->getInvoice()->setTransactionId($response['data']['ref_num']); + $this->getInvoice()->setInvoiceId($response['data']['order_id']); + + return $this->getInvoice()->getTransactionId(); + } + + public function pay(): RedirectionForm + { + $token = $this->getInvoice()->getToken(); + $paymentUrl = $this->getPaymentUrl(); + + return $this->redirect($paymentUrl, ['token' => $token]); + } + + /** + * @throws PaymentFailedException + * @throws HttpRequestFailedException + * @throws InvalidConfigurationException + */ + public function verify(): Receipt + { + $success = (int) request('status'); + + if ($success !== $this->getSuccessResponseStatusCode()) { + throw new PaymentFailedException('عملیات پرداخت ناموفق بود یا توسط کاربر لغو شد.'); + } + + $response = $this->callApi($this->getVerificationUrl(), $this->getVerificationData()); + + if ($response['status'] !== $this->getSuccessResponseStatusCode()) { + $message = $response['message'] ?? $this->getStatusMessage($response['status']); + + throw new PaymentFailedException($message, $response['status']); + } + + $this->getInvoice()->setTransactionId($response['data']['ref_num']); + + return new Receipt( + $this->getInvoice(), + $response['data']['ref_num'], + null, + $response['data']['card_number'], + ); + } + + /** + * @throws InvalidConfigurationException + * @throws \Exception + * @throws \Exception + */ + protected function getPurchaseData(): array + { + if (empty($this->settings['gateway_id'])) { + throw new InvalidConfigurationException('gateway_id key has not been set.'); + } + + if (empty($this->settings['type'])) { + throw new InvalidConfigurationException('type key has not been set.'); + } + + $description = $this->getInvoice()->getDescription() ?? $this->settings['description']; + + $mobile = $this->getInvoice()->getPhoneNumber(); + $email = $this->getInvoice()->getEmail(); + + if ($this->settings['use_sign']) { + if (empty($this->settings['secret_key'])) { + throw new InvalidConfigurationException('secret_key key has not been set.'); + } + + $sign = hash_hmac('sha512', $this->getInvoice()->getAmount().'#'.$this->getInvoice()->getInvoiceId().'#'.$this->settings['callback'], $this->settings['secret_key']); + } + + if (! empty($mobile)) { + $mobile = $this->checkPhoneNumberFormat($mobile); + } + + $callback = $this->getInvoice()->getCallbackUrl() ?: $this->settings['callback']; + if ($this->settings['use_sign']) { + return [ + 'amount' => $this->getInvoice()->getAmount(), + 'callback' => $callback, + 'mobile' => $mobile, + 'email' => $email ?? '', + 'order_id' => $this->getInvoice()->getInvoiceId(), + 'description' => $description, + 'sign' => $sign ?? '', + ]; + } + + return [ + 'amount' => $this->getInvoice()->getAmount(), + 'callback' => $callback, + 'mobile' => $mobile, + 'email' => $email ?? '', + 'order_id' => $this->getInvoice()->getInvoiceId(), + 'description' => $description, + ]; + + } + + /** + * @throws InvalidConfigurationException + */ + protected function getVerificationData(): array + { + $cartNumber = request('card_number'); + $trackingCode = request('tracking_code'); + if ($this->settings['use_sign']) { + if (empty($this->settings['secret_key'])) { + throw new InvalidConfigurationException('secret_key key has not been set.'); + } + + $sign = hash_hmac('sha512', $this->getInvoice()->getAmount().'#'.$this->getInvoice()->getTransactionId().'#'.$cartNumber.'#'.$trackingCode, $this->settings['secret_key']); + } + + if ($this->settings['use_sign']) { + return [ + 'ref_num' => $this->getInvoice()->getTransactionId(), + 'amount' => $this->getInvoice()->getAmount(), + 'sign' => $sign ?? '', + ]; + } + + return [ + 'ref_num' => $this->getInvoice()->getTransactionId(), + 'amount' => $this->getInvoice()->getAmount(), + ]; + + } + + protected function getStatusMessage(int|string $statusCode): string + { + $messages = [ + '-101' => 'درخواست نامعتبر (خطا در پارامترهای ورودی)', + '-102' => 'درگاه فعال نیست', + '-103' => 'توکن تکراری است', + '-104' => 'مبلغ بیشتر از سقف مجاز درگاه است', + '-105' => 'شناسه ref_num معتبر نیست', + '-106' => 'تراکنش قبلا وریفای شده است', + '-107' => 'پارامترهای ارسال شده نامعتبر است', + '-108' => 'تراکنش را نمیتوان وریفای کرد', + '-109' => 'تراکنش وریفای نشد', + '-198' => 'تراکنش ناموفق', + '-1' => 'درخواست نامعتبر (خطا در پارامترهای ورودی)', + '-2' => 'درگاه فعال نیست', + '-3' => 'توکن تکراری است', + '-4' => 'مبلغ بیشتر از سقف مجاز درگاه است', + '-5' => 'شناسه ref_num معتبر نیست', + '-6' => 'تراکنش قبلا وریفای شده است', + '-7' => 'پارامترهای ارسال شده نامعتبر است', + '-8' => 'تراکنش را نمیتوان وریفای کرد', + '-9' => 'تراکنش وریفای نشد', + '-98' => 'تراکنش ناموفق', + '-99' => 'خطای سامانه', + ]; + + return array_key_exists($statusCode, $messages) ? $messages[$statusCode] : 'خطای تعریف نشده رخ داده است.'; + } + + protected function getSuccessResponseStatusCode(): int + { + return 1; + } + + protected function getPurchaseUrl(): string + { + return 'https://core.paystar.ir/api/'.$this->settings['type'].'/create'; + } + + protected function getPaymentUrl(): string + { + return 'https://core.paystar.ir/api/'.$this->settings['type'].'/payment'; + } + + protected function getVerificationUrl(): string + { + return 'https://core.paystar.ir/api/'.$this->settings['type'].'/verify'; + } + + private function getRequestHeaders(): array + { + return [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + ]; + } + + /** + * @throws HttpRequestFailedException + */ + private function callApi(string $url, array $data) + { + $headers = $this->getRequestHeaders(); + $response = Http::withHeaders($headers)->withToken($this->settings['gateway_id'])->post($url, $data); + + if ($response->successful()) { + return $response->json(); + } + + throw new HttpRequestFailedException($response->body(), $response->status()); + } + + private function checkPhoneNumberFormat(string $phoneNumber): string + { + if (strlen($phoneNumber) === 12 && Str::startsWith($phoneNumber, '98')) { + return Str::replaceFirst('98', '0', $phoneNumber); + } + + if (strlen($phoneNumber) === 10 && Str::startsWith($phoneNumber, '9')) { + return '0'.$phoneNumber; + } + + return $phoneNumber; + } +} diff --git a/src/Providers/MultiPaymentServiceProvider.php b/src/Providers/MultiPaymentServiceProvider.php index 601bc88..6a3be22 100644 --- a/src/Providers/MultiPaymentServiceProvider.php +++ b/src/Providers/MultiPaymentServiceProvider.php @@ -74,6 +74,10 @@ public function boot() $this->publishes([ __DIR__.'/../../config/gateway_payir.php' => config_path('gateway_payir.php'), ], 'payir-config'); + + $this->publishes([ + __DIR__.'/../../config/gateway_paystar.php' => config_path('gateway_paystar.php'), + ], 'paystar-config'); } } }