├── cover.png ├── .gitignore ├── views ├── fail.blade.php ├── success.blade.php └── layout.blade.php ├── src ├── Exceptions │ ├── PaymentRequestException.php │ ├── RoutesNotDefinedException.php │ ├── CardTokenInactiveException.php │ ├── ApiCredentialsException.php │ ├── UnsupportedCurrencyException.php │ └── UnsupportedLanguageException.php ├── Requests │ ├── GetBalance.php │ ├── JustPay.php │ ├── AddCard.php │ ├── GetTransactionInfo.php │ ├── Refund.php │ ├── Commit.php │ └── PayWithCard.php ├── Enums │ ├── Language.php │ ├── Method.php │ ├── Currency.php │ ├── SwitchableEnum.php │ └── Status.php ├── Concerns │ ├── PayRequest.php │ ├── ApiRequest.php │ └── PayRequestAttributes.php ├── Facades │ └── Payze.php ├── Traits │ ├── HasCards.php │ └── HasTransactions.php ├── Events │ ├── PayzeTransactionPaid.php │ └── PayzeTransactionFailed.php ├── Observers │ └── PayzeTransactionObserver.php ├── Objects │ └── Split.php ├── Controllers │ ├── PayzeController.php.stub │ └── PayzeController.php ├── Models │ ├── PayzeLog.php │ ├── PayzeTransaction.php │ └── PayzeCardToken.php ├── Console │ └── Commands │ │ └── UpdateIncompleteTransactions.php ├── PayzeServiceProvider.php └── Payze.php ├── database └── migrations │ ├── create_payze_logs_table.php.stub │ ├── add_transaction_id_to_payze_logs_table.php.stub │ ├── add_default_and_details_columns_to_payze_card_tokens_table.php.stub │ ├── create_payze_card_tokens_table.php.stub │ └── create_payze_transactions_table.php.stub ├── composer.json ├── config └── payze.php ├── CHANGELOG.md └── README.md /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payzeio/laravel-payze/HEAD/cover.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor 3 | tests/temp 4 | composer.lock 5 | phpunit.xml 6 | .env 7 | .phpunit.result.cache 8 | .php_cs.cache 9 | -------------------------------------------------------------------------------- /views/fail.blade.php: -------------------------------------------------------------------------------- 1 | @extends('payze::layout') 2 | 3 | @section('status', 'fail') 4 | @section('title', 'Payment failed - Payze') 5 | @section('message', 'Payment failed. Please try again') 6 | -------------------------------------------------------------------------------- /views/success.blade.php: -------------------------------------------------------------------------------- 1 | @extends('payze::layout') 2 | 3 | @section('status', 'success') 4 | @section('title', 'Payment completed - Payze') 5 | @section('message', 'Payment completed successfully') 6 | -------------------------------------------------------------------------------- /src/Exceptions/PaymentRequestException.php: -------------------------------------------------------------------------------- 1 | morphMany(PayzeCardToken::class, 'model'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Traits/HasTransactions.php: -------------------------------------------------------------------------------- 1 | morphMany(PayzeTransaction::class, 'model'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Exceptions/UnsupportedCurrencyException.php: -------------------------------------------------------------------------------- 1 | message = sprintf('Unsupported Currency "%s"', $currency); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Exceptions/UnsupportedLanguageException.php: -------------------------------------------------------------------------------- 1 | message = sprintf('Unsupported Language "%s"', $language); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Requests/JustPay.php: -------------------------------------------------------------------------------- 1 | amount($amount); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Events/PayzeTransactionPaid.php: -------------------------------------------------------------------------------- 1 | transaction = $transaction; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Events/PayzeTransactionFailed.php: -------------------------------------------------------------------------------- 1 | transaction = $transaction; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Enums/SwitchableEnum.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('message')->nullable(); 19 | $table->json('payload')->nullable(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists(config('payze.logs_table')); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "payzeio/laravel-payze", 3 | "description": "Payze.io Payment Integration for Laravel", 4 | "type": "library", 5 | "require": { 6 | "php": ">=7.4|^8.0|^8.1|^8.2", 7 | "ext-json": "*", 8 | "guzzlehttp/guzzle": "^6|^7", 9 | "illuminate/support": ">=5.3" 10 | }, 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Levan Lotuashvili", 15 | "email": "l.lotuashvili@gmail.com" 16 | } 17 | ], 18 | "autoload": { 19 | "psr-4": { 20 | "PayzeIO\\LaravelPayze\\": "src" 21 | } 22 | }, 23 | "extra": { 24 | "laravel": { 25 | "providers": [ 26 | "PayzeIO\\LaravelPayze\\PayzeServiceProvider" 27 | ], 28 | "aliases": { 29 | "Payze": "PayzeIO\\LaravelPayze\\Facades\\Payze" 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Observers/PayzeTransactionObserver.php: -------------------------------------------------------------------------------- 1 | is_completed && !$transaction->getOriginal('is_completed'); 17 | 18 | if ($completed && ($transaction->is_paid && !$transaction->getOriginal('is_paid'))) { 19 | event(new PayzeTransactionPaid($transaction)); 20 | } elseif ($completed && (!$transaction->is_paid && !$transaction->getOriginal('is_paid'))) { 21 | event(new PayzeTransactionFailed($transaction)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Concerns/ApiRequest.php: -------------------------------------------------------------------------------- 1 | method; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function toRequest(): array 26 | { 27 | return []; 28 | } 29 | 30 | /** 31 | * Return new instance 32 | * 33 | * @return static 34 | */ 35 | public static function request(): self 36 | { 37 | return new static(...func_get_args()); 38 | } 39 | 40 | /** 41 | * Process request via Payze facade 42 | * 43 | * @return mixed 44 | */ 45 | public function process() 46 | { 47 | return Payze::process($this); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /database/migrations/add_transaction_id_to_payze_logs_table.php.stub: -------------------------------------------------------------------------------- 1 | foreignId('transaction_id')->nullable()->after('id')->constrained(config('payze.transactions_table'))->cascadeOnUpdate()->nullOnDelete(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table(config('payze.logs_table'), function (Blueprint $table) { 29 | $table->dropConstrainedForeignId('transaction_id'); 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/Objects/Split.php: -------------------------------------------------------------------------------- 1 | amount = $amount; 38 | $this->iban = $iban; 39 | $this->payIn = $payIn; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function toRequest(): array 46 | { 47 | return [ 48 | 'amount' => $this->amount, 49 | 'iban' => $this->iban, 50 | 'payIn' => $this->payIn, 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Requests/AddCard.php: -------------------------------------------------------------------------------- 1 | amount($amount); 29 | } 30 | 31 | /** 32 | * Set the model for card token 33 | * 34 | * @param \Illuminate\Database\Eloquent\Model $model 35 | * 36 | * @return $this 37 | */ 38 | public function assignTo(Model $model): self 39 | { 40 | $this->assignTo = $model; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * @return \Illuminate\Database\Eloquent\Model|null 47 | */ 48 | public function getAssignedModel(): ?Model 49 | { 50 | return $this->assignTo; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /database/migrations/add_default_and_details_columns_to_payze_card_tokens_table.php.stub: -------------------------------------------------------------------------------- 1 | boolean('default')->default(false)->after('active'); 18 | $table->string('cardholder')->nullable()->after('card_mask'); 19 | $table->string('brand')->nullable()->after('cardholder'); 20 | $table->date('expiration_date')->nullable()->after('brand'); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::table(config('payze.card_tokens_table'), function (Blueprint $table) { 32 | $table->dropColumn([ 33 | 'default', 34 | 'cardholder', 35 | 'brand', 36 | 'expiration_date', 37 | ]); 38 | }); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/Requests/GetTransactionInfo.php: -------------------------------------------------------------------------------- 1 | transactionId = Payze::parseTransaction($transaction); 30 | } 31 | 32 | /** 33 | * Process request via Payze facade 34 | * 35 | * @return \PayzeIO\LaravelPayze\Models\PayzeTransaction 36 | */ 37 | public function process(): PayzeTransaction 38 | { 39 | return Payze::processTransaction($this); 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function toRequest(): array 46 | { 47 | return [ 48 | 'transactionId' => $this->transactionId, 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /views/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @yield('title') 8 | 9 | 10 | 11 | 12 | 39 | 40 | 41 | 42 |
@yield('message')
43 | 44 |
45 | @yield('home_button', 'Return home') 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /database/migrations/create_payze_card_tokens_table.php.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->bigInteger('transaction_id')->unsigned()->nullable(); 19 | $table->foreign('transaction_id') 20 | ->references('id') 21 | ->on(config('payze.transactions_table')) 22 | ->onUpdate('cascade') 23 | ->onDelete('cascade'); 24 | $table->bigInteger('model_id')->unsigned()->nullable(); 25 | $table->string('model_type')->nullable(); 26 | $table->boolean('active')->default(0); 27 | $table->string('card_mask')->nullable(); 28 | $table->text('token'); 29 | $table->timestamps(); 30 | 31 | $table->index(['model_id', 'model_type']); 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | * 38 | * @return void 39 | */ 40 | public function down() 41 | { 42 | Schema::dropIfExists(config('payze.card_tokens_table')); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /config/payze.php: -------------------------------------------------------------------------------- 1 | (bool) env('PAYZE_LOG', env('APP_ENV') === 'local'), 9 | 10 | /* 11 | * Enable/Disable SSL verification in Guzzle 12 | */ 13 | 'verify_ssl' => (bool) env('PAYZE_VERIFY_SSL', true), 14 | 15 | /* 16 | * Success & Fail route names. 17 | * Update these if you have defined routes under name/namespace, for example "api.payze.succes" 18 | */ 19 | 'routes' => [ 20 | 'success' => 'payze.success', 21 | 'fail' => 'payze.fail', 22 | ], 23 | 24 | /* 25 | * Success & Fail view names. 26 | * Set names of success and fail views. Setting null will redirect to "/" by default 27 | */ 28 | 'views' => [ 29 | 'success' => 'payze::success', 30 | 'fail' => 'payze::fail', 31 | ], 32 | 33 | /* 34 | * Name on transactions table in database 35 | */ 36 | 'transactions_table' => 'payze_transactions', 37 | 38 | /* 39 | * Name of logs table in database 40 | */ 41 | 'logs_table' => 'payze_logs', 42 | 43 | /* 44 | * Name of card tokens table in database 45 | */ 46 | 'card_tokens_table' => 'payze_card_tokens', 47 | 48 | /* 49 | * API key for Payze 50 | */ 51 | 'api_key' => env('PAYZE_API_KEY'), 52 | 53 | /* 54 | * API secret for Payze 55 | */ 56 | 'api_secret' => env('PAYZE_API_SECRET'), 57 | 58 | ]; 59 | -------------------------------------------------------------------------------- /src/Requests/Refund.php: -------------------------------------------------------------------------------- 1 | transactionId = Payze::parseTransaction($transaction); 36 | $this->amount = $amount; 37 | } 38 | 39 | /** 40 | * Process request via Payze facade 41 | * 42 | * @return \PayzeIO\LaravelPayze\Models\PayzeTransaction 43 | */ 44 | public function process(): PayzeTransaction 45 | { 46 | return Payze::processTransaction($this, 'transaction'); 47 | } 48 | 49 | /** 50 | * @return array 51 | */ 52 | public function toRequest(): array 53 | { 54 | return array_filter([ 55 | 'transactionId' => $this->transactionId, 56 | 'amount' => $this->amount ?: null, 57 | ]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Enums/Status.php: -------------------------------------------------------------------------------- 1 | where('active', true)` 10 | 11 | After: `$query->where('expiration_date', '>=', Carbon::now())` 12 | 13 | Please refer to [this section](README.md#card-token-model) to read more details. 14 | 15 | ## v2.0.0 16 | 17 | - Bumped PHP to version 7.4, Added property types ([7ec95e2](https://github.com/payzeio/laravel-payze/commit/7ec95e29b5a7e220cdde68384dfaabf955f9c134)) 18 | - Use `Relation::morphMap()` aliases for transactions and card tokens tables ([133dcab](https://github.com/payzeio/laravel-payze/commit/133dcab7e7526c1c99678e22eafa8f271caf744a)) 19 | - Fixed split method ([0a7a719](https://github.com/payzeio/laravel-payze/commit/0a7a719cce6be862f73055107dc97e16cac02e64)) 20 | - Minor bug fixes 21 | 22 | ## v1.9.0 23 | 24 | - Added Transaction ID column to PayzeLog model ([ee30e45](https://github.com/payzeio/laravel-payze/commit/ee30e45ce52bd20a6bfb4e70eee300eb8787a30d)) 25 | 26 | ## v1.8.5 27 | 28 | - Added support of UZB language ([d6dd3a7](https://github.com/payzeio/laravel-payze/commit/d6dd3a7ba2a909e319fd77dc92f355e5497829a9)) 29 | 30 | ## v1.8.3 31 | 32 | - Added support of UZS currency ([ee76cc5](https://github.com/payzeio/laravel-payze/commit/ee76cc5f8b26683f639586ed59f772cee6f39bcc)) 33 | -------------------------------------------------------------------------------- /src/Controllers/PayzeController.php.stub: -------------------------------------------------------------------------------- 1 | transactionId = Payze::parseTransaction($transaction); 36 | $this->amount($amount); 37 | } 38 | 39 | /** 40 | * @param float $amount 41 | * 42 | * @return $this 43 | */ 44 | public function amount(float $amount): self 45 | { 46 | $this->amount = max($amount, 0); 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * Process request via Payze facade 53 | * 54 | * @return \PayzeIO\LaravelPayze\Models\PayzeTransaction 55 | */ 56 | public function process(): PayzeTransaction 57 | { 58 | return Payze::processTransaction($this, 'data'); 59 | } 60 | 61 | public function toRequest(): array 62 | { 63 | return array_filter([ 64 | 'transactionId' => $this->transactionId, 65 | 'amount' => $this->amount ?: null, 66 | ]); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Requests/PayWithCard.php: -------------------------------------------------------------------------------- 1 | active, new CardTokenInactiveException); 36 | 37 | $this->cardToken = $cardToken; 38 | $this->amount($amount); 39 | } 40 | 41 | /** 42 | * Process request via Payze facade 43 | * 44 | * @return \PayzeIO\LaravelPayze\Models\PayzeTransaction 45 | */ 46 | public function process(): PayzeTransaction 47 | { 48 | return Payze::processTransaction($this, 'transactionInfo'); 49 | } 50 | 51 | /** 52 | * @return array 53 | * @throws \PayzeIO\LaravelPayze\Exceptions\RoutesNotDefinedException 54 | * @throws \Throwable 55 | */ 56 | public function toRequest(): array 57 | { 58 | return array_merge(parent::toRequest(), [ 59 | 'cardToken' => $this->cardToken->getToken(), 60 | ]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /database/migrations/create_payze_transactions_table.php.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->bigInteger('model_id')->unsigned()->nullable(); 19 | $table->string('model_type')->nullable(); 20 | $table->string('method')->nullable(); 21 | $table->string('status'); 22 | $table->boolean('is_paid')->default(0); 23 | $table->boolean('is_completed')->default(0); 24 | $table->string('transaction_id')->unique()->index(); 25 | $table->decimal('amount', 10); 26 | $table->decimal('final_amount', 10)->nullable(); 27 | $table->decimal('refunded', 10)->nullable(); 28 | $table->decimal('commission')->nullable(); 29 | $table->boolean('refundable')->default(0); 30 | $table->string('currency'); 31 | $table->string('lang'); 32 | $table->json('split')->nullable(); 33 | $table->boolean('can_be_committed')->default(0); 34 | $table->string('result_code')->nullable(); 35 | $table->string('card_mask')->nullable(); 36 | $table->json('log')->nullable(); 37 | $table->timestamps(); 38 | 39 | $table->index(['model_id', 'model_type']); 40 | }); 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | * 46 | * @return void 47 | */ 48 | public function down() 49 | { 50 | Schema::dropIfExists(config('payze.transactions_table')); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/Models/PayzeLog.php: -------------------------------------------------------------------------------- 1 | 'array', 47 | ]; 48 | 49 | /** 50 | * PayzeLog constructor. 51 | * 52 | * @param array $attributes 53 | */ 54 | public function __construct(array $attributes = []) 55 | { 56 | parent::__construct($attributes); 57 | 58 | $this->table = config('payze.logs_table', 'payze_logs'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Console/Commands/UpdateIncompleteTransactions.php: -------------------------------------------------------------------------------- 1 | where('created_at', '<=', now()->subMinutes(20))->get(); 36 | 37 | if ($transactions->isEmpty()) { 38 | $this->info('All transactions are completed'); 39 | 40 | return; 41 | } 42 | 43 | $this->info(sprintf('Updating %s transactions...', $transactions->count())); 44 | 45 | $transactions->each(function (PayzeTransaction $transaction) { 46 | try { 47 | $result = GetTransactionInfo::request($transaction)->process(); 48 | } catch (Exception $e) { 49 | $this->error(sprintf('Can\'t update transaction (#%s) %s, setting status to Error.', $transaction->id, $transaction->transaction_id)); 50 | 51 | $transaction->update([ 52 | 'status' => Status::ERROR, 53 | 'is_completed' => true, 54 | ]); 55 | 56 | return; 57 | } 58 | 59 | $this->info(sprintf('%s - %s', $transaction->transaction_id, $result->status)); 60 | }); 61 | 62 | $this->info(sprintf('%s transactions updated successfully', $transactions->count())); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/PayzeServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 16 | $this->publishes([ 17 | __DIR__ . '/../database/migrations/create_payze_transactions_table.php.stub' => $this->getMigrationFileName('create_payze_transactions_table.php', '_01'), 18 | __DIR__ . '/../database/migrations/create_payze_logs_table.php.stub' => $this->getMigrationFileName('create_payze_logs_table.php', '_02'), 19 | __DIR__ . '/../database/migrations/create_payze_card_tokens_table.php.stub' => $this->getMigrationFileName('create_payze_card_tokens_table.php', '_03'), 20 | __DIR__ . '/../database/migrations/add_transaction_id_to_payze_logs_table.php.stub' => $this->getMigrationFileName('add_transaction_id_to_payze_logs_table.php', '_04'), 21 | __DIR__ . '/../database/migrations/add_default_and_details_columns_to_payze_card_tokens_table.php.stub' => $this->getMigrationFileName('add_default_and_details_columns_to_payze_card_tokens_table.php', '_05'), 22 | ], 'payze-migrations'); 23 | 24 | $this->publishes([ 25 | __DIR__ . '/../config/payze.php' => config_path('payze.php'), 26 | ], 'payze-config'); 27 | 28 | $this->publishes([ 29 | __DIR__ . '/Controllers/PayzeController.php.stub' => app_path('Http/Controllers/PayzeController.php'), 30 | ], 'payze-controllers'); 31 | } 32 | 33 | $this->mergeConfigFrom(__DIR__ . '/../config/payze.php', 'payze'); 34 | 35 | $this->loadViewsFrom(__DIR__ . '/../views', 'payze'); 36 | 37 | PayzeTransaction::observe(PayzeTransactionObserver::class); 38 | } 39 | 40 | /** 41 | * Returns existing migration file if found, else uses the current timestamp. 42 | * 43 | * @param $migrationFileName 44 | * @param string $prefix 45 | * 46 | * @return string 47 | */ 48 | protected function getMigrationFileName($migrationFileName, string $prefix = ''): string 49 | { 50 | $timestamp = date('Y_m_d_His') . $prefix; 51 | 52 | return Collection::make($this->app->databasePath() . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR) 53 | ->flatMap(fn($path) => File::glob($path . '*_' . $migrationFileName)) 54 | ->push($this->app->databasePath() . "/migrations/{$timestamp}_$migrationFileName") 55 | ->first(); 56 | } 57 | 58 | public function register() 59 | { 60 | $this->app->bind(Payze::class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Controllers/PayzeController.php: -------------------------------------------------------------------------------- 1 | getTransaction($request); 29 | 30 | if (!$transaction->is_paid) { 31 | return redirect()->route(config('payze.routes.fail'), $request->query()); 32 | } 33 | 34 | $response = $this->successResponse($transaction, $request); 35 | 36 | return $response ?? $this->response('success'); 37 | } 38 | 39 | /** 40 | * Failed payment view 41 | * 42 | * @param \Illuminate\Http\Request $request 43 | * 44 | * @return mixed 45 | */ 46 | public function fail(Request $request) 47 | { 48 | $transaction = $this->getTransaction($request); 49 | 50 | if ($transaction->is_paid) { 51 | return redirect()->route(config('payze.routes.success'), $request->query()); 52 | } 53 | 54 | $response = $this->failResponse($transaction, $request); 55 | 56 | return $response ?? $this->response('fail'); 57 | } 58 | 59 | /** 60 | * Update information in database and return transaction 61 | * 62 | * @param \Illuminate\Http\Request $request 63 | * 64 | * @return \PayzeIO\LaravelPayze\Models\PayzeTransaction 65 | */ 66 | protected function getTransaction(Request $request): PayzeTransaction 67 | { 68 | abort_unless($request->has($this->key), 404); 69 | 70 | $id = $request->input($this->key); 71 | 72 | /* 73 | * Check if transaction is incomplete 74 | * Fixes security issue. Avoids triggering success callbacks on completed transactions more than once 75 | */ 76 | PayzeTransaction::where('transaction_id', $id)->incomplete()->firstOrFail(); 77 | 78 | return GetTransactionInfo::request($id)->process(); 79 | } 80 | 81 | /** 82 | * Return a view or redirect to index page, based on config 83 | * 84 | * @param string $status 85 | * 86 | * @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse 87 | */ 88 | protected function response(string $status) 89 | { 90 | $view = config('payze.views.' . $status); 91 | 92 | return $view ? view($view) : redirect('/'); 93 | } 94 | 95 | /** 96 | * Success Response 97 | * Should be overridden in custom controller, or will be used a default one 98 | * 99 | * @param \PayzeIO\LaravelPayze\Models\PayzeTransaction $transaction 100 | * @param \Illuminate\Http\Request $request 101 | * 102 | * @return mixed 103 | */ 104 | protected function successResponse(PayzeTransaction $transaction, Request $request) 105 | { 106 | // Override in controller 107 | } 108 | 109 | /** 110 | * Fail Response 111 | * Should be overridden in custom controller, or will be used a default one 112 | * 113 | * @param \PayzeIO\LaravelPayze\Models\PayzeTransaction $transaction 114 | * @param \Illuminate\Http\Request $request 115 | * 116 | * @return mixed 117 | */ 118 | protected function failResponse(PayzeTransaction $transaction, Request $request) 119 | { 120 | // Override in controller 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Models/PayzeTransaction.php: -------------------------------------------------------------------------------- 1 | 'float', 107 | 'final_amount' => 'float', 108 | 'commission' => 'float', 109 | 'split' => 'array', 110 | 'can_be_committed' => 'boolean', 111 | 'refunded' => 'float', 112 | 'refundable' => 'boolean', 113 | 'log' => 'array', 114 | ]; 115 | 116 | /** 117 | * PayzeTranscation constructor. 118 | * 119 | * @param array $attributes 120 | */ 121 | public function __construct(array $attributes = []) 122 | { 123 | parent::__construct($attributes); 124 | 125 | $this->table = config('payze.transactions_table', 'payze_transactions'); 126 | } 127 | 128 | /** 129 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 130 | */ 131 | public function model(): MorphTo 132 | { 133 | return $this->morphTo(); 134 | } 135 | 136 | /** 137 | * @return \Illuminate\Database\Eloquent\Relations\HasOne 138 | */ 139 | public function card(): HasOne 140 | { 141 | return $this->hasOne(PayzeCardToken::class, 'transaction_id')->withInactive(); 142 | } 143 | 144 | /** 145 | * @param \Illuminate\Database\Eloquent\Builder $query 146 | * 147 | * @return \Illuminate\Database\Eloquent\Builder 148 | */ 149 | public function scopePaid(Builder $query): Builder 150 | { 151 | return $query->where('is_paid', true); 152 | } 153 | 154 | /** 155 | * @param \Illuminate\Database\Eloquent\Builder $query 156 | * 157 | * @return \Illuminate\Database\Eloquent\Builder 158 | */ 159 | public function scopeUnpaid(Builder $query): Builder 160 | { 161 | return $query->where('is_paid', false); 162 | } 163 | 164 | /** 165 | * @param \Illuminate\Database\Eloquent\Builder $query 166 | * 167 | * @return \Illuminate\Database\Eloquent\Builder 168 | */ 169 | public function scopeCompleted(Builder $query): Builder 170 | { 171 | return $query->where('is_completed', true); 172 | } 173 | 174 | /** 175 | * @param \Illuminate\Database\Eloquent\Builder $query 176 | * 177 | * @return \Illuminate\Database\Eloquent\Builder 178 | */ 179 | public function scopeIncomplete(Builder $query): Builder 180 | { 181 | return $query->where('is_completed', false); 182 | } 183 | 184 | /** 185 | * @param \Illuminate\Database\Eloquent\Builder $query 186 | * 187 | * @return \Illuminate\Database\Eloquent\Builder 188 | */ 189 | public function scopeRefundable(Builder $query): Builder 190 | { 191 | return $query->where('refundable', true); 192 | } 193 | 194 | /** 195 | * @param \Illuminate\Database\Eloquent\Builder $query 196 | * 197 | * @return \Illuminate\Database\Eloquent\Builder 198 | */ 199 | public function scopeNonRefundable(Builder $query): Builder 200 | { 201 | return $query->where('refundable', false); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/Models/PayzeCardToken.php: -------------------------------------------------------------------------------- 1 | 'boolean', 69 | 'default' => 'boolean', 70 | 'expiration_date' => 'datetime', 71 | ]; 72 | 73 | /** 74 | * PayzeCardToken constructor. 75 | * 76 | * @param array $attributes 77 | */ 78 | public function __construct(array $attributes = []) 79 | { 80 | parent::__construct($attributes); 81 | 82 | $this->table = config('payze.card_tokens_table', 'payze_card_tokens'); 83 | } 84 | 85 | protected static function booted() 86 | { 87 | /* 88 | * Add global active scope 89 | */ 90 | static::addGlobalScope('active', fn(Builder $builder) => $builder->where('active', true)); 91 | } 92 | 93 | /** 94 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 95 | */ 96 | public function transaction(): BelongsTo 97 | { 98 | return $this->belongsTo(PayzeTransaction::class, 'transaction_id'); 99 | } 100 | 101 | /** 102 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 103 | */ 104 | public function model(): MorphTo 105 | { 106 | return $this->morphTo(); 107 | } 108 | 109 | /** 110 | * @param \Illuminate\Database\Eloquent\Builder $query 111 | * 112 | * @return \Illuminate\Database\Eloquent\Builder 113 | */ 114 | public function scopeWithInactive(Builder $query): Builder 115 | { 116 | return $query->withoutGlobalScope('active'); 117 | } 118 | 119 | /** 120 | * @param \Illuminate\Database\Eloquent\Builder $query 121 | * 122 | * @return \Illuminate\Database\Eloquent\Builder 123 | */ 124 | public function scopeDefault(Builder $query): Builder 125 | { 126 | return $query->where('default', true); 127 | } 128 | 129 | /** 130 | * @param \Illuminate\Database\Eloquent\Builder $query 131 | * 132 | * @return \Illuminate\Database\Eloquent\Builder 133 | */ 134 | public function scopeActive(Builder $query): Builder 135 | { 136 | return $query->where('expiration_date', '>=', Carbon::now()); 137 | } 138 | 139 | /** 140 | * @param \Illuminate\Database\Eloquent\Builder $query 141 | * 142 | * @return \Illuminate\Database\Eloquent\Builder 143 | */ 144 | public function scopeInactive(Builder $query): Builder 145 | { 146 | return $query->withInactive()->where('active', false); 147 | } 148 | 149 | /** 150 | * @param \Illuminate\Database\Eloquent\Builder $query 151 | * 152 | * @return \Illuminate\Database\Eloquent\Builder 153 | */ 154 | public function scopeExpired(Builder $query): Builder 155 | { 156 | return $query->where('expiration_date', '<', Carbon::now()); 157 | } 158 | 159 | /** 160 | * Encrypt a card token before saving to DB 161 | * 162 | * @param string $value 163 | */ 164 | public function setTokenAttribute(string $value): void 165 | { 166 | if (empty($value)) { 167 | return; 168 | } 169 | 170 | $this->attributes['token'] = Crypt::encryptString($value); 171 | } 172 | 173 | /** 174 | * Return a decrypted token 175 | * 176 | * @return string|null 177 | */ 178 | public function getToken(): string 179 | { 180 | return Crypt::decryptString($this->token); 181 | } 182 | 183 | /** 184 | * Mark current card as default 185 | * Unmark all other cards of the same model 186 | * 187 | * @return $this 188 | */ 189 | public function markAsDefault(): self 190 | { 191 | self::where('model_type', $this->model_type) 192 | ->where('model_id', $this->model_id) 193 | ->where('id', '!=', $this->id) 194 | ->where('default', true) 195 | ->update(['default' => false]); 196 | 197 | return tap($this)->update([ 198 | 'default' => true, 199 | ]); 200 | } 201 | 202 | /** 203 | * Check if current card is not expired 204 | * 205 | * @return bool 206 | */ 207 | public function isActive(): bool 208 | { 209 | if (empty($this->expiration_date)) { 210 | return true; 211 | } 212 | 213 | return $this->expiration_date->isFuture(); 214 | } 215 | 216 | /** 217 | * Check if current card is expired 218 | * 219 | * @return bool 220 | */ 221 | public function isExpired(): bool 222 | { 223 | if (empty($this->expiration_date)) { 224 | return false; 225 | } 226 | 227 | return $this->expiration_date->isPast(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/Concerns/PayRequestAttributes.php: -------------------------------------------------------------------------------- 1 | amount = max($amount, 0); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Split transaction to different IBANs 77 | * 78 | * @param mixed $splits 79 | * 80 | * @return $this 81 | * @throws \PayzeIO\LaravelPayze\Exceptions\PaymentRequestException 82 | * @throws \Throwable 83 | */ 84 | public function split($splits): self 85 | { 86 | $splits = !is_array($splits) ? func_get_args() : $splits; 87 | 88 | foreach ($splits as $split) { 89 | throw_unless(is_a($split, Split::class), new PaymentRequestException('Incorrect format. Please pass Split object')); 90 | } 91 | 92 | $this->split = $splits; 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * Switch payment page language 99 | * 100 | * @param string $lang 101 | * 102 | * @return $this 103 | * @throws \PayzeIO\LaravelPayze\Exceptions\UnsupportedLanguageException|\Throwable 104 | */ 105 | public function language(string $lang): self 106 | { 107 | throw_unless(Language::check($lang), new UnsupportedLanguageException($lang)); 108 | 109 | $this->lang = $lang; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Switch payment currency 116 | * 117 | * @param string $currency 118 | * 119 | * @return $this 120 | * @throws \PayzeIO\LaravelPayze\Exceptions\UnsupportedCurrencyException|\Throwable 121 | */ 122 | public function currency(string $currency): self 123 | { 124 | $currency = strtoupper($currency); 125 | 126 | throw_unless(Currency::check($currency), new UnsupportedCurrencyException($currency)); 127 | 128 | $this->currency = $currency; 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * Set the model for transaction 135 | * 136 | * @param \Illuminate\Database\Eloquent\Model $model 137 | * 138 | * @return $this 139 | */ 140 | public function for(Model $model): self 141 | { 142 | $this->model = $model; 143 | 144 | return $this; 145 | } 146 | 147 | /** 148 | * Set preauthorize option for transaction 149 | * 150 | * @param bool $preauthorize 151 | * 152 | * @return $this 153 | */ 154 | public function preauthorize(bool $preauthorize = true): self 155 | { 156 | $this->preauthorize = $preauthorize; 157 | 158 | return $this; 159 | } 160 | 161 | /** 162 | * Set raw option for transaction 163 | * 164 | * @param bool $raw 165 | * 166 | * @return $this 167 | */ 168 | public function raw(bool $raw = true): self 169 | { 170 | $this->raw = $raw; 171 | 172 | return $this; 173 | } 174 | 175 | /** 176 | * The user will be redirected to this URL, If the transaction is successful 177 | * 178 | * @param string $url 179 | * 180 | * @return $this 181 | */ 182 | public function callback(string $url): self 183 | { 184 | $this->callback = $url; 185 | 186 | return $this; 187 | } 188 | 189 | /** 190 | * The user will be redirected to this URL, if the transaction fails 191 | * 192 | * @param string $url 193 | * 194 | * @return $this 195 | */ 196 | public function callbackError(string $url): self 197 | { 198 | $this->callbackError = $url; 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * @return float 205 | */ 206 | public function getAmount(): float 207 | { 208 | return $this->amount; 209 | } 210 | 211 | /** 212 | * @return string 213 | */ 214 | public function getCurrency(): string 215 | { 216 | return $this->currency; 217 | } 218 | 219 | /** 220 | * @return string 221 | */ 222 | public function getLanguage(): string 223 | { 224 | return $this->lang; 225 | } 226 | 227 | /** 228 | * @return bool 229 | */ 230 | public function getPreauthorize(): bool 231 | { 232 | return $this->preauthorize; 233 | } 234 | 235 | /** 236 | * @return bool 237 | */ 238 | public function getRaw(): bool 239 | { 240 | return $this->raw; 241 | } 242 | 243 | /** 244 | * @return array 245 | */ 246 | public function getSplit(): array 247 | { 248 | return $this->split; 249 | } 250 | 251 | /** 252 | * @return \Illuminate\Database\Eloquent\Model|null 253 | */ 254 | public function getModel(): ?Model 255 | { 256 | return $this->model; 257 | } 258 | 259 | /** 260 | * @return array 261 | * @throws \PayzeIO\LaravelPayze\Exceptions\RoutesNotDefinedException 262 | * @throws \Throwable 263 | */ 264 | public function toRequest(): array 265 | { 266 | $defaultRoutes = config('payze.routes'); 267 | 268 | if ($this->callback) { 269 | $successName = $this->callback; 270 | } else { 271 | throw_if(empty($defaultRoutes['success'] ?? false) || !Route::has($defaultRoutes['success']), new RoutesNotDefinedException); 272 | 273 | $successName = route($defaultRoutes['success']); 274 | } 275 | 276 | if ($this->callbackError) { 277 | $failName = $this->callbackError; 278 | } else { 279 | throw_if(empty($defaultRoutes['fail'] ?? false) || !Route::has($defaultRoutes['fail']), new RoutesNotDefinedException); 280 | 281 | $failName = route($defaultRoutes['fail']); 282 | } 283 | 284 | return [ 285 | 'amount' => $this->amount, 286 | 'currency' => $this->currency, 287 | 'lang' => $this->lang, 288 | 'preauthorize' => $this->preauthorize, 289 | 'callback' => $successName, 290 | 'callbackError' => $failName, 291 | 'split' => filled($this->split) ? array_map(fn(Split $split) => $split->toRequest(), $this->split) : [], 292 | ]; 293 | } 294 | 295 | /** 296 | * @return array 297 | */ 298 | public function toModel(): array 299 | { 300 | return [ 301 | 'model_id' => !empty($this->model) ? $this->model->id : null, 302 | 'model_type' => !empty($this->model) ? Payze::modelType($this->model) : null, 303 | 'method' => $this->method, 304 | 'amount' => $this->amount, 305 | 'currency' => $this->currency, 306 | 'lang' => $this->lang, 307 | 'split' => filled($this->split) ? array_map(fn(Split $split) => $split->toRequest(), $this->split) : [], 308 | ]; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/Payze.php: -------------------------------------------------------------------------------- 1 | getMethod(); 41 | $data = $request->toRequest(); 42 | 43 | $this->log("Starting [$method] payment.", compact('method', 'data')); 44 | $response = $this->request($method, $data)['response']; 45 | 46 | $url = $response['transactionUrl']; 47 | $id = $response['transactionId']; 48 | 49 | throw_if(empty($id) || empty($url), new PaymentRequestException('Transaction ID is missing')); 50 | 51 | $this->log( 52 | "Transaction [$id] created", 53 | compact('id', 'response'), 54 | $transaction = $this->logTransaction($response ?? [], $request) 55 | ); 56 | 57 | if ($method === Method::ADD_CARD && $request instanceof AddCard && filled($response['cardId'] ?? false)) { 58 | $this->saveCard($response['cardId'], $transaction, $request->getAssignedModel()); 59 | } 60 | 61 | return $request->getRaw() ? $response : redirect($url); 62 | } 63 | 64 | /** 65 | * @param \PayzeIO\LaravelPayze\Concerns\ApiRequest $request 66 | * @param string|null $key 67 | * 68 | * @return \PayzeIO\LaravelPayze\Models\PayzeTransaction 69 | * @throws \GuzzleHttp\Exception\GuzzleException 70 | * @throws \PayzeIO\LaravelPayze\Exceptions\ApiCredentialsException 71 | * @throws \PayzeIO\LaravelPayze\Exceptions\PaymentRequestException 72 | * @throws \Throwable 73 | */ 74 | public function processTransaction(ApiRequest $request, string $key = null): PayzeTransaction 75 | { 76 | $response = $this->process($request); 77 | 78 | if ($key) { 79 | $response = $response[$key]; 80 | } 81 | 82 | return Payze::logTransaction($response, is_a($request, PayRequestAttributes::class) ? $request : null); 83 | } 84 | 85 | /** 86 | * @param \PayzeIO\LaravelPayze\Concerns\ApiRequest $request 87 | * @param bool $raw 88 | * 89 | * @return array 90 | * @throws \GuzzleHttp\Exception\GuzzleException 91 | * @throws \PayzeIO\LaravelPayze\Exceptions\ApiCredentialsException 92 | * @throws \PayzeIO\LaravelPayze\Exceptions\PaymentRequestException 93 | * @throws \Throwable 94 | */ 95 | public function process(ApiRequest $request, bool $raw = false): array 96 | { 97 | $method = $request->getMethod(); 98 | $data = $request->toRequest(); 99 | 100 | $this->log("Sending [$method] request", $data); 101 | 102 | $response = $this->request($method, $data); 103 | 104 | $this->log("Received [$method] response", $response); 105 | 106 | return $raw ? $response : $response['response']; 107 | } 108 | 109 | /** 110 | * @param string $token 111 | * @param \PayzeIO\LaravelPayze\Models\PayzeTransaction $transaction 112 | * @param \Illuminate\Database\Eloquent\Model|null $model 113 | * 114 | * @return \PayzeIO\LaravelPayze\Models\PayzeCardToken 115 | */ 116 | protected function saveCard(string $token, PayzeTransaction $transaction, ?Model $model = null): PayzeCardToken 117 | { 118 | return PayzeCardToken::create([ 119 | 'token' => $token, 120 | 'transaction_id' => $transaction->id, 121 | 'model_id' => optional($model)->id ?? $transaction->model_id, 122 | 'model_type' => filled($model) ? Payze::modelType($model) : $transaction->model_type, 123 | ]); 124 | } 125 | 126 | /** 127 | * Create/update transaction entry in database 128 | * 129 | * @param array $data 130 | * @param \PayzeIO\LaravelPayze\Concerns\PayRequestAttributes|null $request 131 | * 132 | * @return \PayzeIO\LaravelPayze\Models\PayzeTransaction 133 | */ 134 | public function logTransaction(array $data, PayRequestAttributes $request = null): PayzeTransaction 135 | { 136 | return tap(PayzeTransaction::firstOrNew([ 137 | 'transaction_id' => $data['transactionId'], 138 | ]), function (PayzeTransaction $transaction) use ($data, $request) { 139 | $transaction->fill(array_merge($request ? $request->toModel() : [], array_filter([ 140 | 'split' => $data['split'] ?? null, 141 | 'status' => $status = $data['status'] ?? Status::CREATED, 142 | 'is_paid' => Status::isPaid($status), 143 | 'is_completed' => Status::isCompleted($status), 144 | 'commission' => $data['commission'] ?? null, 145 | 'final_amount' => $data['finalAmount'] ?? null, 146 | 'can_be_committed' => $data['canBeCommitted'] ?? $data['getCanBeCommitted'] ?? false, 147 | 'refunded' => $data['refunded'] ?? null, 148 | 'refundable' => $data['refundable'] ?? false, 149 | 'card_mask' => $data['cardMask'] ?? null, 150 | 'result_code' => $data['resultCode'] ?? null, 151 | 'log' => $data['log'] ?? null, 152 | ]))); 153 | 154 | foreach (['amount', 'currency'] as $field) { 155 | if (empty($transaction->$field) && !empty($data[$field])) { 156 | $transaction->$field = $data[$field]; 157 | } 158 | } 159 | 160 | if (empty($transaction->lang)) { 161 | $transaction->lang = Language::DEFAULT; 162 | } 163 | 164 | $transaction->save(); 165 | 166 | if ($transaction->is_paid) { 167 | $card = $transaction->card; 168 | 169 | if ($card && !$card->active) { 170 | $card->update([ 171 | 'active' => true, 172 | 'default' => PayzeCardToken::where('model_type', $card->model_type)->where('model_id', $card->model_id)->where('default', true)->doesntExist(), 173 | 'card_mask' => $transaction->card_mask, 174 | 'cardholder' => $data['cardholder'] ?? null, 175 | 'brand' => $data['cardBrand'] ?? null, 176 | 'expiration_date' => $this->parseExpirationDate($data['expirationDate'] ?? null), 177 | ]); 178 | } 179 | } 180 | }); 181 | } 182 | 183 | protected function parseExpirationDate(?string $date): ?Carbon 184 | { 185 | if (empty($date)) { 186 | return null; 187 | } 188 | 189 | $month = substr($date, 0, 2); 190 | $year = substr($date, -2); 191 | 192 | // Some merchants may receive an expiration date in reversed order 193 | // Month is always less than a year, so we can easily detect it and reverse 194 | if (intval($month) > intval($year)) { 195 | [$year, $month] = [$month, $year]; 196 | } 197 | 198 | return Carbon::createFromFormat('dmy', '01' . $month . $year)->endOfMonth(); 199 | } 200 | 201 | /** 202 | * Create log entry in database 203 | * 204 | * @param string $message 205 | * @param array $data 206 | * @param \PayzeIO\LaravelPayze\Models\PayzeTransaction|null $transaction 207 | */ 208 | public function log(string $message, array $data = [], PayzeTransaction $transaction = null): void 209 | { 210 | if (!config('payze.log')) { 211 | return; 212 | } 213 | 214 | PayzeLog::create([ 215 | 'transaction_id' => optional($transaction)->id, 216 | 'message' => $message, 217 | 'payload' => $data, 218 | ]); 219 | } 220 | 221 | /** 222 | * Send API request 223 | * 224 | * @param string $method 225 | * @param array $data 226 | * 227 | * @return array 228 | * @throws \GuzzleHttp\Exception\GuzzleException 229 | * @throws \PayzeIO\LaravelPayze\Exceptions\PaymentRequestException 230 | * @throws \PayzeIO\LaravelPayze\Exceptions\ApiCredentialsException 231 | * @throws \Throwable 232 | */ 233 | public function request(string $method, array $data = []): array 234 | { 235 | $key = config('payze.api_key'); 236 | $secret = config('payze.api_secret'); 237 | 238 | throw_if(empty($key) || empty($secret), new ApiCredentialsException); 239 | 240 | try { 241 | $response = json_decode((new Client)->post('https://payze.io/api/v1', [ 242 | 'headers' => [ 243 | 'Content-Type' => 'application/json', 244 | 'user-agent' => 'laravel-payze', 245 | ], 246 | 'json' => [ 247 | 'method' => $method, 248 | 'apiKey' => $key, 249 | 'apiSecret' => $secret, 250 | 'data' => $data ?: new stdClass, 251 | ], 252 | 'verify' => config('payze.verify_ssl', true), 253 | ])->getBody()->getContents(), true); 254 | } catch (RequestException $exception) { 255 | throw new PaymentRequestException($exception->getMessage()); 256 | } 257 | 258 | throw_unless(empty($response['response']['error']), new PaymentRequestException($response['response']['error'] ?? 'Error')); 259 | 260 | return $response; 261 | } 262 | 263 | /** 264 | * Get a model type regarding relation morph map 265 | * 266 | * @param \Illuminate\Database\Eloquent\Model $model 267 | * 268 | * @return string 269 | */ 270 | public static function modelType(Model $model): string 271 | { 272 | $morphMap = array_flip(Relation::morphMap()); 273 | $class = get_class($model); 274 | 275 | return Arr::get($morphMap, $class, $class); 276 | } 277 | 278 | /** 279 | * @param $transaction 280 | * 281 | * @return string 282 | * @throws \PayzeIO\LaravelPayze\Exceptions\PaymentRequestException 283 | * @throws \Throwable 284 | */ 285 | public static function parseTransaction($transaction): string 286 | { 287 | $isString = is_string($transaction) && filled($transaction); 288 | $isTransaction = is_a($transaction, PayzeTransaction::class); 289 | 290 | throw_unless($isString || $isTransaction, new PaymentRequestException('Please specify valid transaction')); 291 | 292 | if ($isTransaction) { 293 | return $transaction->transaction_id; 294 | } 295 | 296 | return $transaction; 297 | } 298 | 299 | /** 300 | * Define success and fail routes 301 | * 302 | * @param string $controller 303 | * @param string $successMethod 304 | * @param string $failMethod 305 | * 306 | * @return void 307 | */ 308 | public static function routes(string $controller = \App\Http\Controllers\PayzeController::class, string $successMethod = 'success', string $failMethod = 'fail'): void 309 | { 310 | Route::prefix('payze')->name('payze.')->group(function () use ($controller, $successMethod, $failMethod) { 311 | Route::get('success', [$controller, $successMethod])->name('success'); 312 | Route::get('fail', [$controller, $failMethod])->name('fail'); 313 | }); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Payze.io Integration Package 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/payzeio/laravel-payze.svg)](https://packagist.org/packages/payzeio/laravel-payze) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/payzeio/laravel-payze.svg)](https://packagist.org/packages/payzeio/laravel-payze) 5 | [![Downloads Month](https://img.shields.io/packagist/dm/payzeio/laravel-payze.svg)](https://packagist.org/packages/payzeio/laravel-payze) 6 | 7 | This package allows you to process payments with Payze.io from your Laravel application. 8 | 9 | ![Laravel Payze.io Integration](cover.png) 10 | 11 | ### Changelog 12 | 13 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 14 | 15 | ### Upgrading 16 | 17 | After upgrading to a newer version, make sure to run [publish command](#publish-migrations-and-config-by-running) to publish the latest migrations. Also, please copy new [config file](config/payze.php) contents to your existing one. 18 | 19 | ## Table of Contents 20 | 21 | - [Installation](#installation) 22 | - [API Keys](#api-keys) 23 | - [Define Routes](#define-routes) 24 | - [Config](#config) 25 | - [Log](#log) 26 | - [SSL Verification](#ssl-verification) 27 | - [Routes](#routes) 28 | - [Views](#views) 29 | - [Transactions Table](#transactions-table) 30 | - [Logs Table](#logs-table) 31 | - [Card Tokens Table](#card-tokens-table) 32 | - [API Key](#api-key) 33 | - [API Secret](#api-secret) 34 | - [Payments & Requests](#payments--requests) 35 | - [Just Pay](#just-pay) 36 | - [Add (Save) Card](#add-save-card) 37 | - [Pay with a Saved Card](#pay-with-a-saved-card) 38 | - [Commit](#commit) 39 | - [Refund](#refund) 40 | - [Transaction Info](#transaction-info) 41 | - [Merchant's Balance](#merchants-balance) 42 | - [Payment Request Options](#payment-request-options) 43 | - [Amount](#amount) 44 | - [Currency](#currency) 45 | - [Language](#language) 46 | - [Preauthorize](#preauthorize) 47 | - [Associated Model](#associated-model) 48 | - [Split Money](#split-money) 49 | - [Raw Data](#raw-data) 50 | - [Controller](#controller) 51 | - [Events](#events) 52 | - [Relationships](#relationships) 53 | - [Transactions Relationship](#transactions-relationship) 54 | - [Cards Relationship](#cards-relationship) 55 | - [Models](#models) 56 | - [Transaction Model](#transaction-model) 57 | - [Card Token Model](#card-token-model) 58 | - [Log Model](#log-model) 59 | - [Abandoned Transactions](#abandoned-transactions) 60 | - [Authors](#authors) 61 | 62 | ## Installation 63 | 64 | ``` 65 | composer require payzeio/laravel-payze 66 | ``` 67 | 68 | #### For Laravel <= 5.4 69 | 70 | If you're using Laravel 5.4 or lower, you have to manually add a service provider in your `config/app.php` file. 71 | Open `config/app.php` and add `PayzeServiceProvider` to the `providers` array. 72 | 73 | ```php 74 | 'providers' => [ 75 | # Other providers 76 | PayzeIO\LaravelPayze\PayzeServiceProvider::class, 77 | ], 78 | ``` 79 | 80 | #### Publish migrations and config by running: 81 | 82 | ``` 83 | php artisan vendor:publish --provider="PayzeIO\LaravelPayze\PayzeServiceProvider" 84 | ``` 85 | 86 | And run migrations: 87 | 88 | ``` 89 | php artisan migrate 90 | ``` 91 | 92 | ### API Keys 93 | 94 | Go to [Payze.io](https://payze.io) website and generate an API key. Place key and secret to .env file: 95 | 96 | ``` 97 | PAYZE_API_KEY=PayzeApiKey 98 | PAYZE_API_SECRET=PayzeApiSecret 99 | ``` 100 | 101 | ### Define Routes 102 | 103 | You have to define success and fail routes in your application in order to finalize transactions. Go to your `web.php` (or wherever you store routes) and add the following: 104 | 105 | `routes()` function takes 3 **optional** parameters: 106 | 107 | **Controller:** Controller name, default: `App\Http\Controller\PayzeController` 108 | 109 | **Success Method:** Success method name, default: `success` 110 | 111 | **Fail Method:** Fail method name, default: `fail` 112 | 113 | ```php 114 | use PayzeIO\LaravelPayze\Facades\Payze; 115 | 116 | // Other routes... 117 | 118 | Payze::routes(); 119 | ``` 120 | 121 | These routes will have the names `payze.success` and `payze.fail`. If you have defined them under some namespace, then you can update names in config. For example, if you defined payze routes in api.php and that file has the name `api.`, then your routes will be `api.payze.success` and `api.payze.fail`. Update them in `config/payze.php` file, stored in `routes` array. 122 | 123 | ## Config 124 | 125 | The general variables are stored in `config/payze.php` file. More details: 126 | 127 | ### Log 128 | 129 | Enable/disable database detailed logging on every request/transaction. By default, the log is enabled on the local environment only. You can override the value from `.env` or directly from the config file. 130 | 131 | ### SSL Verification 132 | 133 | Enable/Disable SSL verification in Guzzle client to avoid SSL problems on some servers. Enabled by default. 134 | 135 | ### Routes 136 | 137 | Success and fail routes names, which are used to identify the finished transactions and update transaction status in the database. 138 | 139 | Update route names only if you have defined routes in a different namespace (like `api`). For example, you will have `api.payze.success` and `api.payze.fail` URLs. 140 | 141 | ### Views 142 | 143 | Success and fail view names, which are displayed after a transaction is complete. You can override them and use your own pages with your own layout. 144 | 145 | By default, it uses an empty page with just status text (green/red colors) and a "return home" button. 146 | 147 | ### Transactions Table 148 | 149 | The name of the table in the database, which is used to store all the transactions. 150 | 151 | ### Logs Table 152 | 153 | The name of the table in the database, which is used to store detailed logs about transactions and API requests. 154 | 155 | ### Card Tokens Table 156 | 157 | The name of the table in the database, which is used to store all the saved card tokens. 158 | 159 | ### API Key 160 | 161 | API key of your [Payze.io](https://payze.io) account. 162 | 163 | ### API Secret 164 | 165 | API secret of your [Payze.io](https://payze.io) account. 166 | 167 | ## Payments & Requests 168 | 169 | All the requests are sent by corresponding classes, which extends the same class (PayzeIO\LaravelPayze\Concerns\ApiRequest). 170 | 171 | All requests are called statically by `request()` function (passing constructor data), then chain all the needed data and then `process()`. 172 | 173 | Detailed instructions about needed data and options are in the [next section](#payment-request-options). 174 | 175 | ### Just Pay 176 | 177 | If you need a one-time payment, then you should use the Just Pay function. 178 | 179 | **Parameters:** 180 | 181 | - `Amount` - `float`, required 182 | 183 | **Return:** `Illuminate\Http\RedirectResponse` 184 | 185 | ```php 186 | use PayzeIO\LaravelPayze\Requests\JustPay; 187 | 188 | return JustPay::request(1) 189 | ->for($order) // optional 190 | ->preauthorize() // optional 191 | ->process(); 192 | ``` 193 | 194 | ### Add (Save) Card 195 | 196 | Saving a card gives you a card token which you use for further manual charges without customer interaction. You can charge any amount and also save a card in one action, or you can set the amount to 0 to just save a card (Some banks may charge 0.1GEL and refund for saving a card). 197 | 198 | Card tokens are saved in [database](#card-tokens-table) and can be accessed by [PayzeCardToken](src/Models/PayzeCardToken.php) model or [cards relationship](#cards-relationship). 199 | 200 | After requesting a payment, a card token is created in a database with an inactive status. After a successful charge, the card token becomes active automatically. 201 | 202 | **IMPORTANT!** If you want to associate a card token with the user and a transaction with an order, then you should use `assignTo` method, which receives a model instance of the owner of a card token. 203 | 204 | **Parameters:** 205 | 206 | - `Amount` - `float`, optional, default: `0` 207 | 208 | **Methods:** 209 | 210 | - `assignTo` - `Illuminate\Database\Eloquent\Model`, optional, default: `null` 211 | 212 | **Return:** `Illuminate\Http\RedirectResponse` 213 | 214 | ```php 215 | use PayzeIO\LaravelPayze\Requests\AddCard; 216 | 217 | return AddCard::request(1) 218 | ->for($order) // transaction will be assigned to order 219 | ->assignTo($user) // optional: card token will be assigned to user. if not present, then will be assigned to order 220 | ->process(); 221 | ``` 222 | 223 | ### Pay with a Saved Card 224 | 225 | You can pay with a saved card token anytime without customer interaction. 226 | 227 | Card tokens can be accessed by [PayzeCardToken](src/Models/PayzeCardToken.php) model or [cards relationship](#cards-relationship). Read more about [card tokens model here.](#card-token-model) 228 | 229 | **Parameters:** 230 | 231 | - `CardToken` - `PayzeIO\LaravelPayze\Models\PayzeCardToken`, required 232 | - `Amount` - `float`, optional, default: `0` 233 | 234 | **Return:** `PayzeIO\LaravelPayze\Models\PayzeTransaction` 235 | 236 | ```php 237 | use PayzeIO\LaravelPayze\Requests\PayWithCard; 238 | 239 | // Get user's non-expired, default card 240 | $card = $user->cards()->active()->default()->firstOrFail(); 241 | 242 | return PayWithCard::request($card, 15) 243 | ->for($order) // optional 244 | ->process(); 245 | ``` 246 | 247 | ### Commit 248 | 249 | Commit (charge) a blocked ([preauthorized](#preauthorize)) transaction. 250 | 251 | **Parameters:** 252 | 253 | - `TransactionId` - `string|PayzeIO\LaravelPayze\Models\PayzeTransaction`, required 254 | - `Amount` - `float`, optional, default: `0`, (can be partially charged). 0 will charge full amount 255 | 256 | **Return:** `PayzeIO\LaravelPayze\Models\PayzeTransaction` 257 | 258 | ```php 259 | use PayzeIO\LaravelPayze\Requests\Commit; 260 | 261 | return Commit::request($transaction)->process(); 262 | ``` 263 | 264 | ### Refund 265 | 266 | Refund a [refundable](#refundable-scope) transaction. 267 | 268 | **Parameters:** 269 | 270 | - `TransactionId` - `string|PayzeIO\LaravelPayze\Models\PayzeTransaction`, required 271 | - `Amount` - `float`, optional, default: `0`, (can be partially refunded). 0 will refund full amount 272 | 273 | **Return:** `PayzeIO\LaravelPayze\Models\PayzeTransaction` 274 | 275 | ```php 276 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 277 | use PayzeIO\LaravelPayze\Requests\Refund; 278 | 279 | $transaction = PayzeTransaction::refundable()->latest()->firstOrFail(); 280 | 281 | return Refund::request($transaction)->process(); 282 | ``` 283 | 284 | ### Transaction Info 285 | 286 | Get transaction info and update in the database. 287 | 288 | **Parameters:** 289 | 290 | - `TransactionId` - `string|PayzeIO\LaravelPayze\Models\PayzeTransaction`, required 291 | 292 | **Return:** `PayzeIO\LaravelPayze\Models\PayzeTransaction` 293 | 294 | ```php 295 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 296 | use PayzeIO\LaravelPayze\Requests\GetTransactionInfo; 297 | 298 | $transaction = PayzeTransaction::latest()->firstOrFail(); 299 | 300 | return GetTransactionInfo::request($transaction)->process(); 301 | ``` 302 | 303 | ### Merchant's balance 304 | 305 | Get balance info from the merchant's account. 306 | 307 | **Return:** `array` 308 | 309 | ```php 310 | use PayzeIO\LaravelPayze\Requests\GetBalance; 311 | 312 | return GetBalance::request()->process(); 313 | ``` 314 | 315 | ## Payment Request Options 316 | 317 | You can pass these parameters to all the payment requests in Payze package. 318 | 319 | ### Amount 320 | 321 | All payment requests have an amount in the constructor, but also there is a separate method for changing the amount. 322 | 323 | ```php 324 | use PayzeIO\LaravelPayze\Requests\JustPay; 325 | 326 | // Request 1 GEL originally 327 | $request = JustPay::request(1); 328 | 329 | // Some things happened, updating amount 330 | return $request->amount(10)->process(); 331 | ``` 332 | 333 | ### Currency 334 | 335 | You can change your payment's currency by calling `currency()` function on the request. Default: `GEL` 336 | 337 | See supported currencies in [currencies enum file](src/Enums/Currency.php). 338 | 339 | **Recommended:** Pass currency by using an enum instead of directly passing a string. 340 | 341 | ```php 342 | use PayzeIO\LaravelPayze\Enums\Currency; 343 | use PayzeIO\LaravelPayze\Requests\JustPay; 344 | 345 | return JustPay::request(1)->currency(Currency::USD)->process(); 346 | ``` 347 | 348 | ### Language 349 | 350 | You can change your payment page's language by calling `language()` function on the request. Default: `ge` 351 | 352 | See supported languages in [languages enum file](src/Enums/Language.php). 353 | 354 | **Recommended:** Pass language by using an enum instead of directly passing a string. 355 | 356 | ```php 357 | use PayzeIO\LaravelPayze\Enums\Language; 358 | use PayzeIO\LaravelPayze\Requests\JustPay; 359 | 360 | return JustPay::request(1)->language(Language::ENG)->process(); 361 | ``` 362 | 363 | ### Preauthorize 364 | 365 | Preauthorize method is used to block the amount for some time and then manually charge ([commit](#commit)) the transaction. For example, if you are selling products which have to be produced after the order, block (preauthorize) transaction on order and manually charge ([commit](#commit)) after your product are ready. 366 | 367 | ### Associated Model 368 | 369 | You can associate any Eloquent model to a transaction by calling `for()` function on the request. For example, pass an order instance to a payment request for checking the order's payment status after payment. 370 | 371 | ```php 372 | use App\Models\Order; 373 | use PayzeIO\LaravelPayze\Requests\JustPay; 374 | 375 | $order = Order::findOrFail($orderId); 376 | 377 | return JustPay::request(1)->for($order)->process(); 378 | ``` 379 | 380 | ### Split Money 381 | 382 | You can split the money into different bank accounts. For example, you have a marketplace where users sell their products, and you get a commission for that. You can simply split transferred money easily instead of manually transferring from a bank account to a seller on every order. 383 | 384 | You have to call `split()` function on the request, which accepts list/array of `PayzeIO\LaravelPayze\Objects\Split` object(s). 385 | 386 | Split object has three parameters: `Amount`, `Receiver's IBAN`, and `Pay In (optional)` (delay in days before transferring the money). 387 | 388 | For example, the cost of a product is 20GEL. You have to get your commission (10%) and transfer the rest to a seller. 389 | 390 | ```php 391 | use PayzeIO\LaravelPayze\Objects\Split; 392 | use PayzeIO\LaravelPayze\Requests\JustPay; 393 | 394 | return JustPay::request(20) 395 | ->split( 396 | new Split(2, "Your IBAN"), // Transfer 2GEL immediately 397 | new Split(18, "Seller's IBAN", 3) // Transfer 18GEL after 3 days (for example, as an insurance before processing the order) 398 | )->process(); 399 | ``` 400 | 401 | ### Raw Data 402 | 403 | By default, when you request a payment, it will return a RedirectResponse. You can call `raw()` function and payment request will return the original data instead of a RedirectResponse. 404 | 405 | ```php 406 | use PayzeIO\LaravelPayze\Requests\JustPay; 407 | 408 | $request = JustPay::request(20)->raw()->process(); 409 | 410 | log($request['transactionId']); 411 | 412 | return $request['transactionUrl']; 413 | ``` 414 | 415 | ## Controller 416 | 417 | Default controller should be published after running publish command from [Installation section](#installation). You can add your custom logic to your controller in `successResponse` and `failResponse` methods. For example, you can set flash message, send notifications, mark order as complete or anything you want from that methods. 418 | 419 | **IMPORTANT!** If you return any non-NULL value from `successResponse` and `failResponse` methods, then that response will be used on success/fail routes. Otherwise [default logic](#views) will be used. 420 | 421 | ## Events 422 | 423 | Events are fired after successful or failed transactions. You can [define listeners](https://laravel.com/docs/8.x/events#defining-listeners) in your application in order to mark an order as paid, notify a customer or whatever you need. 424 | 425 | Both events have `$transaction` property. 426 | 427 | Paid Event: `PayzeIO\LaravelPayze\Events\PayzeTransactionPaid` 428 | 429 | Failed Event: `PayzeIO\LaravelPayze\Events\PayzeTransactionFailed` 430 | 431 | ## Relationships 432 | 433 | You can add `transactions` and `cards` relationships to your models with traits to easily access associated entries. 434 | 435 | ### Transactions Relationship 436 | 437 | Add `HasTransactions` trait to your model. 438 | 439 | ```php 440 | use PayzeIO\LaravelPayze\Traits\HasTransactions; 441 | 442 | class Order extends Model 443 | { 444 | use HasTransactions; 445 | } 446 | ``` 447 | 448 | Now you can access transactions by calling `$order->transactions`. 449 | 450 | ### Cards Relationship 451 | 452 | Add `HasCards` trait to your model. 453 | 454 | ```php 455 | use PayzeIO\LaravelPayze\Traits\HasCards; 456 | 457 | class User extends Model 458 | { 459 | use HasCards; 460 | } 461 | ``` 462 | 463 | Now you can access saved cards by calling `$user->cards`. 464 | 465 | ## Models 466 | 467 | ### Transaction Model 468 | 469 | You can access all transactions logged in the database by `PayzeIO\LaravelPayze\Models\PayzeTransaction` model. 470 | 471 | Get all transactions: 472 | 473 | ```php 474 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 475 | 476 | PayzeTransaction::all(); 477 | ``` 478 | 479 | #### Paid Scope 480 | 481 | Filter paid transactions with `paid()` scope. 482 | 483 | ```php 484 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 485 | 486 | PayzeTransaction::paid()->get(); 487 | ``` 488 | 489 | #### Unpaid Scope 490 | 491 | Filter unpaid transactions with `unpaid()` scope. 492 | 493 | ```php 494 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 495 | 496 | PayzeTransaction::unpaid()->get(); 497 | ``` 498 | 499 | #### Completed Scope 500 | 501 | Filter completed transactions with `completed()` scope. 502 | 503 | ```php 504 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 505 | 506 | PayzeTransaction::completed()->get(); 507 | ``` 508 | 509 | #### Incomplete Scope 510 | 511 | Filter incomplete transactions with `incomplete()` scope. 512 | 513 | ```php 514 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 515 | 516 | PayzeTransaction::incomplete()->get(); 517 | ``` 518 | 519 | #### Refundable Scope 520 | 521 | Filter refundable transactions with `refundable()` scope. 522 | 523 | ```php 524 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 525 | 526 | PayzeTransaction::refundable()->get(); 527 | ``` 528 | 529 | #### Non-Refundable Scope 530 | 531 | Filter non-refundable transactions with `nonrefundable()` scope. 532 | 533 | ```php 534 | use PayzeIO\LaravelPayze\Models\PayzeTransaction; 535 | 536 | PayzeTransaction::nonrefundable()->get(); 537 | ``` 538 | 539 | ### Card Token Model 540 | 541 | You can access all saved card tokens logged in the database by `PayzeIO\LaravelPayze\Models\PayzeCardToken` model. 542 | 543 | **NOTICE:** After starting AddCard payment, new database entry is created with non-active token which gets activated after successful payment. So `Active` card refers to a valid token, which can be used in future payments. 544 | 545 | Get all active tokens: 546 | 547 | Tokens are automatically filtered by a global scope and only returns active tokens. 548 | 549 | ```php 550 | use PayzeIO\LaravelPayze\Models\PayzeCardToken; 551 | 552 | PayzeCardToken::all(); 553 | ``` 554 | 555 | Active card tokens have `card_mask`, `cardholder`, `brand`, `expiration_date` attributes, which can be helpful for a user to choose correct card. 556 | 557 | #### Active (Non-Expired) Scope 558 | 559 | Since tokens are automatically filtered by a global scope, `active()` scope now returns non-expired card tokens based on expiration date. 560 | 561 | Filter active (non-expired) card tokens with `active()` scope. 562 | 563 | ```php 564 | use PayzeIO\LaravelPayze\Models\PayzeCardToken; 565 | 566 | PayzeCardToken::active()->get(); 567 | ``` 568 | 569 | #### WithInactive Scope 570 | 571 | Tokens are automatically filtered by a global scope and only returns active tokens. If you want to include inactive card tokens in the list, you should add `withInactive()` scope to a query: 572 | 573 | ```php 574 | use PayzeIO\LaravelPayze\Models\PayzeCardToken; 575 | 576 | PayzeCardToken::withInactive()->get(); 577 | ``` 578 | 579 | #### Inactive Scope 580 | 581 | Filter inactive card tokens with `inactive()` scope. This method already includes `withInactive()` scope, so you don't have to specify it manually. 582 | 583 | ```php 584 | use PayzeIO\LaravelPayze\Models\PayzeCardToken; 585 | 586 | PayzeCardToken::inactive()->get(); 587 | ``` 588 | 589 | #### Is Expired 590 | 591 | You can check if already fetched PayzeCardToken model instance is expired or not. 592 | 593 | Method will return false if expiration date is not filled in database. 594 | 595 | ```php 596 | use PayzeIO\LaravelPayze\Models\PayzeCardToken; 597 | 598 | $token = PayzeCardToken::latest()->get(); 599 | 600 | $token->isExpired(); 601 | ``` 602 | 603 | #### Is Active (Non-expired) 604 | 605 | You can check if already fetched PayzeCardToken model instance is expired or not. 606 | 607 | Method will return true if expiration date is not filled in database. 608 | 609 | ```php 610 | use PayzeIO\LaravelPayze\Models\PayzeCardToken; 611 | 612 | $card = PayzeCardToken::latest()->get(); 613 | 614 | $card->isActive(); 615 | ``` 616 | 617 | #### Mark as Default 618 | 619 | You can set current card as a default. All other cards will be unmarked automatically. 620 | 621 | ```php 622 | use PayzeIO\LaravelPayze\Models\PayzeCardToken; 623 | 624 | $card = PayzeCardToken::latest()->get(); 625 | 626 | $card->markAsDefault(); 627 | ``` 628 | 629 | ### Log Model 630 | 631 | You can access all logs from the database by `PayzeIO\LaravelPayze\Models\PayzeLog` model. 632 | 633 | Get all logs: 634 | 635 | ```php 636 | use PayzeIO\LaravelPayze\Models\PayzeLog; 637 | 638 | PayzeLog::all(); 639 | ``` 640 | 641 | ## Abandoned Transactions 642 | 643 | Abandoned transactions with status `Created` are automatically reject after about 10 minutes, so you have to run a scheduler to update those transactions' statuses. 644 | 645 | If you don't already have a scheduler configured, read [how to configure](https://laravel.com/docs/scheduling#running-the-scheduler) here. 646 | 647 | Register our console command in `app/Console/Kernel.php`'s `$commands` variable: 648 | 649 | ```php 650 | use PayzeIO\LaravelPayze\Console\Commands\UpdateIncompleteTransactions; 651 | 652 | protected $commands = [ 653 | // Other commands 654 | UpdateIncompleteTransactions::class, 655 | ]; 656 | ``` 657 | 658 | Then add a command in a schedule in `app/Console/Kernel.php`'s `schedule` function. We recommend running a job every 30 minutes, but it's totally up to you and your application needs. 659 | 660 | ```php 661 | use PayzeIO\LaravelPayze\Console\Commands\UpdateIncompleteTransactions; 662 | 663 | protected function schedule(Schedule $schedule) 664 | { 665 | // Other commands 666 | $schedule->command(UpdateIncompleteTransactions::class)->everyThirtyMinutes(); 667 | } 668 | ``` 669 | 670 | ## Authors 671 | 672 | - [Levan Lotuashvili](https://github.com/Lotuashvili) 673 | - [All Contributors](../../contributors) 674 | --------------------------------------------------------------------------------