diff --git a/README.md b/README.md index ed72139..477ea37 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - [Webhooks Events]() - [Verify webhook signature manually](#Verify-webhook-signature) - [Validate webhook signature manually](#validate-webhook-signature) + - [Commands](#commands) - [Testing and test cards](#Testing-and-test-cards) ## Introduction @@ -565,6 +566,16 @@ class YouCanPayWebhooksController extends Controller } ``` +### Commands + +If you need to clean the pending transactions, there's a command + +```shell +php artisan youcanpay:clean-pending-transactions +``` + +You can add it to the scheduler that can run the command every single time, so the clean will be automatic and depend on the `tolerance` value that is defined in the `youcanpay` config file. + ## Testing and test cards | **CARD** | **CVV** | **DATE** | **BEHAVIOUR** | @@ -604,7 +615,3 @@ The MIT License (MIT). Please see [License File](LICENSE.md) for more informatio ## Laravel Package Boilerplate This package was generated using the [Laravel Package Boilerplate](https://laravelpackageboilerplate.com). - -``` - -``` diff --git a/config/youcanpay.php b/config/youcanpay.php index ed0849d..15c0711 100644 --- a/config/youcanpay.php +++ b/config/youcanpay.php @@ -15,6 +15,15 @@ "success_redirect_uri" => env("SUCCCESS_REDIRECT_URI"), - "fail_redirect_uri" => env("FAIL_REDIRECT_URI") + "fail_redirect_uri" => env("FAIL_REDIRECT_URI"), + + /* + * The tolerance value at which to look to the old pending transactions, + * and remove them from the database. + * By default it's set to 48 hours + */ + "transaction" => [ + "tolerance" => env('TRANSACTION_TOLERANCE', 60 * 60 * 48), + ] ]; diff --git a/database/factories/TransactionFactory.php b/database/factories/TransactionFactory.php index 983b2bf..df3ef99 100644 --- a/database/factories/TransactionFactory.php +++ b/database/factories/TransactionFactory.php @@ -37,4 +37,19 @@ public function definition() 'payload'=> [] ]; } + + + /** + * Indicate that the transaction is pending. + * + * @return \Illuminate\Database\Eloquent\Factories\Factory + */ + public function pending() + { + return $this->state(function (array $attributes) { + return [ + 'status' => YouCanPayStatus::pending(), + ]; + }); + } } diff --git a/src/Console/CleanPendingTransactionCommand.php b/src/Console/CleanPendingTransactionCommand.php new file mode 100644 index 0000000..8f42b87 --- /dev/null +++ b/src/Console/CleanPendingTransactionCommand.php @@ -0,0 +1,43 @@ +where('created_at', '<=', Carbon::now()) + ->where('created_at', '>=', Carbon::now()->subSeconds($tolerance ?? 60*60*24)) + ->get(); + + foreach ($transactions as $transaction) { + $transaction->delete(); + } + } +} diff --git a/src/Facades/LaravelYouCanPay.php b/src/Facades/LaravelYouCanPay.php index 18b5f48..dc1aafc 100644 --- a/src/Facades/LaravelYouCanPay.php +++ b/src/Facades/LaravelYouCanPay.php @@ -4,9 +4,6 @@ use Illuminate\Support\Facades\Facade; -/** - * @see \Devinweb\LaravelYouCanPay\Skeleton\SkeletonClass - */ class LaravelYouCanPay extends Facade { /** diff --git a/src/LaravelYoucanPay.php b/src/LaravelYoucanPay.php deleted file mode 100644 index abacb56..0000000 --- a/src/LaravelYoucanPay.php +++ /dev/null @@ -1,297 +0,0 @@ - 'required', - 'amount' => 'required', - ]; - - /** - * Price - * - * @var int - */ - private $price; - - /** - * Order id - * - * @var string - */ - private $order_id; - - /** - * Youcan Pay instance - * - * @var \YouCan\Pay\YouCanPay - */ - private $youcanpay_instance; - - /** - * The config data - * - * @var array - */ - private $config; - - /** - * Tokenization fileds - * - * @var array - */ - private $required_tokenization_fields; - - /** - * Ip address - * - * @var string - */ - private $ip; - - /** - * @var \YouCan\Pay\API\Endpoints\TokenEndpoint - */ - private $token; - - /** - * The metadata is the data retrieved after the response or in the webhook - * - * @var array - */ - private $metadata; - - /** - * The Customer info - * - * @var array - */ - private $customer_info; - - /** - * The default customer model class name. - * - * @var string - */ - public static $customerModel = 'App\\Models\\User'; - - /** - * Create a new LaravelYouCanPay instance. - * - * @return void - */ - public function __construct() - { - $this->config = config('youcanpay'); - - if ($this->config['sandboxMode']) { - YouCanPay::setIsSandboxMode(true); - } - - $this->required_tokenization_fields = self::REQUIRED_FIELDS; - - $this->youcanpay_instance = YouCanPay::instance()->useKeys($this->config['private_key'], $this->config['public_key']); - } - - /** - * Create a Tokenization - * - * @param array $paramters - * @param \Illuminate\Http\Request $request - * @return $this - */ - public function createTokenization(array $attributes, Request $request) - { - $this->validateTokenizationParameters($attributes); - - $this->price = Arr::get($attributes, 'amount'); - - $this->order_id = Arr::get($attributes, 'order_id'); - - $this->ip = $request->ip(); - - $this->metadata = $this->metadata??[]; - - $this->customer_info = $this->customer_info??[]; - - $this->token =app(CreateToken::class)( - $this->youcanpay_instance, - [ - 'attributes' => $attributes, - 'config' => $this->config, - 'customer_info' => $this->customer_info, - 'metadata' => $this->metadata, - 'ip'=> $this->ip - ] - ); - - return $this; - } - - /** - * Set the customer model class name. - * - * @param string $customerModel - * @return void - */ - public static function useCustomerModel($customerModel) - { - static::$customerModel = $customerModel; - } - - /** - * Get the customer instance by its YouCanPay ID. - * - * @param string|null $orderId - * @return \Illuminate\Database\Eloquent\Model|null - */ - public function findBillable($orderId) - { - return $orderId ? Transaction::where('order_id', $orderId)->first()->user : null; - } - - /** - * Set the customer data - * - * @param array $customer_info - * @return $this - */ - public function setCustomerInfo(array $customer_info) - { - $this->customer_info = $customer_info; - - return $this; - } - - /** - * Set the metadata data to use them when the app receive a callback - * - * @param array $metadata - * @return $this - */ - public function setMetadata(array $metadata) - { - $this->metadata = $metadata; - - return $this; - } - - /** - * Get the token id - * - * @return string - */ - public function getId() - { - $this->createPendingTransaction(); - - return $this->token->getId(); - } - - /** - * Get the payment URL - * - * @param string|null $lang - * @return string - */ - public function getPaymentURL(?string $lang=null) - { - $this->createPendingTransaction(); - $lang = $lang ?? config('app.locale'); - - return $this->token->getPaymentURL($lang); - } - - - /** - * Check the keys (private_key, public_key) - * - * @param string|null $privateKey - * @param string|null $publicKey - * @return boolean - */ - public function checkKeys(?string $privateKey = null, ?string $publicKey = null): bool - { - return YouCanPay::instance()->checkKeys($privateKey, $publicKey); - } - - - /** - * Verify the webhook signature - * - * @param string $signature - * @param array $payload - * @return boolean - */ - public function verifyWebhookSignature(string $signature, array $payload): bool - { - return $this->youcanpay_instance->verifyWebhookSignature($signature, $payload); - } - - /** - * Validate the webhook signature - * - * @param string $signature - * @param array $payload - * @throws \YouCan\Pay\API\Exceptions\InvalidWebhookSignatureException - * @return void - */ - public function validateWebhookSignature(string $signature, array $payload): void - { - $this->youcanpay_instance->validateWebhookSignature($signature, $payload); - } - - /** - * Validate the toknization required fields - * - * @param array $attributes - * @throws \InvalidArgumentException - * @return void - */ - private function validateTokenizationParameters(array $attributes) - { - foreach ($this->required_tokenization_fields as $key => $value) { - if ($value == 'required' && !Arr::has($attributes, $key)) { - throw new InvalidArgumentException("The ${key} must be availabe in the array"); - } - } - } - - /** - * Create pending transaction - * - * @return void - */ - private function createPendingTransaction() - { - $email = Arr::get($this->customer_info, 'email'); - $user = (new static::$customerModel)->where('email', $email)->first(); - - Transaction::create([ - 'user_id' => $user? $user->id : null, - 'name' => 'default', - 'order_id' => $this->order_id, - 'status' => YouCanPayStatus::PENDING(), - 'price' => $this->price, - 'payload' => [ - 'payload' => [ - 'metadata' => $this->metadata, - 'customer' => $this->customer_info - ] - ] - ]); - } -} diff --git a/src/Providers/LaravelYouCanPayServiceProvider.php b/src/Providers/LaravelYouCanPayServiceProvider.php index c1f836b..4bcbc0d 100644 --- a/src/Providers/LaravelYouCanPayServiceProvider.php +++ b/src/Providers/LaravelYouCanPayServiceProvider.php @@ -2,6 +2,7 @@ namespace Devinweb\LaravelYouCanPay\Providers; +use Devinweb\LaravelYouCanPay\Console\CleanPendingTransactionCommand; use Devinweb\LaravelYouCanPay\Http\Middleware\VerifyWebhookSignature; use Devinweb\LaravelYouCanPay\LaravelYouCanPay; use Illuminate\Support\ServiceProvider; @@ -20,6 +21,7 @@ public function boot() $this->registerRoutes(); $this->registerMigrations(); $this->registerPublishing(); + $this->registerCommands(); } /** @@ -96,4 +98,18 @@ protected function registerMiddleware() $router = $this->app->make(Router::class); $router->aliasMiddleware('verify-youcanpay-webhook-signature', VerifyWebhookSignature::class); } + + /** + * Register the package's commands. + * + * @return void + */ + protected function registerCommands() + { + if ($this->app->runningInConsole()) { + $this->commands([ + CleanPendingTransactionCommand::class, + ]); + } + } } diff --git a/src/Providers/LaravelYoucanPayServiceProvider.php b/src/Providers/LaravelYoucanPayServiceProvider.php deleted file mode 100644 index c1f836b..0000000 --- a/src/Providers/LaravelYoucanPayServiceProvider.php +++ /dev/null @@ -1,99 +0,0 @@ -registerMiddleware(); - $this->registerRoutes(); - $this->registerMigrations(); - $this->registerPublishing(); - } - - /** - * Register the package migrations. - * - * @return void - */ - protected function registerMigrations() - { - if ($this->app->runningInConsole()) { - $this->loadMigrationsFrom(__DIR__.'/../../database/migrations'); - } - } - - - /** - * Register the package's publishable resources. - * - * @return void - */ - protected function registerPublishing() - { - if ($this->app->runningInConsole()) { - $this->publishes([ - __DIR__.'/../../config/youcanpay.php' => config_path('youcanpay.php'), - ], 'youcanpay-config'); - - - $this->publishes([ - __DIR__.'/../../database/migrations' => $this->app->databasePath('migrations'), - ], 'youcanpay-migrations'); - } - } - - - /** - * Register the application services. - */ - public function register() - { - // Automatically apply the package configuration - $this->mergeConfigFrom(__DIR__.'/../../config/youcanpay.php', 'youcanpay'); - - // Register the main class to use with the facade - $this->app->singleton('laravel-youcan-pay', function () { - return new LaravelYouCanPay; - }); - } - - - /** - * Register the package routes. - * - * @return void - */ - protected function registerRoutes() - { - Route::group([ - 'prefix' => 'youcanpay', - 'namespace' => 'Devinweb\LaravelYouCanPay\Http\Controllers', - 'as' => 'youcanpay.', - ], function () { - $this->loadRoutesFrom(__DIR__.'/../../routes/web.php'); - }); - } - - /** - * Undocumented function - * - * @return void - */ - protected function registerMiddleware() - { - $router = $this->app->make(Router::class); - $router->aliasMiddleware('verify-youcanpay-webhook-signature', VerifyWebhookSignature::class); - } -} diff --git a/tests/CommandTest.php b/tests/CommandTest.php new file mode 100644 index 0000000..6cbe36d --- /dev/null +++ b/tests/CommandTest.php @@ -0,0 +1,40 @@ +now = Carbon::now(); + } + + /** + * @test + * @return void + */ + public function test_clean_pending_transactions() + { + $tolerance = config('youcanpay.transaction.tolerance'); + Transaction::factory()->pending()->count(3)->create(); + $this->assertDatabaseCount('transactions', 3); + $this->now = now()->addSeconds($tolerance); + Carbon::setTestNow($this->now); + $this->artisan('youcanpay:clean-pending-transactions'); + $this->assertDatabaseCount('transactions', 0); + } +}