diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 100% rename from .php_cs.dist rename to .php-cs-fixer.dist.php diff --git a/Makefile b/Makefile index b535084b3..7c3ace459 100755 --- a/Makefile +++ b/Makefile @@ -86,3 +86,13 @@ upgrading-module-test-$(VERSION): npm-package-install: cd views/assets && npm i && npm run build + +prepare-zip: + composer install --no-dev --optimize-autoloader --classmap-authoritative + composer dump-autoload --no-dev --optimize --classmap-authoritative + cp .github/.htaccess vendor/.htaccess + rm -rf .git .docker .editorconfig .github tests .php-cs-fixer.php Makefile cypress .docker cypress.config.js cypress.env.json docker-compose*.yml .gitignore bin codeception.yml package-lock.json package.json .php_cs.dist .php-cs-fixer.dist + + + + diff --git a/changelog.md b/changelog.md index b4396ccb2..2d0a3e828 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,16 @@ # Changelog # +## Changes in release 6.2.0 ## ++ New payment methods: Bancomat and Alma ++ Apple certificate update ++ Conflicting services fix ++ Bootstrap 5 compatibility improved ++ Maximum fee field ++ Multi carrier for subscription orders added ++ Design improvements in BO ++ Improved subscription creation logic + ## Changes in release 6.1.1 ## + Updated translations for Dutch, German, English and French languages + Added credit card translations for Italian and Spanish languages diff --git a/composer.json b/composer.json index d86cafc06..ab171d36f 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ }, "require-dev": { "roave/security-advisories": "dev-latest", + "invertus/prestashop-models": "^1.0", "prestashop/php-dev-tools": "*", "phpunit/phpunit": "~5.7" }, @@ -32,7 +33,8 @@ "autoload": { "psr-4": { "Mollie\\": "src/", - "Mollie\\Subscription\\": "subscription/" + "Mollie\\Subscription\\": "subscription/", + "Mollie\\Shared\\": "shared/" }, "classmap": [ "mollie.php", diff --git a/controllers/front/ajax.php b/controllers/front/ajax.php index 1df5a79fd..5e222be7d 100644 --- a/controllers/front/ajax.php +++ b/controllers/front/ajax.php @@ -13,10 +13,12 @@ use Mollie\Adapter\ConfigurationAdapter; use Mollie\Adapter\ToolsAdapter; use Mollie\Controller\AbstractMollieController; +use Mollie\Errors\Http\HttpStatusCode; use Mollie\Exception\FailedToProvidePaymentFeeException; +use Mollie\Infrastructure\Response\JsonResponse; use Mollie\Provider\PaymentFeeProviderInterface; -use Mollie\Repository\CurrencyRepositoryInterface; -use Mollie\Subscription\Exception\SubscriptionProductValidationException; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; +use Mollie\Subscription\Exception\ExceptionCode; use Mollie\Subscription\Validator\CanProductBeAddedToCartValidator; use Mollie\Utility\NumberUtility; @@ -185,30 +187,35 @@ private function displayCheckoutError(): void private function validateProduct(): void { - /** @var CanProductBeAddedToCartValidator $cartValidation */ - $cartValidation = $this->module->getService(CanProductBeAddedToCartValidator::class); + /** @var CanProductBeAddedToCartValidator $canProductBeAddedToCartValidator */ + $canProductBeAddedToCartValidator = $this->module->getService(CanProductBeAddedToCartValidator::class); $product = Tools::getValue('product'); - $productCanBeAdded = true; - $message = ''; - try { - $cartValidation->validate((int) $product['id_product_attribute']); - } catch (SubscriptionProductValidationException $e) { - $productCanBeAdded = false; - $message = $this->module->l('Please note: Only one subscription product can be added to the cart at a time.', self::FILE_NAME); + $canProductBeAddedToCartValidator->validate((int) ($product['id_product_attribute'] ?? 0)); + } catch (\Throwable $exception) { + if ($exception->getCode() === ExceptionCode::CART_ALREADY_HAS_SUBSCRIPTION_PRODUCT) { + $this->ajaxResponse(JsonResponse::error( + $this->module->l('Please note: Only one subscription product can be added to the cart at a time.', self::FILE_NAME), + HttpStatusCode::HTTP_BAD_REQUEST + )); + } + + if ($exception->getCode() === ExceptionCode::CART_INVALID_SUBSCRIPTION_SETTINGS) { + $this->ajaxResponse(JsonResponse::error( + $this->module->l('Subscription service is disabled. Please change the attribute to Subscription: none.', self::FILE_NAME), + HttpStatusCode::HTTP_BAD_REQUEST + )); + } + + $this->ajaxResponse(JsonResponse::error( + $this->module->l('Unknown error. Try again or change the attribute to Subscription: none.', self::FILE_NAME), + HttpStatusCode::HTTP_BAD_REQUEST + )); } - $this->ajaxRender( - json_encode( - [ - 'success' => true, - 'isValid' => $productCanBeAdded, - 'message' => $message, - ] - ) - ); + $this->ajaxResponse(JsonResponse::success([])); } private function returnDefaultOrderSummaryBlock(Cart $cart, array $errorData = [], array $presentedCart = null): void diff --git a/controllers/front/recurringOrderDetail.php b/controllers/front/recurringOrderDetail.php index 41fcb8dbf..610f28a17 100644 --- a/controllers/front/recurringOrderDetail.php +++ b/controllers/front/recurringOrderDetail.php @@ -56,13 +56,28 @@ public function initContent() $recurringOrderId = (int) Tools::getValue('id_mol_recurring_order'); $recurringOrderId = Validate::isUnsignedId($recurringOrderId) ? $recurringOrderId : false; + $failureRedirectUrl = Context::getContext()->link->getModuleLink($this->module->name, 'subscriptions', [], true); + /** @var RecurringOrderRepositoryInterface $recurringOrderRepository */ $recurringOrderRepository = $this->module->getService(RecurringOrderRepositoryInterface::class); - $recurringOrder = $recurringOrderRepository->findOneBy(['id_mol_recurring_order' => $recurringOrderId]); + try { + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = $recurringOrderRepository->findOrFail([ + 'id_mol_recurring_order' => $recurringOrderId, + ]); + } catch (\Throwable $exception) { + // TODO add notification about data retrieve failure + + Tools::redirect($failureRedirectUrl); + + return; + } + + if ((int) $recurringOrder->id_customer !== (int) $this->context->customer->id) { + Tools::redirect($failureRedirectUrl); - if (!Validate::isLoadedObject($recurringOrder) || (int) $recurringOrder->id_customer !== (int) $this->context->customer->id) { - Tools::redirect(Context::getContext()->link->getModuleLink($this->module->name, 'subscriptions', [], true)); + return; } /** @var PrestaLoggerInterface $logger */ @@ -82,10 +97,13 @@ public function initContent() 'Exception code' => $exception->getCode(), ]); - Tools::redirect(Context::getContext()->link->getModuleLink($this->module->name, 'subscriptions', [], true)); + Tools::redirect($failureRedirectUrl); + + return; } parent::initContent(); + $this->context->controller->addCSS($this->module->getPathUri() . 'views/css/front/subscription/customer_order_detail.css'); $this->setTemplate('module:mollie/views/templates/front/subscription/customerRecurringOrderDetail.tpl'); } diff --git a/controllers/front/subscriptions.php b/controllers/front/subscriptions.php index 3776aa0fe..3ad31e3fc 100644 --- a/controllers/front/subscriptions.php +++ b/controllers/front/subscriptions.php @@ -10,7 +10,7 @@ * @codingStandardsIgnoreStart */ -use Mollie\Repository\MolCustomerRepository; +use Mollie\Repository\MolCustomerRepositoryInterface; use Mollie\Subscription\Presenter\RecurringOrdersPresenter; /* @@ -52,40 +52,29 @@ class mollieSubscriptionsModuleFrontController extends ModuleFrontController */ public $display_column_left; - /** - * @throws PrestaShopException - */ public function initContent() { $this->display_column_right = false; $this->display_column_left = false; - $context = Context::getContext(); - if (empty($context->customer->id)) { + + if (empty($this->context->customer->id)) { Tools::redirect('index.php'); } - /** @var MolCustomerRepository $molCustomerRepository */ - $molCustomerRepository = $this->module->getService(MolCustomerRepository::class); + /** @var MolCustomerRepositoryInterface $molCustomerRepository */ + $molCustomerRepository = $this->module->getService(MolCustomerRepositoryInterface::class); /** @var RecurringOrdersPresenter $recurringOrdersPresenter */ $recurringOrdersPresenter = $this->module->getService(RecurringOrdersPresenter::class); - $molCustomer = $molCustomerRepository->findOneBy(['email' => $context->customer->email]); - - $recurringOrdersPresentData = []; - if ($molCustomer) { - $recurringOrdersPresentData = $recurringOrdersPresenter->present($molCustomer->customer_id); - } - - parent::initContent(); - - $this->context->smarty->assign([ - 'recurringOrdersData' => $recurringOrdersPresentData, + /** @var ?\MolCustomer $molCustomer */ + $molCustomer = $molCustomerRepository->findOneBy([ + 'email' => $this->context->customer->email, ]); - $this->context->smarty->tpl_vars['page']->value['body_classes']['page-customer-account'] = true; - - $this->setTemplate('module:mollie/views/templates/front/subscription/customerSubscriptionsData.tpl'); + $this->prepareTemplate( + $molCustomer ? $recurringOrdersPresenter->present($molCustomer->customer_id) : [] + ); } public function setMedia() @@ -97,4 +86,17 @@ public function setMedia() $this->context->controller->addJS($js_path . 'front.js'); $this->context->controller->addCSS($css_path . 'customerPersonalData.css'); } + + private function prepareTemplate(array $recurringOrdersPresentData = []): void + { + parent::initContent(); + + $this->context->smarty->assign([ + 'recurringOrdersData' => $recurringOrdersPresentData, + ]); + + $this->context->smarty->tpl_vars['page']->value['body_classes']['page-customer-account'] = true; + + $this->setTemplate('module:mollie/views/templates/front/subscription/customerSubscriptionsData.tpl'); + } } diff --git a/mails/en/mollie_subscription_cancel.html b/mails/en/mollie_subscription_cancel.html index 73517e93e..0922507b0 100644 --- a/mails/en/mollie_subscription_cancel.html +++ b/mails/en/mollie_subscription_cancel.html @@ -576,7 +576,7 @@ > -ReferenceProductUnit priceQuantityTotal price +ReferenceProductUnit price(tax excl.)QuantityTotal price subscription_referenceproduct_nameunit_pricequantitytotal_price @@ -677,4 +677,4 @@

- \ No newline at end of file + diff --git a/mails/en/mollie_subscription_cancel.txt b/mails/en/mollie_subscription_cancel.txt index b6af45818..f7a2f4597 100644 --- a/mails/en/mollie_subscription_cancel.txt +++ b/mails/en/mollie_subscription_cancel.txt @@ -1,9 +1,9 @@ -Hi firstName lastName, - - -We are emailing you because the following item was set to renew and that the payment method on file failed. Please log in into your account and update the payment method. - -We’ll keep trying to process your payment however if it continues to fail your subscription will get cancelled. -t -Reference Product Unit price Quantity Total price -subscription_reference product_name unit_price quantity total_price +Hi firstName lastName, + + +We are emailing you because the following item was set to renew and that the payment method on file failed. Please log in into your account and update the payment method. + +We’ll keep trying to process your payment however if it continues to fail your subscription will get cancelled. + +Reference Product Unit price(tax excl.) Quantity Total price +subscription_reference product_name unit_price quantity total_price diff --git a/mails/en/mollie_subscription_carrier_update.html b/mails/en/mollie_subscription_carrier_update.html new file mode 100644 index 000000000..3e2197e9d --- /dev/null +++ b/mails/en/mollie_subscription_carrier_update.html @@ -0,0 +1,680 @@ + + + + + Payment + + + + + + + + + + + + + + + + + + + + + + + + +
+
 
+ +
+ + + + + + +
+
+ + + + + + +
+
+ + + + + + +
+ + + + + + +
+
+
+
+
+ +
+ + + + + + +
+
+ + + + + + +
+ + + + + + +
+
Hi firstName lastName,
+
+
+
+
+
+ +
+ + + + + + +
+
+ + + + + + +
+

+
+
+
+
+ +
+ + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+
We are emailing you because the following subscription carrier was updated.
+
+
+
+
Log in to you account to review updated subscription information.
+
+
+
+
+
+ +
t + + + + + + + + +
ReferenceProductUnit price(tax excl.)QuantityTotal price
subscription_referenceproduct_nameunit_pricequantitytotal_price
+
+
+
+
+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +
+ +

+ +

+ +

+ +

+

+ + + diff --git a/mails/en/mollie_subscription_carrier_update.txt b/mails/en/mollie_subscription_carrier_update.txt new file mode 100644 index 000000000..93eb81f03 --- /dev/null +++ b/mails/en/mollie_subscription_carrier_update.txt @@ -0,0 +1,8 @@ +Hi firstName lastName, + +We are emailing you because the following subscription carrier was updated. + +Log in to you account to review updated subscription information. + +Reference Product Unit price(tax excl.) Quantity Total price +subscription_reference product_name unit_price quantity total_price diff --git a/mails/en/mollie_subscription_payment_failed.html b/mails/en/mollie_subscription_payment_failed.html index b136eff26..48dca56d7 100644 --- a/mails/en/mollie_subscription_payment_failed.html +++ b/mails/en/mollie_subscription_payment_failed.html @@ -566,7 +566,7 @@ > -ReferenceProductUnit priceQuantityTotal price +ReferenceProductUnit price(tax excl.)QuantityTotal price subscription_referenceproduct_nameunit_pricequantitytotal_price diff --git a/mails/en/mollie_subscription_payment_failed.txt b/mails/en/mollie_subscription_payment_failed.txt index b1e76f549..6bb73fa29 100644 --- a/mails/en/mollie_subscription_payment_failed.txt +++ b/mails/en/mollie_subscription_payment_failed.txt @@ -1,7 +1,7 @@ -Hi firstName lastName, - - -We are emailing you because we have retried to process the payment and failed. Your subscription has been cancelled. - -Reference Product Unit price Quantity Total price -subscription_reference product_name unit_price quantity total_price +Hi firstName lastName, + + +We are emailing you because we have retried to process the payment and failed. Your subscription has been cancelled. + +Reference Product Unit price(tax excl.) Quantity Total price +subscription_reference product_name unit_price quantity total_price diff --git a/mollie.php b/mollie.php index 67d595207..0439674d1 100755 --- a/mollie.php +++ b/mollie.php @@ -26,17 +26,16 @@ use Mollie\Repository\PaymentMethodRepositoryInterface; use Mollie\Service\ExceptionService; use Mollie\ServiceProvider\LeagueServiceContainerProvider; -use Mollie\Subscription\Exception\SubscriptionProductValidationException; use Mollie\Subscription\Handler\CustomerAddressUpdateHandler; +use Mollie\Subscription\Handler\UpdateSubscriptionCarrierHandler; use Mollie\Subscription\Install\AttributeInstaller; use Mollie\Subscription\Install\DatabaseTableInstaller; use Mollie\Subscription\Install\HookInstaller; use Mollie\Subscription\Install\Installer; -use Mollie\Subscription\Logger\NullLogger; +use Mollie\Subscription\Provider\SubscriptionProductProvider; use Mollie\Subscription\Repository\LanguageRepository as LanguageAdapter; use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; use Mollie\Subscription\Validator\CanProductBeAddedToCartValidator; -use Mollie\Subscription\Verification\HasSubscriptionProductInCart; use Mollie\Utility\PsVersionUtility; use Mollie\Verification\IsPaymentInformationAvailable; use PrestaShop\PrestaShop\Core\Localization\Locale\Repository; @@ -85,7 +84,7 @@ public function __construct() { $this->name = 'mollie'; $this->tab = 'payments_gateways'; - $this->version = '6.1.1'; + $this->version = '6.2.0'; $this->author = 'Mollie B.V.'; $this->need_instance = 1; $this->bootstrap = true; @@ -180,7 +179,7 @@ public function install() $subscriptionInstaller = new Installer( new DatabaseTableInstaller(), new AttributeInstaller( - new NullLogger(), + $this->getService(PrestaLoggerInterface::class), $this->getService(ConfigurationAdapter::class), $this, new LanguageAdapter(), @@ -1014,14 +1013,14 @@ public function hookDisplayProductAdditionalInfo() return ''; } - public function hookActionCartUpdateQuantityBefore($params) + public function hookActionCartUpdateQuantityBefore($params): void { - /** @var CanProductBeAddedToCartValidator $cartValidation */ - $cartValidation = $this->getService(CanProductBeAddedToCartValidator::class); + /** @var CanProductBeAddedToCartValidator $canProductBeAddedToCartValidator */ + $canProductBeAddedToCartValidator = $this->getService(CanProductBeAddedToCartValidator::class); try { - $cartValidation->validate((int) $params['id_product_attribute']); - } catch (SubscriptionProductValidationException $e) { + $canProductBeAddedToCartValidator->validate((int) $params['id_product_attribute']); + } catch (\Throwable $exception) { $product = $this->makeProductNotOrderable($params['product']); $params['product'] = $product; @@ -1273,6 +1272,39 @@ public function hookActionObjectAddressDeleteAfter(array $params): void $this->addPreventDeleteErrorMessage(); } + public function hookActionCarrierUpdate(array $params): void + { + $oldCarrierId = $params['id_carrier'] ?? 0; + $newCarrier = $params['carrier'] ?? null; + + if (empty($oldCarrierId) || empty($newCarrier)) { + return; + } + + /** @var UpdateSubscriptionCarrierHandler $subscriptionCarrierUpdateHandler */ + $subscriptionCarrierUpdateHandler = $this->getService(UpdateSubscriptionCarrierHandler::class); + + /** @var ConfigurationAdapter $configuration */ + $configuration = $this->getService(ConfigurationAdapter::class); + + /** @var PrestaLoggerInterface $logger */ + $logger = $this->getService(PrestaLoggerInterface::class); + + if ((int) $oldCarrierId !== (int) $configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID)) { + return; + } + + $failedSubscriptionOrderIdsToUpdate = $subscriptionCarrierUpdateHandler->run((int) $newCarrier->id); + + if (empty($failedSubscriptionOrderIdsToUpdate)) { + return; + } + + $logger->error('Failed to update subscription carrier for all orders.', [ + 'failed_subscription_order_ids' => json_encode($failedSubscriptionOrderIdsToUpdate), + ]); + } + public function hookActionFrontControllerAfterInit(): void { $this->frontControllerAfterInit(); @@ -1293,16 +1325,20 @@ private function frontControllerAfterInit(): void return; } - /** @var HasSubscriptionProductInCart $hasSubscriptionProductInCart */ - $hasSubscriptionProductInCart = $this->getService(HasSubscriptionProductInCart::class); + if (empty($this->context->cart) || empty($this->context->cart->getProducts())) { + return; + } - /** @var Link $link */ - $link = $this->getService(Link::class); + /** @var SubscriptionProductProvider $subscriptionProductProvider */ + $subscriptionProductProvider = $this->getService(SubscriptionProductProvider::class); - if (!$hasSubscriptionProductInCart->verify()) { + if (empty($subscriptionProductProvider->getProduct($this->context->cart->getProducts()))) { return; } + /** @var Link $link */ + $link = $this->getService(Link::class); + $this->context->controller->warning[] = $this->l('Customer must be logged in to buy subscription item.'); $this->context->controller->redirectWithNotifications($link->getPageLink('authentication')); diff --git a/src/Repository/CurrencyRepository.php b/shared/Core/Shared/Repository/CurrencyRepository.php similarity index 94% rename from src/Repository/CurrencyRepository.php rename to shared/Core/Shared/Repository/CurrencyRepository.php index 440b55379..488e5af33 100644 --- a/src/Repository/CurrencyRepository.php +++ b/shared/Core/Shared/Repository/CurrencyRepository.php @@ -34,9 +34,10 @@ * @codingStandardsIgnoreStart */ -namespace Mollie\Repository; +namespace Mollie\Shared\Core\Shared\Repository; use Currency; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; diff --git a/src/Repository/CurrencyRepositoryInterface.php b/shared/Core/Shared/Repository/CurrencyRepositoryInterface.php similarity index 93% rename from src/Repository/CurrencyRepositoryInterface.php rename to shared/Core/Shared/Repository/CurrencyRepositoryInterface.php index 891e93609..4197b94fa 100644 --- a/src/Repository/CurrencyRepositoryInterface.php +++ b/shared/Core/Shared/Repository/CurrencyRepositoryInterface.php @@ -34,7 +34,9 @@ * @codingStandardsIgnoreStart */ -namespace Mollie\Repository; +namespace Mollie\Shared\Core\Shared\Repository; + +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; if (!defined('_PS_VERSION_')) { exit; diff --git a/shared/Core/Shared/index.php b/shared/Core/Shared/index.php new file mode 100644 index 000000000..88355f610 --- /dev/null +++ b/shared/Core/Shared/index.php @@ -0,0 +1,11 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Shared\Infrastructure\Exception; + +use Mollie\Exception\Code\ExceptionCode; +use Mollie\Exception\MollieException; + +class MollieDatabaseException extends MollieException +{ + public static function failedToFindRecord(string $className, array $keyValues): self + { + return new self( + sprintf( + 'Model [%s] was not found by [%s]', + $className, + json_encode($keyValues) + ), + ExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD + ); + } +} diff --git a/shared/Infrastructure/Exception/index.php b/shared/Infrastructure/Exception/index.php new file mode 100644 index 000000000..88355f610 --- /dev/null +++ b/shared/Infrastructure/Exception/index.php @@ -0,0 +1,11 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Shared\Infrastructure\Repository; + +use Mollie\Shared\Infrastructure\Exception\MollieDatabaseException; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class AbstractRepository implements ReadOnlyRepositoryInterface +{ + /** + * @var string + */ + private $fullyClassifiedClassName; + + public function __construct(string $fullyClassifiedClassName) + { + $this->fullyClassifiedClassName = $fullyClassifiedClassName; + } + + /** {@inheritdoc} */ + public function findAll(int $langId = null): \PrestaShopCollection + { + return new \PrestaShopCollection($this->fullyClassifiedClassName, $langId); + } + + /** {@inheritdoc} */ + public function findOneBy(array $keyValueCriteria, int $langId = null): ?\ObjectModel + { + $psCollection = new \PrestaShopCollection($this->fullyClassifiedClassName, $langId); + + foreach ($keyValueCriteria as $field => $value) { + $psCollection = $psCollection->where($field, '=', $value); + } + + $first = $psCollection->getFirst(); + + /* @phpstan-ignore-next-line */ + return false === $first ? null : $first; + } + + /** {@inheritdoc} */ + public function findAllBy(array $keyValueCriteria, int $langId = null): ?\PrestaShopCollection + { + $psCollection = new \PrestaShopCollection($this->fullyClassifiedClassName, $langId); + + foreach ($keyValueCriteria as $field => $value) { + $psCollection = $psCollection->where($field, '=', $value); + } + + $all = $psCollection->getAll(); + + /* @phpstan-ignore-next-line */ + return false === $all ? null : $all; + } + + /** {@inheritdoc} */ + public function findOrFail(array $keyValueCriteria, int $langId = null): \ObjectModel + { + try { + $value = $this->findOneBy($keyValueCriteria, $langId); + } catch (\Throwable $exception) { + throw MollieDatabaseException::unknownError($exception); + } + + if (!$value) { + throw MollieDatabaseException::failedToFindRecord($this->fullyClassifiedClassName, $keyValueCriteria); + } + + return $value; + } +} diff --git a/shared/Infrastructure/Repository/ReadOnlyRepositoryInterface.php b/shared/Infrastructure/Repository/ReadOnlyRepositoryInterface.php new file mode 100644 index 000000000..2244ab005 --- /dev/null +++ b/shared/Infrastructure/Repository/ReadOnlyRepositoryInterface.php @@ -0,0 +1,48 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Shared\Infrastructure\Repository; + +use Mollie\Exception\MollieException; + +if (!defined('_PS_VERSION_')) { + exit; +} + +interface ReadOnlyRepositoryInterface +{ + /** + * @throws \PrestaShopException + */ + public function findAll(int $langId = null): \PrestaShopCollection; + + /** + * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] + * + * @throws \PrestaShopException + */ + public function findOneBy(array $keyValueCriteria, int $langId = null): ?\ObjectModel; + + /** + * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] + * + * @throws \PrestaShopException + */ + public function findAllBy(array $keyValueCriteria, int $langId = null): ?\PrestaShopCollection; + + /** + * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] + * + * @throws MollieException + */ + public function findOrFail(array $keyValueCriteria, int $langId = null): \ObjectModel; +} diff --git a/shared/Infrastructure/Repository/index.php b/shared/Infrastructure/Repository/index.php new file mode 100644 index 000000000..88355f610 --- /dev/null +++ b/shared/Infrastructure/Repository/index.php @@ -0,0 +1,11 @@ + $this->taxRulesGroupRepository->getTaxRulesGroups($this->context->getShopId()), 'tab' => $generalSettings, 'onlyOrderMethods' => Config::ORDER_API_ONLY_METHODS, + 'onlyPaymentsMethods' => Config::PAYMENT_API_ONLY_METHODS, 'displayErrors' => $this->configuration->get(Config::MOLLIE_DISPLAY_ERRORS), 'methodDescription' => TagsUtility::ppTags( $this->module->l('[1]Read more[/1] about the differences between Payments and Orders API.', self::FILE_NAME), diff --git a/src/Builder/InvoicePdfTemplateBuilder.php b/src/Builder/InvoicePdfTemplateBuilder.php index 50a45dc60..b1187e756 100644 --- a/src/Builder/InvoicePdfTemplateBuilder.php +++ b/src/Builder/InvoicePdfTemplateBuilder.php @@ -13,8 +13,8 @@ namespace Mollie\Builder; use Currency; -use Mollie\Repository\CurrencyRepositoryInterface; use Mollie\Repository\MolOrderPaymentFeeRepositoryInterface; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; use MolOrderPaymentFee; use Order; use PrestaShop\PrestaShop\Core\Localization\Locale; diff --git a/src/Config/Config.php b/src/Config/Config.php index fb0963599..a20d2dd87 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -110,6 +110,7 @@ class Config 'sandbox' => 'MOLLIE_SANDBOX_SINGLE_CLICK_PAYMENT', 'production' => 'MOLLIE_PRODUCTION_SINGLE_CLICK_PAYMENT', ]; + const MOLLIE_IMAGES = 'MOLLIE_IMAGES'; const MOLLIE_SHOW_RESEND_PAYMENT_LINK = 'MOLLIE_SHOW_RESEND_PAYMENT_LINK'; const MOLLIE_ISSUERS = [ @@ -162,6 +163,7 @@ class Config const MOLLIE_CARRIER_CUSTOM_URL = 'MOLLIE_CARRIER_CUSTOM_URL_'; const MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID = 'MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID'; + const MOLLIE_SUBSCRIPTION_ENABLED = 'MOLLIE_SUBSCRIPTION_ENABLED'; const MOLLIE_METHOD_ENABLED = 'MOLLIE_METHOD_ENABLED_'; const MOLLIE_METHOD_TITLE = 'MOLLIE_METHOD_TITLE_'; @@ -280,6 +282,10 @@ class Config self::MOLLIE_in3_METHOD_ID, ]; + const PAYMENT_API_ONLY_METHODS = [ + PaymentMethod::ALMA, + ]; + const ROUTE_RESEND_SECOND_CHANCE_PAYMENT_MESSAGE = 'mollie_module_admin_resend_payment_message'; const PAYMENT_FEE_SKU = 'payment-fee-sku'; @@ -317,6 +323,8 @@ class Config 'in3' => 'in3', 'billie' => 'Billie', 'twint' => 'TWINT', + 'bancomat' => 'Bancomat', + 'alma' => 'Alma', 'blik' => 'BLIK', 'klarna' => 'Pay with Klarna.', ]; diff --git a/src/DTO/OrderData.php b/src/DTO/OrderData.php index f1696cb50..ebb19ef8d 100644 --- a/src/DTO/OrderData.php +++ b/src/DTO/OrderData.php @@ -433,6 +433,7 @@ public function jsonSerialize() 'familyName' => $this->cleanUpInput($this->getBillingAddress()->lastname), 'email' => $this->cleanUpInput($this->getEmail()), 'title' => $this->cleanUpInput($this->getTitle()), + 'phone' => $this->cleanUpInput($this->getBillingPhoneNumber()), ], 'shippingAddress' => [ 'organizationName' => $this->cleanUpInput($this->getShippingAddress()->company), @@ -445,6 +446,7 @@ public function jsonSerialize() 'familyName' => $this->cleanUpInput($this->getShippingAddress()->lastname), 'email' => $this->cleanUpInput($this->getEmail()), 'title' => $this->cleanUpInput($this->getTitle()), + 'phone' => $this->cleanUpInput($this->getDeliveryPhoneNumber()), ], 'redirectUrl' => $this->getRedirectUrl(), 'webhookUrl' => $this->getWebhookUrl(), diff --git a/src/Exception/Code/ExceptionCode.php b/src/Exception/Code/ExceptionCode.php index 133a43ff1..58c496a9e 100644 --- a/src/Exception/Code/ExceptionCode.php +++ b/src/Exception/Code/ExceptionCode.php @@ -25,6 +25,7 @@ class ExceptionCode public const INFRASTRUCTURE_LOCK_EXISTS = 1003; public const INFRASTRUCTURE_LOCK_ON_ACQUIRE_IS_MISSING = 1004; public const INFRASTRUCTURE_LOCK_ON_RELEASE_IS_MISSING = 1005; + public const INFRASTRUCTURE_FAILED_TO_FIND_RECORD = 1006; public const FAILED_TO_FIND_CUSTOMER_ADDRESS = 2001; diff --git a/src/Handler/Certificate/Files/apple-developer-merchantid-domain-association b/src/Handler/Certificate/Files/apple-developer-merchantid-domain-association index 66afb9887..570d6b01e 100644 --- a/src/Handler/Certificate/Files/apple-developer-merchantid-domain-association +++ b/src/Handler/Certificate/Files/apple-developer-merchantid-domain-association @@ -1 +1 @@ -7B227073704964223A2244394337463730314338433646324336463344363536433039393434453332323030423137364631353245353844393134304331433533414138323436453630222C2276657273696F6E223A312C22637265617465644F6E223A313536363930343337353936352C227369676E6174757265223A223330383030363039326138363438383666373064303130373032613038303330383030323031303133313066333030643036303936303836343830313635303330343032303130353030333038303036303932613836343838366637306430313037303130303030613038303330383230336533333038323033383861303033303230313032303230383463333034313439353139643534333633303061303630383261383634386365336430343033303233303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330316531373064333133393330333533313338333033313333333233353337356131373064333233343330333533313336333033313333333233353337356133303566333132353330323330363033353530343033306331633635363336333264373336643730326436323732366636623635373232643733363936373665356635353433333432643530353234663434333131343330313230363033353530343062306330623639346635333230353337393733373436353664373333313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330353933303133303630373261383634386365336430323031303630383261383634386365336430333031303730333432303030346332313537376564656264366337623232313866363864643730393061313231386463376230626436663263323833643834363039356439346166346135343131623833343230656438313166333430376538333333316631633534633366376562333232306436626164356434656666343932383938393365376330663133613338323032313133303832303230643330306330363033353531643133303130316666303430323330303033303166303630333535316432333034313833303136383031343233663234396334346639336534656632376536633466363238366333666132626266643265346233303435303630383262303630313035303530373031303130343339333033373330333530363038326230363031303530353037333030313836323936383734373437303361326632663666363337333730326536313730373036633635326536333666366432663666363337333730333033343264363137303730366336353631363936333631333333303332333038323031316430363033353531643230303438323031313433303832303131303330383230313063303630393261383634383836663736333634303530313330383166653330383163333036303832623036303130353035303730323032333038316236306338316233353236353663363936313665363336353230366636653230373436383639373332303633363537323734363936363639363336313734363532303632373932303631366537393230373036313732373437393230363137333733373536643635373332303631363336333635373037343631366536333635323036663636323037343638363532303734363836353665323036313730373036633639363336313632366336353230373337343631366536343631373236343230373436353732366437333230363136653634323036333666366536343639373436393666366537333230366636363230373537333635326332303633363537323734363936363639363336313734363532303730366636633639363337393230363136653634323036333635373237343639363636393633363137343639366636653230373037323631363337343639363336353230373337343631373436353664363536653734373332653330333630363038326230363031303530353037303230313136326136383734373437303361326632663737373737373265363137303730366336353265363336663664326636333635373237343639363636393633363137343635363137353734363836663732363937343739326633303334303630333535316431663034326433303262333032396130323761303235383632333638373437343730336132663266363337323663326536313730373036633635326536333666366432663631373037303663363536313639363336313333326536333732366333303164303630333535316430653034313630343134393435376462366664353734383138363839383937363266376535373835303765373962353832343330306530363033353531643066303130316666303430343033303230373830333030663036303932613836343838366637363336343036316430343032303530303330306130363038326138363438636533643034303330323033343930303330343630323231303062653039353731666537316531653733356235356535616661636234633732666562343435663330313835323232633732353130303262363165626436663535303232313030643138623335306135646436646436656231373436303335623131656232636538376366613365366166366362643833383038393064633832636464616136333330383230326565333038323032373561303033303230313032303230383439366432666266336139386461393733303061303630383261383634386365336430343033303233303637333131623330313930363033353530343033306331323431373037303663363532303532366636663734323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303165313730643331333433303335333033363332333333343336333333303561313730643332333933303335333033363332333333343336333333303561333037613331326533303263303630333535303430333063323534313730373036633635323034313730373036633639363336313734363936663665323034393665373436353637373236313734363936663665323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303539333031333036303732613836343863653364303230313036303832613836343863653364303330313037303334323030303466303137313138343139643736343835643531613565323538313037373665383830613265666465376261653464653038646663346239336531333335366435363635623335616532326430393737363064323234653762626130386664373631376365383863623736626236363730626563386538323938346666353434356133383166373330383166343330343630363038326230363031303530353037303130313034336133303338333033363036303832623036303130353035303733303031383632613638373437343730336132663266366636333733373032653631373037303663363532653633366636643266366636333733373033303334326436313730373036633635373236663666373436333631363733333330316430363033353531643065303431363034313432336632343963343466393365346566323765366334663632383663336661326262666432653462333030663036303335353164313330313031666630343035333030333031303166663330316630363033353531643233303431383330313638303134626262306465613135383333383839616134386139396465626562646562616664616362323461623330333730363033353531643166303433303330326533303263613032616130323838363236363837343734373033613266326636333732366332653631373037303663363532653633366636643266363137303730366336353732366636663734363336313637333332653633373236633330306530363033353531643066303130316666303430343033303230313036333031303036306132613836343838366637363336343036303230653034303230353030333030613036303832613836343863653364303430333032303336373030333036343032333033616366373238333531313639396231383666623335633335366361363262666634313765646439306637353464613238656265663139633831356534326237383966383938663739623539396639386435343130643866396465396332666530323330333232646435343432316230613330353737366335646633333833623930363766643137376332633231366439363466633637323639383231323666353466383761376431623939636239623039383932313631303639393066303939323164303030303331383230313863333038323031383830323031303133303831383633303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333032303834633330343134393531396435343336333030643036303936303836343830313635303330343032303130353030613038313935333031383036303932613836343838366637306430313039303333313062303630393261383634383836663730643031303730313330316330363039326138363438383666373064303130393035333130663137306433313339333033383332333733313331333133323335333535613330326130363039326138363438383666373064303130393334333131643330316233303064303630393630383634383031363530333034303230313035303061313061303630383261383634386365336430343033303233303266303630393261383634383836663730643031303930343331323230343230303864323631373363653335643465646134616139623866343763643439336639333034653763366631633866393239393238333361373537373736393438323330306130363038326138363438636533643034303330323034343733303435303232313030613439373261353733346466616362303131343162613366626238663062626665616162626233363161366536613839663666366361373132363165333039303032323032343439396561643530356238376664366331333134656135343963663431316339326632656437323837303533303738636562626638326462306561376633303030303030303030303030227D +7B227073704964223A2244394337463730314338433646324336463344363536433039393434453332323030423137364631353245353844393134304331433533414138323436453630222C2276657273696F6E223A312C22637265617465644F6E223A313731353230333937373439362C227369676E6174757265223A223330383030363039326138363438383666373064303130373032613038303330383030323031303133313064333030623036303936303836343830313635303330343032303133303830303630393261383634383836663730643031303730313030303061303830333038323033653333303832303338386130303330323031303230323038313636333463386230653330353731373330306130363038326138363438636533643034303330323330376133313265333032633036303335353034303330633235343137303730366336353230343137303730366336393633363137343639366636653230343936653734363536373732363137343639366636653230343334313230326432303437333333313236333032343036303335353034306230633164343137303730366336353230343336353732373436393636363936333631373436393666366532303431373537343638366637323639373437393331313333303131303630333535303430613063306134313730373036633635323034393665363332653331306233303039303630333535303430363133303235353533333031653137306433323334333033343332333933313337333433373332333735613137306433323339333033343332333833313337333433373332333635613330356633313235333032333036303335353034303330633163363536333633326437333664373032643632373236663662363537323264373336393637366535663535343333343264353035323466343433313134333031323036303335353034306230633062363934663533323035333739373337343635366437333331313333303131303630333535303430613063306134313730373036633635323034393665363332653331306233303039303630333535303430363133303235353533333035393330313330363037326138363438636533643032303130363038326138363438636533643033303130373033343230303034633231353737656465626436633762323231386636386464373039306131323138646337623062643666326332383364383436303935643934616634613534313162383334323065643831316633343037653833333331663163353463336637656233323230643662616435643465666634393238393839336537633066313361333832303231313330383230323064333030633036303335353164313330313031666630343032333030303330316630363033353531643233303431383330313638303134323366323439633434663933653465663237653663346636323836633366613262626664326534623330343530363038326230363031303530353037303130313034333933303337333033353036303832623036303130353035303733303031383632393638373437343730336132663266366636333733373032653631373037303663363532653633366636643266366636333733373033303334326436313730373036633635363136393633363133333330333233303832303131643036303335353164323030343832303131343330383230313130333038323031306330363039326138363438383666373633363430353031333038316665333038316333303630383262303630313035303530373032303233303831623630633831623335323635366336393631366536333635323036663665323037343638363937333230363336353732373436393636363936333631373436353230363237393230363136653739323037303631373237343739323036313733373337353664363537333230363136333633363537303734363136653633363532303666363632303734363836353230373436383635366532303631373037303663363936333631363236633635323037333734363136653634363137323634323037343635373236643733323036313665363432303633366636653634363937343639366636653733323036663636323037353733363532633230363336353732373436393636363936333631373436353230373036663663363936333739323036313665363432303633363537323734363936363639363336313734363936663665323037303732363136333734363936333635323037333734363137343635366436353665373437333265333033363036303832623036303130353035303730323031313632613638373437343730336132663266373737373737326536313730373036633635326536333666366432663633363537323734363936363639363336313734363536313735373436383666373236393734373932663330333430363033353531643166303432643330326233303239613032376130323538363233363837343734373033613266326636333732366332653631373037303663363532653633366636643266363137303730366336353631363936333631333332653633373236633330316430363033353531643065303431363034313439343537646236666435373438313836383938393736326637653537383530376537396235383234333030653036303335353164306630313031666630343034303330323037383033303066303630393261383634383836663736333634303631643034303230353030333030613036303832613836343863653364303430333032303334393030333034363032323130306336663032336362323631346262333033383838613136323938336531613933663130353666353066613738636462396261346361323431636331346532356530323231303062653363643064666431363234376636343934343735333830653964343463323238613130383930613361316463373234623862346362383838393831386263333038323032656533303832303237356130303330323031303230323038343936643266626633613938646139373330306130363038326138363438636533643034303330323330363733313162333031393036303335353034303330633132343137303730366336353230353236663666373432303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330316531373064333133343330333533303336333233333334333633333330356131373064333233393330333533303336333233333334333633333330356133303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330353933303133303630373261383634386365336430323031303630383261383634386365336430333031303730333432303030346630313731313834313964373634383564353161356532353831303737366538383061326566646537626165346465303864666334623933653133333536643536363562333561653232643039373736306432323465376262613038666437363137636538386362373662623636373062656338653832393834666635343435613338316637333038316634333034363036303832623036303130353035303730313031303433613330333833303336303630383262303630313035303530373330303138363261363837343734373033613266326636663633373337303265363137303730366336353265363336663664326636663633373337303330333432643631373037303663363537323666366637343633363136373333333031643036303335353164306530343136303431343233663234396334346639336534656632376536633466363238366333666132626266643265346233303066303630333535316431333031303166663034303533303033303130316666333031663036303335353164323330343138333031363830313462626230646561313538333338383961613438613939646562656264656261666461636232346162333033373036303335353164316630343330333032653330326361303261613032383836323636383734373437303361326632663633373236633265363137303730366336353265363336663664326636313730373036633635373236663666373436333631363733333265363337323663333030653036303335353164306630313031666630343034303330323031303633303130303630613261383634383836663736333634303630323065303430323035303033303061303630383261383634386365336430343033303230333637303033303634303233303361636637323833353131363939623138366662333563333536636136326266663431376564643930663735346461323865626566313963383135653432623738396638393866373962353939663938643534313064386639646539633266653032333033323264643534343231623061333035373736633564663333383362393036376664313737633263323136643936346663363732363938323132366635346638376137643162393963623962303938393231363130363939306630393932316430303030333138323031383833303832303138343032303130313330383138363330376133313265333032633036303335353034303330633235343137303730366336353230343137303730366336393633363137343639366636653230343936653734363536373732363137343639366636653230343334313230326432303437333333313236333032343036303335353034306230633164343137303730366336353230343336353732373436393636363936333631373436393666366532303431373537343638366637323639373437393331313333303131303630333535303430613063306134313730373036633635323034393665363332653331306233303039303630333535303430363133303235353533303230383136363334633862306533303537313733303062303630393630383634383031363530333034303230316130383139333330313830363039326138363438383666373064303130393033333130623036303932613836343838366637306430313037303133303163303630393261383634383836663730643031303930353331306631373064333233343330333533303338333233313333333233353337356133303238303630393261383634383836663730643031303933343331316233303139333030623036303936303836343830313635303330343032303161313061303630383261383634386365336430343033303233303266303630393261383634383836663730643031303930343331323230343230613734383431366661393062663836376138346238343566646338623039333162653530393364616637653232626635323763336436343162373931653937383330306130363038326138363438636533643034303330323034343733303435303232303333623733376461386365326431343666363833616365326362623634343730333537636335363264383533303531656131363363353836373461313933303030323231303065396665626434326136373338326563626263343339316561376265373839353336646432646335323937366561613237663939333566386330353164393963303030303030303030303030227D \ No newline at end of file diff --git a/src/Install/Installer.php b/src/Install/Installer.php index 49fabbf5d..15720d22d 100644 --- a/src/Install/Installer.php +++ b/src/Install/Installer.php @@ -171,6 +171,7 @@ public static function getHooks() 'actionObjectOrderPaymentAddAfter', 'displayProductAdditionalInfo', 'displayCustomerAccount', + 'actionCarrierUpdate', ]; } @@ -215,6 +216,7 @@ protected function initConfig() $this->configurationAdapter->updateValue(Config::MOLLIE_BANCONTACT_QR_CODE_ENABLED, 0); $this->configurationAdapter->updateValue(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, 0); + $this->configurationAdapter->updateValue(Config::MOLLIE_SUBSCRIPTION_ENABLED, 0); } public function setDefaultCarrierStatuses() diff --git a/src/Install/Uninstall.php b/src/Install/Uninstall.php index 10b8c2566..8a15ced6c 100644 --- a/src/Install/Uninstall.php +++ b/src/Install/Uninstall.php @@ -96,6 +96,7 @@ private function deleteConfig() Config::METHODS_CONFIG, Config::MOLLIE_MAIL_WHEN_COMPLETED, Config::MOLLIE_API_KEY_TEST, + Config::MOLLIE_SUBSCRIPTION_ENABLED, Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, ]; diff --git a/src/Logger/PrestaLogger.php b/src/Logger/PrestaLogger.php index 950dd4cbc..e476f1c2a 100644 --- a/src/Logger/PrestaLogger.php +++ b/src/Logger/PrestaLogger.php @@ -22,6 +22,7 @@ class PrestaLogger implements PrestaLoggerInterface { + // TODO move this as a shared service for subscriptions and main source // TODO refactor whole logger logic and implement leftover methods /** @var ConfigurationAdapter */ @@ -86,7 +87,9 @@ public function info($message, array $context = []) public function debug($message, array $context = []) { - throw new NotImplementedException('not implemented method'); + // TODO implement single method, which handles logging + + $this->info($message, $context); } public function log($level, $message, array $context = []) diff --git a/src/Repository/AbstractRepository.php b/src/Repository/AbstractRepository.php deleted file mode 100644 index 547156bec..000000000 --- a/src/Repository/AbstractRepository.php +++ /dev/null @@ -1,85 +0,0 @@ - - * @copyright Mollie B.V. - * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md - * - * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart - */ - -namespace Mollie\Repository; - -use ObjectModel; -use PrestaShopCollection; -use PrestaShopException; - -if (!defined('_PS_VERSION_')) { - exit; -} - -class AbstractRepository implements ReadOnlyRepositoryInterface -{ - /** - * @var string - */ - private $fullyClassifiedClassName; - - /** - * @param string|\stdClass $fullyClassifiedClassName - */ - public function __construct($fullyClassifiedClassName) - { - if (is_object($fullyClassifiedClassName)) { - $this->fullyClassifiedClassName = get_class($fullyClassifiedClassName); - - return; - } - $this->fullyClassifiedClassName = $fullyClassifiedClassName; - } - - public function findAll() - { - return new PrestaShopCollection($this->fullyClassifiedClassName); - } - - /** - * @return ObjectModel|null - * - * @throws PrestaShopException - */ - public function findOneBy(array $keyValueCriteria) - { - $psCollection = new PrestaShopCollection($this->fullyClassifiedClassName); - - foreach ($keyValueCriteria as $field => $value) { - $psCollection = $psCollection->where($field, '=', $value); - } - - $first = $psCollection->getFirst(); - - /* @phpstan-ignore-next-line */ - return false === $first ? null : $first; - } - - /** - * @return PrestaShopCollection|null - * - * @throws PrestaShopException - */ - public function findAllBy(array $keyValueCriteria) - { - $psCollection = new PrestaShopCollection($this->fullyClassifiedClassName); - - foreach ($keyValueCriteria as $field => $value) { - $psCollection = $psCollection->where($field, '=', $value); - } - - $all = $psCollection->getAll(); - - /* @phpstan-ignore-next-line */ - return false === $all ? null : $all; - } -} diff --git a/src/Repository/AddressFormatRepository.php b/src/Repository/AddressFormatRepository.php index cb419c13c..e9d8ef6c4 100644 --- a/src/Repository/AddressFormatRepository.php +++ b/src/Repository/AddressFormatRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/AddressFormatRepositoryInterface.php b/src/Repository/AddressFormatRepositoryInterface.php index f7ec691c6..102ad2d9c 100644 --- a/src/Repository/AddressFormatRepositoryInterface.php +++ b/src/Repository/AddressFormatRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/AddressRepository.php b/src/Repository/AddressRepository.php index 55d30605a..a4249a70a 100644 --- a/src/Repository/AddressRepository.php +++ b/src/Repository/AddressRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/AddressRepositoryInterface.php b/src/Repository/AddressRepositoryInterface.php index d18a807af..4d5f0ebc1 100644 --- a/src/Repository/AddressRepositoryInterface.php +++ b/src/Repository/AddressRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/CarrierRepository.php b/src/Repository/CarrierRepository.php index 2131dbe21..0399999d6 100644 --- a/src/Repository/CarrierRepository.php +++ b/src/Repository/CarrierRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/CarrierRepositoryInterface.php b/src/Repository/CarrierRepositoryInterface.php index fb38a9f44..c54b1b97d 100644 --- a/src/Repository/CarrierRepositoryInterface.php +++ b/src/Repository/CarrierRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/CartRepository.php b/src/Repository/CartRepository.php index da4e538b9..066cd43b5 100644 --- a/src/Repository/CartRepository.php +++ b/src/Repository/CartRepository.php @@ -12,7 +12,7 @@ namespace Mollie\Repository; -use Cart; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; @@ -22,6 +22,6 @@ class CartRepository extends AbstractRepository implements CartRepositoryInterfa { public function __construct() { - parent::__construct(Cart::class); + parent::__construct(\Cart::class); } } diff --git a/src/Repository/CartRepositoryInterface.php b/src/Repository/CartRepositoryInterface.php index c2afd896b..a23bd25ad 100644 --- a/src/Repository/CartRepositoryInterface.php +++ b/src/Repository/CartRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/CartRuleRepository.php b/src/Repository/CartRuleRepository.php index 21566ca07..50ad30a44 100644 --- a/src/Repository/CartRuleRepository.php +++ b/src/Repository/CartRuleRepository.php @@ -12,7 +12,7 @@ namespace Mollie\Repository; -use CartRule; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; @@ -22,6 +22,6 @@ final class CartRuleRepository extends AbstractRepository implements CartRuleRep { public function __construct() { - parent::__construct(CartRule::class); + parent::__construct(\CartRule::class); } } diff --git a/src/Repository/CartRuleRepositoryInterface.php b/src/Repository/CartRuleRepositoryInterface.php index aad9a5914..8c3397e6d 100644 --- a/src/Repository/CartRuleRepositoryInterface.php +++ b/src/Repository/CartRuleRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/CountryRepository.php b/src/Repository/CountryRepository.php index 69fdd3396..64d6a3a82 100644 --- a/src/Repository/CountryRepository.php +++ b/src/Repository/CountryRepository.php @@ -14,6 +14,7 @@ use Country; use Db; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; diff --git a/src/Repository/CountryRepositoryInterface.php b/src/Repository/CountryRepositoryInterface.php index 3f9f5f13f..62650ef3f 100644 --- a/src/Repository/CountryRepositoryInterface.php +++ b/src/Repository/CountryRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/CustomerRepository.php b/src/Repository/CustomerRepository.php index 178230bc5..66f7996e4 100644 --- a/src/Repository/CustomerRepository.php +++ b/src/Repository/CustomerRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/CustomerRepositoryInterface.php b/src/Repository/CustomerRepositoryInterface.php index eee2e18e4..99d0c7fed 100644 --- a/src/Repository/CustomerRepositoryInterface.php +++ b/src/Repository/CustomerRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/GenderRepository.php b/src/Repository/GenderRepository.php index 807dc21bc..c8599f880 100644 --- a/src/Repository/GenderRepository.php +++ b/src/Repository/GenderRepository.php @@ -36,6 +36,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/GenderRepositoryInterface.php b/src/Repository/GenderRepositoryInterface.php index 40e4e7ecc..0a40a63e6 100644 --- a/src/Repository/GenderRepositoryInterface.php +++ b/src/Repository/GenderRepositoryInterface.php @@ -36,10 +36,12 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } -interface GenderRepositoryInterface extends \Mollie\Repository\ReadOnlyRepositoryInterface +interface GenderRepositoryInterface extends ReadOnlyRepositoryInterface { } diff --git a/src/Repository/MolCustomerRepository.php b/src/Repository/MolCustomerRepository.php index af6088392..2bb12042f 100644 --- a/src/Repository/MolCustomerRepository.php +++ b/src/Repository/MolCustomerRepository.php @@ -12,19 +12,16 @@ namespace Mollie\Repository; -use MolCustomer; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; } -class MolCustomerRepository extends AbstractRepository +class MolCustomerRepository extends AbstractRepository implements MolCustomerRepositoryInterface { - public function findOneBy(array $keyValueCriteria): ?MolCustomer + public function __construct() { - /** @var ?MolCustomer $result */ - $result = parent::findOneBy($keyValueCriteria); - - return $result; + parent::__construct(\MolCustomer::class); } } diff --git a/src/Repository/MolCustomerRepositoryInterface.php b/src/Repository/MolCustomerRepositoryInterface.php new file mode 100644 index 000000000..847d09dc1 --- /dev/null +++ b/src/Repository/MolCustomerRepositoryInterface.php @@ -0,0 +1,24 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Repository; + +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +// TODO replace all direct calls to repository with this interface +interface MolCustomerRepositoryInterface extends ReadOnlyRepositoryInterface +{ +} diff --git a/src/Repository/MolOrderPaymentFeeRepository.php b/src/Repository/MolOrderPaymentFeeRepository.php index 4cca4fdf7..a7dcb4a6f 100644 --- a/src/Repository/MolOrderPaymentFeeRepository.php +++ b/src/Repository/MolOrderPaymentFeeRepository.php @@ -12,6 +12,7 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; use MolOrderPaymentFee; if (!defined('_PS_VERSION_')) { diff --git a/src/Repository/MolOrderPaymentFeeRepositoryInterface.php b/src/Repository/MolOrderPaymentFeeRepositoryInterface.php index 9d39dec27..72e139c32 100644 --- a/src/Repository/MolOrderPaymentFeeRepositoryInterface.php +++ b/src/Repository/MolOrderPaymentFeeRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/OrderCartRuleRepository.php b/src/Repository/OrderCartRuleRepository.php index b0f71d435..0cf150a59 100644 --- a/src/Repository/OrderCartRuleRepository.php +++ b/src/Repository/OrderCartRuleRepository.php @@ -13,6 +13,7 @@ namespace Mollie\Repository; use Db; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; use OrderCartRule; if (!defined('_PS_VERSION_')) { diff --git a/src/Repository/OrderCartRuleRepositoryInterface.php b/src/Repository/OrderCartRuleRepositoryInterface.php index d7c4d73d5..806647d1d 100644 --- a/src/Repository/OrderCartRuleRepositoryInterface.php +++ b/src/Repository/OrderCartRuleRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/OrderRepository.php b/src/Repository/OrderRepository.php index 82d43d1f4..46f9e43da 100644 --- a/src/Repository/OrderRepository.php +++ b/src/Repository/OrderRepository.php @@ -12,6 +12,7 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; use Order; if (!defined('_PS_VERSION_')) { @@ -36,4 +37,16 @@ public function findOneByCartId($id_cart) { return $this->findOneBy(['id_cart' => (int) $id_cart]); } + + /** + * @param int $id_cart + * + * @return \PrestaShopCollection + * + * @throws \PrestaShopException + */ + public function findAllByCartId($id_cart) + { + return $this->findAllBy(['id_cart' => (int) $id_cart]); + } } diff --git a/src/Repository/OrderRepositoryInterface.php b/src/Repository/OrderRepositoryInterface.php index b4ede2e80..b8dc7b49b 100644 --- a/src/Repository/OrderRepositoryInterface.php +++ b/src/Repository/OrderRepositoryInterface.php @@ -12,6 +12,7 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; use Order; if (!defined('_PS_VERSION_')) { diff --git a/src/Repository/PaymentMethodRepository.php b/src/Repository/PaymentMethodRepository.php index 6d88f176f..119b5130e 100644 --- a/src/Repository/PaymentMethodRepository.php +++ b/src/Repository/PaymentMethodRepository.php @@ -17,6 +17,7 @@ use DbQuery; use Exception; use Mollie\Api\Types\PaymentStatus; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; use MolPaymentMethod; use mysqli_result; use PDOStatement; diff --git a/src/Repository/PaymentMethodRepositoryInterface.php b/src/Repository/PaymentMethodRepositoryInterface.php index 7725fa546..696a1cddb 100644 --- a/src/Repository/PaymentMethodRepositoryInterface.php +++ b/src/Repository/PaymentMethodRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/PendingOrderCartRepository.php b/src/Repository/PendingOrderCartRepository.php index 8af8e5045..14af1d001 100644 --- a/src/Repository/PendingOrderCartRepository.php +++ b/src/Repository/PendingOrderCartRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/PendingOrderCartRuleRepository.php b/src/Repository/PendingOrderCartRuleRepository.php index 7ae87c8ae..665262ebb 100644 --- a/src/Repository/PendingOrderCartRuleRepository.php +++ b/src/Repository/PendingOrderCartRuleRepository.php @@ -13,6 +13,7 @@ namespace Mollie\Repository; use Db; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; use MolPendingOrderCartRule; use Order; use OrderCartRule; diff --git a/src/Repository/PendingOrderCartRuleRepositoryInterface.php b/src/Repository/PendingOrderCartRuleRepositoryInterface.php index 179522094..6fb222fae 100644 --- a/src/Repository/PendingOrderCartRuleRepositoryInterface.php +++ b/src/Repository/PendingOrderCartRuleRepositoryInterface.php @@ -12,6 +12,7 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; use MolPendingOrderCartRule; use Order; use OrderCartRule; diff --git a/src/Repository/ProductRepository.php b/src/Repository/ProductRepository.php index 90e17b9a8..e5e94ce83 100644 --- a/src/Repository/ProductRepository.php +++ b/src/Repository/ProductRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/ProductRepositoryInterface.php b/src/Repository/ProductRepositoryInterface.php index fd9cecfd5..a963d2465 100644 --- a/src/Repository/ProductRepositoryInterface.php +++ b/src/Repository/ProductRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/ReadOnlyRepositoryInterface.php b/src/Repository/ReadOnlyRepositoryInterface.php deleted file mode 100644 index 783717dc7..000000000 --- a/src/Repository/ReadOnlyRepositoryInterface.php +++ /dev/null @@ -1,42 +0,0 @@ - - * @copyright Mollie B.V. - * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md - * - * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart - */ - -namespace Mollie\Repository; - -use ObjectModel; -use PrestaShopCollection; - -if (!defined('_PS_VERSION_')) { - exit; -} - -interface ReadOnlyRepositoryInterface -{ - /** - * @return PrestaShopCollection - */ - public function findAll(); - - /** - * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] - * - * @return ObjectModel|null - */ - public function findOneBy(array $keyValueCriteria); - - /** - * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] - * - * @return PrestaShopCollection|null - */ - public function findAllBy(array $keyValueCriteria); -} diff --git a/src/Repository/TaxRepository.php b/src/Repository/TaxRepository.php index 446177045..d74959330 100644 --- a/src/Repository/TaxRepository.php +++ b/src/Repository/TaxRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/TaxRepositoryInterface.php b/src/Repository/TaxRepositoryInterface.php index 04c602b56..03fc29d77 100644 --- a/src/Repository/TaxRepositoryInterface.php +++ b/src/Repository/TaxRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/TaxRuleRepository.php b/src/Repository/TaxRuleRepository.php index 759c22276..e5f3cba82 100644 --- a/src/Repository/TaxRuleRepository.php +++ b/src/Repository/TaxRuleRepository.php @@ -14,6 +14,7 @@ use Db; use DbQuery; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; diff --git a/src/Repository/TaxRuleRepositoryInterface.php b/src/Repository/TaxRuleRepositoryInterface.php index 269478abf..68882042f 100644 --- a/src/Repository/TaxRuleRepositoryInterface.php +++ b/src/Repository/TaxRuleRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Repository/TaxRulesGroupRepository.php b/src/Repository/TaxRulesGroupRepository.php index 5899bd576..80a3dfb3d 100644 --- a/src/Repository/TaxRulesGroupRepository.php +++ b/src/Repository/TaxRulesGroupRepository.php @@ -14,6 +14,7 @@ use Db; use DbQuery; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; diff --git a/src/Repository/TaxRulesGroupRepositoryInterface.php b/src/Repository/TaxRulesGroupRepositoryInterface.php index 4666ea9c1..45366fb4e 100644 --- a/src/Repository/TaxRulesGroupRepositoryInterface.php +++ b/src/Repository/TaxRulesGroupRepositoryInterface.php @@ -12,6 +12,8 @@ namespace Mollie\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/src/Service/ApiService.php b/src/Service/ApiService.php index 09ecdb344..481c5a29b 100644 --- a/src/Service/ApiService.php +++ b/src/Service/ApiService.php @@ -119,7 +119,7 @@ public function getMethodsForConfig(MollieApiClient $api) try { /** Requires local param or fails */ /** @var BaseCollection|MethodCollection $apiMethods */ - $apiMethods = $api->methods->allAvailable(['locale' => '']); + $apiMethods = $api->methods->allAvailable(['locale' => '', 'include' => 'issuers']); $apiMethods = $apiMethods->getArrayCopy(); /** @var Method $method */ foreach ($apiMethods as $key => $method) { diff --git a/src/Service/ConfigFieldService.php b/src/Service/ConfigFieldService.php index 74c275bd0..054904753 100644 --- a/src/Service/ConfigFieldService.php +++ b/src/Service/ConfigFieldService.php @@ -101,8 +101,6 @@ public function getConfigFieldsValues() Config::MOLLIE_STATUS_SHIPPING => $this->configurationAdapter->get(Config::MOLLIE_STATUS_SHIPPING), Config::MOLLIE_MAIL_WHEN_SHIPPING => $this->configurationAdapter->get(Config::MOLLIE_MAIL_WHEN_SHIPPING), Config::MOLLIE_AUTHORIZABLE_PAYMENT_INVOICE_ON_STATUS => $this->configurationAdapter->get(Config::MOLLIE_AUTHORIZABLE_PAYMENT_INVOICE_ON_STATUS), - - Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID => $this->configurationAdapter->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID), ]; if (EnvironmentUtility::getApiKey() && $this->module->getApiClient() !== null) { diff --git a/src/Service/MailService.php b/src/Service/MailService.php index 67599c2bb..843bf685f 100644 --- a/src/Service/MailService.php +++ b/src/Service/MailService.php @@ -18,19 +18,14 @@ use CartRule; use Configuration; use Context; -use Currency; use Customer; use Hook; use Language; use Mail; use Mollie; use Mollie\Adapter\ProductAttributeAdapter; -use Mollie\Adapter\ToolsAdapter; -use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; -use Mollie\Subscription\Repository\RecurringOrdersProductRepositoryInterface; -use Mollie\Utility\NumberUtility; -use MolRecurringOrder; -use MolRecurringOrdersProduct; +use Mollie\Exception\MollieException; +use Mollie\Subscription\Provider\GeneralSubscriptionMailDataProvider; use Order; use OrderState; use PDF; @@ -46,37 +41,24 @@ class MailService { const FILE_NAME = 'MailService'; - /** - * @var Mollie - */ + /** @var Mollie */ private $module; - - /** - * @var Context - */ + /** @var Context */ private $context; /** @var ProductAttributeAdapter */ private $productAttributeAdapter; - /** @var RecurringOrderRepositoryInterface */ - private $recurringOrderRepository; - /** @var RecurringOrdersProductRepositoryInterface */ - private $recurringOrdersProductRepository; - /** @var ToolsAdapter */ - private $toolsAdapter; + /** @var GeneralSubscriptionMailDataProvider */ + private $generalSubscriptionMailDataProvider; public function __construct( Mollie $module, ProductAttributeAdapter $productAttributeAdapter, - RecurringOrderRepositoryInterface $recurringOrderRepository, - RecurringOrdersProductRepositoryInterface $recurringOrdersProductRepository, - ToolsAdapter $toolsAdapter + GeneralSubscriptionMailDataProvider $generalSubscriptionMailDataProvider ) { $this->module = $module; $this->context = Context::getContext(); $this->productAttributeAdapter = $productAttributeAdapter; - $this->recurringOrderRepository = $recurringOrderRepository; - $this->recurringOrdersProductRepository = $recurringOrdersProductRepository; - $this->toolsAdapter = $toolsAdapter; + $this->generalSubscriptionMailDataProvider = $generalSubscriptionMailDataProvider; } public function sendSecondChanceMail(Customer $customer, $checkoutUrl, $methodName, $shopId) @@ -130,84 +112,78 @@ public function sendOrderConfMail(Order $order, $orderStateId) ); } + /** + * @throws MollieException + */ public function sendSubscriptionCancelWarningEmail(int $recurringOrderId): void { - $recurringOrder = $this->recurringOrderRepository->findOneBy(['id_mol_recurring_order' => $recurringOrderId]); - $recurringOrderProduct = $this->recurringOrdersProductRepository->findOneBy(['id_mol_recurring_orders_product' => $recurringOrder->id_mol_recurring_orders_product]); - $customer = new Customer($recurringOrder->id_customer); - $product = new Product($recurringOrderProduct->id_product, false, $customer->id_lang); - - $data = $this->getSubscriptionCancellationWarningMailData($recurringOrder, $recurringOrderProduct, $customer); + $data = $this->generalSubscriptionMailDataProvider->run($recurringOrderId); Mail::Send( - (int) $customer->id_lang, + $data->getLangId(), 'mollie_subscription_cancel', - sprintf(Mail::l('Your payment for the subscription of %s failed', (int) $customer->id_lang), $product->name), - $data, - $customer->email, - implode(' ', [$customer->firstname, $customer->lastname]), + sprintf(Mail::l('Your payment for the subscription of %s failed', $data->getLangId()), $data->getProductName()), + $data->toArray(), + $data->getCustomerEmail(), + implode(' ', [$data->getFirstName(), $data->getLastName()]), null, null, null, null, $this->module->getLocalPath() . 'mails/', false, - (int) $customer->id_shop + $data->getShopId() ); } + /** + * @throws MollieException + */ public function sendSubscriptionPaymentFailWarningMail(int $recurringOrderId): void { - $recurringOrder = $this->recurringOrderRepository->findOneBy(['id_mol_recurring_order' => $recurringOrderId]); - $recurringOrderProduct = $this->recurringOrdersProductRepository->findOneBy(['id_mol_recurring_orders_product' => $recurringOrder->id_mol_recurring_orders_product]); - $customer = new Customer($recurringOrder->id_customer); - $product = new Product($recurringOrderProduct->id_product, false, $customer->id_lang); - - $data = $this->getSubscriptionCancellationWarningMailData($recurringOrder, $recurringOrderProduct, $customer); + $data = $this->generalSubscriptionMailDataProvider->run($recurringOrderId); Mail::Send( - (int) $customer->id_lang, + $data->getLangId(), 'mollie_subscription_payment_failed', - sprintf(Mail::l('Your subscription for %s cancelled', (int) $customer->id_lang), $product->name), - $data, - $customer->email, - implode(' ', [$customer->firstname, $customer->lastname]), + sprintf(Mail::l('Your subscription for %s cancelled', $data->getLangId()), $data->getProductName()), + $data->toArray(), + $data->getCustomerEmail(), + implode(' ', [$data->getFirstName(), $data->getLastName()]), null, null, null, null, $this->module->getLocalPath() . 'mails/', false, - (int) $customer->id_shop + $data->getShopId() ); } - private function getSubscriptionCancellationWarningMailData( - MolRecurringOrder $recurringOrder, - MolRecurringOrdersProduct $recurringOrderProduct, - Customer $customer - ): array { - $product = new Product($recurringOrderProduct->id_product, false, $customer->id_lang); - - $totalPrice = NumberUtility::toPrecision( - (float) $recurringOrder->total_tax_incl, - NumberUtility::DECIMAL_PRECISION - ); - - $unitPrice = NumberUtility::toPrecision( - (float) $recurringOrderProduct->unit_price, - NumberUtility::DECIMAL_PRECISION + /** + * @throws MollieException + */ + public function sendSubscriptionCarrierUpdateMail(int $recurringOrderId): bool + { + $data = $this->generalSubscriptionMailDataProvider->run($recurringOrderId); + + $result = Mail::Send( + $data->getLangId(), + 'mollie_subscription_carrier_update', + sprintf(Mail::l('Your subscription for %s carrier was updated', $data->getLangId()), $data->getProductName()), + $data->toArray(), + $data->getCustomerEmail(), + implode(' ', [$data->getFirstName(), $data->getLastName()]), + null, + null, + null, + null, + $this->module->getLocalPath() . 'mails/', + false, + $data->getShopId() ); - return [ - 'subscription_reference' => $recurringOrder->mollie_subscription_id, - 'product_name' => $product->name, - 'unit_price' => $this->toolsAdapter->displayPrice($unitPrice, new Currency($recurringOrder->id_currency)), - 'quantity' => $recurringOrderProduct->quantity, - 'total_price' => $this->toolsAdapter->displayPrice($totalPrice, new Currency($recurringOrder->id_currency)), - 'firstName' => $customer->firstname, - 'lastName' => $customer->lastname, - ]; + return !(is_bool($result) && !$result); } /** diff --git a/src/Service/OrderStatusService.php b/src/Service/OrderStatusService.php index bfa1dab0a..47fe0d11c 100644 --- a/src/Service/OrderStatusService.php +++ b/src/Service/OrderStatusService.php @@ -16,6 +16,7 @@ use Mollie\Api\Types\OrderStatus; use Mollie\Api\Types\PaymentStatus; use Mollie\Config\Config; +use Mollie\Repository\OrderRepository; use Mollie\Utility\OrderStatusUtility; use Order; use OrderDetail; @@ -36,9 +37,12 @@ class OrderStatusService */ private $mailService; - public function __construct(MailService $mailService) + private $orderRepository; + + public function __construct(MailService $mailService, OrderRepository $orderRepository) { $this->mailService = $mailService; + $this->orderRepository = $orderRepository; } /** @@ -102,20 +106,41 @@ public function setOrderStatus($orderId, $statusId, $useExistingPayment = null, $useExistingPayment = !$order->hasInvoice(); } - $history = new OrderHistory(); - $history->id_order = $order->id; - $history->changeIdOrderState($statusId, $orderId, $useExistingPayment); + $orders = $this->orderRepository->findAllByCartId($order->id_cart); + if (count($orders) > 1) { + foreach ($orders as $subOrder) { + $history = new OrderHistory(); + $history->id_order = $subOrder->id; + $history->changeIdOrderState($statusId, $subOrder->id, $useExistingPayment); - $status = OrderStatusUtility::transformPaymentStatusToPaid($status, Config::STATUS_PAID_ON_BACKORDER); + $status = OrderStatusUtility::transformPaymentStatusToPaid($status, Config::STATUS_PAID_ON_BACKORDER); - if ($this->checkIfOrderConfNeedsToBeSend($statusId)) { - $this->mailService->sendOrderConfMail($order, $statusId); - } + if ($this->checkIfOrderConfNeedsToBeSend($statusId)) { + $this->mailService->sendOrderConfMail($subOrder, $statusId); + } - if ('0' === Configuration::get('MOLLIE_MAIL_WHEN_' . Tools::strtoupper($status))) { - $history->add(); + if ('0' === Configuration::get('MOLLIE_MAIL_WHEN_' . Tools::strtoupper($status))) { + $history->add(); + } else { + $history->addWithemail(true, $templateVars); + } + } } else { - $history->addWithemail(true, $templateVars); + $history = new OrderHistory(); + $history->id_order = $order->id; + $history->changeIdOrderState($statusId, $orderId, $useExistingPayment); + + $status = OrderStatusUtility::transformPaymentStatusToPaid($status, Config::STATUS_PAID_ON_BACKORDER); + + if ($this->checkIfOrderConfNeedsToBeSend($statusId)) { + $this->mailService->sendOrderConfMail($order, $statusId); + } + + if ('0' === Configuration::get('MOLLIE_MAIL_WHEN_' . Tools::strtoupper($status))) { + $history->add(); + } else { + $history->addWithemail(true, $templateVars); + } } } diff --git a/src/Service/PaymentMethodService.php b/src/Service/PaymentMethodService.php index f5f6cd9bf..24fce0c4c 100644 --- a/src/Service/PaymentMethodService.php +++ b/src/Service/PaymentMethodService.php @@ -177,7 +177,9 @@ public function savePaymentMethod($method) $paymentMethod->surcharge_fixed_amount_tax_excl = Tools::getValue(Mollie\Config\Config::MOLLIE_METHOD_SURCHARGE_FIXED_AMOUNT_TAX_EXCL . $method['id']); $paymentMethod->tax_rules_group_id = Tools::getValue(Mollie\Config\Config::MOLLIE_METHOD_TAX_RULES_GROUP_ID . $method['id']); $paymentMethod->surcharge_percentage = Tools::getValue(Mollie\Config\Config::MOLLIE_METHOD_SURCHARGE_PERCENTAGE . $method['id']); - $paymentMethod->surcharge_limit = Tools::getValue(Mollie\Config\Config::MOLLIE_METHOD_SURCHARGE_LIMIT . $method['id']); + $paymentMethod->surcharge_limit = Tools::getValue(Mollie\Config\Config::MOLLIE_METHOD_SURCHARGE_LIMIT . $method['id']) === '' + ? (float) Tools::getValue(Mollie\Config\Config::MOLLIE_METHOD_MAX_AMOUNT . $method['id']) + : Tools::getValue(Mollie\Config\Config::MOLLIE_METHOD_SURCHARGE_LIMIT . $method['id']); $paymentMethod->images_json = json_encode($method['image']); $paymentMethod->live_environment = $environment; $paymentMethod->id_shop = $shopId; diff --git a/src/Service/SettingsSaveService.php b/src/Service/SettingsSaveService.php index 13a3eea50..64a305ff3 100644 --- a/src/Service/SettingsSaveService.php +++ b/src/Service/SettingsSaveService.php @@ -243,6 +243,7 @@ public function saveSettings(&$errors = []) $mollieOrderConfirmationSand = $this->tools->getValue(Config::MOLLIE_SEND_ORDER_CONFIRMATION); $mollieIFrameEnabled = $this->tools->getValue(Config::MOLLIE_IFRAME[$environment ? 'production' : 'sandbox']); $mollieSingleClickPaymentEnabled = $this->tools->getValue(Config::MOLLIE_SINGLE_CLICK_PAYMENT[$environment ? 'production' : 'sandbox']); + $mollieImages = $this->tools->getValue(Config::MOLLIE_IMAGES); $showResentPayment = $this->tools->getValue(Config::MOLLIE_SHOW_RESEND_PAYMENT_LINK); $mollieIssuers = $this->tools->getValue(Config::MOLLIE_ISSUERS[$environment ? 'production' : 'sandbox']); @@ -261,8 +262,6 @@ public function saveSettings(&$errors = []) $applePayDirectStyle = $this->tools->getValue(Config::MOLLIE_APPLE_PAY_DIRECT_STYLE); $isBancontactQrCodeEnabled = $this->tools->getValue(Config::MOLLIE_BANCONTACT_QR_CODE_ENABLED); - $subscriptionsShippingOption = (int) $this->tools->getValue(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); - $mollieShipMain = $this->tools->getValue(Config::MOLLIE_AUTO_SHIP_MAIN); if (!isset($mollieErrors)) { $mollieErrors = false; @@ -317,7 +316,6 @@ public function saveSettings(&$errors = []) $this->configurationAdapter->updateValue(Config::MOLLIE_DEBUG_LOG, (int) $mollieLogger); $this->configurationAdapter->updateValue(Config::MOLLIE_API, $mollieApi); $this->configurationAdapter->updateValue(Config::MOLLIE_VOUCHER_CATEGORY, $voucherCategory); - $this->configurationAdapter->updateValue(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, $subscriptionsShippingOption); $this->configurationAdapter->updateValue( Config::MOLLIE_AUTO_SHIP_STATUSES, json_encode($this->getStatusesValue(Config::MOLLIE_AUTO_SHIP_STATUSES)) diff --git a/src/ServiceProvider/BaseServiceProvider.php b/src/ServiceProvider/BaseServiceProvider.php index c36483ae9..876fbf367 100644 --- a/src/ServiceProvider/BaseServiceProvider.php +++ b/src/ServiceProvider/BaseServiceProvider.php @@ -65,13 +65,12 @@ use Mollie\Repository\CartRuleRepositoryInterface; use Mollie\Repository\CountryRepository; use Mollie\Repository\CountryRepositoryInterface; -use Mollie\Repository\CurrencyRepository; -use Mollie\Repository\CurrencyRepositoryInterface; use Mollie\Repository\CustomerRepository; use Mollie\Repository\CustomerRepositoryInterface; use Mollie\Repository\GenderRepository; use Mollie\Repository\GenderRepositoryInterface; use Mollie\Repository\MolCustomerRepository; +use Mollie\Repository\MolCustomerRepositoryInterface; use Mollie\Repository\MolOrderPaymentFeeRepository; use Mollie\Repository\MolOrderPaymentFeeRepositoryInterface; use Mollie\Repository\OrderRepository; @@ -105,11 +104,13 @@ use Mollie\Service\Shipment\ShipmentInformationSenderInterface; use Mollie\Service\ShipmentService; use Mollie\Service\ShipmentServiceInterface; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepository; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; use Mollie\Subscription\Grid\Accessibility\SubscriptionCancelAccessibility; use Mollie\Subscription\Install\Installer; use Mollie\Subscription\Install\InstallerInterface; -use Mollie\Subscription\Logger\Logger; -use Mollie\Subscription\Logger\LoggerInterface; +use Mollie\Subscription\Repository\CombinationRepository; +use Mollie\Subscription\Repository\CombinationRepositoryInterface; use Mollie\Subscription\Repository\OrderDetailRepository; use Mollie\Subscription\Repository\OrderDetailRepositoryInterface; use Mollie\Subscription\Repository\RecurringOrderRepository; @@ -147,7 +148,6 @@ public function __construct($extendedServices) public function register(Container $container) { /* Logger */ - $this->addService($container, LoggerInterface::class, $container->get(Logger::class)); $this->addService($container, PrestaLoggerInterface::class, $container->get(PrestaLogger::class)); /* Utility */ @@ -161,17 +161,18 @@ public function register(Container $container) $this->addService($container, CountryRepositoryInterface::class, $container->get(CountryRepository::class)); $this->addService($container, PaymentMethodRepositoryInterface::class, $container->get(PaymentMethodRepository::class)); $this->addService($container, GenderRepositoryInterface::class, $container->get(GenderRepository::class)); - $this->addService($container, MolCustomerRepository::class, MolCustomerRepository::class) - ->withArgument('MolCustomer'); + $this->addService($container, CombinationRepositoryInterface::class, $container->get(CombinationRepository::class)); + $this->addService($container, MolCustomerRepositoryInterface::class, $container->get(MolCustomerRepository::class)); + + $service = $this->addService($container, MolCustomerRepository::class, MolCustomerRepository::class); + $this->addServiceArgument($service, 'MolCustomer'); $this->addService($container, UninstallerInterface::class, $container->get(Mollie\Install\DatabaseTableUninstaller::class)); - $this->addService($container, InstallerInterface::class, Installer::class) - ->withArguments([ - $container->get(Mollie\Subscription\Install\DatabaseTableInstaller::class), - $container->get(Mollie\Subscription\Install\AttributeInstaller::class), - $container->get(Mollie\Subscription\Install\HookInstaller::class), - ]); + $service = $this->addService($container, InstallerInterface::class, Installer::class); + $this->addServiceArgument($service, $container->get(Mollie\Subscription\Install\DatabaseTableInstaller::class)); + $this->addServiceArgument($service, $container->get(Mollie\Subscription\Install\AttributeInstaller::class)); + $this->addServiceArgument($service, $container->get(Mollie\Subscription\Install\HookInstaller::class)); $this->addService($container, DecoderInterface::class, JsonDecoder::class); @@ -183,13 +184,11 @@ public function register(Container $container) $this->addService($container, OrderEndpointPaymentTypeHandlerInterface::class, $container->get(OrderEndpointPaymentTypeHandler::class)); $this->addService($container, ShipmentVerificationInterface::class, $container->get(CanSendShipment::class)); $this->addService($container, ShipmentInformationSenderInterface::class, $container->get(ShipmentInformationSender::class)); - $this->addService($container, ShipmentSenderHandlerInterface::class, ShipmentSenderHandler::class) - ->withArguments( - [ - $container->get(ShipmentVerificationInterface::class), - $container->get(ShipmentInformationSenderInterface::class), - ] - ); + + $service = $this->addService($container, ShipmentSenderHandlerInterface::class, ShipmentSenderHandler::class); + + $this->addServiceArgument($service, $container->get(ShipmentVerificationInterface::class)); + $this->addServiceArgument($service, $container->get(ShipmentInformationSenderInterface::class)); $this->addService($container, AddressRepositoryInterface::class, $container->get(AddressRepository::class)); $this->addService($container, AddressFormatRepositoryInterface::class, $container->get(AddressFormatRepository::class)); @@ -214,10 +213,11 @@ public function register(Container $container) $this->addService($container, CarrierRepositoryInterface::class, $container->get(CarrierRepository::class)); $this->addService($container, CartRuleQuantityChangeHandlerInterface::class, $container->get(CartRuleQuantityChangeHandler::class)); - $this->addService($container, RecurringOrderRepositoryInterface::class, RecurringOrderRepository::class) - ->withArgument('MolRecurringOrder'); - $this->addService($container, RecurringOrdersProductRepositoryInterface::class, RecurringOrdersProductRepository::class) - ->withArgument('MolRecurringOrdersProduct'); + $service = $this->addService($container, RecurringOrderRepositoryInterface::class, RecurringOrderRepository::class); + $this->addServiceArgument($service, 'MolRecurringOrder'); + + $service = $this->addService($container, RecurringOrdersProductRepositoryInterface::class, RecurringOrdersProductRepository::class); + $this->addServiceArgument($service, 'MolRecurringOrdersProduct'); $this->addService($container, TemplateParserInterface::class, SmartyTemplateParser::class); @@ -225,8 +225,9 @@ public function register(Container $container) $this->addService($container, PaymentMethodSortProviderInterface::class, PaymentMethodSortProvider::class); $this->addService($container, PhoneNumberProviderInterface::class, PhoneNumberProvider::class); - $this->addService($container, PaymentMethodRestrictionValidationInterface::class, PaymentMethodRestrictionValidation::class) - ->withArgument([ + + $this->addService($container, PaymentMethodRestrictionValidationInterface::class, function () use ($container) { + return new PaymentMethodRestrictionValidation([ $container->get(BasePaymentMethodRestrictionValidator::class), $container->get(VoucherPaymentMethodRestrictionValidator::class), $container->get(EnvironmentVersionSpecificPaymentMethodRestrictionValidator::class), @@ -234,22 +235,23 @@ public function register(Container $container) $container->get(AmountPaymentMethodRestrictionValidator::class), $container->get(B2bPaymentMethodRestrictionValidator::class), ]); + }); $this->addService($container, CustomLogoProviderInterface::class, $container->get(CreditCardLogoProvider::class)); - $this->addService($container, PaymentMethodPositionHandlerInterface::class, PaymentMethodPositionHandler::class) - ->withArgument(PaymentMethodRepositoryInterface::class); + $service = $this->addService($container, PaymentMethodPositionHandlerInterface::class, PaymentMethodPositionHandler::class); + $this->addServiceArgument($service, PaymentMethodRepositoryInterface::class); - $this->addService($container, CertificateHandlerInterface::class, ApplePayDirectCertificateHandler::class) - ->withArgument(Mollie::class); + $service = $this->addService($container, CertificateHandlerInterface::class, ApplePayDirectCertificateHandler::class); + $this->addServiceArgument($service, Mollie::class); $this->addService($container, ProfileIdProviderInterface::class, ProfileIdProvider::class); $this->addService($container, PaymentOptionHandlerInterface::class, $container->get(PaymentOptionHandler::class)); - $this->addService($container, ApiTestFeedbackBuilder::class, ApiTestFeedbackBuilder::class) - ->withArgument($container->get(ModuleFactory::class)->getModuleVersion() ?? '') - ->withArgument(ApiKeyService::class); + $service = $this->addService($container, ApiTestFeedbackBuilder::class, ApiTestFeedbackBuilder::class); + $this->addServiceArgument($service, $container->get(ModuleFactory::class)->getModuleVersion() ?? ''); + $this->addServiceArgument($service, ApiKeyService::class); } private function addService(Container $container, $className, $service) @@ -266,4 +268,13 @@ public function getService($className, $service) return $service; } + + private function addServiceArgument($service, $argument) + { + if (method_exists($service, 'withArgument')) { + return $service->withArgument($argument); + } else { + return $service->addArgument($argument); + } + } } diff --git a/subscription/Action/CreateRecurringOrderAction.php b/subscription/Action/CreateRecurringOrderAction.php new file mode 100644 index 000000000..a609b8d25 --- /dev/null +++ b/subscription/Action/CreateRecurringOrderAction.php @@ -0,0 +1,78 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Action; + +use Mollie\Logger\PrestaLoggerInterface; +use Mollie\Subscription\DTO\CreateRecurringOrderData; +use Mollie\Subscription\Exception\CouldNotCreateRecurringOrder; +use Mollie\Subscription\Exception\MollieSubscriptionException; +use Mollie\Subscription\Utility\ClockInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CreateRecurringOrderAction +{ + /** @var PrestaLoggerInterface */ + private $logger; + /** @var ClockInterface */ + private $clock; + + public function __construct( + PrestaLoggerInterface $logger, + ClockInterface $clock + ) { + $this->logger = $logger; + $this->clock = $clock; + } + + /** + * @throws MollieSubscriptionException + */ + public function run(CreateRecurringOrderData $data): \MolRecurringOrder + { + $this->logger->debug(sprintf('%s - Function called', __METHOD__)); + + try { + $recurringOrder = new \MolRecurringOrder(); + + $recurringOrder->id_mol_recurring_orders_product = $data->getRecurringOrdersProductId(); + $recurringOrder->id_order = $data->getOrderId(); + $recurringOrder->id_cart = $data->getCartId(); + $recurringOrder->id_currency = $data->getCurrencyId(); + $recurringOrder->id_customer = $data->getCustomerId(); + $recurringOrder->id_address_delivery = $data->getDeliveryAddressId(); + $recurringOrder->id_address_invoice = $data->getInvoiceAddressId(); + $recurringOrder->description = $data->getDescription(); + $recurringOrder->status = $data->getStatus(); + $recurringOrder->total_tax_incl = $data->getSubscriptionTotalAmount(); + $recurringOrder->payment_method = $data->getMethod(); + $recurringOrder->next_payment = $data->getNextPayment(); + $recurringOrder->reminder_at = $data->getReminderAt(); + $recurringOrder->cancelled_at = $data->getCancelledAt(); + $recurringOrder->mollie_subscription_id = $data->getMollieSubscriptionId(); + $recurringOrder->mollie_customer_id = $data->getMollieCustomerId(); + $recurringOrder->date_add = $this->clock->getCurrentDate(); + $recurringOrder->date_update = $this->clock->getCurrentDate(); + + $recurringOrder->add(); + } catch (\Throwable $exception) { + throw CouldNotCreateRecurringOrder::unknownError($exception); + } + + $this->logger->debug(sprintf('%s - Function ended', __METHOD__)); + + return $recurringOrder; + } +} diff --git a/subscription/Action/CreateRecurringOrdersProductAction.php b/subscription/Action/CreateRecurringOrdersProductAction.php new file mode 100644 index 000000000..d3d312a72 --- /dev/null +++ b/subscription/Action/CreateRecurringOrdersProductAction.php @@ -0,0 +1,59 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Action; + +use Mollie\Logger\PrestaLoggerInterface; +use Mollie\Subscription\DTO\CreateRecurringOrdersProductData; +use Mollie\Subscription\Exception\CouldNotCreateRecurringOrdersProduct; +use Mollie\Subscription\Exception\MollieSubscriptionException; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CreateRecurringOrdersProductAction +{ + /** @var PrestaLoggerInterface */ + private $logger; + + public function __construct( + PrestaLoggerInterface $logger + ) { + $this->logger = $logger; + } + + /** + * @throws MollieSubscriptionException + */ + public function run(CreateRecurringOrdersProductData $data): \MolRecurringOrdersProduct + { + $this->logger->debug(sprintf('%s - Function called', __METHOD__)); + + try { + $recurringOrdersProduct = new \MolRecurringOrdersProduct(); + + $recurringOrdersProduct->id_product = $data->getProductId(); + $recurringOrdersProduct->id_product_attribute = $data->getProductAttributeId(); + $recurringOrdersProduct->quantity = $data->getProductQuantity(); + $recurringOrdersProduct->unit_price = $data->getUnitPriceTaxExcl(); + + $recurringOrdersProduct->add(); + } catch (\Throwable $exception) { + throw CouldNotCreaterecurringOrdersProduct::unknownError($exception); + } + + $this->logger->debug(sprintf('%s - Function ended', __METHOD__)); + + return $recurringOrdersProduct; + } +} diff --git a/subscription/Action/UpdateRecurringOrderAction.php b/subscription/Action/UpdateRecurringOrderAction.php new file mode 100644 index 000000000..4e898b063 --- /dev/null +++ b/subscription/Action/UpdateRecurringOrderAction.php @@ -0,0 +1,74 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Action; + +use Mollie\Exception\MollieException; +use Mollie\Logger\PrestaLoggerInterface; +use Mollie\Subscription\DTO\UpdateRecurringOrderData; +use Mollie\Subscription\Exception\CouldNotUpdateRecurringOrder; +use Mollie\Subscription\Exception\MollieSubscriptionException; +use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; +use Mollie\Subscription\Utility\ClockInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class UpdateRecurringOrderAction +{ + /** @var PrestaLoggerInterface */ + private $logger; + /** @var ClockInterface */ + private $clock; + /** @var RecurringOrderRepositoryInterface */ + private $recurringOrderRepository; + + public function __construct( + PrestaLoggerInterface $logger, + ClockInterface $clock, + RecurringOrderRepositoryInterface $recurringOrderRepository + ) { + $this->logger = $logger; + $this->clock = $clock; + $this->recurringOrderRepository = $recurringOrderRepository; + } + + /** + * @throws MollieException|MollieSubscriptionException + */ + public function run(UpdateRecurringOrderData $data): \MolRecurringOrder + { + $this->logger->debug(sprintf('%s - Function called', __METHOD__)); + + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = $this->recurringOrderRepository->findOrFail([ + 'id_mol_recurring_order' => $data->getMollieRecurringOrderId(), + ]); + + try { + /* + * NOTE: When more properties will be needed to update, pass them up as nullable parameters. + */ + $recurringOrder->total_tax_incl = $data->getSubscriptionTotalAmount(); + $recurringOrder->date_update = $this->clock->getCurrentDate(); + + $recurringOrder->update(); + } catch (\Throwable $exception) { + throw CouldNotUpdateRecurringOrder::unknownError($exception); + } + + $this->logger->debug(sprintf('%s - Function ended', __METHOD__)); + + return $recurringOrder; + } +} diff --git a/subscription/Action/UpdateSubscriptionAction.php b/subscription/Action/UpdateSubscriptionAction.php new file mode 100644 index 000000000..84088a498 --- /dev/null +++ b/subscription/Action/UpdateSubscriptionAction.php @@ -0,0 +1,81 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Action; + +use Mollie\Factory\ModuleFactory; +use Mollie\Logger\PrestaLoggerInterface; +use Mollie\Subscription\Api\Request\UpdateSubscriptionRequest; +use Mollie\Subscription\Api\SubscriptionApi; +use Mollie\Subscription\DTO\UpdateSubscriptionData; +use Mollie\Subscription\Exception\CouldNotUpdateSubscription; +use Mollie\Subscription\Exception\MollieSubscriptionException; +use Mollie\Utility\SecureKeyUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class UpdateSubscriptionAction +{ + /** @var SubscriptionApi */ + private $subscriptionApi; + /** @var PrestaLoggerInterface */ + private $logger; + /** @var \Mollie */ + private $module; + + public function __construct( + SubscriptionApi $subscriptionApi, + PrestaLoggerInterface $logger, + ModuleFactory $moduleFactory + ) { + $this->subscriptionApi = $subscriptionApi; + $this->logger = $logger; + $this->module = $moduleFactory->getModule(); + } + + /** + * @throws MollieSubscriptionException + */ + public function run(UpdateSubscriptionData $data): void + { + $this->logger->info(sprintf('%s - Function called', __METHOD__)); + + $secureKey = SecureKeyUtility::generateReturnKey( + $data->getCustomerId(), + $data->getCartId(), + $this->module->name + ); + + $metadata = [ + 'secure_key' => $secureKey, + 'subscription_carrier_id' => $data->getSubscriptionCarrierId(), + ]; + + $updateSubscriptionData = new UpdateSubscriptionRequest( + $data->getMollieCustomerId(), + $data->getMollieSubscriptionId(), + null, + $metadata, + $data->getOrderAmount() + ); + + try { + $this->subscriptionApi->updateSubscription($updateSubscriptionData); + } catch (\Throwable $exception) { + throw CouldNotUpdateSubscription::failedToUpdateSubscription($exception, $data->getMollieSubscriptionId()); + } + + $this->logger->info(sprintf('%s - Function ended', __METHOD__)); + } +} diff --git a/subscription/Api/Request/UpdateSubscriptionRequest.php b/subscription/Api/Request/UpdateSubscriptionRequest.php new file mode 100644 index 000000000..6dbe7259e --- /dev/null +++ b/subscription/Api/Request/UpdateSubscriptionRequest.php @@ -0,0 +1,80 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +declare(strict_types=1); + +namespace Mollie\Subscription\Api\Request; + +use Mollie\Subscription\DTO\Object\Amount; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class UpdateSubscriptionRequest +{ + /** @var string */ + private $customerId; + /** @var string */ + private $subscriptionId; + /** @var ?string */ + private $mandateId; + /** @var ?array */ + private $metadata; + /** @var ?Amount */ + private $amount; + + public function __construct( + string $customerId, + string $subscriptionId, + string $mandateId = null, + array $metadata = null, + Amount $amount = null + ) { + $this->customerId = $customerId; + $this->subscriptionId = $subscriptionId; + $this->mandateId = $mandateId; + $this->metadata = $metadata; + $this->amount = $amount; + } + + public function getCustomerId(): string + { + return $this->customerId; + } + + public function getSubscriptionId(): string + { + return $this->subscriptionId; + } + + /** + * @return Amount|null + */ + public function getAmount(): ?Amount + { + return $this->amount; + } + + public function toArray(): array + { + $data = [ + 'mandateId' => $this->mandateId, + 'metadata' => $this->metadata, + 'amount' => $this->amount ? $this->amount->toArray() : null, + ]; + + return array_filter($data, static function ($val) { + return !empty($val); + }); + } +} diff --git a/subscription/Logger/index.php b/subscription/Api/Request/index.php similarity index 100% rename from subscription/Logger/index.php rename to subscription/Api/Request/index.php diff --git a/subscription/Api/SubscriptionApi.php b/subscription/Api/SubscriptionApi.php index eba2bf760..d358f25d5 100644 --- a/subscription/Api/SubscriptionApi.php +++ b/subscription/Api/SubscriptionApi.php @@ -17,10 +17,10 @@ use Mollie\Api\Exceptions\ApiException; use Mollie\Api\MollieApiClient; use Mollie\Api\Resources\Subscription as MollieSubscription; +use Mollie\Subscription\Api\Request\UpdateSubscriptionRequest; use Mollie\Subscription\DTO\CancelSubscriptionData; use Mollie\Subscription\DTO\CreateSubscriptionData; use Mollie\Subscription\DTO\GetSubscriptionData; -use Mollie\Subscription\DTO\UpdateSubscriptionData; use Mollie\Subscription\Exception\SubscriptionApiException; use Mollie\Subscription\Factory\MollieApiFactory; @@ -74,10 +74,14 @@ public function getSubscription(GetSubscriptionData $subscriptionData): MollieSu } } - public function updateSubscription(UpdateSubscriptionData $updateSubscriptionData): MollieSubscription + public function updateSubscription(UpdateSubscriptionRequest $updateSubscriptionData): MollieSubscription { try { - return $this->apiClient->subscriptions->update($updateSubscriptionData->getCustomerId(), $updateSubscriptionData->getSubscriptionId(), $updateSubscriptionData->jsonSerialize()); + return $this->apiClient->subscriptions->update( + $updateSubscriptionData->getCustomerId(), + $updateSubscriptionData->getSubscriptionId(), + $updateSubscriptionData->toArray() + ); } catch (ApiException $e) { throw new SubscriptionApiException('Failed to update subscription', SubscriptionApiException::UPDATE_FAILED, $e); } diff --git a/subscription/Controller/Symfony/SubscriptionController.php b/subscription/Controller/Symfony/SubscriptionController.php index 8adfac607..7dc177088 100644 --- a/subscription/Controller/Symfony/SubscriptionController.php +++ b/subscription/Controller/Symfony/SubscriptionController.php @@ -15,11 +15,15 @@ namespace Mollie\Subscription\Controller\Symfony; use Exception; +use Mollie\Adapter\ConfigurationAdapter; use Mollie\Adapter\Shop; +use Mollie\Config\Config; +use Mollie\Logger\PrestaLoggerInterface; use Mollie\Subscription\Exception\SubscriptionApiException; use Mollie\Subscription\Filters\SubscriptionFilters; use Mollie\Subscription\Grid\SubscriptionGridDefinitionFactory; use Mollie\Subscription\Handler\SubscriptionCancellationHandler; +use Mollie\Subscription\Handler\UpdateSubscriptionCarrierHandler; use Mollie\Utility\PsVersionUtility; use PrestaShop\PrestaShop\Core\Form\FormHandlerInterface; use PrestaShop\PrestaShop\Core\Grid\GridFactoryInterface; @@ -96,6 +100,8 @@ public function submitOptionsAction(Request $request): RedirectResponse return $this->redirectToRoute('admin_subscription_index'); } + $this->updateSubscriptionCarrier($form->getData()['carrier']); + $formHandler->save($form->getData()); $this->addFlash( @@ -106,6 +112,34 @@ public function submitOptionsAction(Request $request): RedirectResponse return $this->redirectToRoute('admin_subscription_index'); } + private function updateSubscriptionCarrier(int $newCarrierId): void + { + /** @var ConfigurationAdapter $configuration */ + $configuration = $this->module->getService(ConfigurationAdapter::class); + $oldCarrierId = $configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); + + if (empty($oldCarrierId) || empty($newCarrierId)) { + $this->addFlash( + 'error', + $this->module->l('Carrier not found', self::FILE_NAME) + ); + } + + /** @var UpdateSubscriptionCarrierHandler $subscriptionCarrierUpdateHandler */ + $subscriptionCarrierUpdateHandler = $this->module->getService(UpdateSubscriptionCarrierHandler::class); + + /** @var PrestaLoggerInterface $logger */ + $logger = $this->module->getService(PrestaLoggerInterface::class); + + $failedSubscriptionOrderIdsToUpdate = $subscriptionCarrierUpdateHandler->run($newCarrierId); + + if (!empty($failedSubscriptionOrderIdsToUpdate)) { + $logger->error('Failed to update subscription carrier for all orders.', [ + 'failed_subscription_order_ids' => json_encode($failedSubscriptionOrderIdsToUpdate), + ]); + } + } + /** * Provides filters functionality. * diff --git a/subscription/DTO/CloneOriginalSubscriptionCartData.php b/subscription/DTO/CloneOriginalSubscriptionCartData.php new file mode 100644 index 000000000..0ebe024af --- /dev/null +++ b/subscription/DTO/CloneOriginalSubscriptionCartData.php @@ -0,0 +1,87 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\DTO; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CloneOriginalSubscriptionCartData +{ + /** @var int */ + private $cartId; + /** @var int */ + private $recurringOrderProductId; + /** @var int */ + private $invoiceAddressId; + /** @var int */ + private $deliveryAddressId; + + private function __construct( + int $cartId, + int $recurringOrderProductId, + int $invoiceAddressId, + int $deliveryAddressId + ) { + $this->cartId = $cartId; + $this->recurringOrderProductId = $recurringOrderProductId; + $this->invoiceAddressId = $invoiceAddressId; + $this->deliveryAddressId = $deliveryAddressId; + } + + /** + * @return int + */ + public function getCartId(): int + { + return $this->cartId; + } + + /** + * @return int + */ + public function getRecurringOrderProductId(): int + { + return $this->recurringOrderProductId; + } + + /** + * @return int + */ + public function getInvoiceAddressId(): int + { + return $this->invoiceAddressId; + } + + /** + * @return int + */ + public function getDeliveryAddressId(): int + { + return $this->deliveryAddressId; + } + + public static function create( + int $cartId, + int $recurringOrderProductId, + int $invoiceAddressId, + int $deliveryAddressId + ): self { + return new self( + $cartId, + $recurringOrderProductId, + $invoiceAddressId, + $deliveryAddressId + ); + } +} diff --git a/subscription/DTO/CreateRecurringOrderData.php b/subscription/DTO/CreateRecurringOrderData.php new file mode 100644 index 000000000..8e7d01de1 --- /dev/null +++ b/subscription/DTO/CreateRecurringOrderData.php @@ -0,0 +1,255 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\DTO; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CreateRecurringOrderData +{ + /** @var int */ + private $recurringOrdersProductId; + /** @var int */ + private $orderId; + /** @var int */ + private $cartId; + /** @var int */ + private $currencyId; + /** @var int */ + private $customerId; + /** @var int */ + private $deliveryAddressId; + /** @var int */ + private $invoiceAddressId; + /** @var string */ + private $description; + /** @var string */ + private $status; + /** @var float */ + private $subscriptionTotalAmount; + /** @var string */ + private $method; + /** @var string */ + private $nextPayment; + /** @var string */ + private $reminderAt; + /** @var string */ + private $cancelledAt; + /** @var string */ + private $mollieSubscriptionId; + /** @var string */ + private $mollieCustomerId; + + private function __construct( + int $recurringOrdersProductId, + int $orderId, + int $cartId, + int $currencyId, + int $customerId, + int $deliveryAddressId, + int $invoiceAddressId, + string $description, + string $status, + float $subscriptionTotalAmount, + string $method, + string $nextPayment, + string $reminderAt, + string $cancelledAt, + string $mollieSubscriptionId, + string $mollieCustomerId + ) { + $this->recurringOrdersProductId = $recurringOrdersProductId; + $this->orderId = $orderId; + $this->cartId = $cartId; + $this->currencyId = $currencyId; + $this->customerId = $customerId; + $this->deliveryAddressId = $deliveryAddressId; + $this->invoiceAddressId = $invoiceAddressId; + $this->description = $description; + $this->status = $status; + $this->subscriptionTotalAmount = $subscriptionTotalAmount; + $this->method = $method; + $this->nextPayment = $nextPayment; + $this->reminderAt = $reminderAt; + $this->cancelledAt = $cancelledAt; + $this->mollieSubscriptionId = $mollieSubscriptionId; + $this->mollieCustomerId = $mollieCustomerId; + } + + /** + * @return int + */ + public function getRecurringOrdersProductId(): int + { + return $this->recurringOrdersProductId; + } + + /** + * @return int + */ + public function getOrderId(): int + { + return $this->orderId; + } + + /** + * @return int + */ + public function getCartId(): int + { + return $this->cartId; + } + + /** + * @return int + */ + public function getCurrencyId(): int + { + return $this->currencyId; + } + + /** + * @return int + */ + public function getCustomerId(): int + { + return $this->customerId; + } + + /** + * @return int + */ + public function getDeliveryAddressId(): int + { + return $this->deliveryAddressId; + } + + /** + * @return int + */ + public function getInvoiceAddressId(): int + { + return $this->invoiceAddressId; + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @return string + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @return float + */ + public function getSubscriptionTotalAmount(): float + { + return $this->subscriptionTotalAmount; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @return string + */ + public function getNextPayment(): string + { + return $this->nextPayment; + } + + /** + * @return string + */ + public function getReminderAt(): string + { + return $this->reminderAt; + } + + /** + * @return string + */ + public function getCancelledAt(): string + { + return $this->cancelledAt; + } + + /** + * @return string + */ + public function getMollieSubscriptionId(): string + { + return $this->mollieSubscriptionId; + } + + /** + * @return string + */ + public function getMollieCustomerId(): string + { + return $this->mollieCustomerId; + } + + public static function create( + int $recurringOrdersProductId, + int $orderId, + int $cartId, + int $currencyId, + int $customerId, + int $deliveryAddressId, + int $invoiceAddressId, + string $description, + string $status, + float $subscriptionTotalAmount, + string $method, + string $nextPayment, + string $reminderAt, + string $cancelledAt, + string $mollieSubscriptionId, + string $mollieCustomerId + ): self { + return new self( + $recurringOrdersProductId, + $orderId, + $cartId, + $currencyId, + $customerId, + $deliveryAddressId, + $invoiceAddressId, + $description, + $status, + $subscriptionTotalAmount, + $method, + $nextPayment, + $reminderAt, + $cancelledAt, + $mollieSubscriptionId, + $mollieCustomerId + ); + } +} diff --git a/subscription/DTO/CreateRecurringOrdersProductData.php b/subscription/DTO/CreateRecurringOrdersProductData.php new file mode 100644 index 000000000..84888c34a --- /dev/null +++ b/subscription/DTO/CreateRecurringOrdersProductData.php @@ -0,0 +1,87 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\DTO; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CreateRecurringOrdersProductData +{ + /** @var int */ + private $productId; + /** @var int */ + private $productAttributeId; + /** @var int */ + private $productQuantity; + /** @var float */ + private $unitPriceTaxExcl; + + private function __construct( + int $productId, + int $productAttributeId, + int $productQuantity, + float $unitPriceTaxExcl + ) { + $this->productId = $productId; + $this->productAttributeId = $productAttributeId; + $this->productQuantity = $productQuantity; + $this->unitPriceTaxExcl = $unitPriceTaxExcl; + } + + /** + * @return int + */ + public function getProductId(): int + { + return $this->productId; + } + + /** + * @return int + */ + public function getProductAttributeId(): int + { + return $this->productAttributeId; + } + + /** + * @return int + */ + public function getProductQuantity(): int + { + return $this->productQuantity; + } + + /** + * @return float + */ + public function getUnitPriceTaxExcl(): float + { + return $this->unitPriceTaxExcl; + } + + public static function create( + int $productId, + int $productAttributeId, + int $productQuantity, + float $unitPriceTaxExcl + ): self { + return new self( + $productId, + $productAttributeId, + $productQuantity, + $unitPriceTaxExcl + ); + } +} diff --git a/subscription/DTO/CreateSpecificPriceData.php b/subscription/DTO/CreateSpecificPriceData.php index 4d156e839..ae65f05f1 100644 --- a/subscription/DTO/CreateSpecificPriceData.php +++ b/subscription/DTO/CreateSpecificPriceData.php @@ -33,7 +33,7 @@ class CreateSpecificPriceData /** @var int */ private $currencyId; - public function __construct( + private function __construct( int $productId, int $productAttributeId, float $price, diff --git a/subscription/DTO/CreateSubscriptionData.php b/subscription/DTO/CreateSubscriptionData.php index 62c4a1ead..86a29f65f 100644 --- a/subscription/DTO/CreateSubscriptionData.php +++ b/subscription/DTO/CreateSubscriptionData.php @@ -105,6 +105,11 @@ public function setMetaData(array $metaData): void $this->metaData = $metaData; } + public function setStartDate(string $startDate): void + { + $this->startDate = $startDate; + } + public function jsonSerialize(): array { $json = []; diff --git a/subscription/DTO/Mail/GeneralSubscriptionMailData.php b/subscription/DTO/Mail/GeneralSubscriptionMailData.php new file mode 100644 index 000000000..028defd7a --- /dev/null +++ b/subscription/DTO/Mail/GeneralSubscriptionMailData.php @@ -0,0 +1,184 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\DTO\Mail; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class GeneralSubscriptionMailData +{ + /** @var string */ + private $mollieSubscriptionId; + /** @var string */ + private $productName; + /** @var float */ + private $productUnitPriceTaxExcl; + /** @var int */ + private $productQuantity; + /** @var float */ + private $totalOrderPriceTaxIncl; + /** @var string */ + private $firstName; + /** @var string */ + private $lastName; + /** @var string */ + private $customerEmail; + /** @var int */ + private $langId; + /** @var int */ + private $shopId; + + public function __construct( + string $mollieSubscriptionId, + string $productName, + float $productUnitPriceTaxExcl, + int $productQuantity, + float $totalOrderPriceTaxIncl, + string $firstName, + string $lastName, + string $customerEmail, + int $langId, + int $shopId + ) { + $this->mollieSubscriptionId = $mollieSubscriptionId; + $this->productName = $productName; + $this->productUnitPriceTaxExcl = $productUnitPriceTaxExcl; + $this->productQuantity = $productQuantity; + $this->totalOrderPriceTaxIncl = $totalOrderPriceTaxIncl; + $this->firstName = $firstName; + $this->lastName = $lastName; + $this->customerEmail = $customerEmail; + $this->langId = $langId; + $this->shopId = $shopId; + } + + /** + * @return string + */ + public function getMollieSubscriptionId(): string + { + return $this->mollieSubscriptionId; + } + + /** + * @return string + */ + public function getProductName(): string + { + return $this->productName; + } + + /** + * @return float + */ + public function getProductUnitPriceTaxExcl(): float + { + return $this->productUnitPriceTaxExcl; + } + + /** + * @return int + */ + public function getProductQuantity(): int + { + return $this->productQuantity; + } + + /** + * @return float + */ + public function getTotalOrderPriceTaxIncl(): float + { + return $this->totalOrderPriceTaxIncl; + } + + /** + * @return string + */ + public function getFirstName(): string + { + return $this->firstName; + } + + /** + * @return string + */ + public function getLastName(): string + { + return $this->lastName; + } + + /** + * @return string + */ + public function getCustomerEmail(): string + { + return $this->customerEmail; + } + + /** + * @return int + */ + public function getLangId(): int + { + return $this->langId; + } + + /** + * @return int + */ + public function getShopId(): int + { + return $this->shopId; + } + + public function toArray(): array + { + return [ + 'subscription_reference' => $this->getMollieSubscriptionId(), + 'product_name' => $this->getProductName(), + 'unit_price' => $this->getProductUnitPriceTaxExcl(), + 'quantity' => $this->getProductQuantity(), + 'total_price' => $this->getTotalOrderPriceTaxIncl(), + 'firstName' => $this->getFirstName(), + 'lastName' => $this->getLastName(), + ]; + } + + public static function create( + string $mollieSubscriptionId, + string $productName, + float $productUnitPriceTaxExcl, + int $productQuantity, + float $totalOrderPriceTaxIncl, + string $firstName, + string $lastName, + string $customerEmail, + int $langId, + int $shopId + ): self { + return new self( + $mollieSubscriptionId, + $productName, + $productUnitPriceTaxExcl, + $productQuantity, + $totalOrderPriceTaxIncl, + $firstName, + $lastName, + $customerEmail, + $langId, + $shopId + ); + } +} diff --git a/subscription/Verification/index.php b/subscription/DTO/Mail/index.php similarity index 100% rename from subscription/Verification/index.php rename to subscription/DTO/Mail/index.php diff --git a/subscription/DTO/Object/Amount.php b/subscription/DTO/Object/Amount.php index e4c004070..1b8daf10d 100644 --- a/subscription/DTO/Object/Amount.php +++ b/subscription/DTO/Object/Amount.php @@ -39,6 +39,23 @@ public function __construct(float $value, string $currency) $this->currency = $currency; } + /** + * @return float + */ + public function getValue(): float + { + return $this->value; + } + + public function toArray(): array + { + return [ + 'value' => (string) number_format($this->value, 2, '.', ''), + 'currency' => $this->currency, + ]; + } + + // TODO jsonSerialize should be only used for json_encode operation. If values needs to be casted to array, use method above. public function jsonSerialize(): array { return [ diff --git a/subscription/DTO/SubscriptionCarrierDeliveryPriceData.php b/subscription/DTO/SubscriptionCarrierDeliveryPriceData.php new file mode 100644 index 000000000..a311642ce --- /dev/null +++ b/subscription/DTO/SubscriptionCarrierDeliveryPriceData.php @@ -0,0 +1,101 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\DTO; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class SubscriptionCarrierDeliveryPriceData +{ + /** @var int */ + private $deliveryAddressId; + /** @var int */ + private $cartId; + /** @var int */ + private $customerId; + /** @var array */ + private $subscriptionProduct; + /** @var int */ + private $subscriptionCarrierId; + + private function __construct( + int $deliveryAddressId, + int $cartId, + int $customerId, + array $subscriptionProduct, + int $subscriptionCarrierId + ) { + $this->deliveryAddressId = $deliveryAddressId; + $this->cartId = $cartId; + $this->customerId = $customerId; + $this->subscriptionProduct = $subscriptionProduct; + $this->subscriptionCarrierId = $subscriptionCarrierId; + } + + /** + * @return int + */ + public function getDeliveryAddressId(): int + { + return $this->deliveryAddressId; + } + + /** + * @return int + */ + public function getCartId(): int + { + return $this->cartId; + } + + /** + * @return int + */ + public function getCustomerId(): int + { + return $this->customerId; + } + + /** + * @return array + */ + public function getSubscriptionProduct(): array + { + return $this->subscriptionProduct; + } + + /** + * @return int + */ + public function getSubscriptionCarrierId(): int + { + return $this->subscriptionCarrierId; + } + + public static function create( + int $deliveryAddressId, + int $cartId, + int $customerId, + array $subscriptionProduct, + int $subscriptionCarrierId + ): self { + return new self( + $deliveryAddressId, + $cartId, + $customerId, + $subscriptionProduct, + $subscriptionCarrierId + ); + } +} diff --git a/subscription/DTO/SubscriptionOrderAmountProviderData.php b/subscription/DTO/SubscriptionOrderAmountProviderData.php new file mode 100644 index 000000000..e5e3a2b2c --- /dev/null +++ b/subscription/DTO/SubscriptionOrderAmountProviderData.php @@ -0,0 +1,129 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\DTO; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class SubscriptionOrderAmountProviderData +{ + /** @var int */ + private $addressDeliveryId; + /** @var int */ + private $cartId; + /** @var int */ + private $customerId; + /** @var array */ + private $subscriptionProduct; + /** @var int */ + private $subscriptionCarrierId; + /** @var int */ + private $currencyId; + /** @var float */ + private $productPriceTaxIncl; + + private function __construct( + int $addressDeliveryId, + int $cartId, + int $customerId, + array $subscriptionProduct, + int $subscriptionCarrierId, + int $currencyId, + float $productPriceTaxIncl + ) { + $this->addressDeliveryId = $addressDeliveryId; + $this->cartId = $cartId; + $this->customerId = $customerId; + $this->subscriptionProduct = $subscriptionProduct; + $this->subscriptionCarrierId = $subscriptionCarrierId; + $this->currencyId = $currencyId; + $this->productPriceTaxIncl = $productPriceTaxIncl; + } + + /** + * @return int + */ + public function getAddressDeliveryId(): int + { + return $this->addressDeliveryId; + } + + /** + * @return int + */ + public function getCartId(): int + { + return $this->cartId; + } + + /** + * @return int + */ + public function getCustomerId(): int + { + return $this->customerId; + } + + /** + * @return array + */ + public function getSubscriptionProduct(): array + { + return $this->subscriptionProduct; + } + + /** + * @return int + */ + public function getSubscriptionCarrierId(): int + { + return $this->subscriptionCarrierId; + } + + /** + * @return int + */ + public function getCurrencyId(): int + { + return $this->currencyId; + } + + /** + * @return float + */ + public function getProductPriceTaxIncl(): float + { + return $this->productPriceTaxIncl; + } + + public static function create( + int $addressDeliveryId, + int $cartId, + int $customerId, + array $subscriptionProduct, + int $subscriptionCarrierId, + int $currencyId, + float $productPriceTaxIncl + ): self { + return new self( + $addressDeliveryId, + $cartId, + $customerId, + $subscriptionProduct, + $subscriptionCarrierId, + $currencyId, + $productPriceTaxIncl + ); + } +} diff --git a/subscription/DTO/UpdateRecurringOrderData.php b/subscription/DTO/UpdateRecurringOrderData.php new file mode 100644 index 000000000..43fc3867a --- /dev/null +++ b/subscription/DTO/UpdateRecurringOrderData.php @@ -0,0 +1,59 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\DTO; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class UpdateRecurringOrderData +{ + /** @var int */ + private $mollieRecurringOrderId; + /** @var float */ + private $subscriptionTotalAmount; + + private function __construct( + int $mollieRecurringOrderId, + float $subscriptionTotalAmount + ) { + $this->mollieRecurringOrderId = $mollieRecurringOrderId; + $this->subscriptionTotalAmount = $subscriptionTotalAmount; + } + + /** + * @return int + */ + public function getMollieRecurringOrderId(): int + { + return $this->mollieRecurringOrderId; + } + + /** + * @return float + */ + public function getSubscriptionTotalAmount(): float + { + return $this->subscriptionTotalAmount; + } + + public static function create( + int $mollieRecurringOrderId, + float $subscriptionTotalAmount + ): self { + return new self( + $mollieRecurringOrderId, + $subscriptionTotalAmount + ); + } +} diff --git a/subscription/DTO/UpdateSubscriptionData.php b/subscription/DTO/UpdateSubscriptionData.php index eccceaf13..53afb3ee7 100644 --- a/subscription/DTO/UpdateSubscriptionData.php +++ b/subscription/DTO/UpdateSubscriptionData.php @@ -10,48 +10,108 @@ * @codingStandardsIgnoreStart */ -declare(strict_types=1); - namespace Mollie\Subscription\DTO; -use JsonSerializable; +use Mollie\Subscription\DTO\Object\Amount; if (!defined('_PS_VERSION_')) { exit; } -class UpdateSubscriptionData implements JsonSerializable +class UpdateSubscriptionData { /** @var string */ + private $mollieCustomerId; + /** @var string */ + private $mollieSubscriptionId; + /** @var Amount */ + private $orderAmount; + /** @var int */ private $customerId; + /** @var int */ + private $cartId; + /** @var int */ + private $subscriptionCarrierId; - /** @var string */ - private $subscriptionId; + private function __construct( + string $mollieCustomerId, + string $mollieSubscriptionId, + Amount $orderAmount, + int $customerId, + int $cartId, + int $subscriptionCarrierId + ) { + $this->mollieCustomerId = $mollieCustomerId; + $this->mollieSubscriptionId = $mollieSubscriptionId; + $this->orderAmount = $orderAmount; + $this->customerId = $customerId; + $this->cartId = $cartId; + $this->subscriptionCarrierId = $subscriptionCarrierId; + } - /** @var string */ - private $mandateId; + /** + * @return string + */ + public function getMollieCustomerId(): string + { + return $this->mollieCustomerId; + } - public function __construct(string $customerId, string $subscriptionId, string $mandateId) + /** + * @return string + */ + public function getMollieSubscriptionId(): string { - $this->customerId = $customerId; - $this->subscriptionId = $subscriptionId; - $this->mandateId = $mandateId; + return $this->mollieSubscriptionId; } - public function getCustomerId(): string + /** + * @return Amount + */ + public function getOrderAmount(): Amount + { + return $this->orderAmount; + } + + /** + * @return int + */ + public function getCustomerId(): int { return $this->customerId; } - public function getSubscriptionId(): string + /** + * @return int + */ + public function getCartId(): int { - return $this->subscriptionId; + return $this->cartId; } - public function jsonSerialize(): array + /** + * @return int + */ + public function getSubscriptionCarrierId(): int { - return [ - 'mandateId' => $this->mandateId, - ]; + return $this->subscriptionCarrierId; + } + + public static function create( + string $mollieCustomerId, + string $mollieSubscriptionId, + Amount $orderAmount, + int $customerId, + int $cartId, + int $subscriptionCarrierId + ): self { + return new self( + $mollieCustomerId, + $mollieSubscriptionId, + $orderAmount, + $customerId, + $cartId, + $subscriptionCarrierId + ); } } diff --git a/subscription/Logger/LoggerInterface.php b/subscription/Exception/CouldNotCreateRecurringOrder.php similarity index 75% rename from subscription/Logger/LoggerInterface.php rename to subscription/Exception/CouldNotCreateRecurringOrder.php index 1d8f0b5a3..27c111d2d 100644 --- a/subscription/Logger/LoggerInterface.php +++ b/subscription/Exception/CouldNotCreateRecurringOrder.php @@ -10,12 +10,12 @@ * @codingStandardsIgnoreStart */ -namespace Mollie\Subscription\Logger; +namespace Mollie\Subscription\Exception; if (!defined('_PS_VERSION_')) { exit; } -interface LoggerInterface extends \Psr\Log\LoggerInterface +class CouldNotCreateRecurringOrder extends MollieSubscriptionException { } diff --git a/subscription/Exception/CouldNotCreateRecurringOrdersProduct.php b/subscription/Exception/CouldNotCreateRecurringOrdersProduct.php new file mode 100644 index 000000000..d13e0aaa6 --- /dev/null +++ b/subscription/Exception/CouldNotCreateRecurringOrdersProduct.php @@ -0,0 +1,21 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotCreateRecurringOrdersProduct extends MollieSubscriptionException +{ +} diff --git a/subscription/Exception/CouldNotCreateSubscription.php b/subscription/Exception/CouldNotCreateSubscription.php new file mode 100644 index 000000000..34dc02736 --- /dev/null +++ b/subscription/Exception/CouldNotCreateSubscription.php @@ -0,0 +1,73 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotCreateSubscription extends MollieSubscriptionException +{ + public static function invalidSubscriptionSettings(\Throwable $exception): self + { + return new self( + 'Invalid subscription settings.', + ExceptionCode::ORDER_INVALID_SUBSCRIPTION_SETTINGS, + $exception + ); + } + + public static function failedToFindSubscriptionProduct(): self + { + return new self( + 'Failed to find subscription product.', + ExceptionCode::ORDER_FAILED_TO_FIND_SUBSCRIPTION_PRODUCT + ); + } + + public static function failedToCreateSubscriptionData(\Throwable $exception): self + { + return new self( + 'Failed to create subscription data.', + ExceptionCode::ORDER_FAILED_TO_CREATE_SUBSCRIPTION_DATA, + $exception + ); + } + + public static function failedToSubscribeOrder(\Throwable $exception): self + { + return new self( + 'Failed to subscribe order.', + ExceptionCode::ORDER_FAILED_TO_SUBSCRIBE_ORDER, + $exception + ); + } + + public static function failedToCreateRecurringOrdersProduct(\Throwable $exception): self + { + return new self( + 'Failed to create recurring orders product.', + ExceptionCode::ORDER_FAILED_TO_CREATE_RECURRING_ORDERS_PRODUCT, + $exception + ); + } + + public static function failedToCreateRecurringOrder(\Throwable $exception): self + { + return new self( + 'Failed to create recurring order.', + ExceptionCode::ORDER_FAILED_TO_CREATE_RECURRING_ORDER, + $exception + ); + } +} diff --git a/subscription/Exception/CouldNotCreateSubscriptionData.php b/subscription/Exception/CouldNotCreateSubscriptionData.php new file mode 100644 index 000000000..587b4b10c --- /dev/null +++ b/subscription/Exception/CouldNotCreateSubscriptionData.php @@ -0,0 +1,52 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotCreateSubscriptionData extends MollieSubscriptionException +{ + public static function failedToFindMollieCustomer(string $email): self + { + return new self( + sprintf( + 'Failed to find Mollie customer. Email: (%s)', + $email + ), + ExceptionCode::ORDER_FAILED_TO_FIND_MOLLIE_CUSTOMER + ); + } + + public static function failedToRetrieveSubscriptionInterval(\Throwable $exception, int $productAttributeId): self + { + return new self( + sprintf( + 'Failed to retrieve subscription interval. Product attribute ID: (%s)', + $productAttributeId + ), + ExceptionCode::ORDER_FAILED_TO_RETRIEVE_SUBSCRIPTION_INTERVAL, + $exception + ); + } + + public static function failedToProvideSubscriptionOrderAmount(\Throwable $exception): self + { + return new self( + 'Failed to provide subscription order amount.', + ExceptionCode::ORDER_FAILED_TO_PROVIDE_SUBSCRIPTION_ORDER_AMOUNT, + $exception + ); + } +} diff --git a/subscription/Exception/CouldNotHandleOriginalSubscriptionCartCloning.php b/subscription/Exception/CouldNotHandleOriginalSubscriptionCartCloning.php new file mode 100644 index 000000000..7ebb3109c --- /dev/null +++ b/subscription/Exception/CouldNotHandleOriginalSubscriptionCartCloning.php @@ -0,0 +1,55 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotHandleOriginalSubscriptionCartCloning extends MollieSubscriptionException +{ + public static function failedToDuplicateCart(int $cartId): self + { + return new self( + sprintf('Failed to duplicate cart. Cart ID: (%s)', $cartId), + ExceptionCode::RECURRING_ORDER_FAILED_TO_DUPLICATE_CART + ); + } + + public static function subscriptionCartShouldHaveOneProduct(int $cartId): self + { + return new self( + sprintf( + 'Subscription cart should have one product. Cart ID: (%s)', + $cartId + ), + ExceptionCode::RECURRING_ORDER_SUBSCRIPTION_CART_SHOULD_HAVE_ONE_PRODUCT + ); + } + + public static function failedToCreateSpecificPrice( + \Throwable $exception, + int $productId, + int $productAttributeId + ): self { + return new self( + sprintf( + 'Failed to create specific price. Product ID: (%s), product attribute ID: (%s)', + $productId, + $productAttributeId + ), + ExceptionCode::RECURRING_ORDER_FAILED_TO_CREATE_SPECIFIC_PRICE, + $exception + ); + } +} diff --git a/subscription/Exception/CouldNotHandleRecurringOrder.php b/subscription/Exception/CouldNotHandleRecurringOrder.php index 64533dd68..7cea02fb3 100644 --- a/subscription/Exception/CouldNotHandleRecurringOrder.php +++ b/subscription/Exception/CouldNotHandleRecurringOrder.php @@ -18,6 +18,28 @@ class CouldNotHandleRecurringOrder extends MollieSubscriptionException { + public static function failedToHandleSubscriptionCartCloning(\Throwable $exception): self + { + return new self( + 'Failed to handle subscription cart cloning', + ExceptionCode::RECURRING_ORDER_FAILED_TO_HANDLE_SUBSCRIPTION_CART_CLONING, + $exception + ); + } + + public static function failedToMatchSelectedCarrier( + int $activeSubscriptionCarrierId, + int $orderSubscriptionCarrierId + ): self { + return new self( + sprintf('Failed to match selected carrier. active_carrier_id: (%s), order_carrier_id: (%s),', + $activeSubscriptionCarrierId, + $orderSubscriptionCarrierId + ), + ExceptionCode::RECURRING_ORDER_FAILED_TO_MATCH_SELECTED_CARRIER + ); + } + public static function failedToFindSelectedCarrier(): self { return new self( @@ -26,10 +48,14 @@ public static function failedToFindSelectedCarrier(): self ); } - public static function failedToApplySelectedCarrier(): self + public static function failedToApplySelectedCarrier(int $expectedCarrierId, int $currentCarrierId): self { return new self( - 'Failed to apply selected carrier', + sprintf( + 'Failed to apply selected carrier. Expected carrier ID: (%s). Current carrier ID: (%s)', + $expectedCarrierId, + $currentCarrierId + ), ExceptionCode::RECURRING_ORDER_FAILED_TO_APPLY_SELECTED_CARRIER ); } diff --git a/subscription/Exception/CouldNotProvideSubscriptionCarrierDeliveryPrice.php b/subscription/Exception/CouldNotProvideSubscriptionCarrierDeliveryPrice.php index 72fbdae54..95645635f 100644 --- a/subscription/Exception/CouldNotProvideSubscriptionCarrierDeliveryPrice.php +++ b/subscription/Exception/CouldNotProvideSubscriptionCarrierDeliveryPrice.php @@ -18,58 +18,24 @@ class CouldNotProvideSubscriptionCarrierDeliveryPrice extends MollieSubscriptionException { - public static function failedToFindSelectedCarrier(): self + public static function failedToApplySelectedCarrier(int $subscriptionCarrierId): self { return new self( - 'Failed to find selected carrier', - ExceptionCode::ORDER_FAILED_TO_FIND_SELECTED_CARRIER - ); - } - - public static function failedToFindOrderCart(): self - { - return new self( - 'Failed to find order cart', - ExceptionCode::ORDER_FAILED_TO_FIND_ORDER_CART - ); - } - - public static function failedToFindOrderCustomer(): self - { - return new self( - 'Failed to find order customer', - ExceptionCode::ORDER_FAILED_TO_FIND_ORDER_CUSTOMER - ); - } - - public static function failedToApplySelectedCarrier(): self - { - return new self( - 'Failed to apply selected carrier', + sprintf( + 'Failed to apply selected carrier. Subscription carrier ID: (%s)', + $subscriptionCarrierId + ), ExceptionCode::ORDER_FAILED_TO_APPLY_SELECTED_CARRIER ); } - public static function failedToFindOrderDeliveryAddress(): self - { - return new self( - 'Failed to find order delivery address', - ExceptionCode::ORDER_FAILED_TO_FIND_ORDER_DELIVERY_ADDRESS - ); - } - - public static function failedToFindOrderDeliveryCountry(): self - { - return new self( - 'Failed to find order delivery country', - ExceptionCode::ORDER_FAILED_TO_FIND_ORDER_DELIVERY_COUNTRY - ); - } - - public static function failedToGetSelectedCarrierPrice(): self + public static function failedToGetSelectedCarrierPrice(int $subscriptionCarrierId): self { return new self( - 'Failed to get selected carrier price', + sprintf( + 'Failed to get selected carrier price. Subscription carrier ID: (%s)', + $subscriptionCarrierId + ), ExceptionCode::ORDER_FAILED_TO_GET_SELECTED_CARRIER_PRICE ); } diff --git a/subscription/Exception/CouldNotProvideSubscriptionOrderAmount.php b/subscription/Exception/CouldNotProvideSubscriptionOrderAmount.php new file mode 100644 index 000000000..81e1c85c5 --- /dev/null +++ b/subscription/Exception/CouldNotProvideSubscriptionOrderAmount.php @@ -0,0 +1,29 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotProvideSubscriptionOrderAmount extends MollieSubscriptionException +{ + public static function failedToProvideCarrierDeliveryPrice(\Throwable $exception): self + { + return new self( + 'Failed to provide carrier delivery price.', + ExceptionCode::ORDER_FAILED_TO_PROVIDE_CARRIER_DELIVERY_PRICE, + $exception + ); + } +} diff --git a/subscription/Exception/CouldNotUpdateRecurringOrder.php b/subscription/Exception/CouldNotUpdateRecurringOrder.php new file mode 100644 index 000000000..1a1f87360 --- /dev/null +++ b/subscription/Exception/CouldNotUpdateRecurringOrder.php @@ -0,0 +1,21 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotUpdateRecurringOrder extends MollieSubscriptionException +{ +} diff --git a/subscription/Exception/CouldNotUpdateSubscription.php b/subscription/Exception/CouldNotUpdateSubscription.php new file mode 100644 index 000000000..78bac85bb --- /dev/null +++ b/subscription/Exception/CouldNotUpdateSubscription.php @@ -0,0 +1,32 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotUpdateSubscription extends MollieSubscriptionException +{ + public static function failedToUpdateSubscription(\Throwable $exception, string $subscriptionId): self + { + return new self( + sprintf( + 'Failed to update subscription. Subscription ID: (%s)', + $subscriptionId + ), + ExceptionCode::ORDER_FAILED_TO_UPDATE_SUBSCRIPTION, + $exception + ); + } +} diff --git a/subscription/Exception/CouldNotValidateSubscriptionSettings.php b/subscription/Exception/CouldNotValidateSubscriptionSettings.php new file mode 100644 index 000000000..4f2d800f9 --- /dev/null +++ b/subscription/Exception/CouldNotValidateSubscriptionSettings.php @@ -0,0 +1,36 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Exception; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotValidateSubscriptionSettings extends MollieSubscriptionException +{ + public static function subscriptionServiceDisabled(): self + { + return new self( + 'Subscription service disabled.', + ExceptionCode::CART_SUBSCRIPTION_SERVICE_DISABLED + ); + } + + public static function subscriptionCarrierInvalid(): self + { + return new self( + 'Subscription carrier invalid.', + ExceptionCode::CART_SUBSCRIPTION_CARRIER_INVALID + ); + } +} diff --git a/subscription/Exception/ExceptionCode.php b/subscription/Exception/ExceptionCode.php index 43432ad4d..38c4e348d 100644 --- a/subscription/Exception/ExceptionCode.php +++ b/subscription/Exception/ExceptionCode.php @@ -20,25 +20,43 @@ class ExceptionCode { //Order error codes starts from 1000 - public const ORDER_FAILED_TO_FIND_SELECTED_CARRIER = 1001; - public const ORDER_FAILED_TO_FIND_ORDER_CART = 1002; - public const ORDER_FAILED_TO_FIND_ORDER_CUSTOMER = 1003; - public const ORDER_FAILED_TO_APPLY_SELECTED_CARRIER = 1004; - public const ORDER_FAILED_TO_FIND_ORDER_DELIVERY_ADDRESS = 1005; - public const ORDER_FAILED_TO_FIND_ORDER_DELIVERY_COUNTRY = 1006; - public const ORDER_FAILED_TO_GET_SELECTED_CARRIER_PRICE = 1007; - public const ORDER_FAILED_TO_FIND_ORDER = 1008; - public const ORDER_FAILED_TO_FIND_ORDER_DETAIL = 1009; - public const ORDER_FAILED_TO_FIND_PRODUCT = 1010; - public const ORDER_FAILED_TO_FIND_CURRENCY = 1011; + public const ORDER_FAILED_TO_APPLY_SELECTED_CARRIER = 1001; + public const ORDER_FAILED_TO_GET_SELECTED_CARRIER_PRICE = 1002; + public const ORDER_FAILED_TO_FIND_ORDER = 1003; + public const ORDER_FAILED_TO_FIND_ORDER_DETAIL = 1004; + public const ORDER_FAILED_TO_FIND_PRODUCT = 1005; + public const ORDER_FAILED_TO_FIND_CURRENCY = 1006; + public const ORDER_FAILED_TO_FIND_MOLLIE_CUSTOMER = 1007; + public const ORDER_FAILED_TO_RETRIEVE_SUBSCRIPTION_INTERVAL = 1008; + public const ORDER_FAILED_TO_PROVIDE_CARRIER_DELIVERY_PRICE = 1009; + public const ORDER_FAILED_TO_FIND_COMBINATION = 1010; + public const ORDER_FAILED_TO_FIND_MATCHING_INTERVAL = 1011; + public const ORDER_INVALID_SUBSCRIPTION_SETTINGS = 1012; + public const ORDER_FAILED_TO_FIND_SUBSCRIPTION_PRODUCT = 1013; + public const ORDER_FAILED_TO_CREATE_SUBSCRIPTION_DATA = 1014; + public const ORDER_FAILED_TO_SUBSCRIBE_ORDER = 1015; + public const ORDER_FAILED_TO_CREATE_RECURRING_ORDERS_PRODUCT = 1016; + public const ORDER_FAILED_TO_CREATE_RECURRING_ORDER = 1017; + public const ORDER_FAILED_TO_PROVIDE_SUBSCRIPTION_ORDER_AMOUNT = 1018; + public const ORDER_FAILED_TO_UPDATE_SUBSCRIPTION = 1019; //Cart error codes starts from 2000 - public const CART_ALREADY_HAS_SUBSCRIPTION_PRODUCT = 2001; + public const CART_INVALID_SUBSCRIPTION_SETTINGS = 2001; + public const CART_ALREADY_HAS_SUBSCRIPTION_PRODUCT = 2002; + public const CART_SUBSCRIPTION_SERVICE_DISABLED = 2003; + public const CART_SUBSCRIPTION_CARRIER_INVALID = 2004; //Recurring order error codes starts from 3000 public const RECURRING_ORDER_FAILED_TO_FIND_SELECTED_CARRIER = 3001; public const RECURRING_ORDER_FAILED_TO_APPLY_SELECTED_CARRIER = 3002; public const RECURRING_ORDER_CART_AND_PAID_PRICE_ARE_NOT_EQUAL = 3003; + public const RECURRING_ORDER_FAILED_TO_MATCH_SELECTED_CARRIER = 3004; + public const RECURRING_ORDER_FAILED_TO_DUPLICATE_CART = 3005; + public const RECURRING_ORDER_SUBSCRIPTION_CART_SHOULD_HAVE_ONE_PRODUCT = 3006; + public const RECURRING_ORDER_FAILED_TO_CREATE_SPECIFIC_PRICE = 3007; + public const RECURRING_ORDER_FAILED_TO_HANDLE_SUBSCRIPTION_CART_CLONING = 3008; + + public const UNKNOWN_ERROR = 9001; } diff --git a/subscription/Exception/MollieSubscriptionException.php b/subscription/Exception/MollieSubscriptionException.php index 59b721c62..b0168dc55 100644 --- a/subscription/Exception/MollieSubscriptionException.php +++ b/subscription/Exception/MollieSubscriptionException.php @@ -14,12 +14,18 @@ namespace Mollie\Subscription\Exception; -use Exception; - if (!defined('_PS_VERSION_')) { exit; } -class MollieSubscriptionException extends Exception +class MollieSubscriptionException extends \Exception { + public static function unknownError(\Throwable $exception): self + { + return new self( + 'An unknown error error occurred. Please check system logs or contact Mollie support.', + ExceptionCode::UNKNOWN_ERROR, + $exception + ); + } } diff --git a/subscription/Exception/SubscriptionIntervalException.php b/subscription/Exception/SubscriptionIntervalException.php index f2db408a3..24445e360 100644 --- a/subscription/Exception/SubscriptionIntervalException.php +++ b/subscription/Exception/SubscriptionIntervalException.php @@ -20,4 +20,25 @@ class SubscriptionIntervalException extends MollieSubscriptionException { + public static function failedToFindCombination(int $productAttributeId): self + { + return new self( + sprintf( + 'Failed to find combination. Product attribute ID: (%s)', + $productAttributeId + ), + ExceptionCode::ORDER_FAILED_TO_FIND_COMBINATION + ); + } + + public static function failedToFindMatchingInterval(int $productAttributeId): self + { + return new self( + sprintf( + 'Failed to find matching interval. Product attribute ID: (%s)', + $productAttributeId + ), + ExceptionCode::ORDER_FAILED_TO_FIND_MATCHING_INTERVAL + ); + } } diff --git a/subscription/Exception/SubscriptionProductValidationException.php b/subscription/Exception/SubscriptionProductValidationException.php index 439ef2c3e..03c6615f3 100644 --- a/subscription/Exception/SubscriptionProductValidationException.php +++ b/subscription/Exception/SubscriptionProductValidationException.php @@ -20,4 +20,19 @@ class SubscriptionProductValidationException extends MollieSubscriptionException { + public static function invalidSubscriptionSettings(): self + { + return new self( + 'Invalid subscription settings', + ExceptionCode::CART_INVALID_SUBSCRIPTION_SETTINGS + ); + } + + public static function cartAlreadyHasSubscriptionProduct(): self + { + return new self( + 'Cart already has subscription product', + ExceptionCode::CART_ALREADY_HAS_SUBSCRIPTION_PRODUCT + ); + } } diff --git a/subscription/Factory/CreateSubscriptionDataFactory.php b/subscription/Factory/CreateSubscriptionDataFactory.php index c3deca151..0fb1c4b23 100644 --- a/subscription/Factory/CreateSubscriptionDataFactory.php +++ b/subscription/Factory/CreateSubscriptionDataFactory.php @@ -15,18 +15,20 @@ namespace Mollie\Subscription\Factory; use Mollie; +use Mollie\Adapter\ConfigurationAdapter; use Mollie\Adapter\Context; +use Mollie\Config\Config; use Mollie\Repository\MolCustomerRepository; use Mollie\Repository\PaymentMethodRepositoryInterface; use Mollie\Subscription\DTO\CreateSubscriptionData as SubscriptionDataDTO; -use Mollie\Subscription\DTO\Object\Amount; -use Mollie\Subscription\Exception\CouldNotProvideSubscriptionCarrierDeliveryPrice; -use Mollie\Subscription\Exception\SubscriptionIntervalException; -use Mollie\Subscription\Provider\SubscriptionCarrierDeliveryPriceProvider; +use Mollie\Subscription\DTO\SubscriptionOrderAmountProviderData; +use Mollie\Subscription\Exception\CouldNotCreateSubscriptionData; +use Mollie\Subscription\Exception\MollieSubscriptionException; use Mollie\Subscription\Provider\SubscriptionDescriptionProvider; use Mollie\Subscription\Provider\SubscriptionIntervalProvider; +use Mollie\Subscription\Provider\SubscriptionOrderAmountProvider; +use Mollie\Subscription\Provider\SubscriptionStartDateProvider; use Mollie\Subscription\Repository\CombinationRepository; -use Mollie\Subscription\Repository\CurrencyRepository as CurrencyAdapter; use Mollie\Utility\SecureKeyUtility; use Order; @@ -38,83 +40,95 @@ class CreateSubscriptionDataFactory { /** @var MolCustomerRepository */ private $customerRepository; - /** @var SubscriptionIntervalProvider */ private $subscriptionInterval; - /** @var SubscriptionDescriptionProvider */ private $subscriptionDescription; - - /** @var CurrencyAdapter */ - private $currencyAdapter; - - /** @var CombinationRepository */ - private $combination; - /** @var PaymentMethodRepositoryInterface */ private $methodRepository; /** @var Mollie */ private $module; /** @var Context */ private $context; - /** @var SubscriptionCarrierDeliveryPriceProvider */ - private $subscriptionCarrierDeliveryPriceProvider; + /** @var ConfigurationAdapter */ + private $configuration; + /** @var SubscriptionOrderAmountProvider */ + private $subscriptionOrderAmountProvider; + /** @var SubscriptionStartDateProvider */ + private $subscriptionStartDateProvider; + /** @var CombinationRepository */ + private $combination; public function __construct( MolCustomerRepository $customerRepository, SubscriptionIntervalProvider $subscriptionInterval, SubscriptionDescriptionProvider $subscriptionDescription, - CurrencyAdapter $currencyAdapter, - CombinationRepository $combination, PaymentMethodRepositoryInterface $methodRepository, Mollie $module, Context $context, - SubscriptionCarrierDeliveryPriceProvider $subscriptionCarrierDeliveryPriceProvider + ConfigurationAdapter $configuration, + SubscriptionOrderAmountProvider $subscriptionOrderAmountProvider, + SubscriptionStartDateProvider $subscriptionStartDateProvider, + CombinationRepository $combination ) { $this->customerRepository = $customerRepository; $this->subscriptionInterval = $subscriptionInterval; $this->subscriptionDescription = $subscriptionDescription; - $this->currencyAdapter = $currencyAdapter; - $this->combination = $combination; $this->methodRepository = $methodRepository; $this->module = $module; $this->context = $context; - $this->subscriptionCarrierDeliveryPriceProvider = $subscriptionCarrierDeliveryPriceProvider; + $this->configuration = $configuration; + $this->subscriptionOrderAmountProvider = $subscriptionOrderAmountProvider; + $this->subscriptionStartDateProvider = $subscriptionStartDateProvider; + $this->combination = $combination; } /** - * @throws \PrestaShopException - * @throws CouldNotProvideSubscriptionCarrierDeliveryPrice - * @throws SubscriptionIntervalException + * @throws MollieSubscriptionException */ public function build(Order $order, array $subscriptionProduct): SubscriptionDataDTO { - $customer = $order->getCustomer(); - /** @var \MolCustomer $molCustomer */ - //todo: will need to improve mollie module logic to have shop id or card it so that multishop doesn't break - $molCustomer = $this->customerRepository->findOneBy(['email' => $customer->email]); + // TODO modify mol_customer table to hold id_customer (default PS customer ID as it holds id_shop). Then we won't need separate id_shop and id_shop_group column - $combination = $this->combination->getById((int) $subscriptionProduct['id_product_attribute']); - $interval = $this->subscriptionInterval->getSubscriptionInterval($combination); + try { + /** @var \MolCustomer|null $molCustomer */ + $molCustomer = $this->customerRepository->findOneBy([ + 'email' => $order->getCustomer()->email, + ]); + } catch (\Throwable $exception) { + throw CouldNotCreateSubscriptionData::unknownError($exception); + } + + if (!$molCustomer) { + throw CouldNotCreateSubscriptionData::failedToFindMollieCustomer((string) $order->getCustomer()->email); + } + + try { + $interval = $this->subscriptionInterval->getSubscriptionInterval((int) $subscriptionProduct['id_product_attribute']); + } catch (\Throwable $exception) { + throw CouldNotCreateSubscriptionData::failedToRetrieveSubscriptionInterval($exception, (int) $subscriptionProduct['id_product_attribute']); + } - $currency = $this->currencyAdapter->getById((int) $order->id_currency); $description = $this->subscriptionDescription->getSubscriptionDescription($order); + $subscriptionCarrierId = (int) $this->configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); + try { - $deliveryPrice = $this->subscriptionCarrierDeliveryPriceProvider->getPrice( - (int) $order->id_address_delivery, - (int) $order->id_cart, - (int) $order->id_customer, - $subscriptionProduct + $orderAmount = $this->subscriptionOrderAmountProvider->get( + SubscriptionOrderAmountProviderData::create( + (int) $order->id_address_delivery, + (int) $order->id_cart, + (int) $order->id_customer, + $subscriptionProduct, + $subscriptionCarrierId, + (int) $order->id_currency, + (float) $subscriptionProduct['total_price_tax_incl'] + ) ); - } catch (CouldNotProvideSubscriptionCarrierDeliveryPrice $exception) { - // TODO throw generic error when new logger will be implemented - throw $exception; + } catch (\Throwable $exception) { + throw CouldNotCreateSubscriptionData::failedToProvideSubscriptionOrderAmount($exception); } - $orderTotal = (float) $subscriptionProduct['total_price_tax_incl'] + $deliveryPrice; - - $orderAmount = new Amount($orderTotal, $currency->iso_code); $subscriptionData = new SubscriptionDataDTO( $molCustomer->customer_id, $orderAmount, @@ -127,20 +141,23 @@ public function build(Order $order, array $subscriptionProduct): SubscriptionDat 'subscriptionWebhook' )); - $key = SecureKeyUtility::generateReturnKey( + $secureKey = SecureKeyUtility::generateReturnKey( $order->id_customer, $order->id_cart, $this->module->name ); - $subscriptionData->setMetaData( - [ - 'secure_key' => $key, - ] - ); + $subscriptionData->setMetaData([ + 'secure_key' => $secureKey, + 'subscription_carrier_id' => $subscriptionCarrierId, + ]); + + $combination = $this->combination->getById((int) $subscriptionProduct['id_product_attribute']); + $subscriptionData->setStartDate($this->subscriptionStartDateProvider->getSubscriptionStartDate($combination)); // todo: check for solution what to do when mandate is missing $payment = $this->methodRepository->getPaymentBy('cart_id', $order->id_cart); + $subscriptionData->setMandateId($payment['mandate_id']); return $subscriptionData; diff --git a/subscription/Factory/UpdateSubscriptionDataFactory.php b/subscription/Factory/UpdateSubscriptionDataFactory.php index e0586ff5e..8a309cd00 100644 --- a/subscription/Factory/UpdateSubscriptionDataFactory.php +++ b/subscription/Factory/UpdateSubscriptionDataFactory.php @@ -14,7 +14,7 @@ namespace Mollie\Subscription\Factory; -use Mollie\Subscription\DTO\UpdateSubscriptionData; +use Mollie\Subscription\Api\Request\UpdateSubscriptionRequest; use MolRecurringOrder; if (!defined('_PS_VERSION_')) { @@ -23,8 +23,8 @@ class UpdateSubscriptionDataFactory { - public function build(MolRecurringOrder $subscription, string $mandateId): UpdateSubscriptionData + public function build(MolRecurringOrder $subscription, string $mandateId): UpdateSubscriptionRequest { - return new UpdateSubscriptionData($subscription->mollie_customer_id, $subscription->mollie_subscription_id, $mandateId); + return new UpdateSubscriptionRequest($subscription->mollie_customer_id, $subscription->mollie_subscription_id, $mandateId); } } diff --git a/subscription/Form/Options/SubscriptionOptionsConfiguration.php b/subscription/Form/Options/SubscriptionOptionsConfiguration.php index dbd78b5ed..21df3a522 100644 --- a/subscription/Form/Options/SubscriptionOptionsConfiguration.php +++ b/subscription/Form/Options/SubscriptionOptionsConfiguration.php @@ -38,6 +38,7 @@ public function __construct(Configuration $configuration) public function getConfiguration(): array { return [ + 'enable_subscriptions' => $this->configuration->getBoolean(Config::MOLLIE_SUBSCRIPTION_ENABLED), 'carrier' => $this->configuration->getInt(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID), ]; } @@ -51,9 +52,14 @@ public function updateConfiguration(array $configuration): array return []; } + $this->configuration->set( + Config::MOLLIE_SUBSCRIPTION_ENABLED, + (int) $configuration['enable_subscriptions'] + ); + $this->configuration->set( Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, - $configuration['carrier'] + (int) $configuration['carrier'] ); return []; @@ -65,6 +71,7 @@ public function updateConfiguration(array $configuration): array public function validateConfiguration(array $configuration): bool { return isset( + $configuration['enable_subscriptions'], $configuration['carrier'] ); } diff --git a/subscription/Form/Options/SubscriptionOptionsType.php b/subscription/Form/Options/SubscriptionOptionsType.php index fefa5ef2e..3b35adaa5 100644 --- a/subscription/Form/Options/SubscriptionOptionsType.php +++ b/subscription/Form/Options/SubscriptionOptionsType.php @@ -14,6 +14,7 @@ use Module; use PrestaShop\PrestaShop\Core\Form\FormChoiceProviderInterface; +use PrestaShopBundle\Form\Admin\Type\SwitchType; use PrestaShopBundle\Form\Admin\Type\TranslatorAwareType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; @@ -45,6 +46,9 @@ public function __construct( public function buildForm(FormBuilderInterface $builder, array $options): void { $builder + ->add('enable_subscriptions', SwitchType::class, [ + 'required' => true, + ]) ->add('carrier', ChoiceType::class, [ 'required' => true, 'choices' => $this->carrierOptionProvider->getChoices(), diff --git a/subscription/Handler/CloneOriginalSubscriptionCartHandler.php b/subscription/Handler/CloneOriginalSubscriptionCartHandler.php new file mode 100644 index 000000000..7727e4036 --- /dev/null +++ b/subscription/Handler/CloneOriginalSubscriptionCartHandler.php @@ -0,0 +1,143 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Handler; + +use Mollie\Exception\MollieException; +use Mollie\Repository\CartRepositoryInterface; +use Mollie\Subscription\Action\CreateSpecificPriceAction; +use Mollie\Subscription\DTO\CloneOriginalSubscriptionCartData; +use Mollie\Subscription\DTO\CreateSpecificPriceData; +use Mollie\Subscription\Exception\CouldNotHandleOriginalSubscriptionCartCloning; +use Mollie\Subscription\Exception\MollieSubscriptionException; +use Mollie\Subscription\Repository\RecurringOrdersProductRepositoryInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CloneOriginalSubscriptionCartHandler +{ + /** @var CartRepositoryInterface */ + private $cartRepository; + /** @var RecurringOrdersProductRepositoryInterface */ + private $recurringOrdersProductRepository; + /** @var CreateSpecificPriceAction */ + private $createSpecificPriceAction; + + public function __construct( + CartRepositoryInterface $cartRepository, + RecurringOrdersProductRepositoryInterface $recurringOrdersProductRepository, + CreateSpecificPriceAction $createSpecificPriceAction + ) { + $this->cartRepository = $cartRepository; + $this->recurringOrdersProductRepository = $recurringOrdersProductRepository; + $this->createSpecificPriceAction = $createSpecificPriceAction; + } + + /** + * @throws MollieSubscriptionException + * @throws MollieException + */ + public function run(CloneOriginalSubscriptionCartData $data): \Cart + { + /** @var \Cart $originalCart */ + $originalCart = $this->cartRepository->findOrFail([ + 'id_cart' => $data->getCartId(), + ]); + + /** @var array{success: bool, cart: \Cart}|bool $duplicatedCart */ + $duplicatedCart = $originalCart->duplicate(); + + if (!$duplicatedCart || !$duplicatedCart['success']) { + throw CouldNotHandleOriginalSubscriptionCartCloning::failedToDuplicateCart($data->getCartId()); + } + + /** @var \Cart $duplicatedCart */ + $duplicatedCart = $duplicatedCart['cart']; + + /** @var \MolRecurringOrdersProduct $subscriptionProduct */ + $subscriptionProduct = $this->recurringOrdersProductRepository->findOrFail([ + 'id_mol_recurring_orders_product' => $data->getRecurringOrderProductId(), + ]); + + $cartProducts = $duplicatedCart->getProducts(); + + foreach ($cartProducts as $cartProduct) { + if ( + (int) $cartProduct['id_product'] === (int) $subscriptionProduct->id_product && + (int) $cartProduct['id_product_attribute'] === (int) $subscriptionProduct->id_product_attribute + ) { + continue; + } + + $duplicatedCart->deleteProduct((int) $cartProduct['id_product'], (int) $cartProduct['id_product_attribute']); + } + + $cartProducts = $duplicatedCart->getProducts(true); + + if (count($cartProducts) !== 1) { + throw CouldNotHandleOriginalSubscriptionCartCloning::subscriptionCartShouldHaveOneProduct((int) $duplicatedCart->id); + } + + /* + * NOTE: New order can't have soft deleted delivery address + */ + try { + $duplicatedCart->id_address_invoice = $data->getInvoiceAddressId(); + $duplicatedCart->id_address_delivery = $data->getDeliveryAddressId(); + + $duplicatedCart->setProductAddressDelivery( + (int) $cartProducts[0]['id_product'], + (int) $cartProducts[0]['id_product_attribute'], + (int) $cartProducts[0]['id_address_delivery'], + $data->getDeliveryAddressId() + ); + + $duplicatedCart->save(); + } catch (\Throwable $exception) { + throw CouldNotHandleOriginalSubscriptionCartCloning::unknownError($exception); + } + + /* + * Creating temporary specific price for recurring order that will be deleted after order is created + */ + try { + $specificPrice = $this->createSpecificPriceAction->run(CreateSpecificPriceData::create( + (int) $subscriptionProduct->id_product, + (int) $subscriptionProduct->id_product_attribute, + (float) $subscriptionProduct->unit_price, + (int) $duplicatedCart->id_customer, + (int) $duplicatedCart->id_shop, + (int) $duplicatedCart->id_shop_group, + (int) $duplicatedCart->id_currency + )); + } catch (\Throwable $exception) { + throw CouldNotHandleOriginalSubscriptionCartCloning::failedToCreateSpecificPrice($exception, (int) $subscriptionProduct->id_product, (int) $subscriptionProduct->id_product_attribute); + } + + register_shutdown_function([$this, 'onShutdown'], $specificPrice, $duplicatedCart); + + return $duplicatedCart; + } + + /** + * On shutdown, we will remove specific price and whole cart object. + * + * @throws \Throwable + */ + public function onShutdown(\SpecificPrice $specificPrice, \Cart $cart): void + { + $specificPrice->delete(); + $cart->delete(); + } +} diff --git a/subscription/Handler/RecurringOrderHandler.php b/subscription/Handler/RecurringOrderHandler.php index d7bb97a36..6c2cc0594 100644 --- a/subscription/Handler/RecurringOrderHandler.php +++ b/subscription/Handler/RecurringOrderHandler.php @@ -26,24 +26,20 @@ use Mollie\Exception\TransactionException; use Mollie\Logger\PrestaLoggerInterface; use Mollie\Repository\CarrierRepositoryInterface; -use Mollie\Repository\OrderRepositoryInterface; use Mollie\Repository\PaymentMethodRepositoryInterface; use Mollie\Service\MailService; use Mollie\Service\MollieOrderCreationService; use Mollie\Service\OrderStatusService; use Mollie\Service\PaymentMethodService; -use Mollie\Subscription\Action\CreateSpecificPriceAction; use Mollie\Subscription\Api\SubscriptionApi; -use Mollie\Subscription\DTO\CreateSpecificPriceData; +use Mollie\Subscription\DTO\CloneOriginalSubscriptionCartData; use Mollie\Subscription\Exception\CouldNotHandleRecurringOrder; use Mollie\Subscription\Factory\GetSubscriptionDataFactory; use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; -use Mollie\Subscription\Repository\RecurringOrdersProductRepositoryInterface; use Mollie\Subscription\Utility\ClockInterface; use Mollie\Utility\NumberUtility; use Mollie\Utility\SecureKeyUtility; use MolRecurringOrder; -use MolRecurringOrdersProduct; use Order; if (!defined('_PS_VERSION_')) { @@ -74,16 +70,12 @@ class RecurringOrderHandler private $mailService; /** @var ConfigurationAdapter */ private $configuration; - /** @var RecurringOrdersProductRepositoryInterface */ - private $recurringOrdersProductRepository; /** @var CarrierRepositoryInterface */ private $carrierRepository; /** @var PrestaLoggerInterface */ private $logger; - /** @var CreateSpecificPriceAction */ - private $createSpecificPriceAction; - /** @var Mollie\Repository\OrderRepositoryInterface */ - private $orderRepository; + /** @var CloneOriginalSubscriptionCartHandler */ + private $cloneOriginalSubscriptionCartHandler; public function __construct( SubscriptionApi $subscriptionApi, @@ -97,12 +89,10 @@ public function __construct( ClockInterface $clock, MailService $mailService, ConfigurationAdapter $configuration, - RecurringOrdersProductRepositoryInterface $recurringOrdersProductRepository, CarrierRepositoryInterface $carrierRepository, // TODO use subscription logger after it's fixed PrestaLoggerInterface $logger, - CreateSpecificPriceAction $createSpecificPriceAction, - OrderRepositoryInterface $orderRepository + CloneOriginalSubscriptionCartHandler $cloneOriginalSubscriptionCartHandler ) { $this->subscriptionApi = $subscriptionApi; $this->subscriptionDataFactory = $subscriptionDataFactory; @@ -115,17 +105,23 @@ public function __construct( $this->clock = $clock; $this->mailService = $mailService; $this->configuration = $configuration; - $this->recurringOrdersProductRepository = $recurringOrdersProductRepository; $this->carrierRepository = $carrierRepository; $this->logger = $logger; - $this->createSpecificPriceAction = $createSpecificPriceAction; - $this->orderRepository = $orderRepository; + $this->cloneOriginalSubscriptionCartHandler = $cloneOriginalSubscriptionCartHandler; } + /** + * @throws \Throwable + */ public function handle(string $transactionId): string { $transaction = $this->mollie->getApiClient()->payments->get($transactionId); - $recurringOrder = $this->recurringOrderRepository->findOneBy(['mollie_subscription_id' => $transaction->subscriptionId]); + + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = $this->recurringOrderRepository->findOrFail([ + 'mollie_subscription_id' => $transaction->subscriptionId, + ]); + $subscriptionData = $this->subscriptionDataFactory->build((int) $recurringOrder->id); $subscription = $this->subscriptionApi->getSubscription($subscriptionData); @@ -164,66 +160,29 @@ public function handle(string $transactionId): string */ private function createSubscription(Payment $transaction, MolRecurringOrder $recurringOrder, MollieSubscription $subscription): void { - $cart = new Cart($recurringOrder->id_cart); - - /** @var array{success: bool, cart: Cart}|bool $newCart */ - $newCart = $cart->duplicate(); - - if (!$newCart || !$newCart['success']) { - return; - } - - /** @var \Order|null $originalOrder */ - $originalOrder = $this->orderRepository->findOneBy([ - 'id_order' => $recurringOrder->id_order, - ]); - - if (!$originalOrder) { - return; + try { + $newCart = $this->cloneOriginalSubscriptionCartHandler->run( + CloneOriginalSubscriptionCartData::create( + (int) $recurringOrder->id_cart, + (int) $recurringOrder->id_mol_recurring_orders_product, + (int) $recurringOrder->id_address_invoice, + (int) $recurringOrder->id_address_delivery + ) + ); + } catch (\Throwable $exception) { + throw CouldNotHandleRecurringOrder::failedToHandleSubscriptionCartCloning($exception); } - /** @var Cart $newCart */ - $newCart = $newCart['cart']; - - $newCart->id_shop = $originalOrder->id_shop; - $newCart->id_shop_group = $originalOrder->id_shop_group; - - $newCart->update(); - - /** @var MolRecurringOrdersProduct $subscriptionProduct */ - $subscriptionProduct = $this->recurringOrdersProductRepository->findOneBy([ - 'id_mol_recurring_orders_product' => $recurringOrder->id_mol_recurring_orders_product, - ]); - - $cartProducts = $newCart->getProducts(); + $activeSubscriptionCarrierId = (int) $this->configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); + $orderSubscriptionCarrierId = (int) ($subscription->metadata->subscription_carrier_id ?? 0); - foreach ($cartProducts as $cartProduct) { - if ( - (int) $cartProduct['id_product'] === (int) $subscriptionProduct->id_product && - (int) $cartProduct['id_product_attribute'] === (int) $subscriptionProduct->id_product_attribute - ) { - continue; - } - - $newCart->deleteProduct((int) $cartProduct['id_product'], (int) $cartProduct['id_product_attribute']); + if ($activeSubscriptionCarrierId !== $orderSubscriptionCarrierId) { + throw CouldNotHandleRecurringOrder::failedToMatchSelectedCarrier($activeSubscriptionCarrierId, $orderSubscriptionCarrierId); } - /** - * NOTE: New order can't have soft deleted delivery address - */ - $newCart = $this->updateSubscriptionOrderAddress( - $newCart, - (int) $recurringOrder->id_address_invoice, - (int) $recurringOrder->id_address_delivery - ); - - $recurringOrderProduct = new MolRecurringOrdersProduct($recurringOrder->id_mol_recurring_orders_product); - - $subscriptionCarrierId = (int) $this->configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); - /** @var \Carrier|null $carrier */ $carrier = $this->carrierRepository->findOneBy([ - 'id_carrier' => $subscriptionCarrierId, + 'id_carrier' => $activeSubscriptionCarrierId, 'active' => 1, 'deleted' => 0, ]); @@ -238,25 +197,12 @@ private function createSubscription(Payment $transaction, MolRecurringOrder $rec $newCart->update(); - $cartCarrier = (int) ($newCart->getDeliveryOption(null, false, false)[$newCart->id_address_delivery] ?? 0); + $updatedCartCarrierId = (int) ($newCart->getDeliveryOption(null, false, false)[$newCart->id_address_delivery] ?? 0); - if ((int) $carrier->id !== $cartCarrier) { - throw CouldNotHandleRecurringOrder::failedToApplySelectedCarrier(); + if ((int) $carrier->id !== $updatedCartCarrierId) { + throw CouldNotHandleRecurringOrder::failedToApplySelectedCarrier((int) $carrier->id, $updatedCartCarrierId); } - /** - * Creating temporary specific price for recurring order that will be deleted after order is created - */ - $specificPrice = $this->createSpecificPriceAction->run(CreateSpecificPriceData::create( - (int) $recurringOrderProduct->id_product, - (int) $recurringOrderProduct->id_product_attribute, - (float) $recurringOrderProduct->unit_price, - (int) $recurringOrder->id_customer, - (int) $newCart->id_shop, - (int) $newCart->id_shop_group, - (int) $recurringOrder->id_currency - )); - $paymentMethod = $this->paymentMethodService->getPaymentMethod($transaction); $methodName = $paymentMethod->method_name ?: Config::$methods[$transaction->method]; @@ -287,13 +233,9 @@ private function createSubscription(Payment $transaction, MolRecurringOrder $rec $newCart->secure_key ); } catch (\Throwable $exception) { - $specificPrice->delete(); - throw $exception; } - $specificPrice->delete(); - $orderId = (int) Order::getIdByCartId((int) $newCart->id); $order = new Order($orderId); @@ -324,9 +266,15 @@ private function handleFailedTransaction(int $recurringOrderId): void } } + /** + * @throws \Throwable + */ private function cancelSubscription(int $recurringOrderId): void { - $recurringOrder = $this->recurringOrderRepository->findOneBy(['id_mol_recurring_order' => $recurringOrderId]); + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = $this->recurringOrderRepository->findOrFail([ + 'id_mol_recurring_order' => $recurringOrderId, + ]); $recurringOrder->status = SubscriptionStatus::STATUS_CANCELED; $recurringOrder->cancelled_at = $this->clock->getCurrentDate(); @@ -335,25 +283,4 @@ private function cancelSubscription(int $recurringOrderId): void $this->mailService->sendSubscriptionCancelWarningEmail($recurringOrderId); } - - private function updateSubscriptionOrderAddress(Cart $cart, int $addressInvoiceId, int $addressDeliveryId): Cart - { - $cart->id_address_invoice = $addressInvoiceId; - $cart->id_address_delivery = $addressDeliveryId; - - $cartProducts = $cart->getProducts(); - - foreach ($cartProducts as $cartProduct) { - $cart->setProductAddressDelivery( - (int) $cartProduct['id_product'], - (int) $cartProduct['id_product_attribute'], - (int) $cartProduct['id_address_delivery'], - $addressDeliveryId - ); - } - - $cart->save(); - - return $cart; - } } diff --git a/subscription/Handler/SubscriptionCreationHandler.php b/subscription/Handler/SubscriptionCreationHandler.php index 8376b8a33..67203974b 100644 --- a/subscription/Handler/SubscriptionCreationHandler.php +++ b/subscription/Handler/SubscriptionCreationHandler.php @@ -14,13 +14,16 @@ namespace Mollie\Subscription\Handler; -use Mollie\Api\Resources\Subscription; +use Mollie\Subscription\Action\CreateRecurringOrderAction; +use Mollie\Subscription\Action\CreateRecurringOrdersProductAction; use Mollie\Subscription\Api\SubscriptionApi; +use Mollie\Subscription\DTO\CreateRecurringOrderData; +use Mollie\Subscription\DTO\CreateRecurringOrdersProductData; +use Mollie\Subscription\Exception\CouldNotCreateSubscription; +use Mollie\Subscription\Exception\MollieSubscriptionException; use Mollie\Subscription\Factory\CreateSubscriptionDataFactory; -use Mollie\Subscription\Utility\ClockInterface; -use Mollie\Subscription\Validator\SubscriptionProductValidator; -use MolRecurringOrder; -use MolRecurringOrdersProduct; +use Mollie\Subscription\Provider\SubscriptionProductProvider; +use Mollie\Subscription\Validator\SubscriptionSettingsValidator; use Order; if (!defined('_PS_VERSION_')) { @@ -29,88 +32,98 @@ class SubscriptionCreationHandler { - /** @var ClockInterface */ - private $clock; - /** @var SubscriptionApi */ private $subscriptionApi; - /** @var CreateSubscriptionDataFactory */ private $createSubscriptionDataFactory; - /** @var SubscriptionProductValidator */ - private $subscriptionProductValidator; + /** @var SubscriptionSettingsValidator */ + private $subscriptionSettingsValidator; + /** @var CreateRecurringOrdersProductAction */ + private $createRecurringOrdersProductAction; + /** @var CreateRecurringOrderAction */ + private $createRecurringOrderAction; + /** @var SubscriptionProductProvider */ + private $subscriptionProductProvider; public function __construct( - ClockInterface $clock, SubscriptionApi $subscriptionApi, CreateSubscriptionDataFactory $subscriptionDataFactory, - SubscriptionProductValidator $subscriptionProductValidator + SubscriptionSettingsValidator $subscriptionSettingsValidator, + CreateRecurringOrdersProductAction $createRecurringOrdersProductAction, + CreateRecurringOrderAction $createRecurringOrderAction, + SubscriptionProductProvider $subscriptionProductProvider ) { - $this->clock = $clock; $this->subscriptionApi = $subscriptionApi; $this->createSubscriptionDataFactory = $subscriptionDataFactory; - $this->subscriptionProductValidator = $subscriptionProductValidator; + $this->subscriptionSettingsValidator = $subscriptionSettingsValidator; + $this->createRecurringOrdersProductAction = $createRecurringOrdersProductAction; + $this->createRecurringOrderAction = $createRecurringOrderAction; + $this->subscriptionProductProvider = $subscriptionProductProvider; } /** - * @throws \Throwable + * @throws MollieSubscriptionException */ public function handle(Order $order, string $method): void { - $products = $order->getCartProducts(); - $subscriptionProduct = []; - - foreach ($products as $product) { - if (!$this->subscriptionProductValidator->validate((int) $product['id_product_attribute'])) { - continue; - } - - $subscriptionProduct = $product; - - break; + try { + $this->subscriptionSettingsValidator->validate(); + } catch (\Throwable $exception) { + throw CouldNotCreateSubscription::invalidSubscriptionSettings($exception); } - $subscriptionData = $this->createSubscriptionDataFactory->build($order, $subscriptionProduct); - $subscription = $this->subscriptionApi->subscribeOrder($subscriptionData); + $subscriptionProduct = $this->subscriptionProductProvider->getProduct($order->getCartProducts()); - $recurringOrdersProduct = $this->createRecurringOrdersProduct($subscriptionProduct); + if (empty($subscriptionProduct)) { + throw CouldNotCreateSubscription::failedToFindSubscriptionProduct(); + } - $this->createRecurringOrder($recurringOrdersProduct, $order, $subscription, $method); - } + try { + $subscriptionData = $this->createSubscriptionDataFactory->build($order, $subscriptionProduct); + } catch (\Throwable $exception) { + throw CouldNotCreateSubscription::failedToCreateSubscriptionData($exception); + } - private function createRecurringOrdersProduct(array $product): MolRecurringOrdersProduct - { - $recurringOrdersProduct = new MolRecurringOrdersProduct(); - $recurringOrdersProduct->id_product = $product['id_product']; - $recurringOrdersProduct->id_product_attribute = $product['id_product_attribute']; - $recurringOrdersProduct->quantity = $product['product_quantity']; - $recurringOrdersProduct->unit_price = $product['unit_price_tax_excl']; - $recurringOrdersProduct->add(); + try { + $subscription = $this->subscriptionApi->subscribeOrder($subscriptionData); + } catch (\Throwable $exception) { + throw CouldNotCreateSubscription::failedToSubscribeOrder($exception); + } - return $recurringOrdersProduct; - } + try { + $recurringOrdersProduct = $this->createRecurringOrdersProductAction->run( + CreateRecurringOrdersProductData::create( + (int) $subscriptionProduct['id_product'], + (int) $subscriptionProduct['id_product_attribute'], + (int) $subscriptionProduct['product_quantity'], + (float) $subscriptionProduct['unit_price_tax_excl'] + ) + ); + } catch (\Throwable $exception) { + throw CouldNotCreateSubscription::failedToCreateRecurringOrdersProduct($exception); + } - private function createRecurringOrder(MolRecurringOrdersProduct $recurringOrdersProduct, Order $order, Subscription $subscription, string $method): void - { - $recurringOrder = new MolRecurringOrder(); - $recurringOrder->id_mol_recurring_orders_product = $recurringOrdersProduct->id; - $recurringOrder->id_order = $order->id; - $recurringOrder->id_cart = $order->id_cart; - $recurringOrder->id_currency = $order->id_currency; - $recurringOrder->id_customer = $order->id_customer; - $recurringOrder->id_address_delivery = $order->id_address_delivery; - $recurringOrder->id_address_invoice = $order->id_address_invoice; - $recurringOrder->description = $subscription->description; - $recurringOrder->status = $subscription->status; - $recurringOrder->total_tax_incl = (float) $subscription->amount->value; - $recurringOrder->payment_method = $method; - $recurringOrder->next_payment = $subscription->nextPaymentDate; - $recurringOrder->reminder_at = $subscription->nextPaymentDate; //todo: add logic to get reminder date when reminder is done - $recurringOrder->cancelled_at = $subscription->canceledAt; - $recurringOrder->mollie_subscription_id = $subscription->id; - $recurringOrder->mollie_customer_id = $subscription->customerId; - $recurringOrder->date_add = $this->clock->getDateFromTimeStamp(strtotime($subscription->createdAt)); - $recurringOrder->date_update = $this->clock->getCurrentDate(); - $recurringOrder->add(); + try { + $this->createRecurringOrderAction->run(CreateRecurringOrderData::create( + (int) $recurringOrdersProduct->id, + (int) $order->id, + (int) $order->id_cart, + (int) $order->id_currency, + (int) $order->id_customer, + (int) $order->id_address_delivery, + (int) $order->id_address_invoice, + (string) $subscription->description, + (string) $subscription->status, + (float) $subscription->amount->value, + $method, + (string) $subscription->nextPaymentDate, + (string) $subscription->nextPaymentDate, // TODO: add logic to get reminder date when reminder is done + (string) $subscription->canceledAt, + (string) $subscription->id, + (string) $subscription->customerId + )); + } catch (\Throwable $exception) { + throw CouldNotCreateSubscription::failedToCreateRecurringOrder($exception); + } } } diff --git a/subscription/Handler/SubscriptionPaymentMethodUpdateHandler.php b/subscription/Handler/SubscriptionPaymentMethodUpdateHandler.php index 42c40114b..18ee7ffde 100644 --- a/subscription/Handler/SubscriptionPaymentMethodUpdateHandler.php +++ b/subscription/Handler/SubscriptionPaymentMethodUpdateHandler.php @@ -14,7 +14,6 @@ namespace Mollie\Subscription\Handler; -use Mollie\Exception\MollieException; use Mollie\Subscription\Api\PaymentApi; use Mollie\Subscription\Api\SubscriptionApi; use Mollie\Subscription\Factory\UpdateSubscriptionDataFactory; @@ -52,14 +51,15 @@ public function __construct( $this->clock = $clock; } + /** + * @throws \Throwable + */ public function handle(string $transactionId, string $subscriptionId) { $molPayment = $this->paymentApi->getPayment($transactionId); - $recurringOrder = $this->recurringOrderRepository->findOneBy(['mollie_subscription_id' => $subscriptionId]); - if (!$recurringOrder) { - throw new MollieException('Subscription does not exist.'); - } + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = $this->recurringOrderRepository->findOrFail(['mollie_subscription_id' => $subscriptionId]); $subscriptionUpdateData = $this->subscriptionDataFactory->build($recurringOrder, $molPayment->mandateId); $newSubscription = $this->subscriptionApi->updateSubscription($subscriptionUpdateData); diff --git a/subscription/Handler/UpdateSubscriptionCarrierHandler.php b/subscription/Handler/UpdateSubscriptionCarrierHandler.php new file mode 100644 index 000000000..fce320b69 --- /dev/null +++ b/subscription/Handler/UpdateSubscriptionCarrierHandler.php @@ -0,0 +1,213 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Handler; + +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Adapter\Context; +use Mollie\Api\Types\SubscriptionStatus; +use Mollie\Config\Config; +use Mollie\Logger\PrestaLoggerInterface; +use Mollie\Service\MailService; +use Mollie\Subscription\Action\UpdateRecurringOrderAction; +use Mollie\Subscription\Action\UpdateSubscriptionAction; +use Mollie\Subscription\DTO\CloneOriginalSubscriptionCartData; +use Mollie\Subscription\DTO\SubscriptionOrderAmountProviderData; +use Mollie\Subscription\DTO\UpdateRecurringOrderData; +use Mollie\Subscription\DTO\UpdateSubscriptionData; +use Mollie\Subscription\Provider\SubscriptionOrderAmountProvider; +use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class UpdateSubscriptionCarrierHandler +{ + /** @var ConfigurationAdapter */ + private $configuration; + /** @var RecurringOrderRepositoryInterface */ + private $recurringOrderRepository; + /** @var Context */ + private $context; + /** @var UpdateSubscriptionAction */ + private $updateSubscriptionAction; + /** @var UpdateRecurringOrderAction */ + private $updateRecurringOrderAction; + /** @var PrestaLoggerInterface */ + private $logger; + /** @var CloneOriginalSubscriptionCartHandler */ + private $cloneOriginalSubscriptionCartHandler; + /** @var SubscriptionOrderAmountProvider */ + private $subscriptionOrderAmountProvider; + /** @var MailService */ + private $mailService; + + public function __construct( + ConfigurationAdapter $configuration, + RecurringOrderRepositoryInterface $recurringOrderRepository, + Context $context, + UpdateSubscriptionAction $updateSubscriptionAction, + UpdateRecurringOrderAction $updateRecurringOrderAction, + PrestaLoggerInterface $logger, + CloneOriginalSubscriptionCartHandler $cloneOriginalSubscriptionCartHandler, + SubscriptionOrderAmountProvider $subscriptionOrderAmountProvider, + MailService $mailService + ) { + $this->configuration = $configuration; + $this->recurringOrderRepository = $recurringOrderRepository; + $this->context = $context; + $this->updateSubscriptionAction = $updateSubscriptionAction; + $this->updateRecurringOrderAction = $updateRecurringOrderAction; + $this->logger = $logger; + $this->cloneOriginalSubscriptionCartHandler = $cloneOriginalSubscriptionCartHandler; + $this->subscriptionOrderAmountProvider = $subscriptionOrderAmountProvider; + $this->mailService = $mailService; + } + + // TODO feature test this with mocked API request data + public function run(int $newCarrierId): array + { + $activeSubscriptionCarrierId = (int) $this->configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); + + // TODO rethink this. If process failed in any way, maybe merchant would like to repeat it again. We need to track individual orders if they were updated. + if ($newCarrierId === $activeSubscriptionCarrierId) { + $this->logger->debug('Same subscription carrier is saved'); + + return []; + } + + $this->configuration->updateValue( + Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, + $newCarrierId + ); + + /** @var array $recurringOrders + */ + $recurringOrders = $this->recurringOrderRepository->getAllOrdersBasedOnStatuses( + [ + SubscriptionStatus::STATUS_PENDING, + SubscriptionStatus::STATUS_ACTIVE, + SubscriptionStatus::STATUS_SUSPENDED, + ], + $this->context->getShopId() + ); + + $failedSubscriptionOrderIdsToUpdate = []; + + foreach ($recurringOrders as $recurringOrder) { + try { + $duplicatedCart = $this->cloneOriginalSubscriptionCartHandler->run( + CloneOriginalSubscriptionCartData::create( + (int) $recurringOrder['id_cart'], + (int) $recurringOrder['id_recurring_product'], + (int) $recurringOrder['id_invoice_address'], + (int) $recurringOrder['id_delivery_address'] + ) + ); + } catch (\Throwable $exception) { + $failedSubscriptionOrderIdsToUpdate[] = (string) $recurringOrder['mollie_subscription_id']; + + $this->logger->error('Failed to clone subscription cart.', [ + 'Exception message' => $exception->getMessage(), + 'Exception code' => $exception->getCode(), + ]); + + continue; + } + + $subscriptionProduct = $duplicatedCart->getProducts()[0]; + + try { + $orderAmount = $this->subscriptionOrderAmountProvider->get( + SubscriptionOrderAmountProviderData::create( + (int) $duplicatedCart->id_address_delivery, + (int) $duplicatedCart->id, + (int) $duplicatedCart->id_customer, + $subscriptionProduct, + $newCarrierId, + (int) $duplicatedCart->id_currency, + (float) $subscriptionProduct['total_price_tax_incl'] + ) + ); + } catch (\Throwable $exception) { + $failedSubscriptionOrderIdsToUpdate[] = (string) $recurringOrder['mollie_subscription_id']; + + $this->logger->error('Failed to get subscription order amount.', [ + 'Exception message' => $exception->getMessage(), + 'Exception code' => $exception->getCode(), + ]); + + continue; + } + + try { + $this->updateSubscriptionAction->run(UpdateSubscriptionData::create( + (string) $recurringOrder['mollie_customer_id'], + (string) $recurringOrder['mollie_subscription_id'], + $orderAmount, + (int) $duplicatedCart->id_customer, + (int) $duplicatedCart->id, + $newCarrierId + )); + } catch (\Throwable $exception) { + $failedSubscriptionOrderIdsToUpdate[] = (string) $recurringOrder['mollie_subscription_id']; + + $this->logger->error('Failed to update subscription.', [ + 'Exception message' => $exception->getMessage(), + 'Exception code' => $exception->getCode(), + ]); + + continue; + } + + try { + $this->updateRecurringOrderAction->run(UpdateRecurringOrderData::create( + (int) $recurringOrder['id'], + $orderAmount->getValue() + )); + } catch (\Throwable $exception) { + $failedSubscriptionOrderIdsToUpdate[] = (string) $recurringOrder['mollie_subscription_id']; + + $this->logger->error('Failed to update recurring order record.', [ + 'Exception message' => $exception->getMessage(), + 'Exception code' => $exception->getCode(), + ]); + + continue; + } + + try { + $this->mailService->sendSubscriptionCarrierUpdateMail((int) $recurringOrder['id']); + } catch (\Throwable $exception) { + $failedSubscriptionOrderIdsToUpdate[] = (string) $recurringOrder['mollie_subscription_id']; + + $this->logger->error('Failed to send subscription carrier update mail.', [ + 'Exception message' => $exception->getMessage(), + 'Exception code' => $exception->getCode(), + ]); + + continue; + } + } + + return $failedSubscriptionOrderIdsToUpdate; + } +} diff --git a/subscription/Install/AttributeInstaller.php b/subscription/Install/AttributeInstaller.php index ebf541dba..4fdd6c64b 100644 --- a/subscription/Install/AttributeInstaller.php +++ b/subscription/Install/AttributeInstaller.php @@ -18,8 +18,8 @@ use Mollie; use Mollie\Adapter\ConfigurationAdapter; use Mollie\Adapter\ProductAttributeAdapter; +use Mollie\Logger\PrestaLoggerInterface; use Mollie\Subscription\Config\Config; -use Mollie\Subscription\Logger\LoggerInterface; use Mollie\Subscription\Repository\LanguageRepository; use PrestaShopDatabaseException; use PrestaShopException; @@ -43,14 +43,14 @@ class AttributeInstaller extends AbstractInstaller /** @var LanguageRepository */ private $language; - /** @var LoggerInterface */ + /** @var PrestaLoggerInterface */ private $logger; /** @var ProductAttributeAdapter */ private $productAttributeAdapter; public function __construct( - LoggerInterface $logger, + PrestaLoggerInterface $logger, ConfigurationAdapter $configuration, Mollie $module, LanguageRepository $language, diff --git a/subscription/Install/AttributeUninstaller.php b/subscription/Install/AttributeUninstaller.php index 7765bcc42..e5dd017a3 100644 --- a/subscription/Install/AttributeUninstaller.php +++ b/subscription/Install/AttributeUninstaller.php @@ -17,8 +17,8 @@ use Mollie; use Mollie\Adapter\ConfigurationAdapter; use Mollie\Adapter\ProductAttributeAdapter; +use Mollie\Logger\PrestaLoggerInterface; use Mollie\Subscription\Config\Config; -use Mollie\Subscription\Logger\LoggerInterface; use PrestaShopException; use Psr\Log\LogLevel; @@ -36,13 +36,13 @@ class AttributeUninstaller extends AbstractUninstaller /** @var Mollie */ private $module; - /** @var LoggerInterface */ + /** @var PrestaLoggerInterface */ private $logger; /** @var ProductAttributeAdapter */ private $productAttributeAdapter; public function __construct( - LoggerInterface $logger, + PrestaLoggerInterface $logger, ConfigurationAdapter $configuration, Mollie $module, ProductAttributeAdapter $productAttributeAdapter diff --git a/subscription/Logger/Logger.php b/subscription/Logger/Logger.php deleted file mode 100644 index 97bb1f21c..000000000 --- a/subscription/Logger/Logger.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @copyright Mollie B.V. - * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md - * - * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart - */ - -declare(strict_types=1); - -namespace Mollie\Subscription\Logger; - -if (!defined('_PS_VERSION_')) { - exit; -} - -class Logger implements LoggerInterface -{ - const FILE_NAME = 'Logger'; - - const LOG_OBJECT_TYPE = 'mollie_sub_log'; - - // TODO fix this logger - - /** - * @return null - */ - public function emergency($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function alert($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function critical($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function error($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function warning($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function notice($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function info($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function debug($message, array $context = []) - { - return null; - } - - public function log($level, $message, array $context = []): void - { - \PrestaShopLogger::addLog( - $message, - $level, - null, - self::LOG_OBJECT_TYPE - ); - } -} diff --git a/subscription/Logger/NullLogger.php b/subscription/Logger/NullLogger.php deleted file mode 100644 index bdfee6fe0..000000000 --- a/subscription/Logger/NullLogger.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @copyright Mollie B.V. - * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md - * - * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart - */ - -declare(strict_types=1); - -namespace Mollie\Subscription\Logger; - -if (!defined('_PS_VERSION_')) { - exit; -} - -//NOTE only should be used for tests -final class NullLogger implements LoggerInterface -{ - /** - * @return null - */ - public function emergency($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function alert($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function critical($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function error($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function warning($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function notice($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function info($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function debug($message, array $context = []) - { - return null; - } - - /** - * @return null - */ - public function log($level, $message, array $context = []) - { - return null; - } -} diff --git a/subscription/Presenter/OrderDetailPresenter.php b/subscription/Presenter/OrderDetailPresenter.php index a7deae596..87198d0da 100644 --- a/subscription/Presenter/OrderDetailPresenter.php +++ b/subscription/Presenter/OrderDetailPresenter.php @@ -14,9 +14,9 @@ use Mollie\Adapter\Context; use Mollie\Api\Types\SubscriptionStatus; -use Mollie\Repository\CurrencyRepositoryInterface; use Mollie\Repository\OrderRepositoryInterface; use Mollie\Repository\ProductRepositoryInterface; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; use Mollie\Subscription\Exception\CouldNotPresentOrderDetail; use Mollie\Subscription\Repository\OrderDetailRepositoryInterface; use Mollie\Utility\NumberUtility; diff --git a/subscription/Presenter/RecurringOrderPresenter.php b/subscription/Presenter/RecurringOrderPresenter.php index fac199df0..e97eb6f16 100644 --- a/subscription/Presenter/RecurringOrderPresenter.php +++ b/subscription/Presenter/RecurringOrderPresenter.php @@ -59,8 +59,15 @@ public function __construct( */ public function present(int $recurringOrderId): array { - $recurringOrder = $this->recurringOrderRepository->findOneBy(['id_mol_recurring_order' => $recurringOrderId]); - $recurringProduct = $this->recurringOrdersProductRepository->findOneBy(['id_mol_recurring_orders_product' => $recurringOrderId]); + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = $this->recurringOrderRepository->findOrFail([ + 'id_mol_recurring_order' => $recurringOrderId, + ]); + + /** @var \MolRecurringOrdersProduct $recurringProduct */ + $recurringProduct = $this->recurringOrdersProductRepository->findOrFail([ + 'id_mol_recurring_orders_product' => $recurringOrder->id_mol_recurring_orders_product, + ]); $product = new Product($recurringProduct->id_product, false, $this->language->getDefaultLanguageId()); $order = new Order($recurringOrder->id_order); diff --git a/subscription/Presenter/RecurringOrdersPresenter.php b/subscription/Presenter/RecurringOrdersPresenter.php index 2b96939f8..bed0d6131 100644 --- a/subscription/Presenter/RecurringOrdersPresenter.php +++ b/subscription/Presenter/RecurringOrdersPresenter.php @@ -60,9 +60,21 @@ public function __construct( $this->context = $context; } + /** + * @throws \Throwable + */ public function present(string $molCustomerId): array { - $recurringOrders = $this->recurringOrderRepository->findAllBy(['mollie_customer_id' => $molCustomerId])->getResults(); + /** @var ?\PrestaShopCollection $recurringOrders */ + $recurringOrders = $this->recurringOrderRepository->findAllBy([ + 'mollie_customer_id' => $molCustomerId, + ]); + + if (!$recurringOrders) { + return []; + } + + $recurringOrders = $recurringOrders->getResults(); // this part sorts array so that the new ones are at the top usort($recurringOrders, function ($a, $b) { @@ -70,11 +82,19 @@ public function present(string $molCustomerId): array }); $recurringOrdersPresentData = []; + /** @var MolRecurringOrder $recurringOrder */ foreach ($recurringOrders as $recurringOrder) { - $recurringProduct = $this->recurringOrdersProductRepository->findOneBy([ - 'id_mol_recurring_orders_product' => $recurringOrder->id, - ]); + try { + /** @var \MolRecurringOrdersProduct $recurringProduct */ + $recurringProduct = $this->recurringOrdersProductRepository->findOrFail([ + 'id_mol_recurring_orders_product' => $recurringOrder->id_mol_recurring_orders_product, + ]); + } catch (\Throwable $exception) { + // TODO log not found data + + continue; + } $product = new Product($recurringProduct->id_product, false, $this->language->getDefaultLanguageId()); diff --git a/subscription/Provider/GeneralSubscriptionMailDataProvider.php b/subscription/Provider/GeneralSubscriptionMailDataProvider.php new file mode 100644 index 000000000..35fb6b43d --- /dev/null +++ b/subscription/Provider/GeneralSubscriptionMailDataProvider.php @@ -0,0 +1,122 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Provider; + +use Mollie\Adapter\Context; +use Mollie\Exception\MollieException; +use Mollie\Repository\CustomerRepositoryInterface; +use Mollie\Repository\ProductRepositoryInterface; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; +use Mollie\Subscription\DTO\Mail\GeneralSubscriptionMailData; +use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; +use Mollie\Subscription\Repository\RecurringOrdersProductRepositoryInterface; +use Mollie\Utility\NumberUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class GeneralSubscriptionMailDataProvider +{ + /** @var RecurringOrderRepositoryInterface */ + private $recurringOrderRepository; + /** @var RecurringOrdersProductRepositoryInterface */ + private $recurringOrdersProductRepository; + /** @var CustomerRepositoryInterface */ + private $customerRepository; + /** @var ProductRepositoryInterface */ + private $productRepository; + /** @var Context */ + private $context; + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + + public function __construct( + RecurringOrderRepositoryInterface $recurringOrderRepository, + RecurringOrdersProductRepositoryInterface $recurringOrdersProductRepository, + CustomerRepositoryInterface $customerRepository, + ProductRepositoryInterface $productRepository, + Context $context, + CurrencyRepositoryInterface $currencyRepository + ) { + $this->recurringOrderRepository = $recurringOrderRepository; + $this->recurringOrdersProductRepository = $recurringOrdersProductRepository; + $this->customerRepository = $customerRepository; + $this->productRepository = $productRepository; + $this->context = $context; + $this->currencyRepository = $currencyRepository; + } + + /** + * @throws MollieException + */ + public function run(int $recurringOrderId): GeneralSubscriptionMailData + { + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = $this->recurringOrderRepository->findOrFail([ + 'id_mol_recurring_order' => $recurringOrderId, + ]); + + /** @var \MolRecurringOrdersProduct $recurringOrderProduct */ + $recurringOrderProduct = $this->recurringOrdersProductRepository->findOrFail([ + 'id_mol_recurring_orders_product' => $recurringOrder->id_mol_recurring_orders_product, + ]); + + /** @var \Customer $customer */ + $customer = $this->customerRepository->findOrFail([ + 'id_customer' => $recurringOrder->id_customer, + ]); + + /** @var \Product $product */ + $product = $this->productRepository->findOrFail([ + 'id_product' => $recurringOrderProduct->id_product, + ]); + + $productName = is_array($product->name) ? ($product->name[$customer->id_lang] ?? '') : $product->name; + + /** @var \Currency $currency */ + $currency = $this->currencyRepository->findOrFail([ + 'id_currency' => $recurringOrder->id_currency, + ]); + + // TODO would be great to get unitPriceTaxIncl + $unitPriceTaxExcl = (float) $this->context->formatPrice( + NumberUtility::toPrecision( + (float) $recurringOrderProduct->unit_price, + NumberUtility::DECIMAL_PRECISION + ), + (string) $currency->iso_code + ); + + $totalPriceTaxIncl = (float) $this->context->formatPrice( + NumberUtility::toPrecision( + (float) $recurringOrder->total_tax_incl, + NumberUtility::DECIMAL_PRECISION + ), + (string) $currency->iso_code + ); + + return GeneralSubscriptionMailData::create( + (string) $recurringOrder->mollie_subscription_id, + (string) $productName, + $unitPriceTaxExcl, + (int) $recurringOrderProduct->quantity, + $totalPriceTaxIncl, + (string) $customer->firstname, + (string) $customer->lastname, + (string) $customer->email, + (int) $customer->id_lang, + (int) $customer->id_shop + ); + } +} diff --git a/subscription/Provider/SubscriptionCarrierDeliveryPriceProvider.php b/subscription/Provider/SubscriptionCarrierDeliveryPriceProvider.php index 5fbc7d64e..954421409 100644 --- a/subscription/Provider/SubscriptionCarrierDeliveryPriceProvider.php +++ b/subscription/Provider/SubscriptionCarrierDeliveryPriceProvider.php @@ -12,14 +12,15 @@ namespace Mollie\Subscription\Provider; -use Mollie\Adapter\ConfigurationAdapter; -use Mollie\Config\Config; +use Mollie\Exception\MollieException; use Mollie\Repository\AddressRepositoryInterface; use Mollie\Repository\CarrierRepositoryInterface; use Mollie\Repository\CartRepositoryInterface; use Mollie\Repository\CountryRepositoryInterface; use Mollie\Repository\CustomerRepositoryInterface; +use Mollie\Subscription\DTO\SubscriptionCarrierDeliveryPriceData; use Mollie\Subscription\Exception\CouldNotProvideSubscriptionCarrierDeliveryPrice; +use Mollie\Subscription\Exception\MollieSubscriptionException; if (!defined('_PS_VERSION_')) { exit; @@ -27,8 +28,6 @@ class SubscriptionCarrierDeliveryPriceProvider { - /** @var ConfigurationAdapter */ - private $configuration; /** @var CarrierRepositoryInterface */ private $carrierRepository; /** @var AddressRepositoryInterface */ @@ -41,14 +40,12 @@ class SubscriptionCarrierDeliveryPriceProvider private $countryRepository; public function __construct( - ConfigurationAdapter $configuration, CarrierRepositoryInterface $carrierRepository, AddressRepositoryInterface $addressRepository, CustomerRepositoryInterface $customerRepository, CartRepositoryInterface $cartRepository, CountryRepositoryInterface $countryRepository ) { - $this->configuration = $configuration; $this->carrierRepository = $carrierRepository; $this->addressRepository = $addressRepository; $this->customerRepository = $customerRepository; @@ -57,80 +54,58 @@ public function __construct( } /** - * @throws CouldNotProvideSubscriptionCarrierDeliveryPrice + * @throws MollieException|MollieSubscriptionException */ - public function getPrice(int $addressDeliveryId, int $cartId, int $customerId, array $subscriptionProduct): float + public function getPrice(SubscriptionCarrierDeliveryPriceData $data): float { - $subscriptionCarrierId = (int) $this->configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); - - /** @var \Carrier|null $carrier */ - $carrier = $this->carrierRepository->findOneBy([ - 'id_carrier' => $subscriptionCarrierId, + /** @var \Carrier $carrier */ + $carrier = $this->carrierRepository->findOrFail([ + 'id_carrier' => $data->getSubscriptionCarrierId(), 'active' => 1, 'deleted' => 0, ]); - if (!$carrier) { - throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToFindSelectedCarrier(); - } - - /** @var \Cart|null $cart */ - $cart = $this->cartRepository->findOneBy([ - 'id_cart' => $cartId, + /** @var \Cart $cart */ + $cart = $this->cartRepository->findOrFail([ + 'id_cart' => $data->getCartId(), ]); - if (!$cart) { - throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToFindOrderCart(); - } - - /** @var \Customer|null $customer */ - $customer = $this->customerRepository->findOneBy([ - 'id_customer' => $customerId, + /** @var \Customer $customer */ + $customer = $this->customerRepository->findOrFail([ + 'id_customer' => $data->getCustomerId(), ]); - if (!$customer) { - throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToFindOrderCustomer(); - } - $getAvailableOrderCarriers = $this->carrierRepository->getCarriersForOrder( - $this->addressRepository->getZoneById($addressDeliveryId), + $this->addressRepository->getZoneById($data->getDeliveryAddressId()), $customer->getGroups(), $cart ); - if (!in_array($subscriptionCarrierId, array_column($getAvailableOrderCarriers, 'id_carrier'), false)) { - throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToApplySelectedCarrier(); + if (!in_array($data->getSubscriptionCarrierId(), array_column($getAvailableOrderCarriers, 'id_carrier'), false)) { + throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToApplySelectedCarrier($data->getSubscriptionCarrierId()); } - /** @var \Address|bool $address */ - $address = $this->addressRepository->findOneBy([ - 'id_address' => $addressDeliveryId, + /** @var \Address $address */ + $address = $this->addressRepository->findOrFail([ + 'id_address' => $data->getDeliveryAddressId(), ]); - if (!$address) { - throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToFindOrderDeliveryAddress(); - } - - /** @var \Country|bool $country */ - $country = $this->countryRepository->findOneBy([ + /** @var \Country $country */ + $country = $this->countryRepository->findOrFail([ 'id_country' => $address->id_country, ]); - if (!$country) { - throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToFindOrderDeliveryCountry(); - } - /** @var float|bool $deliveryPrice */ $deliveryPrice = $cart->getPackageShippingCost( - $subscriptionCarrierId, + (int) $carrier->id, true, $country, - [$subscriptionProduct], - $this->addressRepository->getZoneById($addressDeliveryId) + [$data->getSubscriptionProduct()], + $this->addressRepository->getZoneById($data->getDeliveryAddressId()) ); if (is_bool($deliveryPrice) && !$deliveryPrice) { - throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToGetSelectedCarrierPrice(); + throw CouldNotProvideSubscriptionCarrierDeliveryPrice::failedToGetSelectedCarrierPrice($data->getSubscriptionCarrierId()); } return (float) $deliveryPrice; diff --git a/subscription/Provider/SubscriptionIntervalProvider.php b/subscription/Provider/SubscriptionIntervalProvider.php index be232827b..fc9813c3b 100644 --- a/subscription/Provider/SubscriptionIntervalProvider.php +++ b/subscription/Provider/SubscriptionIntervalProvider.php @@ -14,11 +14,11 @@ namespace Mollie\Subscription\Provider; -use Combination; use Mollie\Adapter\ConfigurationAdapter; use Mollie\Subscription\Config\Config; use Mollie\Subscription\DTO\Object\Interval; use Mollie\Subscription\Exception\SubscriptionIntervalException; +use Mollie\Subscription\Repository\CombinationRepositoryInterface; if (!defined('_PS_VERSION_')) { exit; @@ -28,10 +28,15 @@ class SubscriptionIntervalProvider { /** @var ConfigurationAdapter */ private $configuration; + /** @var CombinationRepositoryInterface */ + private $combinationRepository; - public function __construct(ConfigurationAdapter $configuration) - { + public function __construct( + ConfigurationAdapter $configuration, + CombinationRepositoryInterface $combinationRepository + ) { $this->configuration = $configuration; + $this->combinationRepository = $combinationRepository; } /** @@ -39,8 +44,17 @@ public function __construct(ConfigurationAdapter $configuration) * * @throws SubscriptionIntervalException */ - public function getSubscriptionInterval(Combination $combination): Interval + public function getSubscriptionInterval(int $productAttributeId): Interval { + /** @var \Combination|null $combination */ + $combination = $this->combinationRepository->findOneBy([ + 'id_product_attribute' => $productAttributeId, + ]); + + if (!$combination) { + throw SubscriptionIntervalException::failedToFindCombination($productAttributeId); + } + foreach ($combination->getWsProductOptionValues() as $attribute) { switch ($attribute['id']) { case $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_DAILY): @@ -54,6 +68,6 @@ public function getSubscriptionInterval(Combination $combination): Interval } } - throw new SubscriptionIntervalException(sprintf('No interval exists for this %s attribute', $combination->id)); + throw SubscriptionIntervalException::failedToFindMatchingInterval($productAttributeId); } } diff --git a/subscription/Provider/SubscriptionOrderAmountProvider.php b/subscription/Provider/SubscriptionOrderAmountProvider.php new file mode 100644 index 000000000..eb3f1db06 --- /dev/null +++ b/subscription/Provider/SubscriptionOrderAmountProvider.php @@ -0,0 +1,75 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Provider; + +use Mollie\Exception\MollieException; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; +use Mollie\Subscription\DTO\Object\Amount; +use Mollie\Subscription\DTO\SubscriptionCarrierDeliveryPriceData; +use Mollie\Subscription\DTO\SubscriptionOrderAmountProviderData; +use Mollie\Subscription\Exception\CouldNotProvideSubscriptionOrderAmount; +use Mollie\Subscription\Exception\MollieSubscriptionException; +use Mollie\Utility\NumberUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class SubscriptionOrderAmountProvider +{ + /** @var SubscriptionCarrierDeliveryPriceProvider */ + private $subscriptionCarrierDeliveryPriceProvider; + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + + public function __construct( + SubscriptionCarrierDeliveryPriceProvider $subscriptionCarrierDeliveryPriceProvider, + CurrencyRepositoryInterface $currencyRepository + ) { + $this->subscriptionCarrierDeliveryPriceProvider = $subscriptionCarrierDeliveryPriceProvider; + $this->currencyRepository = $currencyRepository; + } + + /** + * @throws MollieSubscriptionException + * @throws MollieException + */ + public function get(SubscriptionOrderAmountProviderData $data): Amount + { + try { + $deliveryPrice = $this->subscriptionCarrierDeliveryPriceProvider->getPrice( + SubscriptionCarrierDeliveryPriceData::create( + $data->getAddressDeliveryId(), + $data->getCartId(), + $data->getCustomerId(), + $data->getSubscriptionProduct(), + $data->getSubscriptionCarrierId() + ) + ); + } catch (\Throwable $exception) { + throw CouldNotProvideSubscriptionOrderAmount::failedToProvideCarrierDeliveryPrice($exception); + } + + $orderTotal = NumberUtility::plus( + $data->getProductPriceTaxIncl(), + $deliveryPrice + ); + + /** @var \Currency $currency */ + $currency = $this->currencyRepository->findOrFail([ + 'id_currency' => $data->getCurrencyId(), + ]); + + return new Amount($orderTotal, $currency->iso_code); + } +} diff --git a/subscription/Verification/HasSubscriptionProductInCart.php b/subscription/Provider/SubscriptionProductProvider.php similarity index 58% rename from subscription/Verification/HasSubscriptionProductInCart.php rename to subscription/Provider/SubscriptionProductProvider.php index 7fea7c856..62462bbe9 100644 --- a/subscription/Verification/HasSubscriptionProductInCart.php +++ b/subscription/Provider/SubscriptionProductProvider.php @@ -10,40 +10,35 @@ * @codingStandardsIgnoreStart */ -namespace Mollie\Subscription\Verification; +namespace Mollie\Subscription\Provider; -use Mollie\Adapter\Context; use Mollie\Subscription\Validator\SubscriptionProductValidator; if (!defined('_PS_VERSION_')) { exit; } -class HasSubscriptionProductInCart +class SubscriptionProductProvider { - /** @var Context */ - private $context; /** @var SubscriptionProductValidator */ private $subscriptionProductValidator; public function __construct( - Context $context, SubscriptionProductValidator $subscriptionProductValidator ) { - $this->context = $context; $this->subscriptionProductValidator = $subscriptionProductValidator; } - public function verify(): bool + public function getProduct(array $products): array { - $cartProducts = $this->context->getCartProducts(); - - foreach ($cartProducts as $cartProduct) { - if ($this->subscriptionProductValidator->validate((int) $cartProduct['id_product_attribute'])) { - return true; + foreach ($products as $product) { + if (!$this->subscriptionProductValidator->validate((int) $product['id_product_attribute'])) { + continue; } + + return $product; } - return false; + return []; } } diff --git a/subscription/Provider/SubscriptionStartDateProvider.php b/subscription/Provider/SubscriptionStartDateProvider.php new file mode 100644 index 000000000..ba415d9f4 --- /dev/null +++ b/subscription/Provider/SubscriptionStartDateProvider.php @@ -0,0 +1,76 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +declare(strict_types=1); + +namespace Mollie\Subscription\Provider; + +use Combination; +use DateInterval; +use DateTime; +use DateTimeZone; +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Subscription\Config\Config; +use Mollie\Subscription\Exception\SubscriptionIntervalException; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class SubscriptionStartDateProvider +{ + /** @var ConfigurationAdapter */ + private $configuration; + + public function __construct(ConfigurationAdapter $configuration) + { + $this->configuration = $configuration; + } + + /** + * Returns subscription date time + * + * @return string + * + * @throws SubscriptionIntervalException + */ + public function getSubscriptionStartDate(Combination $combination) + { + $currentTime = new DateTime('now', new DateTimeZone('UTC')); + + foreach ($combination->getWsProductOptionValues() as $attribute) { + switch ($attribute['id']) { + case $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_DAILY): + $interval = new DateInterval('P1D'); + break; + case $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_WEEKLY): + $interval = new DateInterval('P7D'); + break; + case $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_MONTHLY): + $interval = new DateInterval('P1M'); + break; + case $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_YEARLY): + $interval = new DateInterval('P1Y'); + break; + default: + throw new SubscriptionIntervalException(sprintf('No interval exists for this %s attribute', $combination->id)); + } + + // Add the interval to the current time + $currentTime->add($interval); + + return $currentTime->format('Y-m-d'); + } + + throw new SubscriptionIntervalException(sprintf('No interval exists for this %s attribute', $combination->id)); + } +} diff --git a/subscription/Repository/AbstractRepository.php b/subscription/Repository/AbstractRepository.php deleted file mode 100644 index b6c2ed13b..000000000 --- a/subscription/Repository/AbstractRepository.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @copyright Mollie B.V. - * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md - * - * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart - */ - -declare(strict_types=1); - -namespace Mollie\Subscription\Repository; - -use ObjectModel; -use PrestaShopCollection; -use PrestaShopException; - -if (!defined('_PS_VERSION_')) { - exit; -} - -abstract class AbstractRepository -{ - /** - * @var string - */ - private $fullyClassifiedClassName; - - /** - * @param string|\stdClass $fullyClassifiedClassName - */ - public function __construct($fullyClassifiedClassName) - { - if (is_object($fullyClassifiedClassName)) { - $this->fullyClassifiedClassName = get_class($fullyClassifiedClassName); - - return; - } - $this->fullyClassifiedClassName = $fullyClassifiedClassName; - } - - /** - * @return PrestaShopCollection - * - * @throws PrestaShopException - */ - public function findAll() - { - return new PrestaShopCollection($this->fullyClassifiedClassName); - } - - /** - * @return ObjectModel|null - * - * @throws PrestaShopException - */ - public function findOneBy(array $keyValueCriteria) - { - $psCollection = new PrestaShopCollection($this->fullyClassifiedClassName); - - foreach ($keyValueCriteria as $field => $value) { - $psCollection = $psCollection->where($field, '=', $value); - } - - $first = $psCollection->getFirst(); - - /* @phpstan-ignore-next-line */ - return false === $first ? null : $first; - } - - /** - * @return PrestaShopCollection|null - * - * @throws PrestaShopException - */ - public function findAllBy(array $keyValueCriteria) - { - $psCollection = new PrestaShopCollection($this->fullyClassifiedClassName); - - foreach ($keyValueCriteria as $field => $value) { - $psCollection = $psCollection->where($field, '=', $value); - } - - $all = $psCollection->getAll(); - - /* @phpstan-ignore-next-line */ - return false === $all ? null : $all; - } -} diff --git a/subscription/Repository/CombinationRepository.php b/subscription/Repository/CombinationRepository.php index 2d30c74d3..307f5b5f3 100644 --- a/subscription/Repository/CombinationRepository.php +++ b/subscription/Repository/CombinationRepository.php @@ -14,12 +14,19 @@ namespace Mollie\Subscription\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } -class CombinationRepository +class CombinationRepository extends AbstractRepository implements CombinationRepositoryInterface { + public function __construct() + { + parent::__construct(\Combination::class); + } + public function getById(int $id): \Combination { return new \Combination($id); diff --git a/subscription/Repository/CurrencyRepository.php b/subscription/Repository/CombinationRepositoryInterface.php similarity index 71% rename from subscription/Repository/CurrencyRepository.php rename to subscription/Repository/CombinationRepositoryInterface.php index 18348cfbc..61db9ac82 100644 --- a/subscription/Repository/CurrencyRepository.php +++ b/subscription/Repository/CombinationRepositoryInterface.php @@ -10,18 +10,14 @@ * @codingStandardsIgnoreStart */ -declare(strict_types=1); - namespace Mollie\Subscription\Repository; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; + if (!defined('_PS_VERSION_')) { exit; } -class CurrencyRepository +interface CombinationRepositoryInterface extends ReadOnlyRepositoryInterface { - public function getById(int $id): \Currency - { - return new \Currency($id); - } } diff --git a/subscription/Repository/OrderDetailRepository.php b/subscription/Repository/OrderDetailRepository.php index 9b79722cc..9f04e598d 100644 --- a/subscription/Repository/OrderDetailRepository.php +++ b/subscription/Repository/OrderDetailRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Subscription\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/subscription/Repository/OrderDetailRepositoryInterface.php b/subscription/Repository/OrderDetailRepositoryInterface.php index 4ad3825b6..115b54dac 100644 --- a/subscription/Repository/OrderDetailRepositoryInterface.php +++ b/subscription/Repository/OrderDetailRepositoryInterface.php @@ -12,7 +12,7 @@ namespace Mollie\Subscription\Repository; -use Mollie\Repository\ReadOnlyRepositoryInterface; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; if (!defined('_PS_VERSION_')) { exit; diff --git a/subscription/Repository/ProductCombinationRepository.php b/subscription/Repository/ProductCombinationRepository.php deleted file mode 100644 index e3cd0c145..000000000 --- a/subscription/Repository/ProductCombinationRepository.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @copyright Mollie B.V. - * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md - * - * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart - */ - -declare(strict_types=1); - -namespace Mollie\Subscription\Repository; - -if (!defined('_PS_VERSION_')) { - exit; -} - -class ProductCombinationRepository -{ - public function getIds(int $combinationId): array - { - $query = new \DbQuery(); - $query - ->select('combination.id_attribute') - ->from('product_attribute_combination', 'combination') - ->where('combination.id_product_attribute = ' . $combinationId); - - return \Db::getInstance()->executeS($query) ?: []; - } -} diff --git a/subscription/Repository/RecurringOrderRepository.php b/subscription/Repository/RecurringOrderRepository.php index d3213f1c5..3e70cb39e 100644 --- a/subscription/Repository/RecurringOrderRepository.php +++ b/subscription/Repository/RecurringOrderRepository.php @@ -14,8 +14,7 @@ namespace Mollie\Subscription\Repository; -use MolRecurringOrder; -use PrestaShopCollection; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; @@ -23,19 +22,32 @@ class RecurringOrderRepository extends AbstractRepository implements RecurringOrderRepositoryInterface { - public function findOneBy(array $keyValueCriteria): ?MolRecurringOrder + public function __construct() { - /** @var ?MolRecurringOrder $result */ - $result = parent::findOneBy($keyValueCriteria); - - return $result; + parent::__construct(\MolRecurringOrder::class); } - public function findAllBy(array $keyValueCriteria): ?PrestaShopCollection + public function getAllOrdersBasedOnStatuses(array $statuses, int $shopId): array { - /** @var ?PrestaShopCollection $result */ - $result = parent::findAllBy($keyValueCriteria); - - return $result; + $query = new \DbQuery(); + + $query + ->select( + 'mro.id_mol_recurring_order as id, mro.mollie_subscription_id, + mro.mollie_customer_id, mro.id_cart, + mro.id_mol_recurring_orders_product as id_recurring_product, + mro.id_address_invoice, mro.id_delivery_address' + ) + ->from('mol_recurring_order', 'mro') + ->leftJoin( + 'orders', 'o', + 'o.id_order = mro.id_order' + ) + ->where('mro.status IN (\'' . implode("','", $statuses) . '\')') + ->where('o.id_shop = ' . $shopId); + + $result = \Db::getInstance()->executeS($query); + + return !empty($result) ? $result : []; } } diff --git a/subscription/Repository/RecurringOrderRepositoryInterface.php b/subscription/Repository/RecurringOrderRepositoryInterface.php index 7bcf75013..7924a733f 100644 --- a/subscription/Repository/RecurringOrderRepositoryInterface.php +++ b/subscription/Repository/RecurringOrderRepositoryInterface.php @@ -14,36 +14,13 @@ namespace Mollie\Subscription\Repository; -use MolRecurringOrder; -use PrestaShopCollection; -use PrestaShopException; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; if (!defined('_PS_VERSION_')) { exit; } -interface RecurringOrderRepositoryInterface +interface RecurringOrderRepositoryInterface extends ReadOnlyRepositoryInterface { -// TODO add return types for all repositories - - /** - * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] - * - * @return ?MolRecurringOrder - */ - public function findOneBy(array $keyValueCriteria): ?MolRecurringOrder; - - /** - * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] - * - * @return ?PrestaShopCollection - */ - public function findAllBy(array $keyValueCriteria): ?PrestaShopCollection; - - /** - * @return PrestaShopCollection - * - * @throws PrestaShopException - */ - public function findAll(); + public function getAllOrdersBasedOnStatuses(array $statuses, int $shopId): array; } diff --git a/subscription/Repository/RecurringOrdersProductRepository.php b/subscription/Repository/RecurringOrdersProductRepository.php index 84aa9c46e..de2910e39 100644 --- a/subscription/Repository/RecurringOrdersProductRepository.php +++ b/subscription/Repository/RecurringOrdersProductRepository.php @@ -14,8 +14,7 @@ namespace Mollie\Subscription\Repository; -use MolRecurringOrdersProduct; -use PrestaShopCollection; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; if (!defined('_PS_VERSION_')) { exit; @@ -23,19 +22,8 @@ class RecurringOrdersProductRepository extends AbstractRepository implements RecurringOrdersProductRepositoryInterface { - public function findOneBy(array $keyValueCriteria): ?MolRecurringOrdersProduct + public function __construct() { - /** @var ?MolRecurringOrdersProduct $result */ - $result = parent::findOneBy($keyValueCriteria); - - return $result; - } - - public function findAllBy(array $keyValueCriteria): ?PrestaShopCollection - { - /** @var ?PrestaShopCollection $result */ - $result = parent::findAllBy($keyValueCriteria); - - return $result; + parent::__construct(\MolRecurringOrdersProduct::class); } } diff --git a/subscription/Repository/RecurringOrdersProductRepositoryInterface.php b/subscription/Repository/RecurringOrdersProductRepositoryInterface.php index f74937bd6..a5aba6ef3 100644 --- a/subscription/Repository/RecurringOrdersProductRepositoryInterface.php +++ b/subscription/Repository/RecurringOrdersProductRepositoryInterface.php @@ -14,26 +14,12 @@ namespace Mollie\Subscription\Repository; -use MolRecurringOrdersProduct; -use PrestaShopCollection; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; if (!defined('_PS_VERSION_')) { exit; } -interface RecurringOrdersProductRepositoryInterface +interface RecurringOrdersProductRepositoryInterface extends ReadOnlyRepositoryInterface { - /** - * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] - * - * @return ?MolRecurringOrdersProduct - */ - public function findOneBy(array $keyValueCriteria): ?MolRecurringOrdersProduct; - - /** - * @param array $keyValueCriteria - e.g [ 'id_cart' => 5 ] - * - * @return ?PrestaShopCollection - */ - public function findAllBy(array $keyValueCriteria): ?PrestaShopCollection; } diff --git a/subscription/Repository/SpecificPriceRepository.php b/subscription/Repository/SpecificPriceRepository.php index 49dcdd286..a8d8bc20a 100644 --- a/subscription/Repository/SpecificPriceRepository.php +++ b/subscription/Repository/SpecificPriceRepository.php @@ -12,6 +12,8 @@ namespace Mollie\Subscription\Repository; +use Mollie\Shared\Infrastructure\Repository\AbstractRepository; + if (!defined('_PS_VERSION_')) { exit; } diff --git a/subscription/Repository/SpecificPriceRepositoryInterface.php b/subscription/Repository/SpecificPriceRepositoryInterface.php index 62a901ff6..b306b3d73 100644 --- a/subscription/Repository/SpecificPriceRepositoryInterface.php +++ b/subscription/Repository/SpecificPriceRepositoryInterface.php @@ -12,7 +12,7 @@ namespace Mollie\Subscription\Repository; -use Mollie\Repository\ReadOnlyRepositoryInterface; +use Mollie\Shared\Infrastructure\Repository\ReadOnlyRepositoryInterface; if (!defined('_PS_VERSION_')) { exit; diff --git a/subscription/Validator/CanProductBeAddedToCartValidator.php b/subscription/Validator/CanProductBeAddedToCartValidator.php index 0a0ae61c0..64335b244 100644 --- a/subscription/Validator/CanProductBeAddedToCartValidator.php +++ b/subscription/Validator/CanProductBeAddedToCartValidator.php @@ -16,8 +16,9 @@ use Mollie\Adapter\CartAdapter; use Mollie\Adapter\ToolsAdapter; -use Mollie\Subscription\Exception\ExceptionCode; +use Mollie\Subscription\Exception\CouldNotValidateSubscriptionSettings; use Mollie\Subscription\Exception\SubscriptionProductValidationException; +use Mollie\Subscription\Provider\SubscriptionProductProvider; if (!defined('_PS_VERSION_')) { exit; @@ -29,25 +30,30 @@ class CanProductBeAddedToCartValidator private $cart; /** @var SubscriptionProductValidator */ - private $subscriptionProduct; + private $subscriptionProductValidator; /** @var ToolsAdapter */ private $tools; + /** @var SubscriptionSettingsValidator */ + private $subscriptionSettingsValidator; + /** @var SubscriptionProductProvider */ + private $subscriptionProductProvider; public function __construct( CartAdapter $cart, - SubscriptionProductValidator $subscriptionProduct, - ToolsAdapter $tools + SubscriptionProductValidator $subscriptionProductValidator, + ToolsAdapter $tools, + SubscriptionSettingsValidator $subscriptionSettingsValidator, + SubscriptionProductProvider $subscriptionProductProvider ) { $this->cart = $cart; - $this->subscriptionProduct = $subscriptionProduct; + $this->subscriptionProductValidator = $subscriptionProductValidator; $this->tools = $tools; + $this->subscriptionSettingsValidator = $subscriptionSettingsValidator; + $this->subscriptionProductProvider = $subscriptionProductProvider; } /** - * Validates if product can be added to the cart. - * Only 1 subscription product can be to the cart - * * @throws SubscriptionProductValidationException */ public function validate(int $productAttributeId): bool @@ -58,28 +64,42 @@ public function validate(int $productAttributeId): bool return true; } - $isNewSubscriptionProduct = $this->subscriptionProduct->validate($productAttributeId); + if (!$this->subscriptionProductValidator->validate($productAttributeId)) { + return true; + } - return !$isNewSubscriptionProduct || $this->validateIfSubscriptionProductCanBeAdded($productAttributeId); + if (!$this->validateSubscriptionSettings()) { + throw SubscriptionProductValidationException::invalidSubscriptionSettings(); + } + + if (!$this->validateIfSubscriptionProductCanBeAdded($productAttributeId)) { + throw SubscriptionProductValidationException::cartAlreadyHasSubscriptionProduct(); + } + + return true; } - /** - * @throws SubscriptionProductValidationException - */ private function validateIfSubscriptionProductCanBeAdded(int $productAttributeId): bool { - $cartProducts = $this->cart->getProducts(); + $subscriptionProduct = $this->subscriptionProductProvider->getProduct($this->cart->getProducts()); + + if (empty($subscriptionProduct)) { + return true; + } - foreach ($cartProducts as $cartProduct) { - if (!$this->subscriptionProduct->validate((int) $cartProduct['id_product_attribute'])) { - continue; - } + if ((int) $subscriptionProduct['id_product_attribute'] === $productAttributeId) { + return true; + } - if ((int) $cartProduct['id_product_attribute'] === $productAttributeId) { - continue; - } + return false; + } - throw new SubscriptionProductValidationException('Cart already has subscription product', ExceptionCode::CART_ALREADY_HAS_SUBSCRIPTION_PRODUCT); + private function validateSubscriptionSettings(): bool + { + try { + $this->subscriptionSettingsValidator->validate(); + } catch (CouldNotValidateSubscriptionSettings $exception) { + return false; } return true; diff --git a/subscription/Validator/SubscriptionProductValidator.php b/subscription/Validator/SubscriptionProductValidator.php index 0be48321b..03af5128e 100644 --- a/subscription/Validator/SubscriptionProductValidator.php +++ b/subscription/Validator/SubscriptionProductValidator.php @@ -18,8 +18,7 @@ use Mollie\Adapter\ConfigurationAdapter; use Mollie\Adapter\ProductAttributeAdapter; use Mollie\Subscription\Config\Config; -use Mollie\Subscription\Repository\CombinationRepository; -use Mollie\Subscription\Repository\ProductCombinationRepository; +use Mollie\Subscription\Repository\CombinationRepositoryInterface; if (!defined('_PS_VERSION_')) { exit; @@ -29,24 +28,18 @@ class SubscriptionProductValidator { /** @var ConfigurationAdapter */ private $configuration; - - /** @var ProductCombinationRepository */ + /** @var CombinationRepositoryInterface */ private $combinationRepository; - - /** @var CombinationRepository */ - private $combination; /** @var ProductAttributeAdapter */ private $productAttributeAdapter; public function __construct( ConfigurationAdapter $configuration, - ProductCombinationRepository $combinationRepository, - CombinationRepository $combination, + CombinationRepositoryInterface $combinationRepository, ProductAttributeAdapter $productAttributeAdapter ) { $this->configuration = $configuration; $this->combinationRepository = $combinationRepository; - $this->combination = $combination; $this->productAttributeAdapter = $productAttributeAdapter; } @@ -55,11 +48,17 @@ public function __construct( */ public function validate(int $productAttributeId): bool { - $combination = $this->combination->getById($productAttributeId); - $attributeIds = $this->combinationRepository->getIds((int) $combination->id); + /** @var \Combination|null $combination */ + $combination = $this->combinationRepository->findOneBy([ + 'id_product_attribute' => $productAttributeId, + ]); + + if (!$combination) { + return false; + } - foreach ($attributeIds as $attributeId) { - if ($this->isSubscriptionAttribute((int) $attributeId['id_attribute'])) { + foreach ($combination->getWsProductOptionValues() as $attribute) { + if ($this->isSubscriptionAttribute((int) $attribute['id'])) { return true; } } diff --git a/subscription/Validator/SubscriptionSettingsValidator.php b/subscription/Validator/SubscriptionSettingsValidator.php new file mode 100644 index 000000000..e3fbe3235 --- /dev/null +++ b/subscription/Validator/SubscriptionSettingsValidator.php @@ -0,0 +1,73 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Subscription\Validator; + +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Config\Config; +use Mollie\Repository\CarrierRepositoryInterface; +use Mollie\Subscription\Exception\CouldNotValidateSubscriptionSettings; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class SubscriptionSettingsValidator +{ + /** @var ConfigurationAdapter */ + private $configuration; + /** @var CarrierRepositoryInterface */ + private $carrierRepository; + + public function __construct( + ConfigurationAdapter $configuration, + CarrierRepositoryInterface $carrierRepository + ) { + $this->configuration = $configuration; + $this->carrierRepository = $carrierRepository; + } + + /** + * @throws CouldNotValidateSubscriptionSettings + */ + public function validate(): bool + { + if (!$this->isSubscriptionActive()) { + throw CouldNotValidateSubscriptionSettings::subscriptionServiceDisabled(); + } + + if (!$this->isSubscriptionCarrierValid()) { + throw CouldNotValidateSubscriptionSettings::subscriptionCarrierInvalid(); + } + + return true; + } + + private function isSubscriptionActive(): bool + { + return (bool) $this->configuration->get(Config::MOLLIE_SUBSCRIPTION_ENABLED); + } + + private function isSubscriptionCarrierValid(): bool + { + $carrierId = (int) $this->configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); + + /** @var \Carrier|null $carrier */ + $carrier = $this->carrierRepository->findOneBy([ + 'id_carrier' => $carrierId, + 'active' => 1, + 'deleted' => 0, + ]); + + return (bool) $carrier; + } +} diff --git a/tests/Integration/BaseTestCase.php b/tests/Integration/BaseTestCase.php index d5afe0150..c8e4c8f3a 100644 --- a/tests/Integration/BaseTestCase.php +++ b/tests/Integration/BaseTestCase.php @@ -36,6 +36,10 @@ protected function setUp() self::clearCache(); + foreach ($this->truncatableTables() as $table) { + $this->truncateTable($table); + } + // Some tests might have cleared the configuration \Configuration::loadConfiguration(); @@ -171,4 +175,59 @@ private static function clearCache() \Tab::resetStaticCache(); } } + + private function truncatableTables(): array + { + return [ + \Product::$definition['table'], + + \Product::$definition['table'] . '_attribute', + \Product::$definition['table'] . '_attribute_combination', + \Product::$definition['table'] . '_attribute_shop', + \Product::$definition['table'] . '_lang', + \Product::$definition['table'] . '_shop', + + \Address::$definition['table'], + + \Customer::$definition['table'], + \Customer::$definition['table'] . '_group', + + \RangePrice::$definition['table'], + + 'module_carrier', + + \Carrier::$definition['table'], + \Carrier::$definition['table'] . '_group', + \Carrier::$definition['table'] . '_lang', + \Carrier::$definition['table'] . '_shop', + \Carrier::$definition['table'] . '_zone', + \Carrier::$definition['table'] . '_tax_rules_group_shop', + + \CartRule::$definition['table'], + \CartRule::$definition['table'] . '_lang', + \CartRule::$definition['table'] . '_shop', + + \SpecificPrice::$definition['table'], + \SpecificPrice::$definition['table'] . '_rule', + \SpecificPrice::$definition['table'] . '_priority', + + \MolRecurringOrdersProduct::$definition['table'], + \MolRecurringOrder::$definition['table'], + + \Tax::$definition['table'], + \Tax::$definition['table'] . '_lang', + \TaxRule::$definition['table'], + + \TaxRulesGroup::$definition['table'], + \TaxRulesGroup::$definition['table'] . '_shop', + + \MolOrderPaymentFee::$definition['table'], + ]; + } + + public function truncateTable(string $table): void + { + \Db::getInstance()->disableCache(); + \Db::getInstance()->execute('TRUNCATE TABLE `' . _DB_PREFIX_ . $table . '`'); + } } diff --git a/tests/Integration/Factory/CartFactory.php b/tests/Integration/Factory/CartFactory.php index c2eafb84d..031e5f1f7 100644 --- a/tests/Integration/Factory/CartFactory.php +++ b/tests/Integration/Factory/CartFactory.php @@ -12,23 +12,45 @@ namespace Mollie\Tests\Integration\Factory; -class CartFactory implements FactoryInterface -{ - public static function create(array $data = []): \Cart - { - $cart = new \Cart(); +use Invertus\Prestashop\Models\Factory\Factory; - $cart->id_lang = $data['id_lang'] ?? \Configuration::get('PS_LANG_DEFAULT'); - $cart->id_currency = $data['id_currency'] ?? \Configuration::get('PS_CURRENCY_DEFAULT'); - $cart->id_carrier = $data['id_carrier'] ?? CarrierFactory::create()->id; - $cart->id_address_delivery = $data['id_address_delivery'] ?? AddressFactory::create()->id; - $cart->id_address_invoice = $data['id_address_invoice'] ?? AddressFactory::create()->id; - $cart->id_customer = $data['id_customer'] ?? CustomerFactory::create()->id; - - $cart->save(); +class CartFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = \Cart::class; - \Context::getContext()->cart = $cart; + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'id_currency' => \Configuration::get('PS_CURRENCY_DEFAULT'), + 'id_carrier' => function () { + return CarrierFactory::create()->id; + }, + 'id_address_delivery' => function () { + return AddressFactory::create()->id; + }, + 'id_address_invoice' => function ($attributes) { + return $attributes['id_address_delivery']; + }, + 'id_customer' => function () { + return CustomerFactory::create()->id; + }, + ]; + } - return $cart; + public function configure(): self + { + return $this->afterCreating(function (\Cart $cart) { + \Context::getContext()->cart = $cart; + }); } } diff --git a/tests/Integration/Factory/CurrencyFactory.php b/tests/Integration/Factory/CurrencyFactory.php new file mode 100644 index 000000000..57ef30a82 --- /dev/null +++ b/tests/Integration/Factory/CurrencyFactory.php @@ -0,0 +1,41 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Factory; + +use Invertus\Prestashop\Models\Factory\Factory; + +class CurrencyFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = \Currency::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'iso_code' => $this->faker->currencyCode, + 'precision' => $this->faker->numberBetween(2, 6), + 'conversion_rate' => $this->faker->randomFloat(2, 0.5, 1.5), + 'active' => true, + 'deleted' => false, + ]; + } +} diff --git a/tests/Integration/Factory/CustomerFactory.php b/tests/Integration/Factory/CustomerFactory.php index 18c2ff9bb..fa9e1d2a9 100644 --- a/tests/Integration/Factory/CustomerFactory.php +++ b/tests/Integration/Factory/CustomerFactory.php @@ -24,6 +24,8 @@ public static function create(array $data = []): \Customer $customer->passwd = $data['passwd'] ?? 'test-passwd'; $customer->is_guest = $data['is_guest'] ?? false; $customer->siret = $data['siret'] ?? 'test-siret'; + $customer->id_lang = $data['id_lang'] ?? \Context::getContext()->language->id; + $customer->id_shop = $data['id_shop'] ?? \Context::getContext()->shop->id; $customer->save(); diff --git a/tests/Integration/Factory/MolRecurringOrderFactory.php b/tests/Integration/Factory/MolRecurringOrderFactory.php new file mode 100644 index 000000000..7c549bf2c --- /dev/null +++ b/tests/Integration/Factory/MolRecurringOrderFactory.php @@ -0,0 +1,54 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Factory; + +use Invertus\Prestashop\Models\Factory\Factory; + +class MolRecurringOrderFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = \MolRecurringOrder::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'id_mol_recurring_orders_product' => $this->faker->numberBetween(1, 99999), // TODO recurring order product factory + 'id_order' => $this->faker->numberBetween(1, 99999), // TODO order factory + 'id_currency' => $this->faker->numberBetween(1, 99999), // TODO currency factory + 'id_customer' => function () { + return CustomerFactory::create()->id; + }, + 'id_address_delivery' => $this->faker->numberBetween(1, 99999), // TODO address factory + 'id_address_invoice' => $this->faker->numberBetween(1, 99999), // TODO address factory + 'description' => $this->faker->text(20), + 'status' => $this->faker->text(20), + 'total_tax_incl' => $this->faker->numberBetween(10, 100), + 'payment_method' => $this->faker->text(20), + 'next_payment' => $this->faker->date('Y-m-d H:i:s', '+3 weeks'), + 'reminder_at' => $this->faker->date('Y-m-d H:i:s', '+3 weeks'), + 'cancelled_at' => $this->faker->date('Y-m-d H:i:s', '+10 weeks'), + 'mollie_subscription_id' => $this->faker->text(20), + 'mollie_customer_id' => $this->faker->text(20), + 'date_update' => $this->faker->date('Y-m-d H:i:s'), + ]; + } +} diff --git a/tests/Integration/Factory/MolRecurringOrdersProductFactory.php b/tests/Integration/Factory/MolRecurringOrdersProductFactory.php new file mode 100644 index 000000000..824374cfd --- /dev/null +++ b/tests/Integration/Factory/MolRecurringOrdersProductFactory.php @@ -0,0 +1,43 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Factory; + +use Invertus\Prestashop\Models\Factory\Factory; + +class MolRecurringOrdersProductFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = \MolRecurringOrdersProduct::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'id_product' => function () { + return ProductFactory::initialize()->create()->id; + }, + 'id_product_attribute' => 0, // TODO product factory with combinations + 'quantity' => $this->faker->numberBetween(1, 9), + 'unit_price' => $this->faker->randomFloat(2, 10, 99), + 'date_update' => $this->faker->date('Y-m-d H:i:s'), + ]; + } +} diff --git a/tests/Integration/Factory/ProductFactory.php b/tests/Integration/Factory/ProductFactory.php index 66c1e55c6..3f6912822 100644 --- a/tests/Integration/Factory/ProductFactory.php +++ b/tests/Integration/Factory/ProductFactory.php @@ -12,25 +12,46 @@ namespace Mollie\Tests\Integration\Factory; -class ProductFactory implements FactoryInterface +use Invertus\Prestashop\Models\Factory\Factory; + +class ProductFactory extends Factory { - public static function create(array $data = []) + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = \Product::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array { - $product = new \Product(null, false, (int) \Configuration::get('PS_LANG_DEFAULT')); - $product->id_tax_rules_group = $data['id_tax_rules_group'] ?? 1; - $product->name = $data['name'] ?? 'test-name'; - $product->description_short = $data['description_short'] ?? 'test-description_short'; - $product->price = $data['price'] ?? 0; - $product->link_rewrite = \Tools::link_rewrite($product->name); + $name = $this->faker->text(10); - $product->save(); + return [ + 'id_tax_rules_group' => $this->faker->numberBetween(1, 99), // TODO tax rules group factory + 'name' => $name, + 'description_short' => $this->faker->text(50), + 'price' => $this->faker->randomFloat(6, 1000, 10000), + 'link_rewrite' => \Tools::link_rewrite($name), + 'out_of_stock' => 1, + ]; + } - \StockAvailable::setQuantity( - (int) $product->id, - 0, - isset($data['quantity']) ? (int) $data['quantity'] : 1 - ); + public function configure(): self + { + return $this->afterCreating(function (\Product $product) { + \Product::flushPriceCache(); - return $product; + \StockAvailable::setQuantity( + (int) $product->id, + 0, + 10 + ); + }); } } diff --git a/tests/Integration/Service/PaymentMethod/PaymentMethodRestrictionValidation/B2bPaymentMethodRestrictionValidatorTest.php b/tests/Integration/Service/PaymentMethod/PaymentMethodRestrictionValidation/B2bPaymentMethodRestrictionValidatorTest.php index d5ae2401c..90000cf62 100644 --- a/tests/Integration/Service/PaymentMethod/PaymentMethodRestrictionValidation/B2bPaymentMethodRestrictionValidatorTest.php +++ b/tests/Integration/Service/PaymentMethod/PaymentMethodRestrictionValidation/B2bPaymentMethodRestrictionValidatorTest.php @@ -54,9 +54,10 @@ public function testItSuccessfullyValidatedIsValid(): void 'vat_number' => 'vat-number', ]); - $this->contextBuilder->setCart(CartFactory::create()); - $this->contextBuilder->getContext()->cart->id_address_invoice = $billingAddress->id; - $this->contextBuilder->getContext()->cart->id_customer = $customer->id; + CartFactory::initialize()->create([ + 'id_customer' => $customer->id, + 'id_address_invoice' => $billingAddress->id, + ]); /** @var B2bPaymentMethodRestrictionValidator $b2bPaymentMethodRestrictionValidator */ $b2bPaymentMethodRestrictionValidator = $this->getService(B2bPaymentMethodRestrictionValidator::class); @@ -91,9 +92,10 @@ public function testItSuccessfullyValidatedIsValidMissingVatNumberInFormat(): vo $addressFormat->format = 'test-format'; $addressFormat->save(); - $this->contextBuilder->setCart(CartFactory::create()); - $this->contextBuilder->getContext()->cart->id_address_invoice = $billingAddress->id; - $this->contextBuilder->getContext()->cart->id_customer = $customer->id; + CartFactory::initialize()->create([ + 'id_customer' => $customer->id, + 'id_address_invoice' => $billingAddress->id, + ]); /** @var B2bPaymentMethodRestrictionValidator $b2bPaymentMethodRestrictionValidator */ $b2bPaymentMethodRestrictionValidator = $this->getService(B2bPaymentMethodRestrictionValidator::class); @@ -124,9 +126,10 @@ public function testItUnsuccessfullyValidatedIsValidMethodNotSupported(): void 'vat_number' => 'vat-number', ]); - $this->contextBuilder->setCart(CartFactory::create()); - $this->contextBuilder->getContext()->cart->id_address_invoice = $billingAddress->id; - $this->contextBuilder->getContext()->cart->id_customer = $customer->id; + CartFactory::initialize()->create([ + 'id_customer' => $customer->id, + 'id_address_invoice' => $billingAddress->id, + ]); /** @var B2bPaymentMethodRestrictionValidator $b2bPaymentMethodRestrictionValidator */ $b2bPaymentMethodRestrictionValidator = $this->getService(B2bPaymentMethodRestrictionValidator::class); @@ -154,11 +157,11 @@ public function testItUnsuccessfullyValidatedIsValidMissingSiretNumber(): void 'vat_number' => 'vat-number', ]); - $this->contextBuilder->setCart(CartFactory::create([ + CartFactory::initialize()->create([ 'id_customer' => $customer->id, 'id_address_delivery' => $billingAddress->id, 'id_address_invoice' => $billingAddress->id, - ])); + ]); /** @var B2bPaymentMethodRestrictionValidator $b2bPaymentMethodRestrictionValidator */ $b2bPaymentMethodRestrictionValidator = $this->getService(B2bPaymentMethodRestrictionValidator::class); @@ -186,11 +189,11 @@ public function testItUnsuccessfullyValidatedIsValidB2bNotEnabled(): void 'vat_number' => 'vat-number', ]); - $this->contextBuilder->setCart(CartFactory::create([ + CartFactory::initialize()->create([ 'id_customer' => $customer->id, 'id_address_delivery' => $billingAddress->id, 'id_address_invoice' => $billingAddress->id, - ])); + ]); /** @var B2bPaymentMethodRestrictionValidator $b2bPaymentMethodRestrictionValidator */ $b2bPaymentMethodRestrictionValidator = $this->getService(B2bPaymentMethodRestrictionValidator::class); @@ -218,11 +221,11 @@ public function testItUnsuccessfullyValidatedIsValidMissingVatNumberInBothAddres 'vat_number' => '', ]); - $this->contextBuilder->setCart(CartFactory::create([ + CartFactory::initialize()->create([ 'id_customer' => $customer->id, 'id_address_delivery' => $billingAddress->id, 'id_address_invoice' => $billingAddress->id, - ])); + ]); /** @var B2bPaymentMethodRestrictionValidator $b2bPaymentMethodRestrictionValidator */ $b2bPaymentMethodRestrictionValidator = $this->getService(B2bPaymentMethodRestrictionValidator::class); @@ -254,11 +257,11 @@ public function testItUnsuccessfullyValidatedIsValidMissingVatNumberInBillingAdd 'vat_number' => 'test-vat-number', ]); - $this->contextBuilder->setCart(CartFactory::create([ + CartFactory::initialize()->create([ 'id_customer' => $customer->id, - 'id_address_delivery' => $shippingAddress->id, + 'id_address_delivery' => $billingAddress->id, 'id_address_invoice' => $billingAddress->id, - ])); + ]); /** @var B2bPaymentMethodRestrictionValidator $b2bPaymentMethodRestrictionValidator */ $b2bPaymentMethodRestrictionValidator = $this->getService(B2bPaymentMethodRestrictionValidator::class); diff --git a/tests/Integration/Subscription/Action/CreateRecurringOrderActionTest.php b/tests/Integration/Subscription/Action/CreateRecurringOrderActionTest.php new file mode 100644 index 000000000..42be24c9f --- /dev/null +++ b/tests/Integration/Subscription/Action/CreateRecurringOrderActionTest.php @@ -0,0 +1,83 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Subscription\Action; + +use Mollie\Subscription\Action\CreateRecurringOrderAction; +use Mollie\Subscription\DTO\CreateRecurringOrderData; +use Mollie\Tests\Integration\BaseTestCase; + +class CreateRecurringOrderActionTest extends BaseTestCase +{ + public function testItSuccessfullyCreateDatabaseEntry(): void + { + $this->assertDatabaseHasNot(\MolRecurringOrder::class, [ + 'id_mol_recurring_orders_product' => 1, + 'id_order' => 1, + 'id_cart' => 1, + 'id_currency' => 1, + 'id_customer' => 1, + 'id_address_delivery' => 1, + 'id_address_invoice' => 1, + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'mollie_customer_id' => 'test-mollie-customer-id', + 'description' => 'test-description', + 'status' => 'test-status', + 'total_tax_incl' => 19.99, + 'payment_method' => 'test-payment-method', + 'next_payment' => '2023-09-09 12:00:00', + 'reminder_at' => '2023-09-10 12:00:00', + 'cancelled_at' => '2023-09-11 12:00:00', + ]); + + /** @var CreateRecurringOrderAction $createRecurringOrderAction */ + $createRecurringOrderAction = $this->getService(CreateRecurringOrderAction::class); + + $result = $createRecurringOrderAction->run(CreateRecurringOrderData::create( + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 'test-description', + 'test-status', + 19.99, + 'test-payment-method', + '2023-09-09 12:00:00', + '2023-09-10 12:00:00', + '2023-09-11 12:00:00', + 'test-mollie-subscription-id', + 'test-mollie-customer-id' + )); + + $this->assertDatabaseHas(\MolRecurringOrder::class, [ + 'id_mol_recurring_orders_product' => 1, + 'id_order' => 1, + 'id_cart' => 1, + 'id_currency' => 1, + 'id_customer' => 1, + 'id_address_delivery' => 1, + 'id_address_invoice' => 1, + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'mollie_customer_id' => 'test-mollie-customer-id', + 'description' => 'test-description', + 'status' => 'test-status', + 'total_tax_incl' => 19.99, + 'payment_method' => 'test-payment-method', + 'next_payment' => '2023-09-09 12:00:00', + 'reminder_at' => '2023-09-10 12:00:00', + 'cancelled_at' => '2023-09-11 12:00:00', + ]); + } +} diff --git a/tests/Integration/Subscription/Action/CreateRecurringOrdersProductActionTest.php b/tests/Integration/Subscription/Action/CreateRecurringOrdersProductActionTest.php new file mode 100644 index 000000000..be04d646f --- /dev/null +++ b/tests/Integration/Subscription/Action/CreateRecurringOrdersProductActionTest.php @@ -0,0 +1,47 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Subscription\Action; + +use Mollie\Subscription\Action\CreateRecurringOrdersProductAction; +use Mollie\Subscription\DTO\CreateRecurringOrdersProductData; +use Mollie\Tests\Integration\BaseTestCase; + +class CreateRecurringOrdersProductActionTest extends BaseTestCase +{ + public function testItSuccessfullyCreateDatabaseEntry(): void + { + $this->assertDatabaseHasNot(\MolRecurringOrdersProduct::class, [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'quantity' => 1, + 'unit_price' => 19.99, + ]); + + /** @var CreateRecurringOrdersProductAction $createRecurringOrdersProductAction */ + $createRecurringOrdersProductAction = $this->getService(CreateRecurringOrdersProductAction::class); + + $createRecurringOrdersProductAction->run(CreateRecurringOrdersProductData::create( + 1, + 1, + 1, + 19.99 + )); + + $this->assertDatabaseHas(\MolRecurringOrdersProduct::class, [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'quantity' => 1, + 'unit_price' => 19.99, + ]); + } +} diff --git a/tests/Integration/Subscription/Action/CreateSpecificPriceActionTest.php b/tests/Integration/Subscription/Action/CreateSpecificPriceActionTest.php index 30d608b15..d44a51bf1 100644 --- a/tests/Integration/Subscription/Action/CreateSpecificPriceActionTest.php +++ b/tests/Integration/Subscription/Action/CreateSpecificPriceActionTest.php @@ -22,7 +22,7 @@ class CreateSpecificPriceActionTest extends BaseTestCase public function testItSuccessfullyCreateSpecificPrice(): void { /** @var \Product $product */ - $product = ProductFactory::create(); + $product = ProductFactory::initialize()->create(); $this->assertDatabaseHasNot(\SpecificPrice::class, [ 'id_product' => (int) $product->id, @@ -44,7 +44,7 @@ public function testItSuccessfullyCreateSpecificPrice(): void /** @var CreateSpecificPriceAction $createSpecificPrice */ $createSpecificPrice = $this->getService(CreateSpecificPriceAction::class); - $result = $createSpecificPrice->run(CreateSpecificPriceData::create( + $createSpecificPrice->run(CreateSpecificPriceData::create( (int) $product->id, (int) \Product::getDefaultAttribute($product->id), 10.00, @@ -70,8 +70,5 @@ public function testItSuccessfullyCreateSpecificPrice(): void 'from' => '0000-00-00 00:00:00', 'to' => '0000-00-00 00:00:00', ]); - - $product->delete(); - $result->delete(); } } diff --git a/tests/Integration/Subscription/Action/UpdateRecurringOrderActionTest.php b/tests/Integration/Subscription/Action/UpdateRecurringOrderActionTest.php new file mode 100644 index 000000000..56e92a440 --- /dev/null +++ b/tests/Integration/Subscription/Action/UpdateRecurringOrderActionTest.php @@ -0,0 +1,62 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Subscription\Action; + +use Mollie\Exception\Code\ExceptionCode; +use Mollie\Shared\Infrastructure\Exception\MollieDatabaseException; +use Mollie\Subscription\Action\UpdateRecurringOrderAction; +use Mollie\Subscription\DTO\UpdateRecurringOrderData; +use Mollie\Tests\Integration\BaseTestCase; +use Mollie\Tests\Integration\Factory\MolRecurringOrderFactory; + +class UpdateRecurringOrderActionTest extends BaseTestCase +{ + public function testItSuccessfullyUpdatesRecord(): void + { + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = MolRecurringOrderFactory::initialize()->create(); + + $this->assertDatabaseHas(\MolRecurringOrder::class, [ + 'id_mol_recurring_order' => $recurringOrder->id, + 'total_tax_incl' => $recurringOrder->total_tax_incl, + ]); + + /** @var UpdateRecurringOrderAction $updateRecurringOrderAction */ + $updateRecurringOrderAction = $this->getService(UpdateRecurringOrderAction::class); + + $updateRecurringOrderAction->run(UpdateRecurringOrderData::create( + (int) $recurringOrder->id, + 99.99 + )); + + $this->assertDatabaseHas(\MolRecurringOrder::class, [ + 'id_mol_recurring_order' => $recurringOrder->id, + 'total_tax_incl' => 99.99, + ]); + } + + public function testItUnsuccessfullyUpdatesRecordFailedToFindRecurringOrder(): void + { + /** @var UpdateRecurringOrderAction $updateRecurringOrderAction */ + $updateRecurringOrderAction = $this->getService(UpdateRecurringOrderAction::class); + + $this->expectException(MollieDatabaseException::class); + $this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD); + $this->expectExceptionMessageRegExp('/' . \MolRecurringOrder::class . '/'); + + $updateRecurringOrderAction->run(UpdateRecurringOrderData::create( + 0, + 99.99 + )); + } +} diff --git a/tests/Integration/Subscription/Handler/CloneOriginalSubscriptionCartHandlerTest.php b/tests/Integration/Subscription/Handler/CloneOriginalSubscriptionCartHandlerTest.php new file mode 100644 index 000000000..0a5830ea6 --- /dev/null +++ b/tests/Integration/Subscription/Handler/CloneOriginalSubscriptionCartHandlerTest.php @@ -0,0 +1,108 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Subscription\Handler; + +use Mollie\Subscription\DTO\CloneOriginalSubscriptionCartData; +use Mollie\Subscription\Exception\CouldNotHandleOriginalSubscriptionCartCloning; +use Mollie\Subscription\Exception\ExceptionCode; +use Mollie\Subscription\Handler\CloneOriginalSubscriptionCartHandler; +use Mollie\Tests\Integration\BaseTestCase; +use Mollie\Tests\Integration\Factory\AddressFactory; +use Mollie\Tests\Integration\Factory\CartFactory; +use Mollie\Tests\Integration\Factory\MolRecurringOrdersProductFactory; +use Mollie\Tests\Integration\Factory\ProductFactory; + +class CloneOriginalSubscriptionCartHandlerTest extends BaseTestCase +{ + public function testItSuccessfullyHandlesTask(): void + { + /** @var \Cart $originalCart */ + $originalCart = CartFactory::initialize()->create(); + + $invoiceAddress = AddressFactory::create(); + $deliveryAddress = AddressFactory::create(); + + $simpleProduct = ProductFactory::initialize()->create(); + $subscriptionProduct = ProductFactory::initialize()->create(); + + $originalCart->updateQty( + 2, + $simpleProduct->id, + $simpleProduct->getDefaultIdProductAttribute() + ); + + $originalCart->updateQty( + 3, + $subscriptionProduct->id, + $subscriptionProduct->getDefaultIdProductAttribute() + ); + + $recurringOrderProduct = MolRecurringOrdersProductFactory::initialize()->create([ + 'id_product' => (int) $subscriptionProduct->id, + 'id_product_attribute' => $subscriptionProduct->getDefaultIdProductAttribute(), + 'unit_price' => 99.99, + ]); + + /** @var CloneOriginalSubscriptionCartHandler $cloneOriginalSubscriptionCartHandler */ + $cloneOriginalSubscriptionCartHandler = $this->getService(CloneOriginalSubscriptionCartHandler::class); + + $result = $cloneOriginalSubscriptionCartHandler->run(CloneOriginalSubscriptionCartData::create( + (int) $originalCart->id, + (int) $recurringOrderProduct->id, + (int) $invoiceAddress->id, + (int) $deliveryAddress->id + )); + + $newCartProducts = $result->getProducts(true); + + $this->assertCount(1, $newCartProducts); + + $this->assertEquals((int) $subscriptionProduct->id, (int) $newCartProducts[0]['id_product']); + $this->assertEquals($subscriptionProduct->getDefaultIdProductAttribute(), $simpleProduct->getDefaultIdProductAttribute()); + $this->assertEquals(3, (int) $newCartProducts[0]['cart_quantity']); + $this->assertEquals(99.99, (float) $newCartProducts[0]['price_with_reduction_without_tax']); + } + + public function testItUnsuccessfullyHandlesTaskNoSubscriptionProductsAvailable(): void + { + /** @var \Cart $originalCart */ + $originalCart = CartFactory::initialize()->create(); + + $simpleProduct = ProductFactory::initialize()->create(); + + $originalCart->updateQty( + 2, + $simpleProduct->id, + $simpleProduct->getDefaultIdProductAttribute() + ); + + $recurringOrderProduct = MolRecurringOrdersProductFactory::initialize()->create([ + 'id_product' => 0, + 'id_product_attribute' => 0, + 'unit_price' => 99.99, + ]); + + /** @var CloneOriginalSubscriptionCartHandler $cloneOriginalSubscriptionCartHandler */ + $cloneOriginalSubscriptionCartHandler = $this->getService(CloneOriginalSubscriptionCartHandler::class); + + $this->expectException(CouldNotHandleOriginalSubscriptionCartCloning::class); + $this->expectExceptionCode(ExceptionCode::RECURRING_ORDER_SUBSCRIPTION_CART_SHOULD_HAVE_ONE_PRODUCT); + + $cloneOriginalSubscriptionCartHandler->run(CloneOriginalSubscriptionCartData::create( + (int) $originalCart->id, + (int) $recurringOrderProduct->id, + 0, + 0 + )); + } +} diff --git a/tests/Integration/Subscription/Handler/index.php b/tests/Integration/Subscription/Handler/index.php new file mode 100644 index 000000000..d3343995f --- /dev/null +++ b/tests/Integration/Subscription/Handler/index.php @@ -0,0 +1,20 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/tests/Integration/Subscription/Provider/GeneralSubscriptionMailDataProviderTest.php b/tests/Integration/Subscription/Provider/GeneralSubscriptionMailDataProviderTest.php new file mode 100644 index 000000000..c18da9d9f --- /dev/null +++ b/tests/Integration/Subscription/Provider/GeneralSubscriptionMailDataProviderTest.php @@ -0,0 +1,189 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Integration\Subscription\Provider; + +use Mollie\Exception\Code\ExceptionCode; +use Mollie\Shared\Infrastructure\Exception\MollieDatabaseException; +use Mollie\Subscription\Provider\GeneralSubscriptionMailDataProvider; +use Mollie\Tests\Integration\BaseTestCase; +use Mollie\Tests\Integration\Factory\CurrencyFactory; +use Mollie\Tests\Integration\Factory\CustomerFactory; +use Mollie\Tests\Integration\Factory\MolRecurringOrderFactory; +use Mollie\Tests\Integration\Factory\MolRecurringOrdersProductFactory; +use Mollie\Tests\Integration\Factory\ProductFactory; +use Mollie\Utility\NumberUtility; + +class GeneralSubscriptionMailDataProviderTest extends BaseTestCase +{ + public function testItSuccessfullyProvidesData(): void + { + $customer = CustomerFactory::create(); + + /** @var \Currency $currency */ + $currency = CurrencyFactory::initialize()->create(); + + /** @var \Product $product */ + $product = ProductFactory::initialize()->create(); + + /** @var \MolRecurringOrdersProduct $recurringOrderProduct */ + $recurringOrderProduct = MolRecurringOrdersProductFactory::initialize()->create([ + 'id_product' => $product->id, + ]); + + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = MolRecurringOrderFactory::initialize()->create([ + 'id_customer' => $customer->id, + 'id_mol_recurring_orders_product' => $recurringOrderProduct->id, + 'id_currency' => $currency->id, + ]); + + /** @var GeneralSubscriptionMailDataProvider $generalSubscriptionMailDataProvider */ + $generalSubscriptionMailDataProvider = $this->getService(GeneralSubscriptionMailDataProvider::class); + + $result = $generalSubscriptionMailDataProvider->run((int) $recurringOrder->id); + + $expectedProductUnitPriceTaxExcl = (string) NumberUtility::toPrecision( + (float) $recurringOrderProduct->unit_price, + NumberUtility::DECIMAL_PRECISION + ); + + $expectedTotalPriceTaxIncl = (string) NumberUtility::toPrecision( + (float) $recurringOrder->total_tax_incl, + NumberUtility::DECIMAL_PRECISION + ); + + $this->assertEquals((string) $recurringOrder->mollie_subscription_id, $result->getMollieSubscriptionId()); + $this->assertEquals((string) $product->name, $result->getProductName()); + $this->assertEquals($expectedProductUnitPriceTaxExcl, $result->getProductUnitPriceTaxExcl()); + $this->assertEquals((int) $recurringOrderProduct->quantity, $result->getProductQuantity()); + $this->assertEquals($expectedTotalPriceTaxIncl, $result->getTotalOrderPriceTaxIncl()); + $this->assertEquals((string) $customer->firstname, $result->getFirstName()); + $this->assertEquals((string) $customer->lastname, $result->getLastName()); + $this->assertEquals((int) $customer->id_lang, $result->getLangId()); + $this->assertEquals((int) $customer->id_shop, $result->getShopId()); + + $this->assertEquals([ + 'subscription_reference' => (string) $recurringOrder->mollie_subscription_id, + 'product_name' => (string) $product->name, + 'unit_price' => $expectedProductUnitPriceTaxExcl, + 'quantity' => (int) $recurringOrderProduct->quantity, + 'total_price' => $expectedTotalPriceTaxIncl, + 'firstName' => (string) $customer->firstname, + 'lastName' => (string) $customer->lastname, + ], $result->toArray()); + } + + public function testItUnsuccessfullyProvidesDataFailedToFindRecurringOrder(): void + { + /** @var GeneralSubscriptionMailDataProvider $generalSubscriptionMailDataProvider */ + $generalSubscriptionMailDataProvider = $this->getService(GeneralSubscriptionMailDataProvider::class); + + $this->expectException(MollieDatabaseException::class); + $this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD); + $this->expectExceptionMessageRegExp('/' . \MolRecurringOrder::class . '/'); + + $generalSubscriptionMailDataProvider->run(0); + } + + public function testItUnsuccessfullyProvidesDataFailedToFindRecurringOrderProduct(): void + { + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = MolRecurringOrderFactory::initialize()->create([ + 'id_mol_recurring_orders_product' => 0, + ]); + + /** @var GeneralSubscriptionMailDataProvider $generalSubscriptionMailDataProvider */ + $generalSubscriptionMailDataProvider = $this->getService(GeneralSubscriptionMailDataProvider::class); + + $this->expectException(MollieDatabaseException::class); + $this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD); + $this->expectExceptionMessageRegExp('/' . \MolRecurringOrdersProduct::class . '/'); + + $generalSubscriptionMailDataProvider->run((int) $recurringOrder->id); + } + + public function testItUnsuccessfullyProvidesDataFailedToFindCustomer(): void + { + /** @var \MolRecurringOrdersProduct $recurringOrderProduct */ + $recurringOrderProduct = MolRecurringOrdersProductFactory::initialize()->create(); + + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = MolRecurringOrderFactory::initialize()->create([ + 'id_mol_recurring_orders_product' => $recurringOrderProduct->id, + 'id_customer' => 0, + ]); + + /** @var GeneralSubscriptionMailDataProvider $generalSubscriptionMailDataProvider */ + $generalSubscriptionMailDataProvider = $this->getService(GeneralSubscriptionMailDataProvider::class); + + $this->expectException(MollieDatabaseException::class); + $this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD); + $this->expectExceptionMessageRegExp('/' . \Customer::class . '/'); + + $generalSubscriptionMailDataProvider->run((int) $recurringOrder->id); + } + + public function testItUnsuccessfullyProvidesDataFailedToFindProduct(): void + { + /** @var \MolRecurringOrdersProduct $recurringOrderProduct */ + $recurringOrderProduct = MolRecurringOrdersProductFactory::initialize()->create([ + 'id_product' => 0, + ]); + + $customer = CustomerFactory::create(); + + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = MolRecurringOrderFactory::initialize()->create([ + 'id_mol_recurring_orders_product' => $recurringOrderProduct->id, + 'id_customer' => $customer->id, + ]); + + /** @var GeneralSubscriptionMailDataProvider $generalSubscriptionMailDataProvider */ + $generalSubscriptionMailDataProvider = $this->getService(GeneralSubscriptionMailDataProvider::class); + + $this->expectException(MollieDatabaseException::class); + $this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD); + $this->expectExceptionMessageRegExp('/' . \Product::class . '/'); + + $generalSubscriptionMailDataProvider->run((int) $recurringOrder->id); + } + + public function testItUnsuccessfullyProvidesDataFailedToFindCurrency(): void + { + /** @var \Product $product */ + $product = ProductFactory::initialize()->create(); + + /** @var \MolRecurringOrdersProduct $recurringOrderProduct */ + $recurringOrderProduct = MolRecurringOrdersProductFactory::initialize()->create([ + 'id_product' => $product->id, + ]); + + $customer = CustomerFactory::create(); + + /** @var \MolRecurringOrder $recurringOrder */ + $recurringOrder = MolRecurringOrderFactory::initialize()->create([ + 'id_mol_recurring_orders_product' => $recurringOrderProduct->id, + 'id_customer' => $customer->id, + 'id_currency' => 0, + ]); + + /** @var GeneralSubscriptionMailDataProvider $generalSubscriptionMailDataProvider */ + $generalSubscriptionMailDataProvider = $this->getService(GeneralSubscriptionMailDataProvider::class); + + $this->expectException(MollieDatabaseException::class); + $this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD); + $this->expectExceptionMessageRegExp('/' . \Currency::class . '/'); + + $generalSubscriptionMailDataProvider->run((int) $recurringOrder->id); + } +} diff --git a/tests/Integration/Subscription/Provider/SubscriptionCarrierDeliveryPriceProviderTest.php b/tests/Integration/Subscription/Provider/SubscriptionCarrierDeliveryPriceProviderTest.php index 0be142fb1..0268c782e 100644 --- a/tests/Integration/Subscription/Provider/SubscriptionCarrierDeliveryPriceProviderTest.php +++ b/tests/Integration/Subscription/Provider/SubscriptionCarrierDeliveryPriceProviderTest.php @@ -12,8 +12,7 @@ namespace Mollie\Tests\Integration\Subscription\Provider; -use Mollie\Adapter\ConfigurationAdapter; -use Mollie\Config\Config; +use Mollie\Subscription\DTO\SubscriptionCarrierDeliveryPriceData; use Mollie\Subscription\Exception\CouldNotProvideSubscriptionCarrierDeliveryPrice; use Mollie\Subscription\Exception\ExceptionCode; use Mollie\Subscription\Provider\SubscriptionCarrierDeliveryPriceProvider; @@ -25,48 +24,24 @@ class SubscriptionCarrierDeliveryPriceProviderTest extends BaseTestCase { - public function setUp(): void - { - /** @var ConfigurationAdapter $configuration */ - $configuration = $this->getService(ConfigurationAdapter::class); - - $this->subscriptionOrderCarrierId = $configuration->get(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID); - - parent::setUp(); - } - - public function tearDown(): void - { - /** @var ConfigurationAdapter $configuration */ - $configuration = $this->getService(ConfigurationAdapter::class); - - $configuration->updateValue(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, $this->subscriptionOrderCarrierId); - - parent::tearDown(); - } - public function testItSuccessfullyProvidesCarrierDeliveryPrice(): void { $address = AddressFactory::create(); $carrier = CarrierFactory::create([ 'price' => 999.00, ]); - $cart = CartFactory::create([ + + $cart = CartFactory::initialize()->create([ 'id_carrier' => $carrier->id, ]); - /** @var ConfigurationAdapter $configuration */ - $configuration = $this->getService(ConfigurationAdapter::class); - - $configuration->updateValue(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, $carrier->id); - - $targetProduct = ProductFactory::create([ + $targetProduct = ProductFactory::initialize()->create([ 'quantity' => 10, ]); - $product1 = ProductFactory::create([ + $product1 = ProductFactory::initialize()->create([ 'quantity' => 10, ]); - $product2 = ProductFactory::create([ + $product2 = ProductFactory::initialize()->create([ 'quantity' => 10, ]); @@ -81,22 +56,16 @@ public function testItSuccessfullyProvidesCarrierDeliveryPrice(): void $subscriptionCarrierDeliveryPriceProvider = $this->getService(SubscriptionCarrierDeliveryPriceProvider::class); $result = $subscriptionCarrierDeliveryPriceProvider->getPrice( - $address->id, - $cart->id, - $cart->id_customer, - $targetProductArray + SubscriptionCarrierDeliveryPriceData::create( + $address->id, + $cart->id, + $cart->id_customer, + $targetProductArray, + $carrier->id + ) ); $this->assertEquals(999.00, $result); - - $this->removeFactories([ - $carrier, - $address, - $cart, - $targetProduct, - $product1, - $product2, - ]); } public function testItUnsuccessfullyProvidesCarrierDeliveryPriceCarrierIsOutOfZone(): void @@ -108,22 +77,18 @@ public function testItUnsuccessfullyProvidesCarrierDeliveryPriceCarrierIsOutOfZo $address::getZoneById($address->id), ], ]); - $cart = CartFactory::create([ + + $cart = CartFactory::initialize()->create([ 'id_carrier' => $carrier->id, ]); - /** @var ConfigurationAdapter $configuration */ - $configuration = $this->getService(ConfigurationAdapter::class); - - $configuration->updateValue(Config::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, $carrier->id); - - $targetProduct = ProductFactory::create([ + $targetProduct = ProductFactory::initialize()->create([ 'quantity' => 10, ]); - $product1 = ProductFactory::create([ + $product1 = ProductFactory::initialize()->create([ 'quantity' => 10, ]); - $product2 = ProductFactory::create([ + $product2 = ProductFactory::initialize()->create([ 'quantity' => 10, ]); @@ -141,29 +106,13 @@ public function testItUnsuccessfullyProvidesCarrierDeliveryPriceCarrierIsOutOfZo $subscriptionCarrierDeliveryPriceProvider = $this->getService(SubscriptionCarrierDeliveryPriceProvider::class); $subscriptionCarrierDeliveryPriceProvider->getPrice( - $address->id, - $cart->id, - $cart->id_customer, - $targetProductArray + SubscriptionCarrierDeliveryPriceData::create( + $address->id, + $cart->id, + $cart->id_customer, + $targetProductArray, + $carrier->id + ) ); - - $this->removeFactories([ - $carrier, - $address, - $cart, - $targetProduct, - $product1, - $product2, - ]); - } - - /** - * @param \ObjectModel[] $objects - */ - private function removeFactories(array $objects): void - { - foreach ($objects as $object) { - $object->delete(); - } } } diff --git a/tests/Integration/Subscription/Validator/CanProductBeAddedToCartValidatorTest.php b/tests/Integration/Subscription/Validator/CanProductBeAddedToCartValidatorTest.php index 0274d901c..10aa741f1 100644 --- a/tests/Integration/Subscription/Validator/CanProductBeAddedToCartValidatorTest.php +++ b/tests/Integration/Subscription/Validator/CanProductBeAddedToCartValidatorTest.php @@ -13,14 +13,19 @@ namespace Mollie\Tests\Integration\Subscription\Validator; use Mollie\Adapter\CartAdapter; -use Mollie\Adapter\ProductAttributeAdapter; +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Adapter\ToolsAdapter; +use Mollie\Config\Config as SettingsConfig; use Mollie\Subscription\Config\Config; +use Mollie\Subscription\Exception\ExceptionCode; use Mollie\Subscription\Exception\SubscriptionProductValidationException; -use Mollie\Subscription\Repository\CombinationRepository; -use Mollie\Subscription\Repository\ProductCombinationRepository; +use Mollie\Subscription\Provider\SubscriptionProductProvider; use Mollie\Subscription\Validator\CanProductBeAddedToCartValidator; use Mollie\Subscription\Validator\SubscriptionProductValidator; +use Mollie\Subscription\Validator\SubscriptionSettingsValidator; use Mollie\Tests\Integration\BaseTestCase; +use Mollie\Tests\Integration\Factory\CarrierFactory; +use Mollie\Tests\Integration\Factory\ProductFactory; class CanProductBeAddedToCartValidatorTest extends BaseTestCase { @@ -30,7 +35,8 @@ protected function setUp(): void { parent::setUp(); - $product = new \Product(1); + /** @var \Product $product */ + $product = ProductFactory::initialize()->create(); $this->randomAttributeId = self::NORMAL_PRODUCT_ATTRIBUTE_ID; @@ -66,9 +72,21 @@ protected function setUp(): void /** * @dataProvider productDataProvider */ - public function testValidate(string $combinationReference, bool $hasExtraAttribute, array $cartProducts, $expectedResult): void - { - $cartAdapterMock = $this->createMock(CartAdapter::class); + public function testValidate( + string $combinationReference, + // TODO in this test and the others that have getCombination with extraAttributes. + // Idea was to provide various attributes, but not subscription. + // Should modify this to actually provide random (not subscription) attributes. + bool $hasExtraAttribute, + array $cartProducts, + bool $subscriptionEnabled, + int $subscriptionCarrierId, + bool $expectedResult, + string $expectedException, + int $expectedExceptionCode + ): void { + // TODO cart factory + $cart = $this->createMock(CartAdapter::class); $cartProducts = array_map(function (array $product) { return [ @@ -76,23 +94,34 @@ public function testValidate(string $combinationReference, bool $hasExtraAttribu ]; }, $cartProducts); - $cartAdapterMock->method('getProducts')->willReturn($cartProducts); + $cart->method('getProducts')->willReturn($cartProducts); $combination = $this->getCombination($combinationReference, $hasExtraAttribute); + /** @var ConfigurationAdapter $configuration */ + $configuration = $this->getService(ConfigurationAdapter::class); + + /* + * NOTE: setUp parent deletes all the DB records so carrier is being deleted as well. + */ + if ($subscriptionCarrierId) { + $subscriptionCarrierId = CarrierFactory::create()->id; + } + + $configuration->updateValue(SettingsConfig::MOLLIE_SUBSCRIPTION_ENABLED, $subscriptionEnabled); + $configuration->updateValue(SettingsConfig::MOLLIE_SUBSCRIPTION_ORDER_CARRIER_ID, $subscriptionCarrierId); + $subscriptionCartValidator = new CanProductBeAddedToCartValidator( - $cartAdapterMock, - new SubscriptionProductValidator( - $this->configuration, - new ProductCombinationRepository(), - new CombinationRepository(), - new ProductAttributeAdapter() - ), - $this->getService(\Mollie\Adapter\ToolsAdapter::class) + $cart, + $this->getService(SubscriptionProductValidator::class), + $this->getService(ToolsAdapter::class), + $this->getService(SubscriptionSettingsValidator::class), + $this->getService(SubscriptionProductProvider::class) ); - if ($expectedResult !== true) { - $this->expectException($expectedResult); + if (!$expectedResult) { + $this->expectException($expectedException); + $this->expectExceptionCode($expectedExceptionCode); } $canBeAdded = $subscriptionCartValidator->validate($combination); @@ -102,18 +131,58 @@ public function testValidate(string $combinationReference, bool $hasExtraAttribu public function productDataProvider(): array { + $validCarrier = CarrierFactory::create(); + return [ 'One subscription product' => [ 'subscription reference' => Config::SUBSCRIPTION_ATTRIBUTE_DAILY, 'has extra attribute' => false, 'cart products' => [], + 'subscription enabled' => true, + 'subscription carrier ID' => $validCarrier->id, 'expected result' => true, + 'expected exception' => '', + 'expected exception code' => 0, + ], + 'One subscription product disabled subscription' => [ + 'subscription reference' => Config::SUBSCRIPTION_ATTRIBUTE_DAILY, + 'has extra attribute' => false, + 'cart products' => [], + 'subscription enabled' => false, + 'subscription carrier ID' => $validCarrier->id, + 'expected result' => false, + 'expected exception' => SubscriptionProductValidationException::class, + 'expected exception code' => ExceptionCode::CART_INVALID_SUBSCRIPTION_SETTINGS, + ], + 'One subscription product invalid carrier' => [ + 'subscription reference' => Config::SUBSCRIPTION_ATTRIBUTE_DAILY, + 'has extra attribute' => false, + 'cart products' => [], + 'subscription enabled' => true, + 'subscription carrier ID' => 0, + 'expected result' => false, + 'expected exception' => SubscriptionProductValidationException::class, + 'expected exception code' => ExceptionCode::CART_INVALID_SUBSCRIPTION_SETTINGS, ], 'One normal product' => [ 'subscription reference' => '', - 'has extra attribute' => true, + 'has extra attribute' => false, 'cart products' => [], + 'subscription enabled' => true, + 'subscription carrier ID' => $validCarrier->id, 'expected result' => true, + 'expected exception' => '', + 'expected exception code' => 0, + ], + 'One normal product disabled subscription and invalid carrier' => [ + 'subscription reference' => '', + 'has extra attribute' => false, + 'cart products' => [], + 'subscription enabled' => false, + 'subscription carrier ID' => 0, + 'expected result' => true, + 'expected exception' => '', + 'expected exception code' => 0, ], 'Add subscription product but already have normal product in cart' => [ 'subscription reference' => Config::SUBSCRIPTION_ATTRIBUTE_DAILY, @@ -123,7 +192,11 @@ public function productDataProvider(): array 'id_product_attribute' => self::NORMAL_PRODUCT_ATTRIBUTE_ID, ], ], + 'subscription enabled' => true, + 'subscription carrier ID' => $validCarrier->id, 'expected result' => true, + 'expected exception' => '', + 'expected exception code' => 0, ], 'Add subscription product but already have another subscription product in cart' => [ 'subscription reference' => Config::SUBSCRIPTION_ATTRIBUTE_DAILY, @@ -133,27 +206,39 @@ public function productDataProvider(): array 'id_product_attribute' => Config::SUBSCRIPTION_ATTRIBUTE_MONTHLY, ], ], - 'expected result' => SubscriptionProductValidationException::class, + 'subscription enabled' => true, + 'subscription carrier ID' => $validCarrier->id, + 'expected result' => false, + 'expected exception' => SubscriptionProductValidationException::class, + 'expected exception code' => ExceptionCode::CART_ALREADY_HAS_SUBSCRIPTION_PRODUCT, ], 'Add normal product but already have another subscription product in cart' => [ 'subscription reference' => '', - 'has extra attribute' => true, + 'has extra attribute' => false, 'cart products' => [ [ 'id_product_attribute' => Config::SUBSCRIPTION_ATTRIBUTE_MONTHLY, ], ], + 'subscription enabled' => true, + 'subscription carrier ID' => $validCarrier->id, 'expected result' => true, + 'expected exception' => '', + 'expected exception code' => 0, ], 'Add normal product but already have another normal product in cart' => [ 'subscription reference' => '', - 'has extra attribute' => true, + 'has extra attribute' => false, 'cart products' => [ [ 'id_product_attribute' => self::NORMAL_PRODUCT_ATTRIBUTE_ID, ], ], + 'subscription enabled' => true, + 'subscription carrier ID' => $validCarrier->id, 'expected result' => true, + 'expected exception' => '', + 'expected exception code' => 0, ], ]; } @@ -161,6 +246,7 @@ public function productDataProvider(): array private function getCombination(string $combinationReference, bool $hasExtraAttribute): int { $reference = $this->configuration->get($combinationReference); + if ($hasExtraAttribute) { $reference = $reference ? implode('-', [ $this->configuration->get($combinationReference), diff --git a/tests/Integration/Subscription/Validator/SubscriptionOrderValidatorTest.php b/tests/Integration/Subscription/Validator/SubscriptionOrderValidatorTest.php index 1c9af8f6d..84427f120 100644 --- a/tests/Integration/Subscription/Validator/SubscriptionOrderValidatorTest.php +++ b/tests/Integration/Subscription/Validator/SubscriptionOrderValidatorTest.php @@ -13,10 +13,10 @@ namespace Mollie\Tests\Integration\Subscription\Validator; use Mollie\Subscription\Config\Config; -use Mollie\Subscription\Repository\CombinationRepository as CombinationAdapter; use Mollie\Subscription\Validator\SubscriptionOrderValidator; use Mollie\Subscription\Validator\SubscriptionProductValidator; use Mollie\Tests\Integration\BaseTestCase; +use Mollie\Tests\Integration\Factory\ProductFactory; class SubscriptionOrderValidatorTest extends BaseTestCase { @@ -25,7 +25,9 @@ class SubscriptionOrderValidatorTest extends BaseTestCase protected function setUp(): void { parent::setUp(); - $product = new \Product(1); + + /** @var \Product $product */ + $product = ProductFactory::initialize()->create(); $this->randomAttributeId = self::NORMAL_PRODUCT_ATTRIBUTE_ID; @@ -63,20 +65,16 @@ protected function setUp(): void */ public function testValidate(array $orderProducts, $expectedResult): void { - $cartMock = $this->createMock('Cart'); + $cart = $this->createMock('Cart'); $orderProductsMapped = array_map(function ($product) { return $this->getProducts($product); }, $orderProducts); - $cartMock->method('getProducts')->willReturn($orderProductsMapped); + $cart->method('getProducts')->willReturn($orderProductsMapped); - $combinationMock = $this->createMock(CombinationAdapter::class); - $combinationMock - ->method('getById') - ->willReturn(new \Combination(1)); + $subscriptionProductValidator = $this->createMock(SubscriptionProductValidator::class); - $subscriptionProductMock = $this->createMock(SubscriptionProductValidator::class); $mockedValidation = [ [(int) $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_NONE), false], [(int) $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_DAILY), true], @@ -84,13 +82,14 @@ public function testValidate(array $orderProducts, $expectedResult): void [(int) $this->configuration->get(Config::SUBSCRIPTION_ATTRIBUTE_MONTHLY), true], [self::NORMAL_PRODUCT_ATTRIBUTE_ID, false], ]; - $subscriptionProductMock->method('validate')->will( + + $subscriptionProductValidator->method('validate')->will( $this->returnValueMap($mockedValidation) ); - $subscriptionOrderValidator = new SubscriptionOrderValidator($subscriptionProductMock); + $subscriptionOrderValidator = new SubscriptionOrderValidator($subscriptionProductValidator); - $canBeAdded = $subscriptionOrderValidator->validate($cartMock); + $canBeAdded = $subscriptionOrderValidator->validate($cart); $this->assertEquals($expectedResult, $canBeAdded); } diff --git a/tests/Integration/Subscription/Validator/SubscriptionProductValidatorTest.php b/tests/Integration/Subscription/Validator/SubscriptionProductValidatorTest.php index 437ebe1f5..944e958d1 100644 --- a/tests/Integration/Subscription/Validator/SubscriptionProductValidatorTest.php +++ b/tests/Integration/Subscription/Validator/SubscriptionProductValidatorTest.php @@ -15,6 +15,7 @@ use Mollie\Subscription\Config\Config; use Mollie\Subscription\Validator\SubscriptionProductValidator; use Mollie\Tests\Integration\BaseTestCase; +use Mollie\Tests\Integration\Factory\ProductFactory; class SubscriptionProductValidatorTest extends BaseTestCase { @@ -22,7 +23,8 @@ protected function setUp(): void { parent::setUp(); - $product = new \Product(1); + /** @var \Product $product */ + $product = ProductFactory::initialize()->create(); $this->randomAttributeId = 1; @@ -87,7 +89,7 @@ public function productDataProvider(): array ], 'only random attribute' => [ 'subscription reference' => '', - 'has extra attribute' => true, + 'has extra attribute' => false, 'expected result' => false, ], ]; diff --git a/tests/Integration/src/Action/UpdateOrderTotalsActionTest.php b/tests/Integration/src/Action/UpdateOrderTotalsActionTest.php index cb9aa8e4e..dd9047850 100644 --- a/tests/Integration/src/Action/UpdateOrderTotalsActionTest.php +++ b/tests/Integration/src/Action/UpdateOrderTotalsActionTest.php @@ -20,6 +20,9 @@ class UpdateOrderTotalsActionTest extends BaseTestCase { public function testItSuccessfullyUpdatesOrderTotals(): void { + // TODO order factory + $originalOrder = new \Order(1); + /** @var UpdateOrderTotalsAction $updateOrderTotalsAction */ $updateOrderTotalsAction = $this->getService(UpdateOrderTotalsAction::class); @@ -49,5 +52,7 @@ public function testItSuccessfullyUpdatesOrderTotals(): void 'total_paid' => 22.1, ] ); + + $originalOrder->save(); } } diff --git a/tests/Unit/BaseTestCase.php b/tests/Unit/BaseTestCase.php new file mode 100644 index 000000000..54388f5e3 --- /dev/null +++ b/tests/Unit/BaseTestCase.php @@ -0,0 +1,67 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Unit; + +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Adapter\Context; +use Mollie\Factory\ModuleFactory; +use Mollie\Logger\PrestaLoggerInterface; +use Mollie\Repository\OrderRepositoryInterface; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; +use PHPUnit\Framework\TestCase; + +class BaseTestCase extends TestCase +{ + protected $backupGlobals = false; + + /** @var \Mollie */ + public $module; + /** @var ConfigurationAdapter */ + public $configuration; + /** @var Context */ + public $context; + /** @var \Customer */ + public $customer; + /** @var ModuleFactory */ + public $moduleFactory; + /** @var OrderRepositoryInterface */ + public $orderRepository; + /** @var CurrencyRepositoryInterface */ + public $currencyRepository; + /** @var \Cart */ + public $cart; + /** @var PrestaLoggerInterface */ + public $logger; + + protected function setUp(): void + { + $this->module = $this->mock(\Mollie::class); + $this->configuration = $this->mock(ConfigurationAdapter::class); + $this->context = $this->mock(Context::class); + $this->customer = $this->mock(\Customer::class); + $this->moduleFactory = $this->mock(ModuleFactory::class); + $this->orderRepository = $this->mock(OrderRepositoryInterface::class); + $this->currencyRepository = $this->mock(CurrencyRepositoryInterface::class); + $this->cart = $this->mock(\Cart::class); + $this->logger = $this->mock(PrestaLoggerInterface::class); + + parent::setUp(); + } + + public function mock(string $className) + { + return $this->getMockBuilder($className) + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/tests/Unit/Builder/InvoicePdfTemplateBuilderTest.php b/tests/Unit/Builder/InvoicePdfTemplateBuilderTest.php index f02d70f4d..7824b647e 100644 --- a/tests/Unit/Builder/InvoicePdfTemplateBuilderTest.php +++ b/tests/Unit/Builder/InvoicePdfTemplateBuilderTest.php @@ -13,8 +13,8 @@ namespace Mollie\Tests\Unit\Builder; use Mollie\Builder\InvoicePdfTemplateBuilder; -use Mollie\Repository\CurrencyRepositoryInterface; use Mollie\Repository\MolOrderPaymentFeeRepositoryInterface; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; use MolOrderPaymentFee; use PHPUnit\Framework\TestCase; use PrestaShop\PrestaShop\Core\Localization\Locale; diff --git a/tests/Unit/Factory/SubscriptionDataTest.php b/tests/Unit/Subscription/Factory/CreateSubscriptionDataFactoryTest.php similarity index 65% rename from tests/Unit/Factory/SubscriptionDataTest.php rename to tests/Unit/Subscription/Factory/CreateSubscriptionDataFactoryTest.php index 400edd280..186ad5e59 100644 --- a/tests/Unit/Factory/SubscriptionDataTest.php +++ b/tests/Unit/Subscription/Factory/CreateSubscriptionDataFactoryTest.php @@ -12,10 +12,8 @@ declare(strict_types=1); -namespace Mollie\Tests\Unit\Factory; +namespace Mollie\Tests\Unit\Subscription\Factory; -use Mollie; -use Mollie\Adapter\Context; use Mollie\Repository\MolCustomerRepository; use Mollie\Repository\PaymentMethodRepository; use Mollie\Subscription\Constants\IntervalConstant; @@ -23,15 +21,13 @@ use Mollie\Subscription\DTO\Object\Amount; use Mollie\Subscription\DTO\Object\Interval; use Mollie\Subscription\Factory\CreateSubscriptionDataFactory; -use Mollie\Subscription\Provider\SubscriptionCarrierDeliveryPriceProvider; use Mollie\Subscription\Provider\SubscriptionDescriptionProvider; use Mollie\Subscription\Provider\SubscriptionIntervalProvider; -use Mollie\Subscription\Repository\CombinationRepository; -use Mollie\Subscription\Repository\CurrencyRepository; +use Mollie\Subscription\Provider\SubscriptionOrderAmountProvider; +use Mollie\Tests\Unit\BaseTestCase; use Mollie\Utility\SecureKeyUtility; -use PHPUnit\Framework\TestCase; -class SubscriptionDataTest extends TestCase +class CreateSubscriptionDataFactoryTest extends BaseTestCase { private const TEST_ORDER_ID = 1; private const TEST_ORDER_REFERENCE = 111; @@ -45,22 +41,27 @@ class SubscriptionDataTest extends TestCase */ public function testBuildSubscriptionData(string $customerId, float $totalAmount, string $description, SubscriptionDataDTO $expectedResult): void { - $molCustomer = $this->createMock('MolCustomer'); + // TODO replace data provider with multiple methods, which tests various exception cases + + /** @var \MolCustomer $molCustomer */ + $molCustomer = $this->createMock(\MolCustomer::class); $molCustomer->customer_id = $customerId; - $customerRepositoryMock = $this->createMock(MolCustomerRepository::class); - $customerRepositoryMock->method('findOneBy')->willReturn($molCustomer); - $subscriptionIntervalProviderMock = $this->createMock(SubscriptionIntervalProvider::class); - $subscriptionIntervalProviderMock->method('getSubscriptionInterval')->willReturn(new Interval(1, IntervalConstant::DAY)); + $customerRepository = $this->createMock(MolCustomerRepository::class); + $customerRepository->method('findOneBy')->willReturn($molCustomer); + + $interval = new Interval(1, 'day'); + + $subscriptionIntervalProvider = $this->createMock(SubscriptionIntervalProvider::class); + $subscriptionIntervalProvider->method('getSubscriptionInterval')->willReturn($interval); $subscriptionDescriptionProviderMock = $this->createMock(SubscriptionDescriptionProvider::class); $subscriptionDescriptionProviderMock->method('getSubscriptionDescription')->willReturn($description); - $currency = $this->createMock('Currency'); - $currency->iso_code = 'EUR'; + $this->configuration->method('get')->willReturn(1); - $currencyAdapterMock = $this->createMock(CurrencyRepository::class); - $currencyAdapterMock->method('getById')->willReturn($currency); + $subscriptionOrderAmountProvider = $this->createMock(SubscriptionOrderAmountProvider::class); + $subscriptionOrderAmountProvider->method('get')->willReturn(new Amount($totalAmount, 'EUR')); $paymentMethodRepositoryMock = $this->createMock(PaymentMethodRepository::class); $paymentMethodRepositoryMock->method('getPaymentBy')->willReturn( @@ -69,29 +70,26 @@ public function testBuildSubscriptionData(string $customerId, float $totalAmount ] ); - $context = $this->createMock(Context::class); - $context->expects($this->once())->method('getModuleLink')->willReturn('example-link'); + $this->context->method('getModuleLink')->willReturn('example-link'); - $subscriptionCarrierDeliveryPriceProvider = $this->createMock(SubscriptionCarrierDeliveryPriceProvider::class); - $subscriptionCarrierDeliveryPriceProvider->expects($this->once())->method('getPrice')->willReturn(10.00); + $this->module->name = 'mollie'; $subscriptionDataFactory = new CreateSubscriptionDataFactory( - $customerRepositoryMock, - $subscriptionIntervalProviderMock, + $customerRepository, + $subscriptionIntervalProvider, $subscriptionDescriptionProviderMock, - $currencyAdapterMock, - new CombinationRepository(), $paymentMethodRepositoryMock, - new Mollie(), - $context, - $subscriptionCarrierDeliveryPriceProvider + $this->module, + $this->context, + $this->configuration, + $subscriptionOrderAmountProvider ); - $customerMock = $this->createMock('Customer'); - $customerMock->email = 'test.gmail.com'; + $this->customer->email = 'test.gmail.com'; $order = $this->createMock('Order'); - $order->method('getCustomer')->willReturn($customerMock); + $order->method('getCustomer')->willReturn($this->customer); + $order->id = self::TEST_ORDER_ID; $order->reference = self::TEST_ORDER_REFERENCE; $order->id_cart = self::TEST_CART_ID; @@ -101,7 +99,7 @@ public function testBuildSubscriptionData(string $customerId, float $totalAmount $subscriptionProduct = [ 'id_product_attribute' => 1, - 'total_price_tax_incl' => 19.99, + 'total_price_tax_incl' => 29.99, ]; $subscriptionData = $subscriptionDataFactory->build($order, $subscriptionProduct); @@ -117,6 +115,7 @@ public function subscriptionDataProvider() new Interval(1, IntervalConstant::DAY), 'subscription-' . self::TEST_ORDER_REFERENCE ); + $subscriptionDto->setMandateId(self::TEST_MANDATE_ID); $key = SecureKeyUtility::generateReturnKey( @@ -128,6 +127,7 @@ public function subscriptionDataProvider() $subscriptionDto->setMetaData( [ 'secure_key' => $key, + 'subscription_carrier_id' => 1, ] ); diff --git a/tests/Unit/Subscription/Factory/index.php b/tests/Unit/Subscription/Factory/index.php new file mode 100644 index 000000000..88355f610 --- /dev/null +++ b/tests/Unit/Subscription/Factory/index.php @@ -0,0 +1,11 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Unit\Subscription\Handler; + +use Mollie\Api\Resources\Subscription; +use Mollie\Subscription\Action\CreateRecurringOrderAction; +use Mollie\Subscription\Action\CreateRecurringOrdersProductAction; +use Mollie\Subscription\Api\SubscriptionApi; +use Mollie\Subscription\DTO\CreateSubscriptionData; +use Mollie\Subscription\Exception\CouldNotCreateSubscription; +use Mollie\Subscription\Exception\ExceptionCode; +use Mollie\Subscription\Exception\MollieSubscriptionException; +use Mollie\Subscription\Factory\CreateSubscriptionDataFactory; +use Mollie\Subscription\Handler\SubscriptionCreationHandler; +use Mollie\Subscription\Provider\SubscriptionProductProvider; +use Mollie\Subscription\Validator\SubscriptionSettingsValidator; +use Mollie\Tests\Unit\BaseTestCase; +use PHPUnit\Framework\MockObject\MockObject; + +class SubscriptionCreationHandlerTest extends BaseTestCase +{ + /** @var SubscriptionApi */ + private $subscriptionApi; + /** @var CreateSubscriptionDataFactory */ + private $createSubscriptionDataFactory; + /** @var SubscriptionSettingsValidator */ + private $subscriptionSettingsValidator; + /** @var CreateRecurringOrdersProductAction */ + private $createRecurringOrdersProductAction; + /** @var CreateRecurringOrderAction */ + private $createRecurringOrderAction; + /** @var SubscriptionProductProvider */ + private $subscriptionProductProvider; + + public function setUp(): void + { + parent::setUp(); + + $this->subscriptionApi = $this->mock(SubscriptionApi::class); + $this->createSubscriptionDataFactory = $this->mock(CreateSubscriptionDataFactory::class); + $this->subscriptionSettingsValidator = $this->mock(SubscriptionSettingsValidator::class); + $this->createRecurringOrdersProductAction = $this->mock(CreateRecurringOrdersProductAction::class); + $this->createRecurringOrderAction = $this->mock(CreateRecurringOrderAction::class); + $this->subscriptionProductProvider = $this->mock(SubscriptionProductProvider::class); + } + + public function testItSuccessfullyHandlesSubscriptionCreation(): void + { + $this->subscriptionSettingsValidator->expects($this->once())->method('validate'); + + $products = [ + [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + [ + 'id_product' => 2, + 'id_product_attribute' => 2, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + ]; + + /** @var \Order|MockObject $order */ + $order = $this->mock(\Order::class); + $order->id = 1; + $order->id_cart = 1; + $order->id_currency = 1; + $order->id_customer = 1; + $order->id_address_delivery = 1; + $order->id_address_invoice = 1; + + $order->expects($this->once())->method('getCartProducts')->willReturn($products); + + $this->subscriptionProductProvider->expects($this->once())->method('getProduct')->willReturn($products[0]); + + $subscriptionData = $this->mock(CreateSubscriptionData::class); + + $this->createSubscriptionDataFactory->expects($this->once())->method('build')->willReturn($subscriptionData); + + $subscriptionAmount = new \stdClass(); + $subscriptionAmount->value = 19.99; + + /** @var Subscription|MockObject $subscription */ + $subscription = $this->mock(Subscription::class); + $subscription->description = 'test-description'; + $subscription->status = 'test-status'; + $subscription->amount = $subscriptionAmount; + $subscription->nextPaymentDate = '2023-09-09 12:00:00'; + $subscription->canceledAt = '2023-09-10 12:00:00'; + $subscription->id = 'test-subscription-id'; + $subscription->customerId = 'test-customer-id'; + + $this->subscriptionApi->expects($this->once())->method('subscribeOrder')->willReturn($subscription); + + /** @var \MolRecurringOrdersProduct|MockObject $recurringOrdersProduct */ + $recurringOrdersProduct = $this->mock(\MolRecurringOrdersProduct::class); + $recurringOrdersProduct->id = 1; + + $this->createRecurringOrdersProductAction->expects($this->once())->method('run')->willReturn($recurringOrdersProduct); + + $this->createRecurringOrderAction->expects($this->once())->method('run'); + + $subscriptionCreationHandler = new SubscriptionCreationHandler( + $this->subscriptionApi, + $this->createSubscriptionDataFactory, + $this->subscriptionSettingsValidator, + $this->createRecurringOrdersProductAction, + $this->createRecurringOrderAction, + $this->subscriptionProductProvider + ); + + $subscriptionCreationHandler->handle($order, 'test-method'); + } + + public function testItUnsuccessfullyHandlesSubscriptionCreationInvalidSubscriptionSettings(): void + { + $this->subscriptionSettingsValidator->expects($this->once())->method('validate')->willThrowException(new MollieSubscriptionException()); + + /** @var \Order|MockObject $order */ + $order = $this->mock(\Order::class); + + $order->expects($this->never())->method('getCartProducts'); + + $this->subscriptionProductProvider->expects($this->never())->method('getProduct'); + + $this->createSubscriptionDataFactory->expects($this->never())->method('build'); + + $this->subscriptionApi->expects($this->never())->method('subscribeOrder'); + + $this->createRecurringOrdersProductAction->expects($this->never())->method('run'); + + $this->createRecurringOrderAction->expects($this->never())->method('run'); + + $subscriptionCreationHandler = new SubscriptionCreationHandler( + $this->subscriptionApi, + $this->createSubscriptionDataFactory, + $this->subscriptionSettingsValidator, + $this->createRecurringOrdersProductAction, + $this->createRecurringOrderAction, + $this->subscriptionProductProvider + ); + + $this->expectException(CouldNotCreateSubscription::class); + $this->expectExceptionCode(ExceptionCode::ORDER_INVALID_SUBSCRIPTION_SETTINGS); + + $subscriptionCreationHandler->handle($order, 'test-method'); + } + + public function testItUnsuccessfullyHandlesSubscriptionCreationFailedToFindSubscriptionProducts(): void + { + $this->subscriptionSettingsValidator->expects($this->once())->method('validate'); + + $products = [ + [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + [ + 'id_product' => 2, + 'id_product_attribute' => 2, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + ]; + + /** @var \Order|MockObject $order */ + $order = $this->mock(\Order::class); + + $order->expects($this->once())->method('getCartProducts')->willReturn($products); + + $this->subscriptionProductProvider->expects($this->once())->method('getProduct')->willReturn([]); + + $this->createSubscriptionDataFactory->expects($this->never())->method('build'); + + $this->subscriptionApi->expects($this->never())->method('subscribeOrder'); + + $this->createRecurringOrdersProductAction->expects($this->never())->method('run'); + + $this->createRecurringOrderAction->expects($this->never())->method('run'); + + $subscriptionCreationHandler = new SubscriptionCreationHandler( + $this->subscriptionApi, + $this->createSubscriptionDataFactory, + $this->subscriptionSettingsValidator, + $this->createRecurringOrdersProductAction, + $this->createRecurringOrderAction, + $this->subscriptionProductProvider + ); + + $this->expectException(CouldNotCreateSubscription::class); + $this->expectExceptionCode(ExceptionCode::ORDER_FAILED_TO_FIND_SUBSCRIPTION_PRODUCT); + + $subscriptionCreationHandler->handle($order, 'test-method'); + } + + public function testItUnsuccessfullyHandlesSubscriptionCreationFailedToCreateSubscriptionData(): void + { + $this->subscriptionSettingsValidator->expects($this->once())->method('validate'); + + $products = [ + [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + [ + 'id_product' => 2, + 'id_product_attribute' => 2, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + ]; + + /** @var \Order|MockObject $order */ + $order = $this->mock(\Order::class); + + $order->expects($this->once())->method('getCartProducts')->willReturn($products); + + $this->subscriptionProductProvider->expects($this->once())->method('getProduct')->willReturn($products[0]); + + $this->createSubscriptionDataFactory->expects($this->once())->method('build')->willThrowException(new MollieSubscriptionException()); + + $this->subscriptionApi->expects($this->never())->method('subscribeOrder'); + + $this->createRecurringOrdersProductAction->expects($this->never())->method('run'); + + $this->createRecurringOrderAction->expects($this->never())->method('run'); + + $subscriptionCreationHandler = new SubscriptionCreationHandler( + $this->subscriptionApi, + $this->createSubscriptionDataFactory, + $this->subscriptionSettingsValidator, + $this->createRecurringOrdersProductAction, + $this->createRecurringOrderAction, + $this->subscriptionProductProvider + ); + + $this->expectException(CouldNotCreateSubscription::class); + $this->expectExceptionCode(ExceptionCode::ORDER_FAILED_TO_CREATE_SUBSCRIPTION_DATA); + + $subscriptionCreationHandler->handle($order, 'test-method'); + } + + public function testItUnsuccessfullyHandlesSubscriptionCreationFailedToSubscribeOrder(): void + { + $this->subscriptionSettingsValidator->expects($this->once())->method('validate'); + + $products = [ + [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + [ + 'id_product' => 2, + 'id_product_attribute' => 2, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + ]; + + /** @var \Order|MockObject $order */ + $order = $this->mock(\Order::class); + + $order->expects($this->once())->method('getCartProducts')->willReturn($products); + + $this->subscriptionProductProvider->expects($this->once())->method('getProduct')->willReturn($products[0]); + + $subscriptionData = $this->mock(CreateSubscriptionData::class); + + $this->createSubscriptionDataFactory->expects($this->once())->method('build')->willReturn($subscriptionData); + + $this->subscriptionApi->expects($this->once())->method('subscribeOrder')->willThrowException(new MollieSubscriptionException()); + + $this->createRecurringOrdersProductAction->expects($this->never())->method('run'); + + $this->createRecurringOrderAction->expects($this->never())->method('run'); + + $subscriptionCreationHandler = new SubscriptionCreationHandler( + $this->subscriptionApi, + $this->createSubscriptionDataFactory, + $this->subscriptionSettingsValidator, + $this->createRecurringOrdersProductAction, + $this->createRecurringOrderAction, + $this->subscriptionProductProvider + ); + + $this->expectException(CouldNotCreateSubscription::class); + $this->expectExceptionCode(ExceptionCode::ORDER_FAILED_TO_SUBSCRIBE_ORDER); + + $subscriptionCreationHandler->handle($order, 'test-method'); + } + + public function testItUnsuccessfullyHandlesSubscriptionCreationFailedToCreateRecurringOrdersProduct(): void + { + $this->subscriptionSettingsValidator->expects($this->once())->method('validate'); + + $products = [ + [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + [ + 'id_product' => 2, + 'id_product_attribute' => 2, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + ]; + + /** @var \Order|MockObject $order */ + $order = $this->mock(\Order::class); + + $order->expects($this->once())->method('getCartProducts')->willReturn($products); + + $this->subscriptionProductProvider->expects($this->once())->method('getProduct')->willReturn($products[0]); + + $subscriptionData = $this->mock(CreateSubscriptionData::class); + + $this->createSubscriptionDataFactory->expects($this->once())->method('build')->willReturn($subscriptionData); + + /** @var Subscription|MockObject $subscription */ + $subscription = $this->mock(Subscription::class); + + $this->subscriptionApi->expects($this->once())->method('subscribeOrder')->willReturn($subscription); + + $this->createRecurringOrdersProductAction->expects($this->once())->method('run')->willThrowException(new MollieSubscriptionException()); + + $this->createRecurringOrderAction->expects($this->never())->method('run'); + + $subscriptionCreationHandler = new SubscriptionCreationHandler( + $this->subscriptionApi, + $this->createSubscriptionDataFactory, + $this->subscriptionSettingsValidator, + $this->createRecurringOrdersProductAction, + $this->createRecurringOrderAction, + $this->subscriptionProductProvider + ); + + $this->expectException(CouldNotCreateSubscription::class); + $this->expectExceptionCode(ExceptionCode::ORDER_FAILED_TO_CREATE_RECURRING_ORDERS_PRODUCT); + + $subscriptionCreationHandler->handle($order, 'test-method'); + } + + public function testItUnsuccessfullyHandlesSubscriptionCreationFailedToCreateRecurringOrder(): void + { + $this->subscriptionSettingsValidator->expects($this->once())->method('validate'); + + $products = [ + [ + 'id_product' => 1, + 'id_product_attribute' => 1, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + [ + 'id_product' => 2, + 'id_product_attribute' => 2, + 'product_quantity' => 1, + 'unit_price_tax_excl' => 19.99, + ], + ]; + + /** @var \Order|MockObject $order */ + $order = $this->mock(\Order::class); + + $order->expects($this->once())->method('getCartProducts')->willReturn($products); + + $this->subscriptionProductProvider->expects($this->once())->method('getProduct')->willReturn($products[0]); + + $subscriptionData = $this->mock(CreateSubscriptionData::class); + + $this->createSubscriptionDataFactory->expects($this->once())->method('build')->willReturn($subscriptionData); + + $subscriptionAmount = new \stdClass(); + $subscriptionAmount->value = 19.99; + + /** @var Subscription|MockObject $subscription */ + $subscription = $this->mock(Subscription::class); + $subscription->description = 'test-description'; + $subscription->status = 'test-status'; + $subscription->amount = $subscriptionAmount; + $subscription->nextPaymentDate = '2023-09-09 12:00:00'; + $subscription->canceledAt = '2023-09-10 12:00:00'; + $subscription->id = 'test-subscription-id'; + $subscription->customerId = 'test-customer-id'; + + $this->subscriptionApi->expects($this->once())->method('subscribeOrder')->willReturn($subscription); + + /** @var \MolRecurringOrdersProduct|MockObject $recurringOrdersProduct */ + $recurringOrdersProduct = $this->mock(\MolRecurringOrdersProduct::class); + $recurringOrdersProduct->id = 1; + + $this->createRecurringOrdersProductAction->expects($this->once())->method('run')->willReturn($recurringOrdersProduct); + + $this->createRecurringOrderAction->expects($this->once())->method('run')->willThrowException(new MollieSubscriptionException()); + + $subscriptionCreationHandler = new SubscriptionCreationHandler( + $this->subscriptionApi, + $this->createSubscriptionDataFactory, + $this->subscriptionSettingsValidator, + $this->createRecurringOrdersProductAction, + $this->createRecurringOrderAction, + $this->subscriptionProductProvider + ); + + $this->expectException(CouldNotCreateSubscription::class); + $this->expectExceptionCode(ExceptionCode::ORDER_FAILED_TO_CREATE_RECURRING_ORDER); + + $subscriptionCreationHandler->handle($order, 'test-method'); + } +} diff --git a/tests/Unit/Subscription/Handler/UpdateSubscriptionCarrierHandlerTest.php b/tests/Unit/Subscription/Handler/UpdateSubscriptionCarrierHandlerTest.php new file mode 100644 index 000000000..8929b9c7f --- /dev/null +++ b/tests/Unit/Subscription/Handler/UpdateSubscriptionCarrierHandlerTest.php @@ -0,0 +1,427 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Unit\Subscription\Handler; + +use Mollie\Exception\MollieException; +use Mollie\Service\MailService; +use Mollie\Subscription\Action\UpdateRecurringOrderAction; +use Mollie\Subscription\Action\UpdateSubscriptionAction; +use Mollie\Subscription\DTO\Object\Amount; +use Mollie\Subscription\Handler\CloneOriginalSubscriptionCartHandler; +use Mollie\Subscription\Handler\UpdateSubscriptionCarrierHandler; +use Mollie\Subscription\Provider\SubscriptionOrderAmountProvider; +use Mollie\Subscription\Repository\RecurringOrderRepositoryInterface; +use Mollie\Tests\Unit\BaseTestCase; + +class UpdateSubscriptionCarrierHandlerTest extends BaseTestCase +{ + /** @var RecurringOrderRepositoryInterface */ + private $recurringOrderRepository; + /** @var UpdateSubscriptionAction */ + private $updateSubscriptionAction; + /** @var UpdateRecurringOrderAction */ + private $updateRecurringOrderAction; + /** @var CloneOriginalSubscriptionCartHandler */ + private $cloneOriginalSubscriptionCartHandler; + /** @var SubscriptionOrderAmountProvider */ + private $subscriptionOrderAmountProvider; + /** @var MailService */ + private $mailService; + + public function setUp(): void + { + parent::setUp(); + + $this->recurringOrderRepository = $this->mock(RecurringOrderRepositoryInterface::class); + $this->updateSubscriptionAction = $this->mock(UpdateSubscriptionAction::class); + $this->updateRecurringOrderAction = $this->mock(UpdateRecurringOrderAction::class); + $this->cloneOriginalSubscriptionCartHandler = $this->mock(CloneOriginalSubscriptionCartHandler::class); + $this->subscriptionOrderAmountProvider = $this->mock(SubscriptionOrderAmountProvider::class); + $this->mailService = $this->mock(MailService::class); + } + + public function testItSuccessfullyHandles(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(1); + $this->configuration->expects($this->once())->method('updateValue'); + + $this->context->expects($this->once())->method('getShopId')->willReturn(1); + + $this->recurringOrderRepository->expects($this->once())->method('getAllOrdersBasedOnStatuses')->willReturn([ + [ + 'id' => 1, + 'mollie_customer_id' => 'test-mollie-customer-id', + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'id_cart' => 2, + 'id_recurring_product' => 3, + 'id_invoice_address' => 4, + 'id_delivery_address' => 5, + ], + ]); + + $this->cart->id = 3; + $this->cart->id_customer = 1; + $this->cart->id_address_delivery = 5; + $this->cart->id_currency = 1; + + $this->cart->expects($this->once())->method('getProducts')->willReturn([ + [ + 'total_price_tax_incl' => 10.00, + ], + ]); + + $this->cloneOriginalSubscriptionCartHandler->expects($this->once())->method('run')->willReturn($this->cart); + + $this->subscriptionOrderAmountProvider->expects($this->once())->method('get')->willReturn( + new Amount(20.00, 'EUR') + ); + + $this->updateSubscriptionAction->expects($this->once())->method('run'); + + $this->updateRecurringOrderAction->expects($this->once())->method('run'); + + $this->mailService->expects($this->once())->method('sendSubscriptionCarrierUpdateMail'); + + $updateSubscriptionCarrierHandler = new UpdateSubscriptionCarrierHandler( + $this->configuration, + $this->recurringOrderRepository, + $this->context, + $this->updateSubscriptionAction, + $this->updateRecurringOrderAction, + $this->logger, + $this->cloneOriginalSubscriptionCartHandler, + $this->subscriptionOrderAmountProvider, + $this->mailService + ); + + $result = $updateSubscriptionCarrierHandler->run(99); + + $this->assertEmpty($result); + } + + public function testItUnsuccessfullyHandlesMatchingCarrierId(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(1); + $this->configuration->expects($this->never())->method('updateValue'); + + $this->context->expects($this->never())->method('getShopId'); + + $this->recurringOrderRepository->expects($this->never())->method('getAllOrdersBasedOnStatuses'); + + $this->cloneOriginalSubscriptionCartHandler->expects($this->never())->method('run'); + + $this->subscriptionOrderAmountProvider->expects($this->never())->method('get'); + + $this->updateSubscriptionAction->expects($this->never())->method('run'); + + $this->updateRecurringOrderAction->expects($this->never())->method('run'); + + $this->mailService->expects($this->never())->method('sendSubscriptionCarrierUpdateMail'); + + $updateSubscriptionCarrierHandler = new UpdateSubscriptionCarrierHandler( + $this->configuration, + $this->recurringOrderRepository, + $this->context, + $this->updateSubscriptionAction, + $this->updateRecurringOrderAction, + $this->logger, + $this->cloneOriginalSubscriptionCartHandler, + $this->subscriptionOrderAmountProvider, + $this->mailService + ); + + $result = $updateSubscriptionCarrierHandler->run(1); + + $this->assertEmpty($result); + } + + public function testItUnsuccessfullyHandlesFailedToHandleOriginalSubscriptionCartCloning(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(1); + $this->configuration->expects($this->once())->method('updateValue'); + + $this->context->expects($this->once())->method('getShopId')->willReturn(1); + + $this->recurringOrderRepository->expects($this->once())->method('getAllOrdersBasedOnStatuses')->willReturn([ + [ + 'id' => 1, + 'mollie_customer_id' => 'test-mollie-customer-id', + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'id_cart' => 2, + 'id_recurring_product' => 3, + 'id_invoice_address' => 4, + 'id_delivery_address' => 5, + ], + ]); + + $this->cloneOriginalSubscriptionCartHandler->expects($this->once())->method('run')->willThrowException(new MollieException('', 0)); + + $this->subscriptionOrderAmountProvider->expects($this->never())->method('get'); + + $this->updateSubscriptionAction->expects($this->never())->method('run'); + + $this->updateRecurringOrderAction->expects($this->never())->method('run'); + + $this->mailService->expects($this->never())->method('sendSubscriptionCarrierUpdateMail'); + + $updateSubscriptionCarrierHandler = new UpdateSubscriptionCarrierHandler( + $this->configuration, + $this->recurringOrderRepository, + $this->context, + $this->updateSubscriptionAction, + $this->updateRecurringOrderAction, + $this->logger, + $this->cloneOriginalSubscriptionCartHandler, + $this->subscriptionOrderAmountProvider, + $this->mailService + ); + + $result = $updateSubscriptionCarrierHandler->run(99); + + $this->assertCount(1, $result); + } + + public function testItUnsuccessfullyHandlesFailedToProvideSubscriptionOrderAmount(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(1); + $this->configuration->expects($this->once())->method('updateValue'); + + $this->context->expects($this->once())->method('getShopId')->willReturn(1); + + $this->recurringOrderRepository->expects($this->once())->method('getAllOrdersBasedOnStatuses')->willReturn([ + [ + 'id' => 1, + 'mollie_customer_id' => 'test-mollie-customer-id', + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'id_cart' => 2, + 'id_recurring_product' => 3, + 'id_invoice_address' => 4, + 'id_delivery_address' => 5, + ], + ]); + + $this->cart->id = 3; + $this->cart->id_customer = 1; + $this->cart->id_address_delivery = 5; + $this->cart->id_currency = 1; + + $this->cart->expects($this->once())->method('getProducts')->willReturn([ + [ + 'total_price_tax_incl' => 10.00, + ], + ]); + + $this->cloneOriginalSubscriptionCartHandler->expects($this->once())->method('run')->willReturn($this->cart); + + $this->subscriptionOrderAmountProvider->expects($this->once())->method('get')->willThrowException(new MollieException('', 0)); + + $this->updateSubscriptionAction->expects($this->never())->method('run'); + + $this->updateRecurringOrderAction->expects($this->never())->method('run'); + + $this->mailService->expects($this->never())->method('sendSubscriptionCarrierUpdateMail'); + + $updateSubscriptionCarrierHandler = new UpdateSubscriptionCarrierHandler( + $this->configuration, + $this->recurringOrderRepository, + $this->context, + $this->updateSubscriptionAction, + $this->updateRecurringOrderAction, + $this->logger, + $this->cloneOriginalSubscriptionCartHandler, + $this->subscriptionOrderAmountProvider, + $this->mailService + ); + + $result = $updateSubscriptionCarrierHandler->run(99); + + $this->assertCount(1, $result); + } + + public function testItUnsuccessfullyHandlesFailedToUpdateSubscription(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(1); + $this->configuration->expects($this->once())->method('updateValue'); + + $this->context->expects($this->once())->method('getShopId')->willReturn(1); + + $this->recurringOrderRepository->expects($this->once())->method('getAllOrdersBasedOnStatuses')->willReturn([ + [ + 'id' => 1, + 'mollie_customer_id' => 'test-mollie-customer-id', + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'id_cart' => 2, + 'id_recurring_product' => 3, + 'id_invoice_address' => 4, + 'id_delivery_address' => 5, + ], + ]); + + $this->cart->id = 3; + $this->cart->id_customer = 1; + $this->cart->id_address_delivery = 5; + $this->cart->id_currency = 1; + + $this->cart->expects($this->once())->method('getProducts')->willReturn([ + [ + 'total_price_tax_incl' => 10.00, + ], + ]); + + $this->cloneOriginalSubscriptionCartHandler->expects($this->once())->method('run')->willReturn($this->cart); + + $this->subscriptionOrderAmountProvider->expects($this->once())->method('get')->willReturn( + new Amount(20.00, 'EUR') + ); + + $this->updateSubscriptionAction->expects($this->once())->method('run')->willThrowException(new MollieException('', 0)); + + $this->updateRecurringOrderAction->expects($this->never())->method('run'); + + $this->mailService->expects($this->never())->method('sendSubscriptionCarrierUpdateMail'); + + $updateSubscriptionCarrierHandler = new UpdateSubscriptionCarrierHandler( + $this->configuration, + $this->recurringOrderRepository, + $this->context, + $this->updateSubscriptionAction, + $this->updateRecurringOrderAction, + $this->logger, + $this->cloneOriginalSubscriptionCartHandler, + $this->subscriptionOrderAmountProvider, + $this->mailService + ); + + $result = $updateSubscriptionCarrierHandler->run(99); + + $this->assertCount(1, $result); + } + + public function testItUnsuccessfullyHandlesFailedToUpdateRecurringOrder(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(1); + $this->configuration->expects($this->once())->method('updateValue'); + + $this->context->expects($this->once())->method('getShopId')->willReturn(1); + + $this->recurringOrderRepository->expects($this->once())->method('getAllOrdersBasedOnStatuses')->willReturn([ + [ + 'id' => 1, + 'mollie_customer_id' => 'test-mollie-customer-id', + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'id_cart' => 2, + 'id_recurring_product' => 3, + 'id_invoice_address' => 4, + 'id_delivery_address' => 5, + ], + ]); + + $this->cart->id = 3; + $this->cart->id_customer = 1; + $this->cart->id_address_delivery = 5; + $this->cart->id_currency = 1; + + $this->cart->expects($this->once())->method('getProducts')->willReturn([ + [ + 'total_price_tax_incl' => 10.00, + ], + ]); + + $this->cloneOriginalSubscriptionCartHandler->expects($this->once())->method('run')->willReturn($this->cart); + + $this->subscriptionOrderAmountProvider->expects($this->once())->method('get')->willReturn( + new Amount(20.00, 'EUR') + ); + + $this->updateSubscriptionAction->expects($this->once())->method('run'); + + $this->updateRecurringOrderAction->expects($this->once())->method('run')->willThrowException(new MollieException('', 0)); + + $this->mailService->expects($this->never())->method('sendSubscriptionCarrierUpdateMail'); + + $updateSubscriptionCarrierHandler = new UpdateSubscriptionCarrierHandler( + $this->configuration, + $this->recurringOrderRepository, + $this->context, + $this->updateSubscriptionAction, + $this->updateRecurringOrderAction, + $this->logger, + $this->cloneOriginalSubscriptionCartHandler, + $this->subscriptionOrderAmountProvider, + $this->mailService + ); + + $result = $updateSubscriptionCarrierHandler->run(99); + + $this->assertCount(1, $result); + } + + public function testItUnsuccessfullyHandlesFailedToSendSubscriptionCarrierUpdateMail(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(1); + $this->configuration->expects($this->once())->method('updateValue'); + + $this->context->expects($this->once())->method('getShopId')->willReturn(1); + + $this->recurringOrderRepository->expects($this->once())->method('getAllOrdersBasedOnStatuses')->willReturn([ + [ + 'id' => 1, + 'mollie_customer_id' => 'test-mollie-customer-id', + 'mollie_subscription_id' => 'test-mollie-subscription-id', + 'id_cart' => 2, + 'id_recurring_product' => 3, + 'id_invoice_address' => 4, + 'id_delivery_address' => 5, + ], + ]); + + $this->cart->id = 3; + $this->cart->id_customer = 1; + $this->cart->id_address_delivery = 5; + $this->cart->id_currency = 1; + + $this->cart->expects($this->once())->method('getProducts')->willReturn([ + [ + 'total_price_tax_incl' => 10.00, + ], + ]); + + $this->cloneOriginalSubscriptionCartHandler->expects($this->once())->method('run')->willReturn($this->cart); + + $this->subscriptionOrderAmountProvider->expects($this->once())->method('get')->willReturn( + new Amount(20.00, 'EUR') + ); + + $this->updateSubscriptionAction->expects($this->once())->method('run'); + + $this->updateRecurringOrderAction->expects($this->once())->method('run'); + + $this->mailService->expects($this->once())->method('sendSubscriptionCarrierUpdateMail')->willThrowException(new MollieException('', 0)); + + $updateSubscriptionCarrierHandler = new UpdateSubscriptionCarrierHandler( + $this->configuration, + $this->recurringOrderRepository, + $this->context, + $this->updateSubscriptionAction, + $this->updateRecurringOrderAction, + $this->logger, + $this->cloneOriginalSubscriptionCartHandler, + $this->subscriptionOrderAmountProvider, + $this->mailService + ); + + $result = $updateSubscriptionCarrierHandler->run(99); + + $this->assertCount(1, $result); + } +} diff --git a/tests/Unit/Subscription/Handler/index.php b/tests/Unit/Subscription/Handler/index.php new file mode 100644 index 000000000..d3343995f --- /dev/null +++ b/tests/Unit/Subscription/Handler/index.php @@ -0,0 +1,20 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/tests/Unit/Subscription/Presenter/OrderDetailPresenterTest.php b/tests/Unit/Subscription/Presenter/OrderDetailPresenterTest.php index e47429dba..48658fd70 100644 --- a/tests/Unit/Subscription/Presenter/OrderDetailPresenterTest.php +++ b/tests/Unit/Subscription/Presenter/OrderDetailPresenterTest.php @@ -14,9 +14,9 @@ use Mollie\Adapter\Context; use Mollie\Api\Types\SubscriptionStatus; -use Mollie\Repository\CurrencyRepositoryInterface; use Mollie\Repository\OrderRepositoryInterface; use Mollie\Repository\ProductRepositoryInterface; +use Mollie\Shared\Core\Shared\Repository\CurrencyRepositoryInterface; use Mollie\Subscription\Exception\CouldNotPresentOrderDetail; use Mollie\Subscription\Exception\ExceptionCode; use Mollie\Subscription\Presenter\OrderDetailPresenter; diff --git a/tests/Unit/Provider/SubscriptionIntervalTest.php b/tests/Unit/Subscription/Provider/SubscriptionIntervalTest.php similarity index 89% rename from tests/Unit/Provider/SubscriptionIntervalTest.php rename to tests/Unit/Subscription/Provider/SubscriptionIntervalTest.php index e4f15b3bf..5635c9038 100644 --- a/tests/Unit/Provider/SubscriptionIntervalTest.php +++ b/tests/Unit/Subscription/Provider/SubscriptionIntervalTest.php @@ -10,13 +10,14 @@ * @codingStandardsIgnoreStart */ -namespace Mollie\Subscription\Tests\Unit\Provider; +namespace Mollie\Tests\Unit\Subscription\Provider; use Mollie\Adapter\ConfigurationAdapter; use Mollie\Subscription\Config\Config; use Mollie\Subscription\DTO\Object\Interval; use Mollie\Subscription\Exception\SubscriptionIntervalException; use Mollie\Subscription\Provider\SubscriptionIntervalProvider; +use Mollie\Subscription\Repository\CombinationRepositoryInterface; use PHPUnit\Framework\TestCase; class SubscriptionIntervalTest extends TestCase @@ -27,20 +28,26 @@ class SubscriptionIntervalTest extends TestCase public function testGetSubscriptionInterval(array $attributeId, array $mockedGetResults, ?Interval $expectedInterval): void { $configurationMock = $this->createMock(ConfigurationAdapter::class); - $configurationMock - ->expects($this->any()) - ->method('get') - ->will( - $this->returnValueMap($mockedGetResults) - ); - $subscriptionIntervalProvider = new SubscriptionIntervalProvider($configurationMock); + $configurationMock->method('get')->will( + $this->returnValueMap($mockedGetResults) + ); + + $combination = $this->createMock('Combination'); + $combination->method('getWsProductOptionValues')->willReturn($attributeId); + + $combinationRepository = $this->createMock(CombinationRepositoryInterface::class); + $combinationRepository->method('findOneBy')->willReturn($combination); + + $subscriptionIntervalProvider = new SubscriptionIntervalProvider( + $configurationMock, + $combinationRepository + ); if ($expectedInterval === null) { $this->expectException(SubscriptionIntervalException::class); } - $combination = $this->createMock('Combination'); - $combination->method('getWsProductOptionValues')->willReturn($attributeId); - $description = $subscriptionIntervalProvider->getSubscriptionInterval($combination); + + $description = $subscriptionIntervalProvider->getSubscriptionInterval(1); $this->assertEquals($expectedInterval, $description); } diff --git a/tests/Unit/Subscription/Provider/SubscriptionOrderAmountProviderTest.php b/tests/Unit/Subscription/Provider/SubscriptionOrderAmountProviderTest.php new file mode 100644 index 000000000..3fe6f176a --- /dev/null +++ b/tests/Unit/Subscription/Provider/SubscriptionOrderAmountProviderTest.php @@ -0,0 +1,107 @@ +subscriptionCarrierDeliveryPriceProvider = $this->mock(SubscriptionCarrierDeliveryPriceProvider::class); + } + + public function testItSuccessfullyProvidesData(): void + { + $this->subscriptionCarrierDeliveryPriceProvider->expects($this->once())->method('getPrice')->willReturn(12.34); + + $currency = $this->mock(\Currency::class); + + $currency->iso_code = 'EUR'; + + $this->currencyRepository->expects($this->once())->method('findOrFail')->willReturn($currency); + + $subscriptionOrderAmountProvider = new SubscriptionOrderAmountProvider( + $this->subscriptionCarrierDeliveryPriceProvider, + $this->currencyRepository + ); + + $result = $subscriptionOrderAmountProvider->get(SubscriptionOrderAmountProviderData::create( + 1, + 2, + 3, + [], + 4, + 5, + 10.00 + )); + + $this->assertEquals([ + 'value' => 22.34, + 'currency' => 'EUR', + ], $result->toArray()); + } + + public function testItUnsuccessfullyProvidesDataFailedToProvideCarrierDeliveryPrice(): void + { + $this->subscriptionCarrierDeliveryPriceProvider->expects($this->once())->method('getPrice')->willThrowException(new \Exception('', 0)); + + $this->currencyRepository->expects($this->never())->method('findOneBy'); + + $subscriptionOrderAmountProvider = new SubscriptionOrderAmountProvider( + $this->subscriptionCarrierDeliveryPriceProvider, + $this->currencyRepository + ); + + $this->expectException(CouldNotProvideSubscriptionOrderAmount::class); + $this->expectExceptionCode(ExceptionCode::ORDER_FAILED_TO_PROVIDE_CARRIER_DELIVERY_PRICE); + + $subscriptionOrderAmountProvider->get(SubscriptionOrderAmountProviderData::create( + 1, + 2, + 3, + [], + 4, + 5, + 10.00 + )); + } + + public function testItUnsuccessfullyProvidesDataFailedToFindCurrency(): void + { + $this->subscriptionCarrierDeliveryPriceProvider->expects($this->once())->method('getPrice')->willReturn(12.34); + + $this->currencyRepository->expects($this->once())->method('findOrFail')->willThrowException(MollieDatabaseException::failedToFindRecord(\Currency::class, [])); + + $subscriptionOrderAmountProvider = new SubscriptionOrderAmountProvider( + $this->subscriptionCarrierDeliveryPriceProvider, + $this->currencyRepository + ); + + $this->expectException(MollieDatabaseException::class); + $this->expectExceptionCode(GeneralExceptionCode::INFRASTRUCTURE_FAILED_TO_FIND_RECORD); + $this->expectExceptionMessageRegExp('/' . \Currency::class . '/'); + + $subscriptionOrderAmountProvider->get(SubscriptionOrderAmountProviderData::create( + 1, + 2, + 3, + [], + 4, + 5, + 10.00 + )); + } +} diff --git a/tests/Unit/Subscription/Provider/index.php b/tests/Unit/Subscription/Provider/index.php new file mode 100644 index 000000000..88355f610 --- /dev/null +++ b/tests/Unit/Subscription/Provider/index.php @@ -0,0 +1,11 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Tests\Unit\Subscription\Validator; + +use Mollie\Repository\CarrierRepositoryInterface; +use Mollie\Subscription\Exception\CouldNotValidateSubscriptionSettings; +use Mollie\Subscription\Exception\ExceptionCode; +use Mollie\Subscription\Validator\SubscriptionSettingsValidator; +use Mollie\Tests\Unit\BaseTestCase; + +class SubscriptionSettingsValidatorTest extends BaseTestCase +{ + /** @var CarrierRepositoryInterface */ + private $carrieRepository; + + public function setUp(): void + { + $this->carrieRepository = $this->mock(CarrierRepositoryInterface::class); + + parent::setUp(); + } + + public function testItSuccessfullyValidatesSubscriptionSettings(): void + { + $this->configuration->expects($this->exactly(2))->method('get')->willReturnOnConsecutiveCalls(true, 1); + + $carrier = $this->mock(\Carrier::class); + + $this->carrieRepository->expects($this->once())->method('findOneBy')->willReturn($carrier); + + $subscriptionSettingsValidator = new SubscriptionSettingsValidator( + $this->configuration, + $this->carrieRepository + ); + + $result = $subscriptionSettingsValidator->validate(); + + $this->assertEquals(true, $result); + } + + public function testItUnsuccessfullyValidatesSubscriptionSettingsSubscriptionInactive(): void + { + $this->configuration->expects($this->once())->method('get')->willReturn(false); + + $subscriptionSettingsValidator = new SubscriptionSettingsValidator( + $this->configuration, + $this->carrieRepository + ); + + $this->expectException(CouldNotValidateSubscriptionSettings::class); + $this->expectExceptionCode(ExceptionCode::CART_SUBSCRIPTION_SERVICE_DISABLED); + + $subscriptionSettingsValidator->validate(); + } + + public function testItUnsuccessfullyValidatesSubscriptionSettingsSubscriptionCarrierInvalid(): void + { + $this->configuration->expects($this->exactly(2))->method('get')->willReturnOnConsecutiveCalls(true, 1); + + $this->carrieRepository->expects($this->once())->method('findOneBy')->willReturn(null); + + $subscriptionSettingsValidator = new SubscriptionSettingsValidator( + $this->configuration, + $this->carrieRepository + ); + + $this->expectException(CouldNotValidateSubscriptionSettings::class); + $this->expectExceptionCode(ExceptionCode::CART_SUBSCRIPTION_CARRIER_INVALID); + + $subscriptionSettingsValidator->validate(); + } +} diff --git a/tests/Unit/Subscription/Validator/index.php b/tests/Unit/Subscription/Validator/index.php new file mode 100644 index 000000000..d3343995f --- /dev/null +++ b/tests/Unit/Subscription/Validator/index.php @@ -0,0 +1,20 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/tests/seed/settings1785/defines.inc.php b/tests/seed/settings1785/defines.inc.php new file mode 100644 index 000000000..e69de29bb diff --git a/tests/seed/settings1785/parameters.php b/tests/seed/settings1785/parameters.php new file mode 100755 index 000000000..e69de29bb diff --git a/upgrade/Upgrade-6.0.6.php b/upgrade/Upgrade-6.0.6.php new file mode 100644 index 000000000..206269a22 --- /dev/null +++ b/upgrade/Upgrade-6.0.6.php @@ -0,0 +1,32 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +use Mollie\Adapter\ConfigurationAdapter; +use Mollie\Config\Config; + +if (!defined('_PS_VERSION_')) { + exit; +} + +function upgrade_module_6_0_6(Mollie $module): bool +{ + updateConfigurationValues606($module); + + return true; +} + +function updateConfigurationValues606(Mollie $module) +{ + /** @var ConfigurationAdapter $configuration */ + $configuration = $module->getService(ConfigurationAdapter::class); + + $configuration->updateValue(Config::MOLLIE_SUBSCRIPTION_ENABLED, '0'); +} diff --git a/views/js/front/subscription/product.js b/views/js/front/subscription/product.js index 07977b18d..b7f8c6ad8 100644 --- a/views/js/front/subscription/product.js +++ b/views/js/front/subscription/product.js @@ -62,12 +62,20 @@ $(document).ready(function () { action: 'validateProduct', product: product }, - success: function (response) { - response = jQuery.parseJSON(response); + error: function (error) { + const response = jQuery.parseJSON(error.responseText); - if (!response.isValid) { - noticeMessage(response.message); + const responseErrors = response.errors; + + if ($.isArray(responseErrors)) { + console.error(responseErrors[0]) + noticeMessage(responseErrors[0]); + + return; } + + console.error(responseErrors) + noticeMessage(responseErrors); } }) } diff --git a/views/templates/admin/Subscription/subscriptions-settings.html.twig b/views/templates/admin/Subscription/subscriptions-settings.html.twig index 7c3806775..916c9eac3 100644 --- a/views/templates/admin/Subscription/subscriptions-settings.html.twig +++ b/views/templates/admin/Subscription/subscriptions-settings.html.twig @@ -26,7 +26,15 @@ settings {{ 'Subscription options'|trans }}
-
+
+
+ {# TODO translations will be enabled only after we will migrate to moden translation system #} + {{ ps.label_with_help('Enable subscription service'|trans, 'Toggle this option to enable products to be bought as recurring items.'|trans) }} +
+ {{ form_errors(subscriptionOptions.enable_subscriptions) }} + {{ form_widget(subscriptionOptions.enable_subscriptions) }} +
+
{# TODO translations will be enabled only after we will migrate to moden translation system #} {{ ps.label_with_help('Carrier to use in subscription orders'|trans, 'WARNING: do not change selection after getting first subscription order.'|trans) }} diff --git a/views/templates/admin/_configure/helpers/form/form.tpl b/views/templates/admin/_configure/helpers/form/form.tpl index 8914d7d95..c160578c6 100644 --- a/views/templates/admin/_configure/helpers/form/form.tpl +++ b/views/templates/admin/_configure/helpers/form/form.tpl @@ -199,7 +199,9 @@ {if !in_array($paymentMethod.id, $input.onlyOrderMethods)} {/if} - + {if !in_array($paymentMethod.id, $input.onlyPaymentsMethods)} + + {/if}

{$input.methodDescription} @@ -363,7 +365,7 @@ + value="{if $methodObj->surcharge_limit === $paymentMethod.maximumAmount}{else}{$methodObj->surcharge_limit|escape:'html':'UTF-8'}{/if}">

diff --git a/views/templates/front/order-confirmation-table.tpl b/views/templates/front/order-confirmation-table.tpl index 82dfef426..cd169d02e 100644 --- a/views/templates/front/order-confirmation-table.tpl +++ b/views/templates/front/order-confirmation-table.tpl @@ -24,7 +24,7 @@ {if is_array($product.customizations) && $product.customizations|count} {foreach from=$product.customizations item="customization"}