diff --git a/composer.json b/composer.json index a4ef9a8..2b6eba3 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "kalimeromk/casys-laravel", "description": "Integration of casys payment method", "require": { - "php": ">=7.4.0", + "php": ">=8.0", "ext-json": "*", "ext-soap": "*" }, @@ -43,6 +43,7 @@ }, "require-dev": { "orchestra/testbench": "^5.0|^6.0|^7.0|8.0", - "mockery/mockery": "^1.4|^1.5|^1.6|^1.7" + "mockery/mockery": "^1.4|^1.5|^1.6|^1.7", + "larastan/larastan": "^2.0" } } diff --git a/composer.lock b/composer.lock index 3635ac2..7c634c4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "39c17ba2d5b1017414f7619a16e140c6", + "content-hash": "e3afe7deef4b61871c90ae8d2e7cb92d", "packages": [], "packages-dev": [ { @@ -955,6 +955,102 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "larastan/larastan", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/larastan/larastan.git", + "reference": "238fdbfba3aae133cdec73e99826c9b0232141f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/larastan/larastan/zipball/238fdbfba3aae133cdec73e99826c9b0232141f7", + "reference": "238fdbfba3aae133cdec73e99826c9b0232141f7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^9.47.0 || ^10.0.0", + "illuminate/container": "^9.47.0 || ^10.0.0", + "illuminate/contracts": "^9.47.0 || ^10.0.0", + "illuminate/database": "^9.47.0 || ^10.0.0", + "illuminate/http": "^9.47.0 || ^10.0.0", + "illuminate/pipeline": "^9.47.0 || ^10.0.0", + "illuminate/support": "^9.47.0 || ^10.0.0", + "php": "^8.0.2", + "phpmyadmin/sql-parser": "^5.6.0", + "phpstan/phpstan": "^1.9.8" + }, + "require-dev": { + "nikic/php-parser": "^4.15.2", + "orchestra/testbench": "^7.19.0|^8.0.0", + "phpunit/phpunit": "^9.5.27" + }, + "suggest": { + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "NunoMaduro\\Larastan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" + ], + "support": { + "issues": "https://github.com/larastan/larastan/issues", + "source": "https://github.com/larastan/larastan/tree/2.4.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/canvural", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2023-02-05T12:19:17+00:00" + }, { "name": "laravel/framework", "version": "v10.0.3", @@ -2624,6 +2720,93 @@ ], "time": "2024-07-21T15:55:45+00:00" }, + { + "name": "phpmyadmin/sql-parser", + "version": "5.10.1", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/sql-parser.git", + "reference": "b14fd66496a22d8dd7f7e2791edd9e8674422f17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/b14fd66496a22d8dd7f7e2791edd9e8674422f17", + "reference": "b14fd66496a22d8dd7f7e2791edd9e8674422f17", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpmyadmin/motranslator": "<3.0" + }, + "require-dev": { + "phpbench/phpbench": "^1.1", + "phpmyadmin/coding-standard": "^3.0", + "phpmyadmin/motranslator": "^4.0 || ^5.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.9.12", + "phpstan/phpstan-phpunit": "^1.3.3", + "phpunit/phpunit": "^8.5 || ^9.6", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.11", + "zumba/json-serializer": "~3.0.2" + }, + "suggest": { + "ext-mbstring": "For best performance", + "phpmyadmin/motranslator": "Translate messages to your favorite locale" + }, + "bin": [ + "bin/highlight-query", + "bin/lint-query", + "bin/sql-parser", + "bin/tokenize-query" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\SqlParser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "A validating SQL lexer and parser with a focus on MySQL dialect.", + "homepage": "https://github.com/phpmyadmin/sql-parser", + "keywords": [ + "analysis", + "lexer", + "parser", + "query linter", + "sql", + "sql lexer", + "sql linter", + "sql parser", + "sql syntax highlighter", + "sql tokenizer" + ], + "support": { + "issues": "https://github.com/phpmyadmin/sql-parser/issues", + "source": "https://github.com/phpmyadmin/sql-parser" + }, + "funding": [ + { + "url": "https://www.phpmyadmin.net/donate/", + "type": "other" + } + ], + "time": "2024-11-10T04:10:31+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -7931,8 +8114,9 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.3.0", - "ext-json": "*" + "php": ">=7.4.0", + "ext-json": "*", + "ext-soap": "*" }, "platform-dev": {}, "plugin-api-version": "2.6.0" diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..54817df --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +includes: + - vendor/larastan/larastan/extension.neon + - vendor/nesbot/carbon/extension.neon + +parameters: + + paths: + - src/ + + # Level 9 is the highest level + level: 9 \ No newline at end of file diff --git a/src/CasysServiceProvider.php b/src/CasysServiceProvider.php index b93652b..dd3304e 100644 --- a/src/CasysServiceProvider.php +++ b/src/CasysServiceProvider.php @@ -3,26 +3,26 @@ namespace Kalimero\Casys; use Illuminate\Support\ServiceProvider; -use Kalimero\Casys\Http\Service\RecurringPayment; +use Kalimero\Casys\Interfaces\RecurringPaymentInterface; +use Kalimero\Casys\Service\RecurringPayment; +use SoapClient; +use InvalidArgumentException; class CasysServiceProvider extends ServiceProvider { - - /** * Bootstrap the application events. * * @return void */ - public function boot() + public function boot(): void { - - $this->loadViewsFrom(__DIR__.'/resources/views', 'casys'); - $this->loadRoutesFrom(__DIR__.'/routes/casys.php'); + $this->loadViewsFrom(__DIR__ . '/resources/views', 'casys'); + $this->loadRoutesFrom(__DIR__ . '/routes/casys.php'); $this->publishes([ __DIR__ . '/resources/views' => resource_path('views/vendor/casys'), - __DIR__ . '/config/casys.php' => config_path('/casys.php'), + __DIR__ . '/config/casys.php' => config_path('casys.php'), ]); } @@ -31,13 +31,22 @@ public function boot() * * @return void */ - public function register() + public function register(): void { $this->registerConfig(); - $this->app->bind(RecurringPayment::class, function () { - return new RecurringPayment(); - }); + $this->app->singleton(RecurringPaymentInterface::class, function ($app) { + $wsdl = config('casys.RecurrentPaymentWsdl'); + + // Ensure the WSDL is valid + if (!is_string($wsdl) || empty($wsdl)) { + throw new InvalidArgumentException('The WSDL URL for the Recurrent Payment service must be a valid non-empty string.'); + } + + $soapClient = new SoapClient($wsdl); + + return new RecurringPayment($soapClient); + }); } /** @@ -45,9 +54,8 @@ public function register() * * @return void */ - protected function registerConfig() + protected function registerConfig(): void { - $this->mergeConfigFrom(__DIR__.'/config/casys.php', 'casys'); + $this->mergeConfigFrom(__DIR__ . '/config/casys.php', 'casys'); } - } diff --git a/src/Http/Controllers/CasysController.php b/src/Http/Controllers/CasysController.php index 10bf6ee..11b244f 100644 --- a/src/Http/Controllers/CasysController.php +++ b/src/Http/Controllers/CasysController.php @@ -6,7 +6,8 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; use Illuminate\Routing\Controller; -use Kalimero\Casys\Http\Service\Casys; +use Kalimero\Casys\Service\Casys; +use stdClass; class CasysController extends Controller { @@ -27,7 +28,7 @@ public function __construct(Casys $casys) * * @return Application|Factory|View */ - public function index() + public function index(): View|Factory|Application { return view('casys::loader'); } @@ -35,11 +36,11 @@ public function index() /** * Generate and display the payment data. * - * @param $client - * @param $amount + * @param stdClass $client An object containing client data (name, last_name, country, email). + * @param float $amount The amount to be paid. * @return Application|Factory|View */ - public function getCasys($client, $amount) + public function getCasys(stdClass $client, float $amount): View|Factory|Application { $casysData = $this->casys->getCasysData($client, $amount); return view('casys::index', compact('casysData')); @@ -50,7 +51,7 @@ public function getCasys($client, $amount) * * @return Application|Factory|View */ - public function success() + public function success(): View|Factory|Application { return view('casys::okurl')->with('success', 'Your transaction was successful'); } @@ -60,7 +61,7 @@ public function success() * * @return Application|Factory|View */ - public function fail() + public function fail(): View|Factory|Application { return view('casys::failurl')->with('error', 'Your transaction failed'); } diff --git a/src/Http/Controllers/RecurringPaymentController.php b/src/Http/Controllers/RecurringPaymentController.php index e7e15ae..e26c1ec 100644 --- a/src/Http/Controllers/RecurringPaymentController.php +++ b/src/Http/Controllers/RecurringPaymentController.php @@ -2,41 +2,44 @@ namespace Kalimero\Casys\Http\Controllers; -use Exception; +use Illuminate\Http\JsonResponse; use Illuminate\Routing\Controller; -use Kalimero\Casys\Http\Requests\handleRecurringPaymentRequest; -use Kalimero\Casys\Http\Service\RecurringPayment; +use Kalimero\Casys\Http\Requests\HandleRecurringPaymentRequest; +use Kalimero\Casys\Interfaces\RecurringPaymentInterface; class RecurringPaymentController extends Controller { - protected RecurringPayment $recurringPayment; + protected RecurringPaymentInterface $recurringPayment; - public function __construct(RecurringPayment $recurringPayment) + public function __construct(RecurringPaymentInterface $recurringPayment) { $this->recurringPayment = $recurringPayment; } /** - * Handle recurring payment request. + * Handle the recurring payment request. + * + * @param HandleRecurringPaymentRequest $request + * @return JsonResponse */ - public function handleRecurringPayment(handleRecurringPaymentRequest $request) + public function handleRecurringPayment(HandleRecurringPaymentRequest $request): JsonResponse { - try { - $validated = $request->validated(); - $response = $this->recurringPayment->sendPayment( - $validated['merchant_id'], - $validated['rp_ref'], - $validated['rp_ref_id'], - $validated['amount'], - $validated['password'] - ); + $validated = $request->validate([ + 'merchant_id' => 'required|string', + 'rp_ref' => 'required|string', + 'rp_ref_id' => 'required|string', + 'amount' => 'required|integer', + 'password' => 'required|string', + ]); - return response()->json($response); - } catch (Exception $e) { - return response()->json([ - 'success' => false, - 'error' => $e->getMessage(), - ]); - } + $response = $this->recurringPayment->sendPayment( + $validated['merchant_id'], + $validated['rp_ref'], + $validated['rp_ref_id'], + $validated['amount'], + $validated['password'] + ); + + return response()->json($response); } } diff --git a/src/Http/Requests/handleRecurringPaymentRequest.php b/src/Http/Requests/HandleRecurringPaymentRequest.php similarity index 64% rename from src/Http/Requests/handleRecurringPaymentRequest.php rename to src/Http/Requests/HandleRecurringPaymentRequest.php index fd7e3a2..a42a76c 100644 --- a/src/Http/Requests/handleRecurringPaymentRequest.php +++ b/src/Http/Requests/HandleRecurringPaymentRequest.php @@ -4,8 +4,13 @@ use Illuminate\Foundation\Http\FormRequest; -class handleRecurringPaymentRequest extends FormRequest +class HandleRecurringPaymentRequest extends FormRequest { + /** + * Define validation rules for the recurring payment request. + * + * @return array + */ public function rules(): array { return [ @@ -17,6 +22,11 @@ public function rules(): array ]; } + /** + * Authorize the request. + * + * @return bool + */ public function authorize(): bool { return true; diff --git a/src/Http/Service/RecurringPayment.php b/src/Http/Service/RecurringPayment.php deleted file mode 100644 index fa15ff3..0000000 --- a/src/Http/Service/RecurringPayment.php +++ /dev/null @@ -1,52 +0,0 @@ -soapClient = new SoapClient('https://www.cpay.com.mk/Recurring/RecurringPaymentsWS.wsdl'); - } - - /** - * Perform a recurring payment. - * - * @param string $merchantID - * @param string $rpRef - * @param string $rpRefID - * @param int $amount - * @param string $password - * @return array - * @throws Exception - */ - public function sendPayment(string $merchantID, string $rpRef, string $rpRefID, int $amount, string $password): array - { - $md5 = md5($merchantID . $rpRefID . $rpRef . $amount . $password); - - $response = $this->soapClient->__soapCall('sendPayment', [ - 'Amount' => $amount, - 'MerchantID' => $merchantID, - 'RPRef' => $rpRef, - 'RPRefID' => $rpRefID, - 'MD5' => $md5, - ]); - - if ($response->Success) { - return [ - 'success' => true, - 'payment_reference' => $response->CPayPaymentRef, - ]; - } - - return [ - 'success' => false, - 'error_description' => $response->ErrorDecription ?? 'Unknown error', - ]; - } -} diff --git a/src/Interfaces/RecurringPaymentInterface.php b/src/Interfaces/RecurringPaymentInterface.php new file mode 100644 index 0000000..9ae37ce --- /dev/null +++ b/src/Interfaces/RecurringPaymentInterface.php @@ -0,0 +1,16 @@ + + */ + public function sendPayment(string $merchantID, string $rpRef, string $rpRefID, int $amount, string $password): array; +} \ No newline at end of file diff --git a/src/Http/Service/Casys.php b/src/Service/Casys.php similarity index 57% rename from src/Http/Service/Casys.php rename to src/Service/Casys.php index ac5ecbc..ec809c7 100644 --- a/src/Http/Service/Casys.php +++ b/src/Service/Casys.php @@ -1,14 +1,20 @@ , user: array, checkSumHeader: string} + */ + public function getCasysData(stdClass $client, float $amount): array { $requiredData = [ 'AmountToPay' => (int) round($amount) > 0 ? (int) round($amount) * 100 : null, @@ -23,8 +29,6 @@ public function getCasysData($client, $amount): array 'OriginalCurrency' => config('casys.AmountCurrency'), ]; - $this->validateRequiredData($requiredData); - $userData = [ 'FirstName' => $client->name, 'LastName' => $client->last_name, @@ -32,8 +36,6 @@ public function getCasysData($client, $amount): array 'Email' => $client->email, ]; - $this->validateUserData($userData); - $checkSumHeader = implode(',', array_merge(array_keys($requiredData), array_keys($userData))); $checkSumHeaderLengths = implode('', array_merge(array_values($requiredData), array_values($userData))); $checkSumHeaderParams = $checkSumHeaderLengths . md5(config('casys.Password')); @@ -53,30 +55,4 @@ public function getCasysData($client, $amount): array 'checkSumHeader' => $checkSumHeader . $checkSumHeaderLengths, ]; } - - private function validateRequiredData($data): void - { - Validator::make($data, [ - 'AmountToPay' => 'required|integer', - 'PayToMerchant' => 'required|integer', - 'MerchantName' => 'required|string|max:255', - 'AmountCurrency' => 'required|string|max:255', - 'Details1' => 'required|integer', - 'Details2' => 'required|string|max:255', - 'PaymentOKURL' => 'required|string|max:255', - 'PaymentFailURL' => 'required|string|max:255', - 'OriginalAmount' => 'required|integer', - 'OriginalCurrency' => 'required|string|max:255', - ])->validate(); - } - - private function validateUserData($userData): void - { - Validator::make($userData, [ - 'FirstName' => 'required|string|max:255', - 'LastName' => 'required|string|max:255', - 'Country' => 'required|string|max:255', - 'Email' => 'required|email', - ])->validate(); - } } diff --git a/src/Service/RecurringPayment.php b/src/Service/RecurringPayment.php new file mode 100644 index 0000000..d5d3e16 --- /dev/null +++ b/src/Service/RecurringPayment.php @@ -0,0 +1,61 @@ +soapClient = $soapClient; + } + + /** + * Perform a recurring payment. + * + * @param string $merchantID + * @param string $rpRef + * @param string $rpRefID + * @param int $amount + * @param string $password + * @return array{success: bool, payment_reference?: string, error_description?: string} + * @throws Exception + */ + public function sendPayment(string $merchantID, string $rpRef, string $rpRefID, int $amount, string $password): array + { + $params = [ + 'Amount' => $amount, + 'MerchantID' => $merchantID, + 'RPRef' => $rpRef, + 'RPRefID' => $rpRefID, + 'MD5' => md5($merchantID . $rpRefID . $rpRef . $amount . $password), + ]; + + try { + $response = $this->soapClient->__soapCall('sendPayment', [$params]); + + if (is_object($response) && isset($response->Success)) { + return $response->Success + ? ['success' => true, 'payment_reference' => $response->CPayPaymentRef ?? null] + : ['success' => false, 'error_description' => $response->ErrorDecription ?? 'Unknown error']; + } + + return ['success' => false, 'error_description' => 'Invalid response format from SOAP service']; + } catch (Exception $e) { + return ['success' => false, 'error_description' => $e->getMessage()]; + } + } +} diff --git a/src/config/casys.php b/src/config/casys.php index 78019e8..994d5a6 100644 --- a/src/config/casys.php +++ b/src/config/casys.php @@ -14,6 +14,7 @@ 'AmountCurrency' => env('AMOUNT_CURRENCY', 'MKD'), 'PaymentOKURL' => env('PAYMENT_OK_URL', 'paymentOKURL'), 'PaymentFailURL' => env('PAYMENT_FAIL_URL', 'paymentFailURL'), - 'Password' => env('CASYS_TOKEN', 'TEST_PASS') + 'Password' => env('CASYS_TOKEN', 'TEST_PASS'), + 'RecurrentPaymentWsdl' => env('RECURRING_PAYMENT_WSDL', 'https://www.cpay.com.mk/Recurring/RecurringPaymentsWS.wsdl'), ];