├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config └── shaparak.php ├── src ├── Contracts │ ├── Factory.php │ ├── Provider.php │ └── Transaction.php ├── Enums │ └── ProviderName.php ├── Exception.php ├── Exceptions │ ├── RefundException.php │ ├── RequestTokenException.php │ ├── SettlementException.php │ └── VerificationException.php ├── Facades │ └── Shaparak.php ├── Helper │ └── Pasargad │ │ ├── RSA.php │ │ ├── RSAKeyType.php │ │ └── RSAProcessor.php ├── Provider │ ├── AbstractProvider.php │ ├── AsanPardakhtProvider.php │ ├── Exception.php │ ├── MellatProvider.php │ ├── MelliProvider.php │ ├── ParsianProvider.php │ ├── PasargadProvider.php │ ├── SaderatProvider.php │ ├── SamanProvider.php │ └── ZarinpalProvider.php ├── ShaparakManager.php └── ShaparakServiceProvider.php ├── translations ├── en │ └── shaparak.php └── fa │ └── shaparak.php └── views └── goto-gate-form.blade.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | composer.lock 4 | phpunit.xml 5 | 6 | *.DS_Store 7 | .DS_Store 8 | # Thumbnails 9 | ._* 10 | 11 | # Files that might appear in the root of a volume 12 | .DocumentRevisions-V100 13 | .fseventsd 14 | .Spotlight-V100 15 | .TemporaryItems 16 | .Trashes 17 | .VolumeIcon.icns 18 | .com.apple.timemachine.donotpresent 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Shaparak Component v1.0 for Laravel 5+ 2 | 3 | ## Laravel 4 | 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2016 Aboozar Ghaffari 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shaparak :: Laravel Online Payment Component 2 | Online Payment Component for Laravel 5+ known as Shaparak component completely compatible with [BankTest](http://banktest.ir) simulator. 3 | Shaparak integrated all Iranian/Shetab payment gateways to one component. 4 | 5 | ## What is Banktest? 6 | - [BankTest](http://banktest.ir) is a sandbox service for all Iranian online payment gateways 7 | - [بانک تست](http://banktest.ir) یک سرویس شبیه ساز درگاه های پرداخت آنلاین ایرانی برای اهداف توسعه و تست نرم افزار می باشد 8 | 9 | ## Support This Project 10 | 11 | Please support the package by giving it :star: and contributing to its development. 12 | 13 | ## Currently supported PSPs: 14 | 15 | - Mellat Bank Gateway - درگاه بانک ملت (به پرداخت ملت) لاراول 16 | - Saman Bank Gateway - درگاه بانک سامان (پرداخت الکترونیک سامان) لاراول 17 | - Saderat Bank Gateway - درگاه بانک صادرات (پرداخت الکترونیک سپهر) لاراول 18 | - Pasargad Bank Gateway - درگاه بانک پاسارگاد (پرداخت الکترونیک پاسارگاد) لاراول 19 | - Parsian Bank Gateway - درگاه بانک پارسیان (تجارت الکترونیک پارسیان) لاراول 20 | - Melli Bank Gateway - درگاه بانک ملی (سداد) لاراول 21 | - ... 22 | 23 | ## Requirements 24 | Shaparak require PHP 7.1+ 25 | 26 | ## Installation 27 | 1. Installation via php composer 28 | 29 | ```bash 30 | composer require php-monsters/shaparak 31 | ``` 32 | 2. Add package service provider to your app service providers: 33 | 34 | ```php 35 | PhpMonsters\Shaparak\ShaparakServiceProvider::class, 36 | ``` 37 | 3. Add package alias to your app aliases: 38 | 39 | ```php 40 | 'Shaparak' => PhpMonsters\Shaparak\Facades\Shaparak::class, 41 | ``` 42 | 4. Publish package assets and configs 43 | 44 | ```bash 45 | php artisan vendor:publish --provider="PhpMonsters\Shaparak\ShaparakServiceProvider" 46 | ``` 47 | 48 | ## Configuration 49 | If you complete installation step correctly, you can find Shaparak config file as `shaparak.php` in you project config file. 50 | 51 | For using sandbox environment you should set ```SHAPARAK_MODE=development``` in your .env file otherwise set ```SHAPARAK_MODE=production``` 52 | 53 | if you choose development mode, Shaparak uses [banktest.ir](https://banktest.ir) as its payment gateway. 54 | 55 | 56 | ## Usage 57 | ### Add required fields to the model migration 58 | ```php 59 | $table->string('token', 40)->nullable(); // It keeps token that we get from the IPG 60 | $table->jsonb('gateway_callback_params')->nullable(); // It keeps the IPG callback parameters (just for tracking and debugging) 61 | 62 | $table->boolean('verified')->default(false); // Transaction verified or not 63 | $table->boolean('after_verified')->default(false); // Transaction settled or not 64 | $table->boolean('reversed')->default(false); // Transaction revered/refunded or not 65 | $table->boolean('accomplished')->default(false); // Transaction accomplished or not 66 | ``` 67 | ### Prepare required model(s) 68 | Your Transaction, Invoice or Order model MUST implement Shaparak Transaction Interface. 69 | ```php 70 | $this->token, 100 | ] 101 | )); 102 | } 103 | 104 | /** 105 | * set gateway token that fetched from ipg provider gateway 106 | */ 107 | public function setGatewayToken(string $token, bool $save = true): bool 108 | { 109 | $this->token = $token; 110 | $this->status = (TransactionStatusEnum::GoneToGate)->value; 111 | 112 | if ($save) { 113 | return $this->save(); 114 | } 115 | 116 | return true; 117 | } 118 | 119 | public function getGatewayToken(): string 120 | { 121 | return $this->token; 122 | } 123 | 124 | /** 125 | * check if you transaction is ready for requesting payment token 126 | */ 127 | public function isReadyForTokenRequest(): bool 128 | { 129 | return intval($this->status) <= (TransactionStatusEnum::Callback)->value; 130 | } 131 | 132 | /** 133 | * check if transaction is ready for requesting verify transaction 134 | */ 135 | public function isReadyForVerify(): bool 136 | { 137 | return intval($this->status) <= (TransactionStatusEnum::Verified)->value; 138 | } 139 | 140 | /** 141 | * check if transaction is ready for requesting inquiry transaction (if supported by gateway) 142 | */ 143 | public function isReadyForInquiry(): bool 144 | { 145 | return intval($this->status) >= (TransactionStatusEnum::GoneToGate)->value; 146 | } 147 | 148 | /** 149 | * check if transaction is ready for requesting settle/... transaction (if needed by gateway) 150 | */ 151 | public function isReadyForSettle(): bool 152 | { 153 | return intval($this->status) == (TransactionStatusEnum::Verified)->value; 154 | } 155 | 156 | /** 157 | * check if transaction is ready to mark as accomplished 158 | */ 159 | public function isReadyForAccomplish(): bool 160 | { 161 | return (intval($this->status) >= (TransactionStatusEnum::Verified)->value) && 162 | (intval($this->status) < (TransactionStatusEnum::Accomplished)->value); 163 | } 164 | 165 | public function ipgProviderSupportsRefund(): bool 166 | { 167 | return ! empty($this->providable) && 168 | $this->providable->refund_support === true; 169 | } 170 | 171 | public function isReadyForReverse(): bool 172 | { 173 | return $this->status === TransactionStatusEnum::Callback->value; 174 | } 175 | 176 | public function isReadyForCancel(): bool 177 | { 178 | return $this->status === TransactionStatusEnum::Verified->value; 179 | } 180 | 181 | /** 182 | * check if transaction is ready for accomplishment (merchant verify) 183 | */ 184 | public function isReadyForRefund(): bool 185 | { 186 | return 187 | $this->ipgProviderSupportsRefund() && 188 | (int) $this->status !== (TransactionStatusEnum::Accomplished)->value; 189 | } 190 | 191 | /** 192 | * update transaction by paid card number (if provided by gateway) 193 | */ 194 | public function setCardNumber(string $cardNumber, bool $save = true): bool 195 | { 196 | $this->cardNumber = $cardNumber; 197 | 198 | if ($save) { 199 | return $this->save(); 200 | } 201 | 202 | return true; 203 | } 204 | 205 | /** 206 | * mark transaction as verified 207 | */ 208 | public function setVerified(bool $save = true): bool 209 | { 210 | $this->status = (TransactionStatusEnum::Verified)->value; 211 | 212 | if ($save) { 213 | return $this->save(); 214 | } 215 | 216 | return true; 217 | } 218 | 219 | /** 220 | * mark transaction as settled/... 221 | */ 222 | public function setSettled(bool $save = true): bool 223 | { 224 | $this->status = (TransactionStatusEnum::Settled)->value; 225 | 226 | if ($save) { 227 | return $this->save(); 228 | } 229 | 230 | return true; 231 | } 232 | 233 | /** 234 | * mark transaction as reversed 235 | */ 236 | public function setRefunded(bool $save = true): bool 237 | { 238 | $this->status = (TransactionStatusEnum::Refund)->value; 239 | 240 | if ($save) { 241 | return $this->save(); 242 | } 243 | 244 | return true; 245 | } 246 | 247 | /** 248 | * get transaction amount 249 | */ 250 | public function getPayableAmount(): int 251 | { 252 | return $this->payable_amount; 253 | } 254 | 255 | /** 256 | * save ipg provider's gateway callback parameters into transaction 257 | */ 258 | public function setCallBackParameters(array $parameters, bool $save = true): bool 259 | { 260 | $this->gateway_callback_params = $parameters; 261 | 262 | if ($save) { 263 | return $this->save(); 264 | } 265 | 266 | return true; 267 | } 268 | 269 | /** 270 | * @return false|mixed|string 271 | */ 272 | public function getCallbackParams(): mixed 273 | { 274 | return $this->gateway_callback_params; 275 | } 276 | } 277 | ``` 278 | 279 | ### Initialize a Shaparak instance 280 | ```php 281 | // method I: init Shaparak by passing custom gateway options 282 | $gatewayProperties = [ 283 | 'terminal_id' => 'X1A3B5CY-X1DT4Z', 284 | 'terminal_pass' => '12345', 285 | ]; 286 | $shaparak = Shaparak::with($psp, $transaction, $gatewayProperties) 287 | ->setParameters($request->all()); 288 | 289 | // method II: init shaparak by config based gateway options 290 | // if you don't pass the third item it will use gateway's options from `config/shaparak.php` config file 291 | $shaparak = Shaparak::with($psp, $transaction) 292 | ->setParameters($request->all()); 293 | ``` 294 | 295 | ### Create goto IPG form (Payment form) 296 | Create a form in order to go to payment gateway. This form is auto-submit by default 297 | ```php 298 | // create your transaction as you desired 299 | $transaction = new Transaction(); 300 | $transaction->user_id = $user->id; 301 | // ... 302 | $transaction->ip_address = $request->getClientIp(); 303 | $transaction->description = $request->input('description'); 304 | $transaction->save(); 305 | 306 | try { 307 | $form = Shaparak::with('saman', $transaction)->getForm(); 308 | } catch (\Exception $e) { 309 | XLog::exception($e); 310 | flash(trans('gate.could_not_create_goto_bank_form'))->error(); 311 | return redirect()->back(); 312 | } 313 | ``` 314 | Show the form in your blade like: 315 | ```php 316 | {!! $form !!} 317 | ``` 318 | 319 | ### Callback URL/route on your application 320 | In your callback action create a Shaparak instance and handle the transaction 321 | ```php 322 | $shaparak = Shaparak::with('saman', $order) 323 | ->setParameters($request->all()); 324 | 325 | if ($shaparak->canContinueWithCallbackParameters($request->all()) !== true) { 326 | flash(trans('gate.could_not_continue_because_of_callback_params'))->error(); 327 | // do what you need 328 | } 329 | 330 | $shaparak->setCallBackParameters($request->all()); 331 | 332 | $verifyResult = $shaparak->verifyTransaction($request->all()); 333 | 334 | ``` 335 | 336 | ### Next steps (optional steps) 337 | Use the following methods based on your needs 338 | 339 | ```php 340 | $inquiryResult = $shaparak->inquiryTransaction($request->all()); 341 | $settleResult = $shaparak->settleTransaction($request->all()); 342 | $refundResult = $shaparak->refundTransaction($request->all()); 343 | ``` 344 | 345 | ## Security 346 | 347 | If you discover any security related issues, please email aboozar.ghf@gmail.com instead of using the issue tracker. 348 | 349 | ## Team 350 | 351 | This component is developed by the following person(s) and a bunch of [awesome contributors](https://github.com/iamtartan/laravel-online-payment/graphs/contributors). 352 | 353 | [![Aboozar Ghaffari](https://avatars2.githubusercontent.com/u/502961?v=3&s=130)](https://github.com/iamtartan) | [![Maryam Nabiyan](https://avatars.githubusercontent.com/u/47553919?s=120&v=4)](https://github.com/maryamnbyn) 354 | --- | --- | 355 | [Aboozar Ghaffari](https://github.com/iamtartan) | [Maryam Nabiyan](https://github.com/maryamnbyn) 356 | 357 | 358 | ## License 359 | 360 | The Laravel Online Payment Module is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-monsters/shaparak", 3 | "description": "Iranian payment gateways handler for laravel based applications", 4 | "keywords": [ 5 | "payment", 6 | "shaparak", 7 | "shetab", 8 | "asanpay", 9 | "bank", 10 | "online payment", 11 | "gateway", 12 | "iran" 13 | ], 14 | "type": "library", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Aboozar Ghaffari ", 19 | "email": "aboozar.ghf@gmail.com" 20 | }, 21 | { 22 | "name": "Milad Kianmehr", 23 | "email": "milad.kian@gmail.com" 24 | }, 25 | { 26 | "name": "Maryam Nabiyan", 27 | "email": "maryam.nbyn@gmail.com" 28 | } 29 | ], 30 | "require": { 31 | "php": ">8.0", 32 | "ext-curl": "*", 33 | "ext-json": "*", 34 | "ext-openssl": "*", 35 | "ext-simplexml": "*", 36 | "ext-soap": "*", 37 | "ext-xml": "*", 38 | "ext-bcmath": "*", 39 | "illuminate/support": ">=8.0", 40 | "illuminate/view": ">=8.0", 41 | "php-monsters/laravel-xlog": "^1.3" 42 | }, 43 | "require-dev": { 44 | "mockery/mockery": "^1.4.2", 45 | "phpunit/phpunit": "^9.5" 46 | }, 47 | "autoload-dev": { 48 | "psr-4": { 49 | "PhpMonsters\\Shaparak\\Tests\\": "tests/" 50 | } 51 | }, 52 | "autoload": { 53 | "psr-4": { 54 | "PhpMonsters\\Shaparak\\": "src/" 55 | } 56 | }, 57 | "extra": { 58 | "laravel": { 59 | "providers": [ 60 | "PhpMonsters\\Shaparak\\ShaparakServiceProvider" 61 | ], 62 | "aliases": { 63 | "Shaparak": "PhpMonsters\\Shaparak\\Facades\\Shaparak" 64 | } 65 | } 66 | }, 67 | "config": { 68 | "sort-packages": true 69 | }, 70 | "minimum-stability": "stable" 71 | } 72 | -------------------------------------------------------------------------------- /config/shaparak.php: -------------------------------------------------------------------------------- 1 | env('SHAPARAK_MODE', 'production'), 18 | 19 | 'banktest_base_url' => 'https://sandbox.banktest.ir', 20 | 21 | 'log' => [ 22 | 'prefix' => 'SHAPARAK->', 23 | ], 24 | 25 | 'iban' => env('SETTLEMENT_IBAN'), 26 | 27 | 'providers' => [ 28 | /* 29 | |-------------------------------------------------------------------------- 30 | | AsanPardakht gateway configuration 31 | |-------------------------------------------------------------------------- 32 | */ 33 | 'asanpardakht' => [ 34 | 'username' => env('AP_USERNAME'), 35 | 'password' => env('AP_PASSWORD'), 36 | 'terminal_id' => env('AP_MERCHANT_ID'), 37 | 'key' => env('AP_KEY'), 38 | 'iv' => env('AP_IV'), 39 | 'cumulative' => env('AP_CUMULATIVE', false), 40 | ], 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Saman gateway configuration 44 | |-------------------------------------------------------------------------- 45 | */ 46 | 'saman' => [ 47 | 'terminal_id' => env('SAMAN_TERMINAL_ID'), 48 | 'terminal_pass' => env('SAMAN_TERMINAL_PASS'), 49 | 'cumulative' => env('SAMAN_CUMULATIVE', false), 50 | ], 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Parsian gateway configuration 54 | |-------------------------------------------------------------------------- 55 | */ 56 | 'parsian' => [ 57 | 'pin' => env('PARSIAN_PIN', ''), 58 | 'cumulative' => env('PARSIAN_CUMULATIVE', false), 59 | ], 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | Pasargad gateway configuration 63 | |-------------------------------------------------------------------------- 64 | */ 65 | 'pasargad' => [ 66 | 'terminal_id' => env('PASARGAD_TERMINAL_ID'), 67 | 'merchant_id' => env('PASARGAD_MERCHANT_ID'), 68 | 'certificate_path' => env('PASARGAD_CERT_PATH', storage_path('shaparak/pasargad/certificate.xml')), 69 | 'cumulative' => env('PASARGAD_CUMULATIVE', false), 70 | ], 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Mellat gateway configuration 74 | |-------------------------------------------------------------------------- 75 | */ 76 | 'mellat' => [ 77 | 'username' => env('MELLAT_USERNAME'), 78 | 'password' => env('MELLAT_PASSWORD'), 79 | 'terminal_id' => env('MELLAT_TERMINAL_ID'), 80 | 'cumulative' => env('MELLAT_CUMULATIVE', false), 81 | ], 82 | /* 83 | |-------------------------------------------------------------------------- 84 | | Melli/Sadad gateway configuration 85 | |-------------------------------------------------------------------------- 86 | */ 87 | 'melli' => [ 88 | 'merchant_id' => env('MELLI_MERCHANT_ID'), 89 | 'terminal_id' => env('MELLI_TERMINAL_ID'), 90 | 'transaction_key' => env('MELLI_TRANS_KEY'), 91 | 'cumulative' => env('MELLI_CUMULATIVE', false), 92 | ], 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Zarinpal gateway configuration 96 | |-------------------------------------------------------------------------- 97 | */ 98 | 'saderat' => [ 99 | 'terminal_id' => env('SADERAT_MERCHANT_ID'), 100 | 'cumulative' => env('SADERAT_CUMULATIVE', false), 101 | ], 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Zarinpal gateway configuration 105 | |-------------------------------------------------------------------------- 106 | */ 107 | 'zarinpal' => [ 108 | 'merchant_id' => env('ZARINPAL_MERCHANT_ID'), 109 | 'cumulative' => env('ZARINPAL_CUMULATIVE', false), 110 | ], 111 | ], 112 | 113 | /* 114 | |-------------------------------------------------------------------------- 115 | | httpClient Options including soapClient/Guzzle/Curl 116 | |-------------------------------------------------------------------------- 117 | | 118 | | options: Options 119 | | 120 | */ 121 | 'httpClientOptions' => [ 122 | 'soap' => [], 123 | 'curl' => [], 124 | ], 125 | ]; 126 | -------------------------------------------------------------------------------- /src/Contracts/Factory.php: -------------------------------------------------------------------------------- 1 | = 0; $i--) { 132 | $digit = ord($data[$i]); 133 | $part_res = bcmul($digit, $radix); 134 | $result = bcadd($result, $part_res); 135 | $radix = bcmul($radix, $base); 136 | } 137 | 138 | return $result; 139 | } 140 | 141 | public static function number_to_binary($number, $blocksize) 142 | { 143 | $base = '256'; 144 | $result = ''; 145 | $div = $number; 146 | while ($div > 0) { 147 | $mod = bcmod($div, $base); 148 | $div = bcdiv($div, $base); 149 | $result = chr($mod).$result; 150 | } 151 | 152 | return str_pad($result, $blocksize, "\x00", STR_PAD_LEFT); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Helper/Pasargad/RSAKeyType.php: -------------------------------------------------------------------------------- 1 | modulus = RSA::binary_to_number(base64_decode($xmlObj->Modulus)); 36 | $this->public_key = RSA::binary_to_number(base64_decode($xmlObj->Exponent)); 37 | $this->private_key = RSA::binary_to_number(base64_decode($xmlObj->D)); 38 | $this->key_length = strlen(base64_decode($xmlObj->Modulus)) * 8; 39 | } 40 | 41 | public function getPublicKey() 42 | { 43 | return $this->public_key; 44 | } 45 | 46 | public function getPrivateKey() 47 | { 48 | return $this->private_key; 49 | } 50 | 51 | public function getKeyLength(): int 52 | { 53 | return $this->key_length; 54 | } 55 | 56 | public function getModulus() 57 | { 58 | return $this->modulus; 59 | } 60 | 61 | public function encrypt($data): string 62 | { 63 | return base64_encode(RSA::rsa_encrypt($data, $this->public_key, $this->modulus, $this->key_length)); 64 | } 65 | 66 | public function decrypt($data) 67 | { 68 | return RSA::rsa_decrypt($data, $this->private_key, $this->modulus, $this->key_length); 69 | } 70 | 71 | public function sign($data): string 72 | { 73 | return RSA::rsa_sign($data, $this->private_key, $this->modulus, $this->key_length); 74 | } 75 | 76 | public function verify($data) 77 | { 78 | return RSA::rsa_verify($data, $this->public_key, $this->modulus, $this->key_length); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Provider/AbstractProvider.php: -------------------------------------------------------------------------------- 1 | environment = $environment; 82 | $this->transaction = $transaction; 83 | $this->httpClientOptions = $httpClientOptions; 84 | $this->bankTestBaseUrl = config('shaparak.banktest_base_url'); 85 | $this->setParameters($configs); 86 | } 87 | 88 | /** 89 | * {@inheritDoc} 90 | */ 91 | public function getForm(): \Illuminate\View\View 92 | { 93 | $formParameters = $this->getFormParameters(); 94 | 95 | return view( 96 | 'shaparak::goto-gate-form', 97 | array_merge($formParameters, [ 98 | 'buttonLabel' => $this->getParameters('submit_label') ?: __('shaparak::shaparak.goto_gate'), 99 | 'autoSubmit' => (bool) $this->getParameters('auto_submit', true), 100 | ]) 101 | ); 102 | } 103 | 104 | /** 105 | * {@inheritDoc} 106 | */ 107 | abstract public function getFormParameters(): array; 108 | 109 | /** 110 | * {@inheritDoc} 111 | */ 112 | public function getParameters(string $key = null, $default = null) 113 | { 114 | if (is_null($key)) { 115 | return $this->parameters; 116 | } 117 | 118 | $key = strtolower($key); 119 | 120 | return $this->parameters[$key] ?? $default; 121 | } 122 | 123 | /** 124 | * {@inheritDoc} 125 | */ 126 | public function setParameters(array $parameters = []): ProviderContract 127 | { 128 | $parameters = array_change_key_case($parameters); 129 | 130 | $this->parameters = array_merge($this->parameters, $parameters); 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * {@inheritDoc} 137 | */ 138 | abstract public function verifyTransaction(): bool; 139 | 140 | /** 141 | * {@inheritDoc} 142 | */ 143 | public function settleTransaction(): bool 144 | { 145 | // default behavior 146 | return $this->getTransaction()->setSettled(); 147 | } 148 | 149 | /** 150 | * {@inheritDoc} 151 | */ 152 | public function accomplishTransaction(): bool 153 | { 154 | // default behavior 155 | return $this->getTransaction()->setAccomplished(); 156 | } 157 | 158 | public function getTransaction(): Transaction 159 | { 160 | return $this->transaction; 161 | } 162 | 163 | /** 164 | * {@inheritDoc} 165 | */ 166 | abstract public function refundTransaction(): bool; 167 | 168 | /** 169 | * {@inheritDoc} 170 | */ 171 | abstract public function getGatewayReferenceId(): string; 172 | 173 | /** 174 | * {@inheritDoc} 175 | */ 176 | abstract public function canContinueWithCallbackParameters(): bool; 177 | 178 | /** 179 | * {@inheritDoc} 180 | */ 181 | public function refundSupport(): bool 182 | { 183 | return $this->refundSupport; 184 | } 185 | 186 | /** 187 | * {@inheritDoc} 188 | */ 189 | public function settlementSupport(): bool 190 | { 191 | return $this->settlementSupport; 192 | } 193 | 194 | /** 195 | * {@inheritDoc} 196 | */ 197 | public function checkRequiredActionParameters(array $parameters): void 198 | { 199 | $parameters = array_map('strtolower', $parameters); 200 | 201 | foreach ($parameters as $parameter) { 202 | if (! array_key_exists($parameter, $this->parameters) || trim($this->parameters[$parameter]) === '') { 203 | throw new Exception("Parameters array must have a not null value for key: '$parameter'"); 204 | } 205 | } 206 | } 207 | 208 | /** 209 | * @throws SoapFault|Exception 210 | */ 211 | protected function getSoapClient(string $action): SoapClient 212 | { 213 | $soapOptions = $this->httpClientOptions ? $this->httpClientOptions['soap'] : []; 214 | 215 | // set soap options if require. see shaparak config 216 | return new SoapClient($this->getUrlFor($action), $soapOptions); 217 | } 218 | 219 | /** 220 | * {@inheritDoc} 221 | */ 222 | abstract public function getUrlFor(string $action): string; 223 | 224 | /** 225 | * fetches callback url from parameters 226 | */ 227 | protected function getCallbackUrl(): string 228 | { 229 | return Str::is('http*', $this->getParameters('callback_url')) ? 230 | $this->getParameters('callback_url') : 231 | $this->getTransaction()->getCallbackUrl(); 232 | } 233 | 234 | /** 235 | * fetches payable amount of the transaction 236 | */ 237 | protected function getAmount(): int 238 | { 239 | return (is_int($this->getParameters('amount')) && ! empty($this->getParameters('amount'))) ? 240 | $this->getParameters('amount') : 241 | $this->getTransaction()->getPayableAmount(); 242 | } 243 | 244 | /** 245 | * fetches payable amount of the transaction 246 | */ 247 | protected function getGatewayOrderId(): int 248 | { 249 | return $this->getTransaction()->getGatewayOrderId(); 250 | } 251 | 252 | abstract protected function getGatewayOrderIdFromCallBackParameters(): string; 253 | 254 | protected function callbackAbuseCheckList(): void 255 | { 256 | if ((string) $this->getTransaction()->gateway_order_id !== $this->getGatewayOrderIdFromCallBackParameters()) { 257 | throw new Exception('shaparak::shaparak.could_not_pass_abuse_checklist'); 258 | } 259 | } 260 | 261 | protected function log(string $message, array $params = [], string $level = 'debug'): void 262 | { 263 | $reflect = new ReflectionClass($this); 264 | $provider = strtolower(str_replace('Provider', '', $reflect->getShortName())); 265 | 266 | $message = $provider.': '.$message; 267 | 268 | Shaparak::log($message, $params, $level); 269 | } 270 | 271 | public function getTransactionResult(): bool 272 | { 273 | return $this->provideTransactionResult; 274 | } 275 | 276 | public function setCallBackParameters(array $parameters): bool 277 | { 278 | return $this->getTransaction()->setCallBackParameters($parameters); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/Provider/AsanPardakhtProvider.php: -------------------------------------------------------------------------------- 1 | 'asanpardakht', 35 | 'method' => self::POST_METHOD, 36 | 'action' => $this->getUrlFor(self::URL_GATEWAY), 37 | 'parameters' => [ 38 | 'RefId' => $this->requestToken(), 39 | 'mobileap' => $this->getParameters('user_mobile'), 40 | ], 41 | ]; 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | public function getUrlFor(string $action): string 48 | { 49 | if ($this->environment === 'production') { 50 | switch ($action) { 51 | case self::URL_GATEWAY: 52 | 53 | return 'https://asan.shaparak.ir'; 54 | 55 | case self::URL_TOKEN: 56 | 57 | return 'https://ipgrest.asanpardakht.ir/v1/Token'; 58 | 59 | case self::URL_VERIFY: 60 | 61 | return 'https://ipgrest.asanpardakht.ir/v1/Verify'; 62 | 63 | case self::URL_RESULT: 64 | 65 | return 'https://ipgrest.asanpardakht.ir/v1/TranResult'; 66 | 67 | case self::URL_SETTLEMENT: 68 | 69 | return 'https://ipgrest.asanpardakht.ir/v1/Settlement'; 70 | 71 | case self::URL_REFUND: 72 | 73 | return 'https://ipgrest.asanpardakht.ir/v1/Reverse'; 74 | 75 | case self::URL_CANCEL: 76 | 77 | return 'https://ipgrest.asanpardakht.ir/v1/Cancel'; 78 | 79 | } 80 | } else { 81 | switch ($action) { 82 | case self::URL_GATEWAY: 83 | 84 | return $this->bankTestBaseUrl.'/ap/asan.shaparak.ir'; 85 | 86 | case self::URL_TOKEN: 87 | 88 | return $this->bankTestBaseUrl.'/ap/ipgrest.asanpardakht.ir/v1/Token'; 89 | 90 | case self::URL_VERIFY: 91 | 92 | return $this->bankTestBaseUrl.'/ap/ipgrest.asanpardakht.ir/v1/Verify'; 93 | 94 | case self::URL_RESULT: 95 | 96 | return $this->bankTestBaseUrl.'/ap/ipgrest.asanpardakht.ir/v1/TranResult'; 97 | 98 | case self::URL_SETTLEMENT: 99 | 100 | return $this->bankTestBaseUrl.'/ap/ipgrest.asanpardakht.ir/v1/Settlement'; 101 | 102 | case self::URL_REFUND: 103 | 104 | return $this->bankTestBaseUrl.'/ap/ipgrest.asanpardakht.ir/v1/Reverse'; 105 | 106 | case self::URL_CANCEL: 107 | 108 | return $this->bankTestBaseUrl.'/ap/ipgrest.asanpardakht.ir/v1/Cancel'; 109 | 110 | } 111 | } 112 | throw new Exception("could not find url for {$action} action"); 113 | } 114 | 115 | /** 116 | * @throws Exception 117 | * @throws RequestTokenException|JsonException 118 | */ 119 | public function requestToken(): string 120 | { 121 | $transaction = $this->getTransaction(); 122 | 123 | if ($transaction->isReadyForTokenRequest() === false) { 124 | throw new RequestTokenException( 125 | 'transaction is not ready for requesting token from payment gateway' 126 | ); 127 | } 128 | 129 | $response = $this->sendParamToAp( 130 | $this->requestTokenData(), 131 | $this->getUrlFor(self::URL_TOKEN), 132 | self::POST_METHOD 133 | ); 134 | 135 | if ($response->successful() && ! empty($response->body())) { 136 | $this->getTransaction()->setGatewayToken( 137 | json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR), 138 | true 139 | ); 140 | 141 | return json_decode($response->body(), false, 512, JSON_THROW_ON_ERROR); 142 | } 143 | 144 | if ($response->status() !== 200) { 145 | //todo: handle error page 146 | throw new Exception(sprintf('shaparak::asanpardakht.error_%s', $response->status())); 147 | } 148 | 149 | throw new Exception('shaparak::shaparak.token_failed'); 150 | } 151 | 152 | /** 153 | * @throws Exception 154 | */ 155 | public function requestTokenData(): array 156 | { 157 | $this->checkRequiredActionParameters([ 158 | 'terminal_id', 159 | 'username', 160 | 'password', 161 | 'local_date', 162 | 'local_time', 163 | ]); 164 | 165 | $data = [ 166 | 'serviceTypeId' => 1, 167 | 'merchantConfigurationId' => $this->getParameters('terminal_id'), 168 | 'localInvoiceId' => $this->getGatewayOrderId(), // get it from Transaction 169 | 'amountInRials' => $this->getAmount(), 170 | 'localDate' => $this->getParameters('local_date').' '.$this->getParameters('local_time'), 171 | 'callbackURL' => $this->getCallbackUrl(), 172 | 'paymentId' => $this->getParameters('payment_id', 0), 173 | 'additionalData' => (string) $this->getParameters('additional_data', ''), 174 | ]; 175 | 176 | return array_merge($data, $this->cumulativeData()); 177 | } 178 | 179 | public function sendParamToAp(array $params, string $url, string $method): mixed 180 | { 181 | return Http::acceptJson()->withHeaders([ 182 | 'usr' => $this->getParameters('username'), 183 | 'pwd' => $this->getParameters('password'), 184 | ])->withOptions([ 185 | 'timeout' => 15, 186 | ])->$method( 187 | $url, 188 | $params 189 | ); 190 | } 191 | 192 | protected function getGatewayOrderIdFromCallBackParameters(): string 193 | { 194 | return (string) $this->getTransaction()->getFromJsonb(key: 'salesOrderID', fieldName: 'gateway_callback_params'); 195 | } 196 | 197 | protected function callbackAbuseCheckList(): void 198 | { 199 | if (!( 200 | $this->getGatewayOrderIdFromCallBackParameters() === (string)$this->getTransaction()->gateway_order_id 201 | && (int)$this->getTransaction()->getFromJsonb(key: 'amount', fieldName: 'gateway_callback_params') === $this->getTransaction()->getPayableAmount() 202 | && (string)$this->getTransaction()->getFromJsonb(key: 'refID', fieldName: 'gateway_callback_params') === $this->getTransaction()->getGatewayToken() 203 | )) { 204 | throw new Exception('shaparak::shaparak.could_not_pass_abuse_checklist'); 205 | } 206 | } 207 | 208 | /** 209 | * {@inheritDoc} 210 | */ 211 | public function verifyTransaction(): bool 212 | { 213 | // required parameters will be checked by getTransactionResult method 214 | 215 | if ($this->getTransaction()->isReadyForVerify() === false) { 216 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 217 | } 218 | 219 | $this->checkRequiredActionParameters([ 220 | 'terminal_id', 221 | ]); 222 | 223 | $this->callbackAbuseCheckList(); 224 | 225 | try { 226 | $response = $this->generateComplementaryOperation(self::URL_VERIFY); 227 | 228 | if ($response !== true) { 229 | return false; 230 | } 231 | $this->getTransaction()->setVerified(true); 232 | 233 | return true; 234 | } catch (\Exception $e) { 235 | $this->log('verifyTransaction: '.$e->getMessage().' #'.$e->getCode(), [], 'error'); 236 | 237 | return false; 238 | } 239 | } 240 | 241 | /** 242 | * @throws Exception 243 | */ 244 | public function getTransactionResult(): bool 245 | { 246 | $this->checkRequiredActionParameters([ 247 | 'username', 248 | 'password', 249 | 'terminal_id', 250 | ]); 251 | $response = $this->sendParamToAp([ 252 | 'localInvoiceId' => $this->getGatewayOrderId(), 253 | 'merchantConfigurationId' => $this->getParameters('terminal_id'), 254 | ], $this->getUrlFor(self::URL_RESULT), self::GET_METHOD); 255 | 256 | if ($response->successful() && $response->status() === 200 && ! empty($response->body())) { 257 | $this->getTransaction()->setCallBackParameters($response->json()); 258 | 259 | return true; 260 | } 261 | 262 | return false; 263 | } 264 | 265 | /** 266 | * Send settle request 267 | * 268 | * 269 | * @throws Exception 270 | * @throws SettlementException 271 | */ 272 | public function settleTransaction(): bool 273 | { 274 | $this->checkRequiredActionParameters([ 275 | 'username', 276 | 'password', 277 | 'terminal_id', 278 | ]); 279 | 280 | if ($this->getTransaction()->isReadyForSettle() === false) { 281 | throw new SettlementException('shaparak::shaparak.could_not_settle_payment'); 282 | } 283 | 284 | try { 285 | $response = $this->generateComplementaryOperation(self::URL_SETTLEMENT); 286 | 287 | if ($response !== true) { 288 | throw new Exception('shaparak::asanpardakht.could_not_settlement_transaction'); 289 | } 290 | 291 | $this->getTransaction()->setSettled(true); 292 | 293 | return true; 294 | } catch (\Exception $e) { 295 | $this->log($e->getMessage(), [], 'error'); 296 | throw new SettlementException( 297 | 'settleTransaction: '.$e->getMessage().' #'.$e->getCode(), 298 | $e->getCode() 299 | ); 300 | } 301 | } 302 | 303 | /** 304 | * {@inheritDoc} 305 | * 306 | * @throws Exception 307 | * @throws RefundException 308 | */ 309 | public function refundTransaction(): bool 310 | { 311 | $this->checkRequiredActionParameters([ 312 | 'username', 313 | 'password', 314 | 'terminal_id', 315 | ]); 316 | if ($this->getTransaction()->isReadyForRefund() === false) { 317 | throw new RefundException(__('shaparak::shaparak.could_not_refund_payment')); 318 | } 319 | 320 | try { 321 | if ($this->getTransaction()->isReadyForReverse()) { 322 | $response = $this->generateComplementaryOperation(self::URL_REFUND); 323 | } elseif ($this->getTransaction()->isReadyForCancel()) { 324 | $response = $this->generateComplementaryOperation(self::URL_CANCEL); 325 | } else { 326 | throw new RefundException(__('shaparak::shaparak.could_not_refund_payment')); 327 | } 328 | 329 | if ($response !== true) { 330 | throw new Exception(__('shaparak::shaparak.refund_unknown_error')); 331 | } 332 | 333 | $this->getTransaction()->setRefunded(true); 334 | 335 | return true; 336 | } catch (\Exception $e) { 337 | $this->log($e->getMessage(), [], 'error'); 338 | 339 | throw new RefundException( 340 | 'refundTransaction: '.$e->getMessage().' #'.$e->getCode(), 341 | $e->getCode() 342 | ); 343 | } 344 | } 345 | 346 | /** 347 | * {@inheritDoc} 348 | */ 349 | public function canContinueWithCallbackParameters(): bool 350 | { 351 | try { 352 | $this->checkRequiredActionParameters([ 353 | 'ReturningParams', 354 | ]); 355 | } catch (\Exception $e) { 356 | return false; 357 | } 358 | 359 | if (! empty($this->getParameters('ReturningParams'))) { 360 | return $this->getTransactionResult(); 361 | } 362 | 363 | return false; 364 | } 365 | 366 | /** 367 | * {@inheritDoc} 368 | */ 369 | public function getGatewayReferenceId(): string 370 | { 371 | $this->checkRequiredActionParameters([ 372 | 'rrn', 373 | ]); 374 | 375 | return $this->getParameters('rrn'); 376 | } 377 | 378 | /** 379 | * @throws Exception 380 | */ 381 | protected function generateComplementaryOperation($method): bool 382 | { 383 | $response = $this->sendParamToAp( 384 | [ 385 | 'payGateTranId' => $this->getTransaction()->getCallbackParams()['payGateTranID'], 386 | 'merchantConfigurationId' => $this->getParameters('terminal_id'), 387 | ], 388 | $this->getUrlFor($method), 389 | self::POST_METHOD 390 | ); 391 | if ($response->successful()) { 392 | return true; 393 | } 394 | 395 | return false; 396 | } 397 | 398 | private function cumulativeData(): array 399 | { 400 | if ($this->getParameters('settlement_portions')) { 401 | return [ 402 | 'settlementPortions' => $this->getParameters('settlement_portions') 403 | ]; 404 | } 405 | return []; 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/Provider/Exception.php: -------------------------------------------------------------------------------- 1 | getTransaction(); 26 | 27 | if ($transaction->isReadyForTokenRequest() === false) { 28 | throw new Exception('transaction is not ready for requesting token from payment gateway'); 29 | } 30 | 31 | $this->checkRequiredActionParameters([ 32 | 'terminal_id', 33 | 'username', 34 | 'password', 35 | ]); 36 | 37 | $sendParams = [ 38 | 'terminalId' => (int) $this->getParameters('terminal_id'), 39 | 'userName' => $this->getParameters('username'), 40 | 'userPassword' => $this->getParameters('password'), 41 | 'orderId' => $this->getGatewayOrderId(), // get it from Transaction 42 | 'amount' => $this->getAmount(), 43 | 'localDate' => $this->getParameters('local_date', date('Ymd')), 44 | 'localTime' => $this->getParameters('local_time', date('His')), 45 | 'additionalData' => (string) $this->getParameters('additional_data'), 46 | 'callBackUrl' => $this->getCallbackUrl(), 47 | 'payerId' => (int) $this->getParameters('payer_id'), 48 | 'user_mobile' => '98' . substr($this->getParameters('user_mobile'), -10) 49 | ]; 50 | 51 | try { 52 | $soapClient = $this->getSoapClient(self::URL_TOKEN); 53 | 54 | if (config('shaparak.providers.mellat.cumulative')) { 55 | $response = $soapClient->bpCumulativeDynamicPayRequest($sendParams); 56 | } else { 57 | $response = $soapClient->bpPayRequest($sendParams); 58 | } 59 | 60 | if (isset($response->return)) { 61 | $response = explode(',', $response->return); 62 | 63 | if ((int) $response[0] === 0) { 64 | $this->getTransaction()->setGatewayToken($response[1]); // update transaction 65 | 66 | return $response[1]; 67 | } 68 | 69 | throw new Exception(sprintf('shaparak::mellat.error_%s', $response[0])); 70 | } 71 | 72 | throw new Exception('shaparak::shaparak.token_failed'); 73 | } catch (SoapFault $e) { 74 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 75 | } 76 | } 77 | 78 | /** 79 | * {@inheritDoc} 80 | * 81 | * @throws Exception 82 | */ 83 | public function getFormParameters(): array 84 | { 85 | return [ 86 | 'gateway' => 'mellat', 87 | 'method' => 'post', 88 | 'action' => $this->getUrlFor(self::URL_GATEWAY), 89 | 'parameters' => [ 90 | 'RefId' => $this->requestToken(), 91 | 'MobileNo' => $this->getParameters('user_mobile'), 92 | ], 93 | ]; 94 | } 95 | 96 | protected function getGatewayOrderIdFromCallBackParameters(): string 97 | { 98 | return (string) $this->getParameters('SaleOrderId'); 99 | } 100 | 101 | protected function callbackAbuseCheckList(): void 102 | { 103 | if (!( 104 | (string)$this->getTransaction()->gateway_order_id === $this->getGatewayOrderIdFromCallBackParameters() 105 | && (int)$this->getParameters('FinalAmount') === $this->getTransaction()->getPayableAmount() 106 | && (string)$this->getParameters('refID') === $this->getTransaction()->getGatewayToken() 107 | )) { 108 | throw new Exception('shaparak::shaparak.could_not_pass_abuse_checklist'); 109 | } 110 | } 111 | 112 | /** 113 | * {@inheritDoc} 114 | * 115 | * @throws Exception 116 | */ 117 | public function verifyTransaction(): bool 118 | { 119 | if ($this->getTransaction()->isReadyForVerify() === false) { 120 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 121 | } 122 | 123 | $this->checkRequiredActionParameters([ 124 | 'terminal_id', 125 | 'username', 126 | 'password', 127 | 'RefId', 128 | 'ResCode', 129 | 'SaleOrderId', 130 | 'SaleReferenceId', 131 | 'CardHolderInfo', 132 | 'CardHolderPan', 133 | ]); 134 | 135 | $this->callbackAbuseCheckList(); 136 | 137 | try { 138 | 139 | $sendParams = [ 140 | 'terminalId' => (int) $this->getParameters('terminal_id'), 141 | 'userName' => $this->getParameters('username'), 142 | 'userPassword' => $this->getParameters('password'), 143 | 'orderId' => (int) $this->getParameters('SaleOrderId'), // same as SaleOrderId 144 | 'saleOrderId' => (int) $this->getParameters('SaleOrderId'), 145 | 'saleReferenceId' => (int) $this->getParameters('SaleReferenceId'), 146 | ]; 147 | 148 | 149 | $soapClient = $this->getSoapClient(self::URL_VERIFY); 150 | 151 | $response = $soapClient->bpVerifyRequest($sendParams); 152 | 153 | if (isset($response->return)) { 154 | if ((int) $response->return !== 0) { 155 | throw new Exception(sprintf('shaparak::mellat.error_%s', $response->return)); 156 | } 157 | 158 | $this->getTransaction()->setCardNumber($this->getParameters('CardHolderPan')); 159 | $this->getTransaction()->setVerified(true); // save() 160 | 161 | return true; 162 | } 163 | 164 | throw new Exception('shaparak::shaparak.verify_failed'); 165 | } catch (SoapFault $e) { 166 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 167 | } 168 | } 169 | 170 | /** 171 | * @return string 172 | * 173 | * @throws Exception 174 | */ 175 | public function inquiryTransaction() :string 176 | { 177 | if ($this->getTransaction()->isReadyForInquiry() === false) { 178 | throw new Exception('shaparak::shaparak.could_not_inquiry_payment'); 179 | } 180 | 181 | $this->checkRequiredActionParameters([ 182 | 'terminal_id', 183 | 'username', 184 | 'password', 185 | 'RefId', 186 | 'ResCode', 187 | 'SaleOrderId', 188 | 'SaleReferenceId', 189 | 'CardHolderInfo', 190 | ]); 191 | 192 | $sendParams = [ 193 | 'terminalId' => (int) $this->getParameters('terminal_id'), 194 | 'userName' => $this->getParameters('username'), 195 | 'userPassword' => $this->getParameters('password'), 196 | 'orderId' => (int) $this->getParameters('SaleOrderId'), // same as SaleOrderId 197 | 'saleOrderId' => (int) $this->getParameters('SaleOrderId'), 198 | 'saleReferenceId' => (int) $this->getParameters('SaleReferenceId'), 199 | ]; 200 | 201 | try { 202 | $soapClient = $this->getSoapClient(self::URL_INQUIRY); 203 | 204 | $response = $soapClient->bpInquiryRequest($sendParams); 205 | 206 | if (isset($response->return)) { 207 | return $response->return; 208 | } 209 | 210 | throw new Exception('shaparak::shaparak.inquiry_failed'); 211 | } catch (SoapFault $e) { 212 | 213 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 214 | } 215 | } 216 | 217 | /** 218 | * Send settle request 219 | * 220 | * 221 | * @throws Exception 222 | */ 223 | public function settleTransaction(): bool 224 | { 225 | if ($this->getTransaction()->isReadyForSettle() === false) { 226 | throw new Exception('shaparak::shaparak.could_not_settle_payment'); 227 | } 228 | 229 | $this->checkRequiredActionParameters([ 230 | 'terminal_id', 231 | 'username', 232 | 'password', 233 | 'RefId', 234 | 'ResCode', 235 | 'SaleOrderId', 236 | 'SaleReferenceId', 237 | 'CardHolderInfo', 238 | ]); 239 | 240 | $sendParams = [ 241 | 'terminalId' => (int) $this->getParameters('terminal_id'), 242 | 'userName' => $this->getParameters('username'), 243 | 'userPassword' => $this->getParameters('password'), 244 | 'orderId' => (int) $this->getParameters('SaleOrderId'), // same as SaleOrderId 245 | 'saleOrderId' => (int) $this->getParameters('SaleOrderId'), 246 | 'saleReferenceId' => (int) $this->getParameters('SaleReferenceId'), 247 | ]; 248 | 249 | try { 250 | $soapClient = $this->getSoapClient(self::URL_SETTLE); 251 | 252 | $response = $soapClient->bpSettleRequest($sendParams); 253 | 254 | if (isset($response->return) && is_numeric($response->return)) { 255 | if ((int) $response->return === 0 || (int) $response->return === 45) { 256 | $this->getTransaction()->setSettled(true); 257 | 258 | return true; 259 | } 260 | 261 | throw new Exception(sprintf('shaparak::mellat.error_%s', $response->return)); 262 | } 263 | 264 | throw new Exception('shaparak::shaparak.invalid_response'); 265 | } catch (SoapFault $e) { 266 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 267 | } 268 | 269 | } 270 | 271 | /** 272 | * {@inheritDoc} 273 | * 274 | * @throws Exception 275 | */ 276 | public function refundTransaction(): bool 277 | { 278 | if ($this->getTransaction()->isReadyForRefund() === false) { 279 | throw new Exception('shaparak::shaparak.could_not_refund_payment'); 280 | } 281 | 282 | $this->checkRequiredActionParameters([ 283 | 'terminal_id', 284 | 'username', 285 | 'password', 286 | 'RefId', 287 | 'ResCode', 288 | 'SaleOrderId', 289 | 'SaleReferenceId', 290 | 'CardHolderInfo', 291 | ]); 292 | 293 | try { 294 | $sendParams = [ 295 | 'terminalId' => (int) $this->getParameters('terminal_id'), 296 | 'userName' => $this->getParameters('username'), 297 | 'userPassword' => $this->getParameters('password'), 298 | 'orderId' => (int) $this->getParameters('SaleOrderId'), // same as SaleOrderId 299 | 'saleOrderId' => (int) $this->getParameters('SaleOrderId'), 300 | 'saleReferenceId' => (int) $this->getParameters('SaleReferenceId'), 301 | ]; 302 | 303 | $soapClient = $this->getSoapClient(self::URL_REFUND); 304 | 305 | $response = $soapClient->bpReversalRequest($sendParams); 306 | 307 | if (isset($response->return) && is_numeric($response->return)) { 308 | if ((int) $response->return === 0 || (int) $response->return === 45) { 309 | $this->getTransaction()->setRefunded(true); 310 | 311 | return true; 312 | } 313 | 314 | throw new Exception('shaparak::mellat.error_'. $response->return); 315 | } 316 | 317 | throw new Exception('shaparak::mellat.errors.invalid_response'); 318 | } catch (SoapFault $e) { 319 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 320 | } 321 | } 322 | 323 | /** 324 | * {@inheritDoc} 325 | */ 326 | public function canContinueWithCallbackParameters(): bool 327 | { 328 | try { 329 | $this->checkRequiredActionParameters([ 330 | 'RefId', 331 | 'ResCode', 332 | 'SaleOrderId', 333 | 'SaleReferenceId', 334 | 'CardHolderInfo', 335 | ]); 336 | } catch (\Exception $e) { 337 | return false; 338 | } 339 | 340 | if (! empty($this->getParameters('RefId'))) { 341 | return true; 342 | } 343 | 344 | return false; 345 | } 346 | 347 | /** 348 | * {@inheritDoc} 349 | * 350 | * @throws Exception 351 | */ 352 | public function getGatewayReferenceId(): string 353 | { 354 | $this->checkRequiredActionParameters([ 355 | 'RefId', 356 | ]); 357 | 358 | return $this->getParameters('RefId'); 359 | } 360 | 361 | /** 362 | * {@inheritDoc} 363 | */ 364 | public function getUrlFor(string $action): string 365 | { 366 | if ($this->environment === 'production') { 367 | return match ($action) { 368 | self::URL_GATEWAY => 'https://bpm.shaparak.ir/pgwchannel/startpay.mellat', 369 | default => 'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl', 370 | }; 371 | } 372 | 373 | return match ($action) { 374 | self::URL_GATEWAY => $this->bankTestBaseUrl . '/mellat/bpm.shaparak.ir/pgwchannel/startpay.mellat', 375 | default => $this->bankTestBaseUrl . '/mellat/bpm.shaparak.ir/pgwchannel/services/pgw?wsdl', 376 | }; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/Provider/MelliProvider.php: -------------------------------------------------------------------------------- 1 | getTransaction(); 17 | 18 | if ($transaction->isReadyForTokenRequest() === false) { 19 | throw new Exception('transaction is not ready for requesting token from payment gateway'); 20 | } 21 | 22 | $this->checkRequiredActionParameters([ 23 | 'terminal_id', 24 | 'merchant_id', 25 | 'transaction_key', 26 | ]); 27 | 28 | $terminalId = $this->getParameters('terminal_id'); 29 | $amount = $this->getAmount(); 30 | $key = $this->getParameters('transaction_key'); 31 | $orderId = $this->getGatewayOrderId(); 32 | 33 | $response = Http::acceptJson() 34 | ->throw() 35 | ->post($this->getUrlFor(self::URL_TOKEN), [ 36 | 'TerminalId' => $terminalId, 37 | 'MerchantId' => $this->getParameters('merchant_id'), 38 | 'Amount' => $amount, 39 | 'SignData' => $this->encryptPKCS7("{$terminalId};{$orderId};{$amount}", "{$key}"), 40 | 'ReturnUrl' => $this->getCallbackUrl(), 41 | 'LocalDateTime' => date('m/d/Y g:i:s a'), 42 | 'OrderId' => $orderId, 43 | ]); 44 | 45 | $resCode = $response->json('ResCode'); 46 | if (is_numeric($resCode) && (int) $resCode === 0) { 47 | $transaction->setGatewayToken($response->json('Token'), true); // update transaction 48 | 49 | return $response->json('Token'); 50 | } 51 | 52 | throw new Exception('shaparak::shaparak.token_failed'); 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | * 58 | * @throws Exception 59 | */ 60 | public function getFormParameters(): array 61 | { 62 | return [ 63 | 'gateway' => 'melli', 64 | 'method' => 'get', 65 | 'action' => $this->getUrlFor(self::URL_GATEWAY), 66 | 'parameters' => [ 67 | 'Token' => $this->requestToken(), 68 | ], 69 | ]; 70 | } 71 | 72 | protected function getGatewayOrderIdFromCallBackParameters(): string 73 | { 74 | return (string) $this->getParameters('OrderId'); 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | * 80 | * @throws Exception 81 | */ 82 | public function verifyTransaction(): bool 83 | { 84 | if ($this->getTransaction()->isReadyForVerify() === false) { 85 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 86 | } 87 | 88 | $this->checkRequiredActionParameters([ 89 | 'terminal_id', 90 | 'merchant_id', 91 | 'transaction_key', 92 | 'OrderId', 93 | 'Token', 94 | 'ResCode', 95 | ]); 96 | 97 | if ((int) $this->getParameters('ResCode') !== 0) { 98 | throw new Exception('could not verify transaction with ResCode: '.$this->getParameters('ResCode')); 99 | } 100 | 101 | $this->callbackAbuseCheckList(); 102 | 103 | $key = $this->getParameters('transaction_key'); 104 | $token = $this->getParameters('Token'); 105 | 106 | $signature = $this->encryptPKCS7($token, $key); 107 | 108 | $response = Http::acceptJson() 109 | ->throw() 110 | ->post($this->getUrlFor(self::URL_VERIFY), [ 111 | 'Token' => $token, 112 | 'SignData' => $signature, 113 | ]); 114 | 115 | $resCode = $response->json('ResCode'); 116 | if (is_numeric($resCode) && (int) $resCode === 0 && (int) $response->json('Amount') === $this->getAmount()) {// got string token 117 | foreach ($response->json() as $k => $v) { 118 | $this->getTransaction()->addExtra($k, $v, false); 119 | } 120 | //$this->getTransaction()->setCardNumber($this->getParameters('accNoVal', @$response->accNoVal), false); 121 | $this->getTransaction()->setVerified(true); 122 | 123 | return true; 124 | } 125 | 126 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 127 | } 128 | 129 | /** 130 | * @throws Exception 131 | */ 132 | public function refundTransaction(): bool 133 | { 134 | // TODO: Implement refundTransaction() method. 135 | throw new Exception('melli gateway does not support refund action right now'); 136 | } 137 | 138 | /** 139 | * {@inheritDoc} 140 | */ 141 | public function canContinueWithCallbackParameters(): bool 142 | { 143 | try { 144 | $this->checkRequiredActionParameters([ 145 | 'OrderId', 146 | 'Token', 147 | 'ResCode', 148 | ]); 149 | } catch (\Exception $e) { 150 | return false; 151 | } 152 | 153 | return (int) $this->getParameters('ResCode') === 0; 154 | } 155 | 156 | /** 157 | * {@inheritDoc} 158 | */ 159 | public function getGatewayReferenceId(): string 160 | { 161 | $this->checkRequiredActionParameters([ 162 | 'Token', 163 | ]); 164 | 165 | return $this->getParameters('Token'); 166 | } 167 | 168 | /** 169 | * {@inheritDoc} 170 | */ 171 | public function getUrlFor(string $action = null): string 172 | { 173 | if ($this->environment === 'production') { 174 | switch ($action) { 175 | case self::URL_GATEWAY: 176 | 177 | return 'https://sadad.shaparak.ir/VPG/Purchase'; 178 | 179 | case self::URL_TOKEN: 180 | 181 | return 'https://sadad.shaparak.ir/vpg/api/v0/Request/PaymentRequest'; 182 | 183 | case self::URL_VERIFY: 184 | 185 | return 'https://sadad.shaparak.ir/vpg/api/v0/Advice/Verify'; 186 | 187 | } 188 | } else { 189 | switch ($action) { 190 | case self::URL_GATEWAY: 191 | 192 | return $this->bankTestBaseUrl.'/melli/sadad.shaparak.ir/VPG/Purchase'; 193 | 194 | case self::URL_TOKEN: 195 | 196 | return $this->bankTestBaseUrl.'/melli/sadad.shaparak.ir/VPG/api/v0/Request/PaymentRequest'; 197 | 198 | case self::URL_VERIFY: 199 | 200 | return $this->bankTestBaseUrl.'/melli/sadad.shaparak.ir/VPG/api/v0/Advice/Verify'; 201 | 202 | } 203 | } 204 | 205 | throw new Exception("could not find url for {$action} action"); 206 | } 207 | 208 | /** 209 | * Create sign data based on (Tripledes(ECB,PKCS7)) algorithm 210 | * 211 | * @param string $str data 212 | * @param string $key key 213 | */ 214 | protected function encryptPKCS7(string $str, string $key): string 215 | { 216 | $key = base64_decode($key); 217 | $cipherText = openssl_encrypt($str, 'DES-EDE3', $key, OPENSSL_RAW_DATA); 218 | 219 | return base64_encode($cipherText); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Provider/ParsianProvider.php: -------------------------------------------------------------------------------- 1 | getTransaction(); 23 | 24 | if ($transaction->isReadyForTokenRequest() === false) { 25 | throw new Exception('transaction is not ready for requesting token from payment gateway'); 26 | } 27 | 28 | $this->checkRequiredActionParameters([ 29 | 'pin', 30 | ]); 31 | 32 | $sendParams = [ 33 | 'LoginAccount' => $this->getParameters('pin'), 34 | 'Amount' => $transaction->getPayableAmount(), 35 | 'OrderId' => $transaction->getGatewayOrderId(), 36 | 'CallBackUrl' => $this->getCallbackUrl(), 37 | 'AdditionalData' => (string) $this->getParameters('additional_date'), 38 | ]; 39 | 40 | try { 41 | $soapClient = $this->getSoapClient(self::URL_SALE); 42 | 43 | $response = $soapClient->SalePaymentRequest(['requestData' => $sendParams]); 44 | 45 | if (isset($response->SalePaymentRequestResult, 46 | $response->SalePaymentRequestResult->Status, 47 | $response->SalePaymentRequestResult->Token)) { 48 | if ((int) $response->SalePaymentRequestResult->Status === 0) { 49 | $this->log("fetched token from gateway: {$response->SalePaymentRequestResult->Token}"); 50 | $this->getTransaction()->setGatewayToken($response->SalePaymentRequestResult->Token); 51 | 52 | return $response->SalePaymentRequestResult->Token; 53 | } 54 | 55 | throw new Exception(sprintf('shaparak::parsian.error_%s', 56 | $response->SalePaymentRequestResult->Status)); 57 | } 58 | 59 | throw new Exception('shaparak::shaparak.token_failed'); 60 | } catch (SoapFault $e) { 61 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 62 | } 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | * 68 | * @throws Exception 69 | */ 70 | public function getFormParameters(): array 71 | { 72 | $token = $this->requestToken(); 73 | 74 | return [ 75 | 'gateway' => 'parsian', 76 | 'method' => 'get', 77 | 'action' => $this->getUrlFor(self::URL_GATEWAY), 78 | 'parameters' => [ 79 | 'token' => $token, 80 | ], 81 | ]; 82 | } 83 | 84 | protected function getGatewayOrderIdFromCallBackParameters(): string 85 | { 86 | return (string) $this->getParameters('OrderId'); 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | * 92 | * @throws Exception 93 | */ 94 | public function verifyTransaction(): bool 95 | { 96 | if ($this->getTransaction()->isReadyForVerify() === false) { 97 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 98 | } 99 | 100 | $this->checkRequiredActionParameters([ 101 | 'pin', 102 | 'Token', 103 | 'Status', 104 | ]); 105 | 106 | $this->callbackAbuseCheckList(); 107 | 108 | if ((int) $this->getParameters('Status') !== 0) { 109 | throw new Exception( 110 | 'could not verify transaction with callback state: '.$this->getParameters('Status') 111 | ); 112 | } 113 | 114 | try { 115 | $sendParams = [ 116 | 'LoginAccount' => $this->getParameters('pin'), 117 | 'Token' => $this->getParameters('Token'), 118 | ]; 119 | 120 | $soapClient = $this->getSoapClient(self::URL_CONFIRM); 121 | 122 | $response = $soapClient->ConfirmPayment(['requestData' => $sendParams]); 123 | 124 | if (isset($response->ConfirmPaymentResult, $response->ConfirmPaymentResult->Status)) { 125 | if ((int) $response->ConfirmPaymentResult->Status === 0) { 126 | //$this->getTransaction()->setCardNumber($this->getParameters('CardNumberMasked'), false); // no save() 127 | $this->getTransaction()->setVerified(true); // save() 128 | 129 | return true; 130 | } 131 | 132 | throw new Exception( 133 | sprintf('shaparak::parsian.error_%s', $response->ConfirmPaymentResult->Status) 134 | ); 135 | } 136 | 137 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 138 | } catch (SoapFault $e) { 139 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 140 | } 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | * 146 | * @throws Exception 147 | */ 148 | public function refundTransaction(): bool 149 | { 150 | if ($this->getTransaction()->isReadyForRefund() === false) { 151 | throw new Exception('shaparak::shaparak.could_not_refund_payment'); 152 | } 153 | 154 | $this->checkRequiredActionParameters([ 155 | 'pin', 156 | 'Token', 157 | ]); 158 | 159 | try { 160 | $sendParams = [ 161 | 'LoginAccount' => $this->getParameters('pin'), 162 | 'Token' => $this->getParameters('Token'), 163 | ]; 164 | 165 | $soapClient = $this->getSoapClient(self::URL_REFUND); 166 | 167 | $response = $soapClient->ReversalRequest(['requestData' => $sendParams]); 168 | 169 | if (isset($response->ReversalRequestResult, $response->ReversalRequestResult->Status)) { 170 | if ((int) $response->ReversalRequestResult->Status === 0) { 171 | $this->getTransaction()->setRefunded(true); 172 | 173 | return true; 174 | } 175 | 176 | throw new Exception( 177 | sprintf('shaparak::parsian.error_%s', $response->ReversalRequestResult->Status) 178 | ); 179 | } 180 | 181 | throw new Exception('larapay::parsian.errors.invalid_response'); 182 | } catch (SoapFault $e) { 183 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 184 | } 185 | } 186 | 187 | /** 188 | * {@inheritDoc} 189 | */ 190 | public function canContinueWithCallbackParameters(): bool 191 | { 192 | try { 193 | $this->checkRequiredActionParameters([ 194 | 'Token', 195 | 'Status', 196 | ]); 197 | } catch (\Exception $e) { 198 | return false; 199 | } 200 | 201 | return (int) $this->getParameters('Status') === 0; 202 | } 203 | 204 | /** 205 | * {@inheritDoc} 206 | * 207 | * @throws Exception 208 | */ 209 | public function getGatewayReferenceId(): string 210 | { 211 | $this->checkRequiredActionParameters([ 212 | 'RRN', 213 | ]); 214 | 215 | return $this->getParameters('RRN'); 216 | } 217 | 218 | /** 219 | * {@inheritDoc} 220 | */ 221 | public function getUrlFor(string $action): string 222 | { 223 | if ($this->environment === 'production') { 224 | switch ($action) { 225 | case self::URL_GATEWAY: 226 | 227 | return 'https://pec.shaparak.ir/NewIPG/'; 228 | 229 | case self::URL_SALE: 230 | 231 | return 'https://pec.shaparak.ir/NewIPGServices/Sale/SaleService.asmx?WSDL'; 232 | 233 | case self::URL_CONFIRM: 234 | 235 | return 'https://pec.shaparak.ir/NewIPGServices/Confirm/ConfirmService.asmx?WSDL'; 236 | 237 | case self::URL_REFUND: 238 | 239 | return 'https://pec.shaparak.ir/NewIPGServices/Reverse/ReversalService.asmx?WSDL'; 240 | 241 | case self::URL_MULTIPLEX: 242 | 243 | return 'https://pec.shaparak.ir/NewIPGServices/MultiplexedSale/OnlineMultiplexedSalePaymentService.asmx?WSDL'; 244 | 245 | } 246 | } else { 247 | switch ($action) { 248 | case self::URL_GATEWAY: 249 | 250 | return $this->bankTestBaseUrl.'/parsian/pec.shaparak.ir/NewIPG'; 251 | 252 | case self::URL_SALE: 253 | 254 | return $this->bankTestBaseUrl.'/parsian/pec.shaparak.ir/NewIPGServices/Sale/SaleService.asmx?wsdl'; 255 | 256 | case self::URL_CONFIRM: 257 | 258 | return $this->bankTestBaseUrl.'/parsian/pec.shaparak.ir/NewIPGServices/Confirm/ConfirmService.asmx?wsdl'; 259 | 260 | case self::URL_REFUND: 261 | 262 | return $this->bankTestBaseUrl.'/parsian/pec.shaparak.ir/NewIPGServices/Reverse/ReversalService.asmx?wsdl'; 263 | 264 | case self::URL_MULTIPLEX: 265 | 266 | return $this->bankTestBaseUrl.'/parsian/pec.shaparak.ir/NewIPGServices/MultiplexedSale/OnlineMultiplexedSalePaymentService.asmx?wsdl'; 267 | 268 | } 269 | } 270 | throw new Exception("could not find url for {$action} action"); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/Provider/PasargadProvider.php: -------------------------------------------------------------------------------- 1 | checkRequiredActionParameters([ 29 | 'terminal_id', 30 | 'merchant_id', 31 | 'certificate_path', 32 | ]); 33 | 34 | return [ 35 | 'gateway' => 'pasargad', 36 | 'method' => 'GET', 37 | 'action' => $this->getUrlFor(self::URL_GATEWAY), 38 | 'parameters' => [ 39 | 'n' => $this->requestToken(), 40 | ], 41 | ]; 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | * 47 | * @throws Exception 48 | */ 49 | public function requestToken(): string 50 | { 51 | $this->log(__METHOD__); 52 | $transaction = $this->getTransaction(); 53 | 54 | if ($transaction->isReadyForTokenRequest() === false) { 55 | throw new Exception('transaction is not ready for requesting token from payment gateway'); 56 | } 57 | 58 | $this->checkRequiredActionParameters([ 59 | 'terminal_id', 60 | 'merchant_id', 61 | 'certificate_path', 62 | ]); 63 | 64 | $response = $this->callApi($this->getUrlFor(self::URL_TOKEN), [ 65 | 'invoicenumber' => $this->getGatewayOrderId(), 66 | 'invoiceDate' => date('Y/m/d H:i:s', strtotime($this->getTransaction()->created_at)), 67 | 'amount' => $this->getAmount(), 68 | 'terminalCode' => $this->getParameters('terminal_id'), 69 | 'merchantCode' => $this->getParameters('merchant_id'), 70 | 'redirectAddress' => $this->getCallbackUrl(), 71 | 'timeStamp' => date('Y/m/d H:i:s'), 72 | 'action' => self::ACTION_GET_TOKEN, 73 | 'subpaymentlist' => $this->getParameters('subpaymentlist'), 74 | ]); 75 | 76 | $this->log(__METHOD__, $response); 77 | 78 | return $response['Token']; 79 | } 80 | 81 | protected function getGatewayOrderIdFromCallBackParameters(): string 82 | { 83 | return (string) $this->getParameters('iN'); 84 | } 85 | 86 | /** 87 | * {@inheritDoc} 88 | * @throws Exception 89 | */ 90 | public function verifyTransaction(): bool 91 | { 92 | $this->log(__METHOD__); 93 | 94 | if ($this->getTransaction()->isReadyForVerify() === false) { 95 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 96 | } 97 | 98 | $this->checkRequiredActionParameters([ 99 | 'terminal_id', 100 | 'merchant_id', 101 | 'certificate_path', 102 | 'iN', 103 | 'iD', 104 | 'tref', 105 | ]); 106 | 107 | $this->callbackAbuseCheckList(); 108 | 109 | // update transaction reference number 110 | if (! empty($this->getParameters('tref'))) { 111 | $this->getTransaction()->setGatewayToken( 112 | $this->getParameters('tref'), 113 | true 114 | ); // update transaction 115 | } else { 116 | throw new Exception('could not verify transaction without tref'); 117 | } 118 | 119 | $response = $this->callApi($this->getUrlFor(self::URL_VERIFY), [ 120 | 'merchantCode' => $this->getParameters('merchant_id'), 121 | 'terminalCode' => $this->getParameters('terminal_id'), 122 | 'invoiceNumber' => $this->getParameters('iN'), 123 | 'invoiceDate' => $this->getParameters('iD'), 124 | 'amount' => $this->getAmount(), 125 | 'timeStamp' => date('Y/m/d H:i:s'), 126 | ]); 127 | 128 | $this->log(__METHOD__, $response); 129 | 130 | if ($response['IsSuccess'] === true) { 131 | $this->getTransaction()->setCardNumber($response['MaskedCardNumber'], false); // no save() 132 | $this->getTransaction()->addExtra('HashedCardNumber', $response['HashedCardNumber'], false); 133 | $this->getTransaction()->addExtra('ShaparakRefNumber', $response['ShaparakRefNumber'], false); 134 | $this->getTransaction()->setVerified(); 135 | 136 | return true; 137 | } 138 | 139 | throw new Exception('shaparak::shaparak.verify_failed'); 140 | } 141 | 142 | /** 143 | * {@inheritDoc} 144 | * @throws Exception 145 | */ 146 | public function refundTransaction(): bool 147 | { 148 | $this->log(__METHOD__); 149 | 150 | if ($this->getTransaction()->isReadyForRefund() === false) { 151 | throw new Exception('shaparak::shaparak.could_not_refund_transaction'); 152 | } 153 | 154 | $this->checkRequiredActionParameters([ 155 | 'terminal_id', 156 | 'merchant_id', 157 | 'certificate_path', 158 | 'iN', 159 | 'iD', 160 | 'tref', 161 | ]); 162 | 163 | $response = $this->callApi($this->getUrlFor(self::URL_REFUND), [ 164 | 'invoiceNumber' => $this->getParameters('iN'), 165 | 'invoiceDate' => $this->getParameters('iD'), 166 | 'terminalCode' => $this->getParameters('terminal_id'), 167 | 'merchantCode' => $this->getParameters('merchant_id'), 168 | 'timeStamp' => date('Y/m/d H:i:s'), 169 | ]); 170 | 171 | $this->log(__METHOD__, $response); 172 | 173 | if ($response['IsSuccess'] === true) { 174 | $this->getTransaction()->setRefunded(); 175 | 176 | return true; 177 | } 178 | 179 | throw new Exception('shaparak::shaparak.refund_failed'); 180 | } 181 | 182 | private function callApi(string $url, array $body, $method = 'post'): array 183 | { 184 | $sign = $this->sign(json_encode($body)); 185 | 186 | $this->log("callApi({$url}) Sign: {$sign}", $body); 187 | 188 | $response = Http::contentType('application/json') 189 | ->acceptJson() 190 | ->withHeaders([ 191 | 'Sign' => $sign, 192 | ]) 193 | ->throw() 194 | ->{$method}($url, $body); 195 | 196 | $this->log($response->json('Message'), $response->json()); 197 | 198 | if ($response->json('IsSuccess') === false) { 199 | throw new Exception(($response->json('Message') ?? 'Invalid Pasargad response!')); 200 | } 201 | 202 | return $response->json(); 203 | } 204 | 205 | private function sign(string $string): string 206 | { 207 | $string = sha1($string, true); 208 | $string = $this->getProcessor()->sign($string); // digital signature 209 | 210 | return base64_encode($string); // base64_encode 211 | } 212 | 213 | /** 214 | * @param string|null $certificatePath 215 | * @return RSAProcessor 216 | */ 217 | private function getProcessor(string $certificatePath = null): RSAProcessor 218 | { 219 | $path = $certificatePath ?: $this->getParameters('certificate_path'); 220 | 221 | return new RSAProcessor($path, RSAKeyType::XMLFile); 222 | } 223 | 224 | /** 225 | * {@inheritDoc} 226 | */ 227 | public function canContinueWithCallbackParameters(): bool 228 | { 229 | try { 230 | $this->checkRequiredActionParameters([ 231 | 'iN', 232 | 'iD', 233 | 'tref', 234 | ]); 235 | } catch (\Exception $e) { 236 | return false; 237 | } 238 | 239 | if (! empty($this->getParameters('tref'))) { 240 | return true; 241 | } 242 | 243 | return false; 244 | } 245 | 246 | /** 247 | * {@inheritDoc} 248 | */ 249 | public function getGatewayReferenceId(): string 250 | { 251 | $this->checkRequiredActionParameters([ 252 | 'tref', 253 | ]); 254 | 255 | return $this->getParameters('tref'); 256 | } 257 | 258 | /** 259 | * {@inheritDoc} 260 | */ 261 | public function getUrlFor(string $action): string 262 | { 263 | if ($this->environment === 'production') { 264 | switch ($action) { 265 | case self::URL_GATEWAY: 266 | 267 | return 'https://pep.shaparak.ir/gateway.aspx'; 268 | 269 | case self::URL_TOKEN: 270 | 271 | return 'https://pep.shaparak.ir/Api/v1/Payment/GetToken'; 272 | 273 | case self::URL_CHECK: 274 | 275 | return 'https://pep.shaparak.ir/Api/v1/Payment/CheckTransactionResult'; 276 | 277 | case self::URL_VERIFY: 278 | 279 | return 'https://pep.shaparak.ir/Api/v1/Payment/VerifyPayment'; 280 | 281 | case self::URL_REFUND: 282 | 283 | return 'https://pep.shaparak.ir/Api/v1/Payment/RefundPayment'; 284 | 285 | case self::UPDATE_SUBPAYMENT: 286 | 287 | return 'https://pep.shaparak.ir/Api/v1/Payment/UpdateInvoiceSubPayment'; 288 | 289 | case self::GET_SUBPAYMENT: 290 | 291 | return 'https://pep.shaparak.ir/Api/v1/Payment/GetSubPaymentsReport'; 292 | 293 | } 294 | } else { 295 | switch ($action) { 296 | case self::URL_GATEWAY: 297 | 298 | return $this->bankTestBaseUrl.'/pasargad/pep.shaparak.ir/gateway.aspx'; 299 | 300 | case self::URL_TOKEN: 301 | 302 | return $this->bankTestBaseUrl.'/pasargad/pep.shaparak.ir/Api/v1/Payment/GetToken'; 303 | 304 | case self::URL_CHECK: 305 | 306 | return $this->bankTestBaseUrl.'/pasargad/pep.shaparak.ir/Api/v1/Payment/CheckTransactionResult'; 307 | 308 | case self::URL_VERIFY: 309 | 310 | return $this->bankTestBaseUrl.'/pasargad/pep.shaparak.ir/Api/v1/Payment/VerifyPayment'; 311 | 312 | case self::URL_REFUND: 313 | 314 | return $this->bankTestBaseUrl.'/pasargad/pep.shaparak.ir/Api/v1/Payment/RefundPayment'; 315 | 316 | case self::UPDATE_SUBPAYMENT: 317 | 318 | return $this->bankTestBaseUrl.'/pasargad/pep.shaparak.ir/Api/v1/Payment/UpdateInvoiceSubPayment'; 319 | 320 | case self::GET_SUBPAYMENT: 321 | 322 | return $this->bankTestBaseUrl.'/pasargad/pep.shaparak.ir/Api/v1/Payment/GetSubPaymentsReport'; 323 | 324 | } 325 | } 326 | throw new Exception("could not find url for {$action} action"); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/Provider/SaderatProvider.php: -------------------------------------------------------------------------------- 1 | 'saderat', 19 | 'method' => 'post', 20 | 'action' => $this->getUrlFor(self::URL_GATEWAY), 21 | 'parameters' => [ 22 | 'token' => $this->requestToken(), 23 | 'TerminalID' => $this->getParameters('terminal_id'), 24 | ], 25 | ]; 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public function getUrlFor(string $action = null): string 32 | { 33 | if ($this->environment === 'production') { 34 | switch ($action) { 35 | case self::URL_GATEWAY: 36 | 37 | return 'https://sepehr.shaparak.ir:8080/Pay'; 38 | 39 | case self::URL_TOKEN: 40 | 41 | return 'https://sepehr.shaparak.ir:8081/V1/PeymentApi/GetToken'; 42 | 43 | case self::URL_VERIFY: 44 | 45 | return 'https://sepehr.shaparak.ir:8081/V1/PeymentApi/Advice'; 46 | 47 | case self::URL_REFUND: 48 | 49 | return 'https://sepehr.shaparak.ir:8081/V1/PeymentApi/Rollback'; 50 | 51 | } 52 | } else { 53 | switch ($action) { 54 | case self::URL_GATEWAY: 55 | 56 | return $this->bankTestBaseUrl.'/saderat/sepehr.shaparak.ir/Pay'; 57 | 58 | case self::URL_TOKEN: 59 | 60 | return $this->bankTestBaseUrl.'/saderat/sepehr.shaparak.ir/V1/PeymentApi/GetToken'; 61 | 62 | case self::URL_VERIFY: 63 | 64 | return $this->bankTestBaseUrl.'/saderat/sepehr.shaparak.ir/V1/PeymentApi/Advice'; 65 | 66 | case self::URL_REFUND: 67 | 68 | return $this->bankTestBaseUrl.'/saderat/sepehr.shaparak.ir/V1/PeymentApi/Rollback'; 69 | 70 | } 71 | } 72 | throw new Exception("could not find url for {$action} action"); 73 | } 74 | 75 | /** 76 | * {@inheritDoc} 77 | * 78 | * @throws Exception 79 | * @throws JsonException 80 | */ 81 | protected function requestToken(): string 82 | { 83 | $this->log(__METHOD__); 84 | 85 | if ($this->getTransaction()->isReadyForTokenRequest() === false) { 86 | throw new Exception('transaction is not ready for requesting token from payment gateway'); 87 | } 88 | 89 | $this->checkRequiredActionParameters([ 90 | 'terminal_id', 91 | ]); 92 | 93 | $response = Http::acceptJson() 94 | ->throw() 95 | ->post($this->getUrlFor(self::URL_TOKEN), [ 96 | 'Amount' => $this->getAmount(), 97 | 'callbackUrl' => $this->getCallbackUrl(), 98 | 'invoiceID' => $this->getGatewayOrderId(), 99 | 'terminalID' => $this->getParameters('terminal_id'), 100 | ]); 101 | 102 | if ($response->json('Status') === 0) { 103 | // got string token 104 | $this->getTransaction()->setGatewayToken( 105 | $response['Accesstoken'], 106 | true 107 | ); // update transaction 108 | 109 | return $response->json('Accesstoken'); 110 | } 111 | 112 | throw new Exception('shaparak::shaparak.token_failed'); 113 | } 114 | 115 | protected function getGatewayOrderIdFromCallBackParameters(): string 116 | { 117 | return (string) $this->getParameters('InvoiceId'); 118 | } 119 | 120 | /** 121 | * {@inheritDoc} 122 | */ 123 | public function verifyTransaction(): bool 124 | { 125 | if ($this->getTransaction()->isReadyForVerify() === false) { 126 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 127 | } 128 | 129 | $this->checkRequiredActionParameters([ 130 | 'terminal_id', 131 | 'RespCode', 132 | 'RespMsg', 133 | 'Amount', 134 | 'InvoiceId', 135 | 'TerminalId', 136 | 'TraceNumber', 137 | 'DatePaid', 138 | 'DigitalReceipt', 139 | 'IssuerBank', 140 | 'CardNumber', 141 | ]); 142 | 143 | $this->callbackAbuseCheckList(); 144 | 145 | $response = Http::acceptJson() 146 | ->throw() 147 | ->post($this->getUrlFor(self::URL_VERIFY), [ 148 | 'digitalreceipt' => $this->getParameters('DigitalReceipt'), 149 | 'Tid' => $this->getParameters('terminal_id'), 150 | ]); 151 | 152 | if (in_array($response->json('Status'), ['OK', 'Duplicate'])) { 153 | if ((int) $response->json('ReturnId') === $this->transaction->getPayableAmount()) { 154 | return $this->getTransaction()->setVerified(); 155 | } 156 | 157 | throw new Exception('shaparak::shaparak.amounts_not_match'); 158 | } 159 | 160 | throw new Exception('shaparak::shaparak.verify_failed'); 161 | } 162 | 163 | /** 164 | * {@inheritDoc} 165 | */ 166 | public function refundTransaction(): bool 167 | { 168 | if ($this->getTransaction()->isReadyForRefund() == false) { 169 | throw new Exception('shaparak::shaparak.could_not_refund_transaction'); 170 | } 171 | 172 | $this->checkRequiredActionParameters([ 173 | 'terminal_id', 174 | 'RespCode', 175 | 'RespMsg', 176 | 'Amount', 177 | 'InvoiceId', 178 | 'TerminalId', 179 | 'TraceNumber', 180 | 'DatePaid', 181 | 'DigitalReceipt', 182 | 'IssuerBank', 183 | 'CardNumber', 184 | ]); 185 | 186 | $response = Http::acceptJson() 187 | ->throw() 188 | ->post($this->getUrlFor(self::URL_REFUND), [ 189 | 'digitalreceipt' => $this->getParameters('DigitalReceipt'), 190 | 'Tid' => $this->getParameters('terminal_id'), 191 | ]); 192 | 193 | if (in_array($response->json('Status'), ['OK', 'Duplicate'])) { 194 | if ((int) $response->json('ReturnId') === $this->transaction->getPayableAmount()) { 195 | return $this->getTransaction()->setRefunded(); 196 | } 197 | 198 | throw new Exception('shaparak::shaparak.amounts_not_match'); 199 | } 200 | 201 | throw new Exception('shaparak::shaparak.refund_failed'); 202 | } 203 | 204 | /** 205 | * {@inheritDoc} 206 | */ 207 | public function canContinueWithCallbackParameters(): bool 208 | { 209 | try { 210 | $this->checkRequiredActionParameters([ 211 | 'RespCode', 212 | ]); 213 | } catch (\Exception $e) { 214 | return false; 215 | } 216 | 217 | $respCode = $this->getParameters('RespCode'); 218 | 219 | return is_numeric($respCode) && (int) $respCode === 0; 220 | } 221 | 222 | /** 223 | * {@inheritDoc} 224 | */ 225 | public function getGatewayReferenceId(): string 226 | { 227 | $this->checkRequiredActionParameters([ 228 | 'RRN', 229 | ]); 230 | 231 | return $this->getParameters('RRN'); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/Provider/SamanProvider.php: -------------------------------------------------------------------------------- 1 | getTransaction(); 19 | 20 | if ($transaction->isReadyForTokenRequest() === false) { 21 | throw new Exception('transaction is not ready for requesting token from payment gateway'); 22 | } 23 | 24 | $this->checkRequiredActionParameters([ 25 | 'terminal_id', 26 | ]); 27 | 28 | $sendParams = [ 29 | 'TermID' => $this->getParameters('terminal_id'), 30 | 'ResNum' => $this->getGatewayOrderId(), // get it from Transaction 31 | 'TotalAmount' => $this->getAmount(), 32 | ]; 33 | 34 | try { 35 | $soapClient = $this->getSoapClient(self::URL_TOKEN); 36 | 37 | $response = $soapClient->__soapCall('RequestToken', $sendParams); 38 | 39 | if (! empty($response)) { 40 | $token = trim($response); 41 | if (strlen($token) >= 20) { // got string token 42 | $this->log("fetched token from gateway: {$token}"); 43 | $transaction->setGatewayToken($token, true); // update transaction 44 | 45 | return $token; 46 | } 47 | 48 | throw new Exception(sprintf('shaparak::saman.error_%s', $response)); 49 | } 50 | 51 | throw new Exception('shaparak::shaparak.token_failed'); 52 | } catch (SoapFault $e) { 53 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 54 | } 55 | } 56 | 57 | /** 58 | * {@inheritDoc} 59 | * 60 | * @throws Exception 61 | */ 62 | public function getFormParameters(): array 63 | { 64 | $token = $this->requestToken(); 65 | 66 | return [ 67 | 'gateway' => 'saman', 68 | 'method' => 'POST', 69 | 'action' => $this->getUrlFor(self::URL_GATEWAY), 70 | 'parameters' => [ 71 | 'Token' => $token, 72 | 'RedirectURL' => $this->getCallbackUrl(), 73 | 'getmethod' => (bool) $this->getParameters('get_method', true), 74 | ], 75 | ]; 76 | } 77 | 78 | protected function getGatewayOrderIdFromCallBackParameters(): string 79 | { 80 | return (string) $this->getParameters('ResNum'); 81 | } 82 | 83 | /** 84 | * {@inheritDoc} 85 | * 86 | * @throws Exception 87 | */ 88 | public function verifyTransaction(): bool 89 | { 90 | if ($this->getTransaction()->isReadyForVerify() === false) { 91 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 92 | } 93 | 94 | $this->checkRequiredActionParameters([ 95 | 'terminal_id', 96 | 'State', 97 | 'StateCode', 98 | 'RefNum', 99 | 'ResNum', 100 | 'TraceNo', 101 | 'SecurePan', 102 | 'CID', 103 | ]); 104 | 105 | if ($this->getParameters('State') !== 'OK') { 106 | throw new Exception('could not verify transaction with callback state: '.$this->getParameters('State')); 107 | } 108 | 109 | $this->callbackAbuseCheckList(); 110 | 111 | try { 112 | $soapClient = $this->getSoapClient(self::URL_VERIFY); 113 | 114 | $response = $soapClient->VerifyTransaction( 115 | $this->getParameters('RefNum'), 116 | $this->getParameters('terminal_id') 117 | ); 118 | 119 | if (isset($response)) { 120 | if ($response > 0 && ($response - $this->getTransaction()->getPayableAmount() < PHP_FLOAT_EPSILON)) { 121 | // double-check the amount by transaction amount 122 | $this->getTransaction()->setCardNumber($this->getParameters('SecurePan'), false); // no save() 123 | $this->getTransaction()->setVerified(true); // save() 124 | 125 | return true; 126 | } 127 | 128 | throw new Exception('shaparak::saman.error_'.(string) $response); 129 | } 130 | 131 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 132 | } catch (SoapFault $e) { 133 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 134 | } 135 | } 136 | 137 | /** 138 | * {@inheritDoc} 139 | * 140 | * @throws Exception 141 | */ 142 | public function refundTransaction(): bool 143 | { 144 | if ($this->refundSupport === false || $this->getTransaction()->isReadyForRefund() === false) { 145 | throw new Exception('shaparak::shaparak.could_not_refund_payment'); 146 | } 147 | 148 | $this->checkRequiredActionParameters([ 149 | 'RefNum', 150 | 'terminal_id', 151 | 'terminal_pass', 152 | ]); 153 | 154 | try { 155 | $soapClient = $this->getSoapClient(self::URL_REFUND); 156 | 157 | $refundAmount = $this->getAmount(); // total amount 158 | 159 | $response = $soapClient->reverseTransaction1( 160 | $this->getParameters('RefNum'), 161 | $this->getParameters('terminal_id'), 162 | $this->getParameters('terminal_pass'), 163 | $refundAmount 164 | ); 165 | 166 | if (isset($response)) { 167 | if ($response == 1) { // check by transaction amount 168 | $this->getTransaction()->setRefunded(true); 169 | 170 | return true; 171 | } 172 | 173 | throw new Exception('shaparak::saman.error_'.strval($response)); 174 | } 175 | 176 | throw new Exception('shaparak::shaparak.could_not_refund_payment'); 177 | } catch (SoapFault $e) { 178 | throw new Exception('SoapFault: '.$e->getMessage().' #'.$e->getCode(), $e->getCode()); 179 | } 180 | } 181 | 182 | /** 183 | * {@inheritDoc} 184 | */ 185 | public function canContinueWithCallbackParameters(): bool 186 | { 187 | try { 188 | $this->checkRequiredActionParameters([ 189 | 'RefNum', 190 | 'State', 191 | ]); 192 | } catch (\Exception $e) { 193 | return false; 194 | } 195 | 196 | return $this->getParameters('State') === 'OK'; 197 | } 198 | 199 | /** 200 | * {@inheritDoc} 201 | * 202 | * @throws Exception 203 | */ 204 | public function getGatewayReferenceId(): string 205 | { 206 | $this->checkRequiredActionParameters([ 207 | 'RefNum', 208 | ]); 209 | 210 | return $this->getParameters('RefNum'); 211 | } 212 | 213 | /** 214 | * {@inheritDoc} 215 | */ 216 | public function getUrlFor(string $action = null): string 217 | { 218 | if ($this->environment === 'production') { 219 | return match ($action) { 220 | self::URL_GATEWAY => 'https://sep.shaparak.ir/Payment.aspx', 221 | self::URL_TOKEN => 'https://sep.shaparak.ir/Payments/InitPayment.asmx?WSDL', 222 | default => 'https://sep.shaparak.ir/payments/referencepayment.asmx?WSDL', 223 | }; 224 | } 225 | 226 | return match ($action) { 227 | self::URL_GATEWAY => $this->bankTestBaseUrl . '/saman/sep.shaparak.ir/payment.aspx', 228 | self::URL_TOKEN => $this->bankTestBaseUrl . '/saman/sep.shaparak.ir/payments/initpayment.asmx?wsdl', 229 | default => $this->bankTestBaseUrl . '/saman/sep.shaparak.ir/payments/referencepayment.asmx?wsdl', 230 | }; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/Provider/ZarinpalProvider.php: -------------------------------------------------------------------------------- 1 | requestToken(); 19 | 20 | return [ 21 | 'gateway' => 'zarinpal', 22 | 'method' => 'GET', 23 | 'action' => $this->getUrlFor(self::URL_GATEWAY).'/'.$token, 24 | 'parameters' => [], 25 | ]; 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | * 31 | * @throws Exception 32 | */ 33 | protected function requestToken(): string 34 | { 35 | $transaction = $this->getTransaction(); 36 | 37 | if ($this->getTransaction()->isReadyForTokenRequest() === false) { 38 | throw new Exception('transaction is not ready for requesting token from payment gateway'); 39 | } 40 | 41 | $this->checkRequiredActionParameters([ 42 | 'merchant_id', 43 | ]); 44 | 45 | $response = Http::acceptJson()->post($this->getUrlFor(self::URL_TOKEN), [ 46 | 'merchant_id' => $this->getParameters('merchant_id'), 47 | 'callback_url' => $this->getCallbackUrl(), 48 | 'amount' => $this->getAmount(), 49 | 'description' => $this->getDescription(), 50 | ]); 51 | 52 | if ($response->successful()) { 53 | if ((int) $response->json('data.code') === 100) { 54 | $transaction->setGatewayToken($response->json('data.authority'), true); // update transaction 55 | return $response->json('data.authority'); 56 | } 57 | 58 | $this->log($response->json('errors.message'), $response->json('errors'), 'error'); 59 | throw new Exception( 60 | $response->json('errors.code').' '.$response->json('errors.message') 61 | ); 62 | } else { 63 | $this->log($response->body()); 64 | } 65 | 66 | throw new Exception('shaparak::shaparak.token_failed'); 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | public function getUrlFor(string $action = null): string 73 | { 74 | if ($this->environment === 'production') { 75 | switch ($action) { 76 | case self::URL_GATEWAY: 77 | 78 | return 'https://www.zarinpal.com/pg/StartPay'; 79 | 80 | case self::URL_TOKEN: 81 | 82 | return 'https://api.zarinpal.com/pg/v4/payment/request.json'; 83 | 84 | case self::URL_VERIFY: 85 | 86 | return 'https://api.zarinpal.com/pg/v4/payment/verify.json'; 87 | 88 | } 89 | } else { 90 | switch ($action) { 91 | case self::URL_GATEWAY: 92 | 93 | return $this->bankTestBaseUrl.'/zarinpal/www.zarinpal.com/pg/StartPay'; 94 | 95 | case self::URL_TOKEN: 96 | 97 | return $this->bankTestBaseUrl.'/zarinpal/api.zarinpal.com/pg/v4/payment/request.json'; 98 | 99 | case self::URL_VERIFY: 100 | 101 | return $this->bankTestBaseUrl.'/zarinpal/api.zarinpal.com/pg/v4/payment/verify.json'; 102 | 103 | } 104 | } 105 | throw new Exception('url destination is not valid!'); 106 | } 107 | 108 | /** 109 | * {@inheritDoc} 110 | */ 111 | public function canContinueWithCallbackParameters(): bool 112 | { 113 | try { 114 | $this->checkRequiredActionParameters([ 115 | 'Authority', 116 | 'Status', 117 | ]); 118 | } catch (\Exception $e) { 119 | return false; 120 | } 121 | 122 | return $this->getParameters('Status') === 'OK'; 123 | } 124 | 125 | /** 126 | * {@inheritDoc} 127 | * 128 | * @throws Exception 129 | */ 130 | public function getGatewayReferenceId(): string 131 | { 132 | $this->checkRequiredActionParameters([ 133 | 'Authority', 134 | ]); 135 | 136 | return $this->getParameters('Authority'); 137 | } 138 | 139 | /** 140 | * {@inheritDoc} 141 | * 142 | * @throws Exception 143 | */ 144 | public function verifyTransaction(): bool 145 | { 146 | if ($this->getTransaction()->isReadyForVerify() === false) { 147 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 148 | } 149 | 150 | $this->checkRequiredActionParameters([ 151 | 'merchant_id', 152 | 'authority', 153 | ]); 154 | 155 | if ($this->getParameters('Status') !== 'OK') { 156 | throw new Exception('could not verify transaction with callback status: '.$this->getParameters('Status')); 157 | } 158 | 159 | $response = Http::acceptJson()->post($this->getUrlFor(self::URL_VERIFY), [ 160 | 'merchant_id' => $this->getParameters('merchant_id'), 161 | 'authority' => $this->getParameters('authority'), 162 | 'amount' => $this->getTransaction()->getPayableAmount(), 163 | ]); 164 | 165 | if ($response->successful()) { 166 | if ((int) $response->json('data.code') === 100 || (int) $response->json('data.code') === 101) { 167 | $this->getTransaction()->setVerified(true); // save() 168 | 169 | return true; 170 | } 171 | 172 | $this->log($response->json('errors.message'), $response->json('errors'), 'error'); 173 | throw new Exception( 174 | $response->json('errors.code').' '.$response->json('errors.message') 175 | ); 176 | } 177 | 178 | throw new Exception('shaparak::shaparak.could_not_verify_transaction'); 179 | } 180 | 181 | public function refundTransaction(): bool 182 | { 183 | return false; 184 | } 185 | 186 | private function getDescription(): string 187 | { 188 | $description = $this->getTransaction()->description; 189 | if (empty($description)) { 190 | $description = sprintf('Payment for Order ID: %s', $this->getTransaction()->id); 191 | } 192 | 193 | return $description; 194 | } 195 | 196 | protected function getGatewayOrderIdFromCallBackParameters(): string 197 | { 198 | return ''; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/ShaparakManager.php: -------------------------------------------------------------------------------- 1 | '.$message; 35 | 36 | forward_static_call(['PhpMonsters\Log\Facades\XLog', $level], $message, $params); 37 | } 38 | 39 | /** 40 | * Get a driver instance. 41 | * 42 | * @param string $driver driver name 43 | * @param array $config runtime configuration for the driver instead of reading from config file 44 | * @return mixed 45 | */ 46 | public function with(string $driver, Transaction $transaction, array $config = []) 47 | { 48 | $this->transaction = $transaction; 49 | 50 | if (! empty($config)) { 51 | $this->runtimeConfig = $config; 52 | } 53 | 54 | return $this->driver($driver); 55 | } 56 | 57 | /** 58 | * Get the default driver name. 59 | * 60 | * @return string 61 | * 62 | * @throws InvalidArgumentException 63 | */ 64 | public function getDefaultDriver() 65 | { 66 | throw new InvalidArgumentException('No Shaparak driver was specified.'); 67 | } 68 | 69 | /** 70 | * Build a Shaparak provider instance. 71 | */ 72 | public function buildProvider(string $provider, array $config): Provider 73 | { 74 | return new $provider( 75 | $this->transaction, 76 | $config, 77 | Arr::get($config, 'mode', config('shaparak.mode', 'production')), 78 | Arr::get($config, 'httpClientOptions', []) 79 | ); 80 | } 81 | 82 | /** 83 | * get provider configuration runtime array or config based configuration 84 | */ 85 | protected function getConfig(ProviderName $driver): array 86 | { 87 | if (empty($this->runtimeConfig)) { 88 | return $this->container['config']["shaparak.providers.{$driver->value}"]; 89 | } 90 | 91 | return $this->runtimeConfig; 92 | } 93 | 94 | /** 95 | * Create an instance of the specified driver. 96 | * 97 | * @return Provider 98 | */ 99 | protected function createSamanDriver() 100 | { 101 | $config = $this->getConfig(ProviderName::SAMAN); 102 | 103 | return $this->buildProvider( 104 | SamanProvider::class, 105 | $config 106 | ); 107 | } 108 | 109 | /** 110 | * Create an instance of the specified driver. 111 | * 112 | * @return Provider 113 | */ 114 | protected function createParsianDriver() 115 | { 116 | $config = $this->getConfig(ProviderName::PARSIAN); 117 | 118 | return $this->buildProvider( 119 | ParsianProvider::class, 120 | $config 121 | ); 122 | } 123 | 124 | /** 125 | * Create an instance of the specified driver. 126 | * 127 | * @return Provider 128 | */ 129 | protected function createPasargadDriver() 130 | { 131 | $config = $this->getConfig(ProviderName::PASARGAD); 132 | 133 | return $this->buildProvider( 134 | PasargadProvider::class, 135 | $config 136 | ); 137 | } 138 | 139 | /** 140 | * Create an instance of the specified driver. 141 | * 142 | * @return Provider 143 | */ 144 | protected function createMellatDriver() 145 | { 146 | $config = $this->getConfig(ProviderName::MELLAT); 147 | 148 | return $this->buildProvider( 149 | MellatProvider::class, 150 | $config 151 | ); 152 | } 153 | 154 | /** 155 | * Create an instance of the specified driver. 156 | * 157 | * @return Provider 158 | */ 159 | protected function createMelliDriver() 160 | { 161 | $config = $this->getConfig(ProviderName::MELLI); 162 | 163 | return $this->buildProvider( 164 | MelliProvider::class, 165 | $config 166 | ); 167 | } 168 | 169 | /** 170 | * Create an instance of the specified driver. 171 | * 172 | * @return Provider 173 | */ 174 | protected function createSaderatDriver() 175 | { 176 | $config = $this->getConfig(ProviderName::SADERAT); 177 | 178 | return $this->buildProvider( 179 | SaderatProvider::class, 180 | $config 181 | ); 182 | } 183 | 184 | /** 185 | * Create an instance of the specified driver. 186 | * 187 | * @return Provider 188 | */ 189 | protected function createAsanPardakhtDriver() 190 | { 191 | $config = $this->getConfig(ProviderName::ASAN_PARDAKHT); 192 | 193 | return $this->buildProvider( 194 | AsanPardakhtProvider::class, 195 | $config 196 | ); 197 | } 198 | 199 | /** 200 | * Create an instance of the specified driver. 201 | * 202 | * @return Provider 203 | */ 204 | protected function createZarinpalDriver() 205 | { 206 | $config = $this->getConfig(ProviderName::ZARINPAL); 207 | 208 | return $this->buildProvider( 209 | ZarinpalProvider::class, 210 | $config 211 | ); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/ShaparakServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerResources(); 18 | $this->registerPublishing(); 19 | } 20 | 21 | /** 22 | * Get the services provided by the provider. 23 | * 24 | * @return void 25 | */ 26 | public function register() 27 | { 28 | $this->app->singleton(Factory::class, function ($app) { 29 | return new ShaparakManager($app); 30 | }); 31 | } 32 | 33 | /** 34 | * Get the services provided by the provider. 35 | * 36 | * @return array 37 | */ 38 | public function provides() 39 | { 40 | return [Factory::class]; 41 | } 42 | 43 | /** 44 | * Determine if the provider is deferred. 45 | * 46 | * @return bool 47 | */ 48 | public function isDeferred() 49 | { 50 | return true; 51 | } 52 | 53 | protected function registerResources() 54 | { 55 | $this->loadViewsFrom(__DIR__.'/../views/', 'shaparak'); 56 | 57 | $this->publishes([ 58 | __DIR__.'/../translations/' => base_path('/lang/vendor/shaparak'), 59 | ], 'translations'); 60 | 61 | $this->loadTranslationsFrom(__DIR__.'/../translations', 'shaparak'); 62 | } 63 | 64 | protected function registerPublishing() 65 | { 66 | $this->publishes([ 67 | __DIR__.'/../views/' => resource_path('/views/vendor/shaparak'), 68 | ], 'views'); 69 | 70 | $this->publishes([ 71 | __DIR__.'/../config/shaparak.php' => config_path('shaparak.php'), 72 | ], 'config'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /translations/en/shaparak.php: -------------------------------------------------------------------------------- 1 | 'canceled_by_user', 5 | 'could_not_request_token' => 'could_not_request_token', 6 | 'could_not_verify_transaction' => 'could_not_verify_transaction', 7 | 'could_not_inquiry_payment' => 'could_not_inquiry_payment', 8 | 'could_not_settle_payment' => 'could_not_settle_payment', 9 | 'could_not_refund_payment' => 'could_not_refund_payment', 10 | 'gate_not_ready' => 'gate_not_ready', 11 | 'goto_gate' => 'goto_gate', 12 | 'invalid_response' => 'invalid_response', 13 | 'token_failed' => 'token_failed', 14 | 'verify_failed' => 'verify_failed', 15 | 'inquiry_failed' => 'inquiry_failed', 16 | 'settle_failed' => 'settle_failed', 17 | 'refund_failed' => 'refund_failed', 18 | ]; 19 | -------------------------------------------------------------------------------- /translations/fa/shaparak.php: -------------------------------------------------------------------------------- 1 | 'تراکنش بانکی توسط کاربر لغو شد', 5 | 'could_not_request_token' => 'امکان درخواست توکن برای این تراکنش وجود ندارد', 6 | 'could_not_verify_transaction' => 'امکان تایید این تراکنش وجود ندارد', 7 | 'could_not_inquiry_payment' => 'امکان استعلام این تراکنش وجود ندارد', 8 | 'could_not_settle_payment' => 'امکان تسویه برای این تراکنش وجود ندارد', 9 | 'could_not_refund_payment' => 'امکان برگشت زدن این تراکنش وجود ندارد', 10 | 'could_not_pass_abuse_checklist' => 'چک لیست امنیتی پاس نشد!', 11 | 'refund_payment_already_exist' => 'تراکنش قبلا برگشت خورده است', 12 | 'gate_not_ready' => 'درگاه پرداختی یا وجود ندارد یا آماده سرویس دهی نمی باشد', 13 | 'gateway_tokens_are_not_same' => 'توکن درگاه با تراکنش برابر نیست!', 14 | 'goto_gate' => 'انتقال به درگاه پرداخت', 15 | 'invalid_response' => 'پاسخ معتبر از سرور درگاه پرداخت دریافت نشد!', 16 | 'token_failed' => 'درخواست توکن تراکنش با موفقیت انجام نشد!', 17 | 'verify_failed' => 'تایید تراکنش با موفقیت انجام نشد!', 18 | 'inquiry_failed' => 'بررسی وضعیت تراکنش با موفقیت انجام نشد!', 19 | 'settle_failed' => 'تایید بعد از تراکنش با موفقیت انجام نشد!', 20 | 'refund_failed' => 'بازگشت تراکنش با موفقیت انجام نشد!', 21 | 'amounts_not_match' => 'مقدار فاکتور با مقدار برگشتی از درگاه برابر نیست!', 22 | ]; 23 | -------------------------------------------------------------------------------- /views/goto-gate-form.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @foreach($parameters as $field => $value) 3 | @if(!is_null($value)) 4 | 5 | @endif 6 | @endforeach 7 | 8 |
9 |
10 | 11 |
12 |
13 |
14 | 15 | @if($autoSubmit === true) 16 | 20 | @endif 21 | 22 | 23 | --------------------------------------------------------------------------------