diff --git a/alma/composer.json b/alma/composer.json index 94bf843a..72b1b29f 100644 --- a/alma/composer.json +++ b/alma/composer.json @@ -11,7 +11,7 @@ }, "require": { "php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4 || ~8.0 || ~8.1", - "alma/alma-php-client": "^2.1.0", + "alma/alma-php-client": "^2.2.0", "ext-json": "*", "ext-openssl": "*", "prestashop/prestashop-accounts-installer": "^v1.0.4", diff --git a/alma/controllers/front/ipn.php b/alma/controllers/front/ipn.php index 496c4641..7eff5931 100644 --- a/alma/controllers/front/ipn.php +++ b/alma/controllers/front/ipn.php @@ -23,7 +23,8 @@ */ use Alma\PrestaShop\API\MismatchException; -use Alma\PrestaShop\Exceptions\RefundException; +use Alma\PrestaShop\Builders\Validators\PaymentValidationBuilder; +use Alma\PrestaShop\Exceptions\PaymentValidationException; use Alma\PrestaShop\Logger; use Alma\PrestaShop\Traits\AjaxTrait; use Alma\PrestaShop\Validators\PaymentValidation; @@ -40,8 +41,21 @@ class AlmaIpnModuleFrontController extends ModuleFrontController { use AjaxTrait; + /** + * @var bool + */ public $ssl = true; + /** + * @var Context + */ + public $context; + + /** + * @var PaymentValidation + */ + protected $paymentValidation; + /** * IPN constructor * @@ -51,14 +65,14 @@ public function __construct() { parent::__construct(); $this->context = Context::getContext(); + $paymentValidationBuilder = new PaymentValidationBuilder(); + $this->paymentValidation = $paymentValidationBuilder->getInstance(); } /** * @return void * * @throws PrestaShopException - * @throws RefundException - * @throws MismatchException */ public function postProcess() { @@ -67,10 +81,14 @@ public function postProcess() header('Content-Type: application/json'); $paymentId = Tools::getValue('pid'); - $validator = new PaymentValidation($this->context, $this->module); try { - $validator->validatePayment($paymentId); + $this->paymentValidation->checkSignature($paymentId, Configuration::get('ALMA_API_KEY'), $_SERVER['HTTP_X_ALMA_SIGNATURE']); + $this->paymentValidation->validatePayment($paymentId); + $this->ajaxRenderAndExit(json_encode(['success' => true])); + } catch (PaymentValidationException $e) { + Logger::instance()->error('[Alma] IPN Payment Validation Error - Message : ' . $e->getMessage()); + $this->ajaxRenderAndExit(json_encode(['error' => $e->getMessage()]), 500); } catch (PaymentValidationError $e) { Logger::instance()->error('ipn payment_validation_error - Message : ' . $e->getMessage()); $this->ajaxRenderAndExit(json_encode(['error' => $e->getMessage()]), 500); @@ -78,7 +96,5 @@ public function postProcess() Logger::instance()->error('ipn payment_validation_mismatch_error - Message : ' . $e->getMessage()); $this->ajaxRenderAndExit(json_encode(['error' => $e->getMessage()]), 200); } - - $this->ajaxRenderAndExit(json_encode(['success' => true])); } } diff --git a/alma/controllers/front/validation.php b/alma/controllers/front/validation.php index ff72fa56..f6ba1b6e 100644 --- a/alma/controllers/front/validation.php +++ b/alma/controllers/front/validation.php @@ -22,6 +22,8 @@ * @license https://opensource.org/licenses/MIT The MIT License */ +use Alma\PrestaShop\API\MismatchException; +use Alma\PrestaShop\Exceptions\PaymentValidationException; use Alma\PrestaShop\Logger; use Alma\PrestaShop\Validators\PaymentValidation; use Alma\PrestaShop\Validators\PaymentValidationError; @@ -72,7 +74,10 @@ public function postProcess() } catch (PaymentValidationError $e) { Logger::instance()->error('payment_validation_error - Message : ' . $e->getMessage()); $redirect_to = $this->fail($e->cart, $e->getMessage()); - } catch (Exception $e) { + } catch (PaymentValidationException $e) { + Logger::instance()->error('payment_validation_error - Message : ' . $e->getMessage()); + $redirect_to = $this->fail($e->cartId, $e->getMessage()); + } catch (MismatchException $e) { Logger::instance()->error('payment_error - Message : ' . $e->getMessage()); $redirect_to = $this->fail(null, $e->getMessage()); } diff --git a/alma/exceptions/PaymentValidationException.php b/alma/exceptions/PaymentValidationException.php new file mode 100644 index 00000000..ac662060 --- /dev/null +++ b/alma/exceptions/PaymentValidationException.php @@ -0,0 +1,43 @@ + + * @copyright 2018-2024 Alma SAS + * @license https://opensource.org/licenses/MIT The MIT License + */ + +namespace Alma\PrestaShop\Exceptions; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class PaymentValidationException extends AlmaException +{ + /** + * @var int + */ + public $cartId; + + public function __construct($message = '', $cartId = -1, $code = 0, $previous = null) + { + parent::__construct($message, $code, $previous); + $this->cartId = $cartId; + } +} diff --git a/alma/lib/Builders/Validators/PaymentValidationBuilder.php b/alma/lib/Builders/Validators/PaymentValidationBuilder.php new file mode 100644 index 00000000..cab41b4a --- /dev/null +++ b/alma/lib/Builders/Validators/PaymentValidationBuilder.php @@ -0,0 +1,52 @@ + + * @copyright 2018-2024 Alma SAS + * @license https://opensource.org/licenses/MIT The MIT License + */ + +namespace Alma\PrestaShop\Builders\Validators; + +use Alma\PrestaShop\Traits\BuilderTrait; +use Alma\PrestaShop\Validators\PaymentValidation; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * PaymentValidationBuilder. + */ +class PaymentValidationBuilder +{ + use BuilderTrait; + + /** + * @return PaymentValidation + */ + public function getInstance() + { + return new PaymentValidation( + $this->getContextFactory(), + $this->getModuleFactory(), + $this->getClientPaymentValidator() + ); + } +} diff --git a/alma/lib/Helpers/ShareOfCheckoutHelper.php b/alma/lib/Helpers/ShareOfCheckoutHelper.php index c4652c8d..d87b95d5 100644 --- a/alma/lib/Helpers/ShareOfCheckoutHelper.php +++ b/alma/lib/Helpers/ShareOfCheckoutHelper.php @@ -185,8 +185,6 @@ public function isSocActivated() || empty($shareOfCheckoutEnabledDate) || !$this->dateHelper->isValidTimeStamp($shareOfCheckoutEnabledDate) ) { - Logger::instance()->info('Share Of Checkout is disabled or invalide date'); - return false; } diff --git a/alma/lib/Traits/BuilderTrait.php b/alma/lib/Traits/BuilderTrait.php index e5b76462..99157931 100644 --- a/alma/lib/Traits/BuilderTrait.php +++ b/alma/lib/Traits/BuilderTrait.php @@ -24,6 +24,7 @@ namespace Alma\PrestaShop\Traits; +use Alma\API\Lib\PaymentValidator; use Alma\PrestaShop\Factories\AddressFactory; use Alma\PrestaShop\Factories\CarrierFactory; use Alma\PrestaShop\Factories\CartFactory; @@ -1202,4 +1203,12 @@ public function getInsuranceApiService($insuranceApiService = null) return new InsuranceApiService(); } + + /** + * @return PaymentValidator + */ + public function getClientPaymentValidator() + { + return new PaymentValidator(); + } } diff --git a/alma/lib/Validators/PaymentValidation.php b/alma/lib/Validators/PaymentValidation.php index 0ef180bb..c782c1c3 100644 --- a/alma/lib/Validators/PaymentValidation.php +++ b/alma/lib/Validators/PaymentValidation.php @@ -25,11 +25,14 @@ namespace Alma\PrestaShop\Validators; use Alma\API\Entities\Payment; +use Alma\API\Lib\PaymentValidator; use Alma\API\RequestError; use Alma\PrestaShop\API\MismatchException; use Alma\PrestaShop\Builders\Helpers\PriceHelperBuilder; use Alma\PrestaShop\Builders\Helpers\SettingsHelperBuilder; use Alma\PrestaShop\Builders\Services\OrderServiceBuilder; +use Alma\PrestaShop\Exceptions\PaymentValidationException; +use Alma\PrestaShop\Exceptions\RefundException; use Alma\PrestaShop\Helpers\ClientHelper; use Alma\PrestaShop\Helpers\PriceHelper; use Alma\PrestaShop\Helpers\RefundHelper; @@ -68,15 +71,23 @@ class PaymentValidation * @var OrderService */ protected $orderService; + /** + * @var PaymentValidator + */ + protected $paymentValidator; /** * @param $context * @param $module */ - public function __construct($context, $module) - { + public function __construct( + $context, + $module, + $clientPaymentValidator + ) { $this->context = $context; $this->module = $module; + $this->paymentValidator = $clientPaymentValidator; $settingsHelperBuilder = new SettingsHelperBuilder(); $this->settingsHelper = $settingsHelperBuilder->getInstance(); @@ -120,6 +131,7 @@ private function isValidCurrency() * * @throws MismatchException * @throws PaymentValidationError + * @throws PaymentValidationException */ public function validatePayment($almaPaymentId) { @@ -210,7 +222,11 @@ public function validatePayment($almaPaymentId) $clientHelper = new ClientHelper(); $refundHelper = new RefundHelper($this->module, $cart, $payment->id, $clientHelper); - $refundHelper->mismatchFullRefund(); + try { + $refundHelper->mismatchFullRefund(); + } catch (RefundException $e) { + throw new PaymentValidationException('[Alma] Error refund from mismatch', $cart->id, 0, $e); + } } $firstInstalment = $payment->payment_plan[0]; @@ -312,6 +328,8 @@ public function validatePayment($almaPaymentId) * @param $cartId * * @return \OrderCore|null + * + * @throws PaymentValidationException */ private function getOrderByCartId($cartId) { @@ -320,7 +338,13 @@ private function getOrderByCartId($cartId) } else { $orderId = (int) \Order::getOrderByCartId((int) $cartId); - return new \Order($orderId); + try { + return new \Order($orderId); + } catch (\PrestaShopDatabaseException $e) { + throw new PaymentValidationException('[Alma] Error Prestashop database', $cartId, 0, $e); + } catch (\PrestaShopException $e) { + throw new PaymentValidationException('[Alma] Error Prestashop', $cartId, 0, $e); + } } } @@ -330,7 +354,7 @@ private function getOrderByCartId($cartId) * When calculating cart amount from an IPN call. * * @param \Cart $cart - * @param \Customer $cart + * @param \Customer $customer * * @return float */ @@ -347,4 +371,27 @@ private function getCartTotals($cart, $customer) return $cartTotals; } + + /** + * @param string $paymentId + * @param string $apiKey + * @param string $signature + * + * @throws PaymentValidationException + */ + public function checkSignature($paymentId, $apiKey, $signature) + { + if (!$paymentId) { + throw new PaymentValidationException('[Alma] Payment ID is missing'); + } + if (!$apiKey) { + throw new PaymentValidationException('[Alma] Api key is missing'); + } + if (!$signature) { + throw new PaymentValidationException('[Alma] Signature is missing'); + } + if (!$this->paymentValidator->isHmacValidated($paymentId, $apiKey, $signature)) { + throw new PaymentValidationException('[Alma] Signature is invalid'); + } + } } diff --git a/alma/lib/Validators/PaymentValidationError.php b/alma/lib/Validators/PaymentValidationError.php index 451f528c..f383fae1 100644 --- a/alma/lib/Validators/PaymentValidationError.php +++ b/alma/lib/Validators/PaymentValidationError.php @@ -28,6 +28,11 @@ exit; } +/** + * Class PaymentValidationError. + * + * @deprecated Use PaymentValidationException instead + */ class PaymentValidationError extends \Exception { public $cart; diff --git a/alma/tests/Unit/Validators/PaymentValidationTest.php b/alma/tests/Unit/Validators/PaymentValidationTest.php new file mode 100644 index 00000000..71bfcc48 --- /dev/null +++ b/alma/tests/Unit/Validators/PaymentValidationTest.php @@ -0,0 +1,113 @@ + + * @copyright 2018-2024 Alma SAS + * @license https://opensource.org/licenses/MIT The MIT License + */ + +namespace Alma\PrestaShop\Tests\Unit\Validators; + +use Alma\API\Lib\PaymentValidator; +use Alma\PrestaShop\Exceptions\PaymentValidationException; +use Alma\PrestaShop\Validators\PaymentValidation; +use PHPUnit\Framework\TestCase; + +class PaymentValidationTest extends TestCase +{ + const API_KEY = 'api_test_abc123'; + const PAYMENT_ID = 'payment_abc123'; + const WRONG_SIGNATURE = 'wrong_signature'; + const GOOD_SIGNATURE = 'good_signature'; + /** + * @var PaymentValidation + */ + protected $paymentValidation; + /** + * @var PaymentValidator + */ + protected $clientPaymentValidator; + + public function setUp() + { + $this->clientPaymentValidator = $this->createMock(PaymentValidator::class); + $this->paymentValidation = new PaymentValidation( + $this->createMock(\Context::class), + $this->createMock(\Module::class), + $this->clientPaymentValidator + ); + } + + public function tearDown() + { + $this->paymentValidation = null; + } + + /** + * @dataProvider checkSignatureWrongParamsDataProvider + * + * @throws PaymentValidationException + */ + public function testCheckSignatureWithoutParamsReturnError($paymentId, $apiKey, $signature) + { + $this->expectException(PaymentValidationException::class); + $this->paymentValidation->checkSignature($paymentId, $apiKey, $signature); + } + + /** + * @throws PaymentValidationException + */ + public function testCheckSignatureWithBadSignatureReturnError() + { + $this->clientPaymentValidator->expects($this->once()) + ->method('isHmacValidated') + ->with(self::PAYMENT_ID, self::API_KEY, self::WRONG_SIGNATURE) + ->willReturn(false); + $this->expectException(PaymentValidationException::class); + $this->paymentValidation->checkSignature(self::PAYMENT_ID, self::API_KEY, self::WRONG_SIGNATURE); + } + + /** + * @throws PaymentValidationException + */ + public function testCheckSignatureWithGoodSignature() + { + $this->clientPaymentValidator->expects($this->once()) + ->method('isHmacValidated') + ->with(self::PAYMENT_ID, self::API_KEY, self::GOOD_SIGNATURE) + ->willReturn(true); + $this->paymentValidation->checkSignature(self::PAYMENT_ID, self::API_KEY, self::GOOD_SIGNATURE); + } + + /** + * @return array[] + */ + public function checkSignatureWrongParamsDataProvider() + { + return [ + 'Without api key' => [self::PAYMENT_ID, '', self::GOOD_SIGNATURE], + 'Without payement id' => ['', self::API_KEY, self::GOOD_SIGNATURE], + 'Without signature' => [self::PAYMENT_ID, self::API_KEY, ''], + 'With api key null' => [self::PAYMENT_ID, null, self::GOOD_SIGNATURE], + 'With payement id null' => [null, self::API_KEY, self::GOOD_SIGNATURE], + 'With signature null' => [self::PAYMENT_ID, self::API_KEY, null], + ]; + } +}