diff --git a/composer.json b/composer.json index c619dad2..1bde58ef 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^7.4 || ^8.0", "sylius/sylius": "~1.9.0 || ~1.10.0 || ~1.11.0 || ~1.12.0", "symfony/messenger": "^4.4 || ^5.2 || ^6.0", - "mollie/mollie-api-php": "^2.0", + "mollie/mollie-api-php": "^2.64.0", "sylius/refund-plugin": "^1.0", "sylius/admin-order-creation-plugin": "^0.12 || ^0.13 || v0.14", "ext-json": "*", diff --git a/doc/installation.md b/doc/installation.md index d0ae3353..9bbd18f3 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -450,7 +450,18 @@ sylius_mollie_plugin: #### 12. Update your database -Apply migration to your database: +Apply migration to your database: (this is for the plugin fresh installation only) +``` +bin/console doctrine:migrations:migrate +``` + +In case if you are updating from older version of plugin (versions < 5.0), you will need to run the following commands before running migrate command. +``` +bin/console doctrine:migrations:version --add --range-from='SyliusMolliePlugin\Migrations\Version20200513092722' --range-to='SyliusMolliePlugin\Migrations\Version20220211040328' +bin/console doctrine:migrations:execute --up 'SyliusMolliePlugin\Migrations\Version20231225151033' +``` + +After running all the above-mentioned commands, run migrate command ``` bin/console doctrine:migrations:migrate ``` diff --git a/src/Action/Api/BaseApiAwareAction.php b/src/Action/Api/BaseApiAwareAction.php index f08f2c9f..66522673 100644 --- a/src/Action/Api/BaseApiAwareAction.php +++ b/src/Action/Api/BaseApiAwareAction.php @@ -23,4 +23,22 @@ public function setApi($mollieApiClient): void $this->mollieApiClient = $mollieApiClient; } + + /** + * Checks if payment should be refunded. As long as there are order items to be refunded, payment will be refunded. + * + * @param \ArrayObject $details + * + * @return bool + */ + public function shouldBeRefunded(\ArrayObject $details): bool + { + if (isset($details['metadata']['refund']) && array_key_exists('items', $details['metadata']['refund'])) { + $items = $details['metadata']['refund']['items']; + + return count($items) > 0 && !empty($items[0]); + } + + return false; + } } diff --git a/src/Action/Api/CreatePaymentAction.php b/src/Action/Api/CreatePaymentAction.php index 235d2453..a0f5304c 100644 --- a/src/Action/Api/CreatePaymentAction.php +++ b/src/Action/Api/CreatePaymentAction.php @@ -5,8 +5,10 @@ namespace SyliusMolliePlugin\Action\Api; +use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository; use SyliusMolliePlugin\Logger\MollieLoggerActionInterface; use SyliusMolliePlugin\Parser\Response\GuzzleNegativeResponseParserInterface; +use SyliusMolliePlugin\Repository\CustomerRepository; use SyliusMolliePlugin\Request\Api\CreatePayment; use Mollie\Api\Exceptions\ApiException; use Mollie\Api\Resources\Payment; @@ -29,14 +31,21 @@ final class CreatePaymentAction extends BaseApiAwareAction /** @var RequestStack */ private $requestStack; + /** + * @var CustomerRepository + */ + private $customerRepository; + public function __construct( MollieLoggerActionInterface $loggerAction, GuzzleNegativeResponseParserInterface $guzzleNegativeResponseParser, - RequestStack $requestStack + RequestStack $requestStack, + EntityRepository $customerRepository ) { $this->loggerAction = $loggerAction; $this->guzzleNegativeResponseParser = $guzzleNegativeResponseParser; $this->requestStack = $requestStack; + $this->customerRepository = $customerRepository; } public function execute($request): void @@ -63,8 +72,26 @@ public function execute($request): void $paymentDetails['locale'] = $details['locale']; } + if (isset($details['metadata']['saveCardInfo']) && $details['metadata']['saveCardInfo'] === "0") { + unset($paymentDetails['customerId']); + } + + if (isset($details['metadata']['useSavedCards']) && $details['metadata']['useSavedCards'] === "1") { + unset($paymentDetails['cardToken']); + } + /** @var Payment $payment */ $payment = $this->mollieApiClient->payments->create($paymentDetails); + + if (isset($details['metadata']['saveCardInfo'])) { + $valueToUpdate = $details['metadata']['saveCardInfo'] === '1' ? '1' : null; + $existingCustomer = $this->customerRepository->findOneBy(['profileId' => $details['customerId']]); + + if ($existingCustomer) { + $existingCustomer->setIsCreditCardSaved($valueToUpdate); + $this->customerRepository->add($existingCustomer); + } + } } catch (ApiException $e) { $message = $this->guzzleNegativeResponseParser->parse($e); $this->loggerAction->addNegativeLog(sprintf('Error with create payment with: %s', $e->getMessage())); diff --git a/src/Action/ConvertMolliePaymentAction.php b/src/Action/ConvertMolliePaymentAction.php index 05740a2a..cb3a5e98 100644 --- a/src/Action/ConvertMolliePaymentAction.php +++ b/src/Action/ConvertMolliePaymentAction.php @@ -104,10 +104,14 @@ public function execute($request): void if (isset($paymentOptions['metadata'])) { $paymentMethod = $paymentOptions['metadata']['molliePaymentMethods'] ?? null; $cartToken = $paymentOptions['metadata']['cartToken']; + $saveCardInfo = $paymentOptions['metadata']['saveCardInfo']; + $useSavedCards = $paymentOptions['metadata']['useSavedCards']; $selectedIssuer = PaymentMethod::IDEAL === $paymentMethod ? $paymentOptions['metadata']['selected_issuer'] : null; } else { $paymentMethod = $paymentOptions['molliePaymentMethods'] ?? null; $cartToken = $paymentOptions['cartToken']; + $saveCardInfo = $paymentOptions['saveCardInfo']; + $useSavedCards = $paymentOptions['useSavedCards']; $selectedIssuer = PaymentMethod::IDEAL === $paymentMethod ? $paymentOptions['issuers']['id'] : null; } @@ -125,6 +129,8 @@ public function execute($request): void 'customer_id' => $customer->getId() ?? null, 'molliePaymentMethods' => $paymentMethod ?? null, 'cartToken' => $cartToken ?? null, + 'saveCardInfo' => $saveCardInfo ?? null, + 'useSavedCards' => $useSavedCards ?? null, 'selected_issuer' => $selectedIssuer ?? null, ], 'full_name' => $customer->getFullName() ?? null, diff --git a/src/Action/RefundAction.php b/src/Action/RefundAction.php index e029ca50..215ff5ff 100644 --- a/src/Action/RefundAction.php +++ b/src/Action/RefundAction.php @@ -57,7 +57,7 @@ public function execute($request): void throw new \Exception(sprintf('API call failed: %s', htmlspecialchars($e->getMessage()))); } - if ($molliePayment->hasRefunds()) { + if (!$this->shouldBeRefunded($details)) { return; } diff --git a/src/Action/RefundOrderAction.php b/src/Action/RefundOrderAction.php index ccbdc9bb..27213b58 100644 --- a/src/Action/RefundOrderAction.php +++ b/src/Action/RefundOrderAction.php @@ -46,7 +46,7 @@ public function execute($request): void $details = ArrayObject::ensureArrayObject($request->getModel()); - if (!array_key_exists('refund', $details['metadata'])) { + if (!array_key_exists('refund', $details['metadata']) || !$this->shouldBeRefunded($details)) { return; } @@ -76,10 +76,6 @@ public function execute($request): void Assert::notNull($molliePayment); - if ($molliePayment->hasRefunds()) { - return; - } - /** @var PaymentInterface $payment */ $payment = $request->getFirstModel(); diff --git a/src/Creator/ApiKeysTestCreator.php b/src/Creator/ApiKeysTestCreator.php index 063a750f..f71b23ba 100644 --- a/src/Creator/ApiKeysTestCreator.php +++ b/src/Creator/ApiKeysTestCreator.php @@ -65,7 +65,10 @@ private function testApiKey(ApiKeyTest $apiKeyTest, string $apiKey): ApiKeyTest $client = $this->mollieApiClient->setApiKey($apiKey); /** @var MethodCollection $methods */ - $methods = $client->methods->allActive(MollieMethodsResolverInterface::PARAMETERS); + $methods = $client->methods->allAvailable(MollieMethodsResolverInterface::PARAMETERS_AVAILABLE); + $filteredMethods = array_filter($methods->getArrayCopy(), array($this, 'filterActiveMethods')); + $methods->exchangeArray($filteredMethods); + $apiKeyTest->setMethods($methods); return $apiKeyTest; @@ -85,4 +88,9 @@ private function testApiKey(ApiKeyTest $apiKeyTest, string $apiKey): ApiKeyTest return $apiKeyTest; } } + + private function filterActiveMethods($method): bool + { + return $method->status === 'activated'; + } } diff --git a/src/EmailSender/PaymentLinkEmailSender.php b/src/EmailSender/PaymentLinkEmailSender.php index 11c2f10c..ff1c20a1 100644 --- a/src/EmailSender/PaymentLinkEmailSender.php +++ b/src/EmailSender/PaymentLinkEmailSender.php @@ -32,10 +32,10 @@ public function __construct( public function sendConfirmationEmail(OrderInterface $order, TemplateMollieEmailTranslationInterface $template): void { - /** @var PaymentInterface $payment */ + /** @var PaymentInterface|null $payment */ $payment = $order->getPayments()->last(); - if (0 === count($payment->getDetails())) { + if (false === $payment || 0 === count($payment->getDetails())) { return; } diff --git a/src/Entity/MollieCustomer.php b/src/Entity/MollieCustomer.php index 8078975d..ac6a7e03 100644 --- a/src/Entity/MollieCustomer.php +++ b/src/Entity/MollieCustomer.php @@ -18,6 +18,9 @@ class MollieCustomer implements ResourceInterface, MollieCustomerInterface /** @var string */ protected $email; + /** @var string|null */ + protected $isCreditCardSaved; + public function getId(): int { return $this->id; @@ -42,4 +45,14 @@ public function setEmail(string $email): void { $this->email = $email; } + + public function isCreditCardSaved(): ?string + { + return $this->isCreditCardSaved; + } + + public function setIsCreditCardSaved(?string $isCreditCardSaved): void + { + $this->isCreditCardSaved = $isCreditCardSaved; + } } diff --git a/src/Entity/MollieCustomerInterface.php b/src/Entity/MollieCustomerInterface.php index 1913f8c3..472d84b6 100644 --- a/src/Entity/MollieCustomerInterface.php +++ b/src/Entity/MollieCustomerInterface.php @@ -16,4 +16,8 @@ public function setProfileId(string $profileId): void; public function getEmail(): string; public function setEmail(string $email): void; + + public function isCreditCardSaved(): ?string; + + public function setIsCreditCardSaved(?string $isCreditCardSaved): void; } diff --git a/src/Entity/MollieGatewayConfig.php b/src/Entity/MollieGatewayConfig.php index d7d2eaaa..6ab9edf2 100644 --- a/src/Entity/MollieGatewayConfig.php +++ b/src/Entity/MollieGatewayConfig.php @@ -30,6 +30,9 @@ class MollieGatewayConfig extends AbstractMethod implements ResourceInterface, M /** @var PaymentSurchargeFeeInterface|null */ protected $paymentSurchargeFee; + /** @var MollieMinMaxInterface|null */ + protected $amountLimits; + /** @var MollieMethodImageInterface|null */ protected $customizeMethodImage; @@ -205,6 +208,16 @@ public function setPosition(?int $position): void $this->position = $position; } + public function getAmountLimits(): ?MollieMinMaxInterface + { + return $this->amountLimits; + } + + public function setAmountLimits(?MollieMinMaxInterface $amountLimits): void + { + $this->amountLimits = $amountLimits; + } + protected function createTranslation(): TranslationInterface { return new MollieGatewayConfigTranslation(); diff --git a/src/Entity/MollieGatewayConfigInterface.php b/src/Entity/MollieGatewayConfigInterface.php index 88f138e8..ebeab261 100644 --- a/src/Entity/MollieGatewayConfigInterface.php +++ b/src/Entity/MollieGatewayConfigInterface.php @@ -68,5 +68,9 @@ public function getPosition(): ?int; public function setPosition(?int $position): void; + public function getAmountLimits(): ?MollieMinMaxInterface; + + public function setAmountLimits(?MollieMinMaxInterface $amountLimits): void; + public function hasTranslationLocale(string $localeCode): bool; } diff --git a/src/Entity/MollieMinMax.php b/src/Entity/MollieMinMax.php new file mode 100644 index 00000000..04d9c472 --- /dev/null +++ b/src/Entity/MollieMinMax.php @@ -0,0 +1,58 @@ +id; + } + + public function getMinimumAmount(): ?float + { + return $this->minimumAmount; + } + + public function setMinimumAmount(?float $minimumAmount): void + { + $this->minimumAmount = $minimumAmount; + } + + public function getMaximumAmount(): ?float + { + return $this->maximumAmount; + } + + public function setMaximumAmount(?float $maximumAmount): void + { + $this->maximumAmount = $maximumAmount; + } + + public function getMollieGatewayConfig(): ?MollieGatewayConfigInterface + { + return $this->mollieGatewayConfig; + } + + public function setMollieGatewayConfig(?MollieGatewayConfigInterface $mollieGatewayConfig): void + { + $this->mollieGatewayConfig = $mollieGatewayConfig; + } + +} diff --git a/src/Entity/MollieMinMaxInterface.php b/src/Entity/MollieMinMaxInterface.php new file mode 100644 index 00000000..8e76711f --- /dev/null +++ b/src/Entity/MollieMinMaxInterface.php @@ -0,0 +1,22 @@ +setMethodId($method->getMethodId()); $mollieGatewayConfig->setName($method->getName()); $mollieGatewayConfig->setMinimumAmount($method->getMinimumAmount()); - $mollieGatewayConfig->setMaximumAmount($method->getMinimumAmount()); + $mollieGatewayConfig->setMaximumAmount($method->getMaximumAmount()); $mollieGatewayConfig->setImage($method->getImage()); $mollieGatewayConfig->setGateway($gateway); $mollieGatewayConfig->setIssuers($method->getIssuers()); diff --git a/src/Form/Extension/GatewayConfigTypeExtension.php b/src/Form/Extension/GatewayConfigTypeExtension.php index 2b7c353e..16be0a75 100644 --- a/src/Form/Extension/GatewayConfigTypeExtension.php +++ b/src/Form/Extension/GatewayConfigTypeExtension.php @@ -7,6 +7,7 @@ use SyliusMolliePlugin\Form\Type\MollieGatewayConfigType; use Sylius\Bundle\PayumBundle\Form\Type\GatewayConfigType; +use SyliusMolliePlugin\Validator\Constraints\MollieGatewayConfigValidatorType; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\FormBuilderInterface; @@ -24,6 +25,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'validation_groups' => ['sylius'], 'constraints' => [ new Valid(), + new MollieGatewayConfigValidatorType(['groups' => 'sylius']) ], ] ); diff --git a/src/Form/Type/MollieGatewayConfigType.php b/src/Form/Type/MollieGatewayConfigType.php index 316314e1..fc401a32 100644 --- a/src/Form/Type/MollieGatewayConfigType.php +++ b/src/Form/Type/MollieGatewayConfigType.php @@ -14,6 +14,7 @@ use SyliusMolliePlugin\Options\Country\Options as CountryOptions; use SyliusMolliePlugin\Payments\Methods\AbstractMethod; use SyliusMolliePlugin\Payments\PaymentTerms\Options; +use SyliusMolliePlugin\Validator\Constraints\MollieMinMaxValidatorType; use SyliusMolliePlugin\Validator\Constraints\PaymentSurchargeType; use Sylius\Bundle\ProductBundle\Form\Type\ProductType as ProductFormType; use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType; @@ -85,6 +86,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => false, 'constraints' => [new PaymentSurchargeType(['groups' => 'sylius'])], ]) + ->add('amountLimits', MollieMinMaxType::class, [ + 'label' => false, + 'required' => false, + ]) ->add('customizeMethodImage', CustomizeMethodImageType::class, [ 'label' => false, ]) diff --git a/src/Form/Type/MollieGatewayConfigurationType.php b/src/Form/Type/MollieGatewayConfigurationType.php index c5c80e42..3374f11c 100644 --- a/src/Form/Type/MollieGatewayConfigurationType.php +++ b/src/Form/Type/MollieGatewayConfigurationType.php @@ -119,10 +119,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void { $data = $event->getData(); - if (isset($data['components']) && true === $data['components']) { - $data['single_click_enabled'] = false; - } - $data['payum.http_client'] = '@sylius_mollie_plugin.mollie_api_client'; $event->setData($data); diff --git a/src/Form/Type/MollieMinMaxType.php b/src/Form/Type/MollieMinMaxType.php new file mode 100644 index 00000000..85d73809 --- /dev/null +++ b/src/Form/Type/MollieMinMaxType.php @@ -0,0 +1,41 @@ +add('minimumAmount', NumberType::class, [ + 'label' => 'sylius_mollie_plugin.ui.min_amount', + 'required' => false, + 'constraints' => [ + new GreaterThan([ + 'value' => 0, + 'message' => 'sylius_mollie_plugin.form.error.greater_than', + 'groups' => ['sylius'], + ]) + ], + ]) + ->add('maximumAmount', NumberType::class, [ + 'label' => 'sylius_mollie_plugin.ui.max_amount', + 'required' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => MollieMinMax::class, + ]); + } +} diff --git a/src/Form/Type/PaymentMollieType.php b/src/Form/Type/PaymentMollieType.php index ee012bb5..6d704910 100644 --- a/src/Form/Type/PaymentMollieType.php +++ b/src/Form/Type/PaymentMollieType.php @@ -19,7 +19,8 @@ final class PaymentMollieType extends AbstractType public function __construct( MolliePaymentsMethodResolverInterface $methodResolver - ) { + ) + { $this->methodResolver = $methodResolver; } @@ -56,6 +57,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void return ['image' => $value->image->svg]; }, ]) - ->add('cartToken', HiddenType::class); + ->add('cartToken', HiddenType::class) + ->add('saveCardInfo', HiddenType::class) + ->add('useSavedCards', HiddenType::class); } } diff --git a/src/Menu/AdminOrderShowMenuListener.php b/src/Menu/AdminOrderShowMenuListener.php index 942210ee..d584fe57 100644 --- a/src/Menu/AdminOrderShowMenuListener.php +++ b/src/Menu/AdminOrderShowMenuListener.php @@ -28,7 +28,7 @@ public function addPaymentlinkButton(OrderShowMenuBuilderEvent $event): void /** @var ?PaymentInterface $payment */ $payment = $order->getPayments()->last(); - if (null === $payment) { + if (null === $payment || $payment === false) { return; } diff --git a/src/Migrations/Version20231020001352.php b/src/Migrations/Version20231020001352.php new file mode 100644 index 00000000..4808fa9b --- /dev/null +++ b/src/Migrations/Version20231020001352.php @@ -0,0 +1,29 @@ +addSql('ALTER TABLE mollie_customer ADD is_credit_card_saved VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE mollie_customer DROP COLUMN is_credit_card_saved'); + } +} diff --git a/src/Migrations/Version20231214133331.php b/src/Migrations/Version20231214133331.php new file mode 100644 index 00000000..7ce20161 --- /dev/null +++ b/src/Migrations/Version20231214133331.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE mollie_configuration_amount_limits (id INT AUTO_INCREMENT NOT NULL, min_amount DOUBLE PRECISION DEFAULT NULL, max_amount DOUBLE PRECISION DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET UTF8 COLLATE `UTF8_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE mollie_configuration ADD amount_limits_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE mollie_configuration ADD CONSTRAINT FK_CONFIG_AMOUNT_LIMITS FOREIGN KEY (amount_limits_id) REFERENCES mollie_configuration_amount_limits(id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE mollie_configuration DROP FOREIGN KEY FK_CONFIG_AMOUNT_LIMITS'); + $this->addSql('ALTER TABLE mollie_configuration DROP COLUMN amount_limits_id'); + $this->addSql('DROP TABLE mollie_configuration_amount_limits'); + } +} diff --git a/src/Migrations/Version20231225151033.php b/src/Migrations/Version20231225151033.php new file mode 100644 index 00000000..598eb810 --- /dev/null +++ b/src/Migrations/Version20231225151033.php @@ -0,0 +1,97 @@ +renameTableIfExists($schema, 'bitbag_onboarding_wizard_status', 'mollie_onboarding_wizard_status'); + if ($schema->hasTable('mollie_configuration')) { + return; + } + $this->renameTableIfExists($schema, 'bitbag_mollie_configuration', 'mollie_configuration'); + $this->renameTableIfExists($schema, 'bitbag_mollie_configuration_surcharge_fee', 'mollie_configuration_surcharge_fee'); + $this->renameTableIfExists($schema, 'bitbag_mollie_configuration_translation', 'mollie_configuration_translation'); + $this->renameTableIfExists($schema, 'bitbag_mollie_customer', 'mollie_customer'); + $this->renameTableIfExists($schema, 'bitbag_mollie_email_template', 'mollie_email_template'); + $this->renameTableIfExists($schema, 'bitbag_mollie_email_template_translation', 'mollie_email_template_translation'); + $this->renameTableIfExists($schema, 'bitbag_mollie_logger', 'mollie_logger'); + $this->renameTableIfExists($schema, 'bitbag_mollie_method_image', 'mollie_method_image'); + $this->renameTableIfExists($schema, 'bitbag_mollie_product_type', 'mollie_product_type'); + $this->renameTableIfExists($schema, 'bitbag_mollie_subscription', 'mollie_subscription'); + $this->renameTableIfExists($schema, 'bitbag_mollie_subscription_orders', 'mollie_subscription_orders'); + $this->renameTableIfExists($schema, 'bitbag_mollie_subscription_payments', 'mollie_subscription_payments'); + $this->renameTableIfExists($schema, 'bitbag_mollie_subscription_configuration', 'mollie_subscription_configuration'); + $this->renameTableIfExists($schema, 'bitbag_mollie_subscription_schedule', 'mollie_subscription_schedule'); + } + + /** + * @param Schema $schema + * + * @return void + */ + public function down(Schema $schema): void + { + $this->renameTableIfExists($schema, 'mollie_onboarding_wizard_status', 'bitbag_onboarding_wizard_status'); + $this->renameTableIfExists($schema, 'mollie_configuration', 'bitbag_mollie_configuration'); + $this->renameTableIfExists($schema, 'mollie_configuration_surcharge_fee', 'bitbag_mollie_configuration_surcharge_fee'); + $this->renameTableIfExists($schema, 'mollie_configuration_translation', 'bitbag_mollie_configuration_translation'); + $this->renameTableIfExists($schema, 'mollie_customer', 'bitbag_mollie_customer'); + $this->renameTableIfExists($schema, 'mollie_email_template', 'bitbag_mollie_email_template'); + $this->renameTableIfExists($schema, 'mollie_email_template_translation', 'bitbag_mollie_email_template_translation'); + $this->renameTableIfExists($schema, 'mollie_logger', 'bitbag_mollie_logger'); + $this->renameTableIfExists($schema, 'mollie_method_image', 'bitbag_mollie_method_image'); + $this->renameTableIfExists($schema, 'mollie_product_type', 'bitbag_mollie_product_type'); + $this->renameTableIfExists($schema, 'mollie_subscription', 'bitbag_mollie_subscription'); + $this->renameTableIfExists($schema, 'mollie_subscription_orders', 'bitbag_mollie_subscription_orders'); + $this->renameTableIfExists($schema, 'mollie_subscription_payments', 'bitbag_mollie_subscription_payments'); + $this->renameTableIfExists($schema, 'mollie_subscription_configuration', 'bitbag_mollie_subscription_configuration'); + $this->renameTableIfExists($schema, 'mollie_subscription_schedule', 'bitbag_mollie_subscription_schedule'); + } + + /** + * @return bool + */ + public function isTransactional(): bool + { + return false; + } + + /** + * @param Schema $schema + * @param string $oldTableName + * @param string $newTableName + * + * @return void + */ + private function renameTableIfExists(Schema $schema, string $oldTableName, string $newTableName): void + { + if ($schema->hasTable($newTableName)) { + return; + } + + if ($schema->hasTable($oldTableName)) { + $this->addSql(sprintf('RENAME TABLE %s TO %s', $oldTableName, $newTableName)); + } + } +} diff --git a/src/Order/OrderPaymentRefund.php b/src/Order/OrderPaymentRefund.php index 20862ea9..f3c78ab7 100644 --- a/src/Order/OrderPaymentRefund.php +++ b/src/Order/OrderPaymentRefund.php @@ -50,7 +50,7 @@ public function refund(UnitsRefunded $units): void /** @var PaymentInterface|null $payment */ $payment = $order->getPayments()->last(); - if (null === $payment) { + if (null === $payment || false === $payment) { $this->loggerAction->addNegativeLog(sprintf('Not fount payment in refund')); throw new NotFoundHttpException(); diff --git a/src/Payments/Methods/Twint.php b/src/Payments/Methods/Twint.php new file mode 100644 index 00000000..1d2307af --- /dev/null +++ b/src/Payments/Methods/Twint.php @@ -0,0 +1,18 @@ +createQueryBuilder('m') + ->leftJoin('m.amountLimits', 'al') + ->addSelect('al.minimumAmount') + ->addSelect('al.maximumAmount') ->where('m.enabled = true') ->andWhere('m.gateway = :gateway') ->setParameter('gateway', $gateway) @@ -21,4 +24,15 @@ public function findAllEnabledByGateway(GatewayConfigInterface $gateway): array ->getResult() ; } + + public function getExistingAmountLimitsById(int $id): array + { + return $this->createQueryBuilder('m') + ->select('m.minimumAmount') + ->addSelect('m.maximumAmount') + ->where('m.id = :id') + ->setParameter('id', $id) + ->getQuery() + ->getResult(); + } } diff --git a/src/Repository/MollieGatewayConfigRepositoryInterface.php b/src/Repository/MollieGatewayConfigRepositoryInterface.php index 2870e238..8090c0b2 100644 --- a/src/Repository/MollieGatewayConfigRepositoryInterface.php +++ b/src/Repository/MollieGatewayConfigRepositoryInterface.php @@ -11,4 +11,6 @@ interface MollieGatewayConfigRepositoryInterface extends RepositoryInterface { public function findAllEnabledByGateway(GatewayConfigInterface $gateway): array; + + public function getExistingAmountLimitsById(int $id): array; } diff --git a/src/Resolver/MollieMethodsResolver.php b/src/Resolver/MollieMethodsResolver.php index 716cb7f3..f426ad1a 100644 --- a/src/Resolver/MollieMethodsResolver.php +++ b/src/Resolver/MollieMethodsResolver.php @@ -77,7 +77,11 @@ public function createForGateway(GatewayConfigInterface $gateway): void $this->mollieMethodsCreator->createMethods($baseCollection, $gateway); } elseif (MollieGatewayFactory::FACTORY_NAME === $gateway->getFactoryName()) { /** @var MethodCollection $allMollieMethods */ - $allMollieMethods = $client->methods->allActive(self::PARAMETERS); + $allMollieMethods = $client->methods->allAvailable(self::PARAMETERS_AVAILABLE); + + $filteredMethods = array_filter($allMollieMethods->getArrayCopy(), array($this, 'filterActiveMethods')); + $allMollieMethods->exchangeArray($filteredMethods); + $this->mollieMethodsCreator->createMethods($allMollieMethods, $gateway); } else { $this->loggerAction->addLog(sprintf('Unable to download methods for "%s"', $gateway->getGatewayName())); @@ -87,4 +91,9 @@ public function createForGateway(GatewayConfigInterface $gateway): void $this->loggerAction->addLog(sprintf('Downloaded all methods from mollie API')); } + + private function filterActiveMethods($method): bool + { + return $method->status === 'activated'; + } } diff --git a/src/Resolver/MollieMethodsResolverInterface.php b/src/Resolver/MollieMethodsResolverInterface.php index 1b5b5ad2..243497f8 100644 --- a/src/Resolver/MollieMethodsResolverInterface.php +++ b/src/Resolver/MollieMethodsResolverInterface.php @@ -11,17 +11,22 @@ interface MollieMethodsResolverInterface { /** @var string[] */ - public const PARAMETERS = [ + public const PARAMETERS = [ 'include' => 'issuers', 'includeWallets' => 'applepay', 'resource' => 'orders', ]; - public const PARAMETERS_RECURRING = [ + public const PARAMETERS_RECURRING = [ 'include' => 'issuers', 'sequenceType' => 'recurring', ]; + /** @var string[] */ + public const PARAMETERS_AVAILABLE = [ + 'include' => 'issuers', + ]; + /** @var string[] */ public const UNSUPPORTED_METHODS = [ PaymentMethod::INGHOMEPAY, diff --git a/src/Resolver/MolliePaymentsMethodResolver.php b/src/Resolver/MolliePaymentsMethodResolver.php index cef9ab7d..9825a3a5 100644 --- a/src/Resolver/MolliePaymentsMethodResolver.php +++ b/src/Resolver/MolliePaymentsMethodResolver.php @@ -21,6 +21,10 @@ final class MolliePaymentsMethodResolver implements MolliePaymentsMethodResolverInterface { + private const MINIMUM_FIELD = 'minimumAmount'; + private const MAXIMUM_FIELD = 'maximumAmount'; + private const FIELD_VALUE = 'value'; + /** @var MollieGatewayConfigRepositoryInterface */ private $mollieGatewayRepository; @@ -90,7 +94,6 @@ public function resolve(): array private function getMolliePaymentOptions(MollieOrderInterface $order, string $countryCode): array { - $allowedMethods = []; $methods = $this->getDefaultOptions(); $factoryName = $this->mollieFactoryNameResolver->resolve($order); @@ -125,12 +128,7 @@ private function getMolliePaymentOptions(MollieOrderInterface $order, string $co return $this->getDefaultOptions(); } - /** @var MollieGatewayConfig $allowedMethod */ - foreach ($paymentConfigs as $allowedMethod) { - if (in_array($allowedMethod->getMethodId(), $allowedMethodsIds, true)) { - $allowedMethods[] = $allowedMethod; - } - } + $allowedMethods = $this->filterPaymentMethods($paymentConfigs, $allowedMethodsIds, (float)$order->getTotal()/100); if (0 === count($allowedMethods)) { return $this->getDefaultOptions(); @@ -148,6 +146,39 @@ private function getMolliePaymentOptions(MollieOrderInterface $order, string $co return $this->productVoucherTypeChecker->checkTheProductTypeOnCart($order, $methods); } + private function filterPaymentMethods(array $paymentConfigs, array $allowedMethodsIds, float $orderTotal) : array + { + $allowedMethods = []; + + /** @var MollieGatewayConfig $allowedMethod */ + foreach ($paymentConfigs as $allowedMethod) { + if (!empty($allowedMethod[0]) && in_array($allowedMethod[0]->getMethodId(), $allowedMethodsIds, true)) { + + $minAmountLimit = $allowedMethod[self::MINIMUM_FIELD]; + if ($minAmountLimit === null && $allowedMethod !== null && $allowedMethod[0]->getMinimumAmount()) { + $minAmountLimit = $allowedMethod[0]->getMinimumAmount()[self::FIELD_VALUE]; + } + + if ($minAmountLimit !== null && $minAmountLimit > $orderTotal) { + continue; + } + + $maxAmountLimit = $allowedMethod[self::MAXIMUM_FIELD]; + if ($maxAmountLimit === null && $allowedMethod !== null && $allowedMethod[0]->getMaximumAmount()) { + $maxAmountLimit = $allowedMethod[0]->getMaximumAmount()[self::FIELD_VALUE]; + } + + if ($maxAmountLimit !== null && $maxAmountLimit < $orderTotal) { + continue; + } + + $allowedMethods[] = $allowedMethod[0]; + } + } + + return $allowedMethods; + } + private function getDefaultOptions(): array { return [ diff --git a/src/Resources/assets/admin/js/molliePayments/app.js b/src/Resources/assets/admin/js/molliePayments/app.js index 45a44bbc..ffa30b7c 100644 --- a/src/Resources/assets/admin/js/molliePayments/app.js +++ b/src/Resources/assets/admin/js/molliePayments/app.js @@ -44,18 +44,6 @@ $(function () { }); }); - $('.mollie-components').change(function () { - if ($(this).is(':checked')) { - $('.mollie-single-click-payment').prop('checked', !$(this).is(':checked')); - } - }); - - $('.mollie-single-click-payment').change(function () { - if ($(this).is(':checked')) { - $('.mollie-components').prop('checked', !$(this).is(':checked')); - } - }); - $('[id$="_paymentType"]').each(function (index) { setPaymentDescription($(this), index); diff --git a/src/Resources/assets/shop/js/mollie/app.js b/src/Resources/assets/shop/js/mollie/app.js index 5740dd5e..c599fd8e 100644 --- a/src/Resources/assets/shop/js/mollie/app.js +++ b/src/Resources/assets/shop/js/mollie/app.js @@ -1,6 +1,7 @@ -const { Mollie } = window; +const {Mollie} = window; $(function () { + var disableValidationMollieComponents = false; let selectedValue = false; let mollieData = $('.online-online-payment__container'); const initialOrderTotal = $('#sylius-summary-grand-total').text(); @@ -55,10 +56,84 @@ $(function () { } if (mollieData.length > 0 && true === components) { - initializeCreditCartFields(selectedValue); + let paymentMethods = document.querySelectorAll('div[class*="online-payment__item--"]'); + + for (let i = 0; i < paymentMethods.length; i++) { + paymentMethods[i].onchange = function (event) { + let target = event.target; + let creditCartComponents = document.querySelectorAll('div[data-testid*="mollie-container--"]'); + + if (target.value === 'creditcard' && creditCartComponents.length === 0) { + toggleMollieComponents(); + initializeCreditCartFields(selectedValue); + + if (isSavedCreditCardCheckboxChecked()) { + const mollieComponentFields = document.querySelector('.mollie-component-fields'); + if (!mollieComponentFields) { + return; + } + + hideMollieComponents(mollieComponentFields); + } + } + } + } + } + + function isSavedCreditCardCheckboxChecked() { + let checkbox = document.getElementById('mollie-sylius-use-saved-credit-card'); + if (!checkbox) { + return null; + } + let parentElement = checkbox.parentNode; + + return parentElement.classList.contains('checked') ? 1 : 0; + } + + function isSaveCreditCardForFutureUseChecked() { + let checkbox = document.getElementById('mollie-sylius-save-credit-card'); + if (!checkbox) { + return null; + } + let parentElement = checkbox.parentNode; + + return parentElement.classList.contains('checked') ? 1 : 0; + } + + function toggleMollieComponents() { + const useSavedCreditCardCheckbox = document.getElementById('mollie-sylius-use-saved-credit-card'); + if (!useSavedCreditCardCheckbox) { + return; + } + + useSavedCreditCardCheckbox.addEventListener('change', function (event) { + const mollieComponentFields = document.querySelector('.mollie-component-fields'); + if (!mollieComponentFields) { + return; + } + + if (event.target.checked) { + hideMollieComponents(mollieComponentFields); + } else { + showMollieComponents(mollieComponentFields); + } + }); + } + + function showMollieComponents(mollieComponentFields) { + disableValidationMollieComponents = false; + mollieComponentFields.classList.remove('mollie-hidden'); + mollieComponentFields.classList.add('display-grid'); + } + + function hideMollieComponents(mollieComponentFields) { + disableValidationMollieComponents = true; + mollieComponentFields.classList.add('mollie-hidden'); + mollieComponentFields.classList.remove('display-grid'); } function initializeCreditCartFields(selectedValue) { + const environment = mollieData.data('environment'); let testmode = true; @@ -76,7 +151,8 @@ $(function () { const formError = document.getElementById('form-error'); const submitButton = document.getElementById('next-step') || document.getElementById('sylius-pay-link'); const tokenField = document.querySelector('[id*="_details_cartToken"]'); - + const saveCardInfoInput = document.querySelector('[id*="_details_saveCardInfo"]'); + const useSavedCardsInput = document.querySelector('[id*="_details_useSavedCards"]'); const cardHolder = mollie.createComponent('cardHolder'); cardHolder.mount('#card-holder'); @@ -138,7 +214,9 @@ $(function () { } form.addEventListener('submit', async (event) => { - if ($('.online-payment__input:checked').val() === 'creditcard') { + useSavedCardsInput.value = isSavedCreditCardCheckboxChecked(); + + if ($('.online-payment__input:checked').val() === 'creditcard' && disableValidationMollieComponents === false) { event.preventDefault(); disableForm(); @@ -155,6 +233,7 @@ $(function () { } tokenField.value = token; + saveCardInfoInput.value = isSaveCreditCardForFutureUseChecked(); form.submit(); } diff --git a/src/Resources/assets/shop/scss/style.scss b/src/Resources/assets/shop/scss/style.scss index d0c998bd..0a6db6c0 100644 --- a/src/Resources/assets/shop/scss/style.scss +++ b/src/Resources/assets/shop/scss/style.scss @@ -4,6 +4,14 @@ grid-gap: 20px; } +.mollie-hidden { + display: none; +} + +.display-grid { + display: grid; +} + .label { display: inline-block; margin-bottom: 8px; diff --git a/src/Resources/config/doctrine/MollieCustomer.orm.xml b/src/Resources/config/doctrine/MollieCustomer.orm.xml index 79f212aa..4af09368 100644 --- a/src/Resources/config/doctrine/MollieCustomer.orm.xml +++ b/src/Resources/config/doctrine/MollieCustomer.orm.xml @@ -11,5 +11,6 @@ + diff --git a/src/Resources/config/doctrine/MollieGatewayConfig.orm.xml b/src/Resources/config/doctrine/MollieGatewayConfig.orm.xml index 621d29b8..7fe55b97 100644 --- a/src/Resources/config/doctrine/MollieGatewayConfig.orm.xml +++ b/src/Resources/config/doctrine/MollieGatewayConfig.orm.xml @@ -36,6 +36,13 @@ + + + + + + + diff --git a/src/Resources/config/doctrine/MollieMinMax.orm.xml b/src/Resources/config/doctrine/MollieMinMax.orm.xml new file mode 100644 index 00000000..0af203f8 --- /dev/null +++ b/src/Resources/config/doctrine/MollieMinMax.orm.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/src/Resources/config/resources.yaml b/src/Resources/config/resources.yaml index 4129d66c..74be207d 100644 --- a/src/Resources/config/resources.yaml +++ b/src/Resources/config/resources.yaml @@ -1,6 +1,7 @@ imports: - { resource: "@SyliusMolliePlugin/Resources/config/resources/mollie_methods.yaml" } - { resource: "@SyliusMolliePlugin/Resources/config/resources/payment_surcharge_fee.yaml" } + - { resource: "@SyliusMolliePlugin/Resources/config/resources/mollie_amount_limits.yaml" } - { resource: "@SyliusMolliePlugin/Resources/config/resources/mollie_method_image.yaml" } - { resource: "@SyliusMolliePlugin/Resources/config/resources/mollie_logger.yaml" } - { resource: "@SyliusMolliePlugin/Resources/config/resources/mollie_customer.yaml" } diff --git a/src/Resources/config/resources/mollie_amount_limits.yaml b/src/Resources/config/resources/mollie_amount_limits.yaml new file mode 100644 index 00000000..8f41f1c3 --- /dev/null +++ b/src/Resources/config/resources/mollie_amount_limits.yaml @@ -0,0 +1,6 @@ +sylius_resource: + resources: + sylius_mollie_plugin.amount_limits: + driver: doctrine/orm + classes: + model: SyliusMolliePlugin\Entity\MollieMinMax diff --git a/src/Resources/config/services/action.xml b/src/Resources/config/services/action.xml index cb8f5a62..80ca6aec 100644 --- a/src/Resources/config/services/action.xml +++ b/src/Resources/config/services/action.xml @@ -8,6 +8,7 @@ + diff --git a/src/Resources/config/services/twig.xml b/src/Resources/config/services/twig.xml index a2df57ca..22d3fe85 100644 --- a/src/Resources/config/services/twig.xml +++ b/src/Resources/config/services/twig.xml @@ -21,6 +21,13 @@ + + + + + + + diff --git a/src/Resources/config/services/validators.xml b/src/Resources/config/services/validators.xml index a548ad6d..ef4f1bef 100644 --- a/src/Resources/config/services/validators.xml +++ b/src/Resources/config/services/validators.xml @@ -36,5 +36,11 @@ + + + + + diff --git a/src/Resources/translations/messages.de.yml b/src/Resources/translations/messages.de.yml index 32abb4aa..6a7bc1cd 100644 --- a/src/Resources/translations/messages.de.yml +++ b/src/Resources/translations/messages.de.yml @@ -52,6 +52,8 @@ sylius_mollie_plugin: card_number: 'Kartennummer' expiry_date: 'Ablaufdatum' veryfication_code: 'Verifizierungscode' + save_card: 'Save credit card for the future purchases' + use_saved_card: 'Use saved credit card information' surcharge_title: 'Aufschlag' splitting_shipment_for_order: 'Lieferung für Auftrag #%number% teilen' split_shipment: 'Lieferung teilen' @@ -123,6 +125,9 @@ sylius_mollie_plugin: sylius_bundle: 'Zum Sylius-Paket' contact_mollie: 'Mollie kontaktieren' more_info_about_mollie: 'Mehr Informationen über Mollie' + min_max: 'Amount limits' + min_amount: 'Minimum amount' + max_amount: 'Maximum amount' form: channel_should_be_unique: 'Mollie ist bereits als Bezahlmethode auf dem Kanal {channels} aktiviert. Mollie kann pro Kanal nur einmal konfiguriert werden.' enabled_buy_now_button_help: 'Die Schaltfläche wird auf der Bezahlseite angezeigt' @@ -144,6 +149,7 @@ sylius_mollie_plugin: tracking_code: 'Sendungsnummer' methods: 'Aktivierte Methoden wählen' expiration_date_time: 'Ablaufdatum und -zeit' + amount_limit_help: 'Please specify the minimum and maximum amount to be applied to the cart. If left blank, the default settings provided by Mollie will be utilized' subscription: cancel: 'Abonnement erfolgreich gekündigt.' onboardingMollie: diff --git a/src/Resources/translations/messages.en.yml b/src/Resources/translations/messages.en.yml index 8762dfad..214f2748 100644 --- a/src/Resources/translations/messages.en.yml +++ b/src/Resources/translations/messages.en.yml @@ -100,6 +100,8 @@ sylius_mollie_plugin: card_number: 'Card number' expiry_date: 'Expiry date' veryfication_code: 'CVC/CVV' + save_card: 'Save credit card for the future purchases' + use_saved_card: 'Use saved credit card information' surcharge_title: 'Surcharge' splitting_shipment_for_order: 'Splitting shipment for Order no. #%number%' split_shipment: 'Split shipment' @@ -174,6 +176,9 @@ sylius_mollie_plugin: sylius_bundle: 'Visit the Sylius bundle' contact_mollie: 'Contact Mollie' more_info_about_mollie: 'More info on Mollie' + min_max: 'Amount limits' + min_amount: 'Minimum amount' + max_amount: 'Maximum amount' form: channel_should_be_unique: 'Mollie payment method is already enabled for {channels} channel. Mollie can only be configured once per channel.' enabled_buy_now_button_help: 'The button is shown on the checkout page' @@ -209,6 +214,7 @@ sylius_mollie_plugin: tracking_code: 'Tracking Code' methods: 'Choose enabled methods' expiration_date_time: 'Expiration date time' + amount_limit_help: 'Please specify the minimum and maximum amount to be applied to the cart. If left blank, the default settings provided by Mollie will be utilized' shop: product_variant: recurring: 'Subscription' diff --git a/src/Resources/translations/messages.fr.yml b/src/Resources/translations/messages.fr.yml index 5f12d85e..176ed719 100644 --- a/src/Resources/translations/messages.fr.yml +++ b/src/Resources/translations/messages.fr.yml @@ -52,6 +52,8 @@ sylius_mollie_plugin: card_number: "Numéro de carte" expiry_date: "Date d'expiration" veryfication_code: "Code de vérification" + save_card: 'Save credit card for the future purchases' + use_saved_card: 'Use saved credit card information' surcharge_title: "Supplément" splitting_shipment_for_order: "Fractionner l'envoi de la commande nº #%number%" split_shipment: "Expédition fractionnée" @@ -123,6 +125,9 @@ sylius_mollie_plugin: sylius_bundle: 'Visiter le lot Sylius' contact_mollie: 'Contacter Mollie' more_info_about_mollie: "Plus d'informations sur Mollie" + min_max: 'Amount limits' + min_amount: 'Minimum amount' + max_amount: 'Maximum amount' form: channel_should_be_unique: "La méthode de paiement Mollie est déjà activée pour le canal {channels}. Mollie ne peut être configuré qu'une seule fois par canal." enabled_buy_now_button_help: 'Le bouton est affiché sur la page de paiement' @@ -144,6 +149,7 @@ sylius_mollie_plugin: tracking_code: "Code de suivi" methods: "Sélectionner les moyens de paiement activés" expiration_date_time: "Date et heure d'expiration" + amount_limit_help: 'Please specify the minimum and maximum amount to be applied to the cart. If left blank, the default settings provided by Mollie will be utilized' subscription: cancel: "L'abonnement a été correctement annulé." onboardingMollie: diff --git a/src/Resources/translations/messages.nl.yml b/src/Resources/translations/messages.nl.yml index 1d3a8922..f1ca1aa6 100644 --- a/src/Resources/translations/messages.nl.yml +++ b/src/Resources/translations/messages.nl.yml @@ -52,6 +52,8 @@ sylius_mollie_plugin: card_number: 'Kaartnummer' expiry_date: 'Vervaldatum' veryfication_code: 'Verificatiecode' + save_card: 'Save credit card for the future purchases' + use_saved_card: 'Use saved credit card information' surcharge_title: 'Toeslag' splitting_shipment_for_order: 'Verzending opsplitsen voor Bestelnr. #%number%' split_shipment: 'Verzending opsplitsen' @@ -123,6 +125,9 @@ sylius_mollie_plugin: sylius_bundle: 'Bezoek de Sylius-bundel' contact_mollie: 'Neem contact op met Mollie' more_info_about_mollie: 'Meer informatie vindt u onder Mollie' + min_max: 'Amount limits' + min_amount: 'Minimum amount' + max_amount: 'Maximum amount' form: channel_should_be_unique: 'De Mollie-betalingsmethode is al ingeschakeld voor het {channels} kanaal. Mollie kan slecht een keer per kanaal worden geconfigureerd.' enabled_buy_now_button_help: 'De knop wordt getoond op de betalingspagina' @@ -144,6 +149,7 @@ sylius_mollie_plugin: tracking_code: 'Trackingcode' methods: 'Kies geactiveerde methodes' expiration_date_time: 'Vervaldatum tijd' + amount_limit_help: 'Please specify the minimum and maximum amount to be applied to the cart. If left blank, the default settings provided by Mollie will be utilized' subscription: cancel: 'Abonnement is succesvol opgezegd.' onboardingMollie: diff --git a/src/Resources/translations/validators.de.yml b/src/Resources/translations/validators.de.yml index 270755df..acce4d26 100644 --- a/src/Resources/translations/validators.de.yml +++ b/src/Resources/translations/validators.de.yml @@ -23,3 +23,6 @@ sylius_mollie_plugin: payment_surcharge_not_empty: 'Wert darf nicht leer sein' greater_than: 'Der Wert muss über Null sein' key_not_empty: 'Je nach Umgebung darf der Mollie-Schlüssel nicht leer sein' + min_greater_than_max: 'Maximum amount limit must be greater than or equal to minimum amount limit.' + min_less_than_mollie_min: 'Minimum amount limit must be greater than or equal to existing Mollie minimum amount limit %amount%' + max_greater_than_mollie_max: 'Maximum amount limit must be less than or equal to existing Mollie maximum amount limit %amount%' diff --git a/src/Resources/translations/validators.en.yml b/src/Resources/translations/validators.en.yml index 77810314..feeb4640 100644 --- a/src/Resources/translations/validators.en.yml +++ b/src/Resources/translations/validators.en.yml @@ -23,3 +23,6 @@ sylius_mollie_plugin: payment_surcharge_not_empty: 'Value cannot be blank' greater_than: 'The value should be greater than zero' key_not_empty: 'Depending on the environment, the mollie key cannot be empty' + min_greater_than_max: 'Maximum amount limit must be greater than or equal to minimum amount limit.' + min_less_than_mollie_min: 'Minimum amount limit must be greater than or equal to existing Mollie minimum amount limit %amount%' + max_greater_than_mollie_max: 'Maximum amount limit must be less than or equal to existing Mollie maximum amount limit %amount%' diff --git a/src/Resources/translations/validators.fr.yml b/src/Resources/translations/validators.fr.yml index 10668bbe..23838455 100644 --- a/src/Resources/translations/validators.fr.yml +++ b/src/Resources/translations/validators.fr.yml @@ -23,3 +23,6 @@ sylius_mollie_plugin: payment_surcharge_not_empty: "La valeur ne peut pas être vide" greater_than: "La valeur doit être supérieure à zéro" key_not_empty: "Selon l'environnement, la clé Mollie ne peut pas être vide" + min_greater_than_max: 'Maximum amount limit must be greater than or equal to minimum amount limit.' + min_less_than_mollie_min: 'Minimum amount limit must be greater than or equal to existing Mollie minimum amount limit %amount%' + max_greater_than_mollie_max: 'Maximum amount limit must be less than or equal to existing Mollie maximum amount limit %amount%' diff --git a/src/Resources/translations/validators.nl.yml b/src/Resources/translations/validators.nl.yml index c672b48f..9ddcb7b6 100644 --- a/src/Resources/translations/validators.nl.yml +++ b/src/Resources/translations/validators.nl.yml @@ -23,3 +23,6 @@ sylius_mollie_plugin: payment_surcharge_not_empty: 'Waarde mag niet leeg zijn' greater_than: 'De waarde moet groter zijn dan nul' key_not_empty: 'Afhankelijk van de omgeving mag de Mollie key niet leeg zijn' + min_greater_than_max: 'Maximum amount limit must be greater than or equal to minimum amount limit.' + min_less_than_mollie_min: 'Minimum amount limit must be greater than or equal to existing Mollie minimum amount limit %amount%' + max_greater_than_mollie_max: 'Maximum amount limit must be less than or equal to existing Mollie maximum amount limit %amount%' diff --git a/src/Resources/views/Shop/PaymentMollie/_creditCardForm.html.twig b/src/Resources/views/Shop/PaymentMollie/_creditCardForm.html.twig index 44802272..efa0b58e 100644 --- a/src/Resources/views/Shop/PaymentMollie/_creditCardForm.html.twig +++ b/src/Resources/views/Shop/PaymentMollie/_creditCardForm.html.twig @@ -1,5 +1,18 @@
-
+ {% if (true == gatewayConfig.config['single_click_enabled'] | default(false)) and getCustomerFromContext() %} + {% if (isCardSaved(order.customer.email)) %} +
+
+ + +
+
+ {% endif %} + {% endif %} + +
@@ -19,10 +32,19 @@
- +
+ + {% if (true == gatewayConfig.config['single_click_enabled'] | default(false)) and getCustomerFromContext() %} +
+ + +
+ {% endif %}
diff --git a/src/Twig/Extension/CustomerCreditCards.php b/src/Twig/Extension/CustomerCreditCards.php new file mode 100644 index 00000000..1ef0588f --- /dev/null +++ b/src/Twig/Extension/CustomerCreditCards.php @@ -0,0 +1,68 @@ +apiClient = $apiClient; + $this->customerRepository = $customerRepository; + $this->customerContext = $customerContext; + } + + /** + * @return TwigFunction[] + */ + public function getFunctions() + { + return [ + new TwigFunction('isCardSaved', [$this, 'isCardSaved']), + new TwigFunction('getCustomerFromContext', [$this, 'getCustomerFromContext']) + ]; + } + + /** + * @param string $customerEmail + * + * @return bool + */ + public function isCardSaved(string $customerEmail) + { + $existingCustomer = $this->customerRepository->findOneBy(['email' => $customerEmail]); + + if ($existingCustomer) { + return $existingCustomer->isCreditCardSaved() === '1'; + } + + return false; + } + + /** + * @return \Sylius\Component\Customer\Model\CustomerInterface|null + */ + public function getCustomerFromContext() + { + return $this->customerContext->getCustomer(); + } +} diff --git a/src/Validator/Constraints/MollieGatewayConfigValidator.php b/src/Validator/Constraints/MollieGatewayConfigValidator.php new file mode 100644 index 00000000..ff49089f --- /dev/null +++ b/src/Validator/Constraints/MollieGatewayConfigValidator.php @@ -0,0 +1,67 @@ +validateAmounts($value, $constraint); + } + + Assert::isInstanceOf($constraint, MollieGatewayConfigValidatorType::class); + } + + private function validateAmounts(PersistentCollection $collection, Constraint $constraint): void + { + $mollieGatewayConfigs = $collection->getSnapshot(); + foreach ($mollieGatewayConfigs as $key => $mollieGatewayConfig) { + $amountLimits = $mollieGatewayConfig->getAmountLimits(); + if (!$amountLimits) { + continue; + } + + $minAmount = $amountLimits->getMinimumAmount(); + $maxAmount = $amountLimits->getMaximumAmount(); + + if ($minAmount !== null && $maxAmount !== null && $minAmount > $maxAmount) { + $this->context->buildViolation($constraint->minGreaterThanMaxMessage) + ->atPath("[{$key}]." . self::AMOUNT_LIMITS_FIELD . '.' . self::MAXIMUM_FIELD) + ->addViolation(); + } + + if ($minAmount !== null && $mollieGatewayConfig->getMinimumAmount()) { + $mollieMinAmount = $mollieGatewayConfig->getMinimumAmount()[self::FIELD_VALUE]; + if ($mollieMinAmount !== null && $mollieMinAmount > $minAmount) { + $this->context->buildViolation($constraint->minLessThanMollieMinMessage) + ->setParameter('%amount%', $mollieMinAmount) + ->atPath("[{$key}]." . self::AMOUNT_LIMITS_FIELD . '.' . self::MINIMUM_FIELD) + ->addViolation(); + } + } + + if ($maxAmount !== null && $mollieGatewayConfig->getMaximumAmount()) { + $mollieMaxAmount = $mollieGatewayConfig->getMaximumAmount()[self::FIELD_VALUE]; + if ($mollieMaxAmount !== null && $mollieMaxAmount < $maxAmount) { + $this->context->buildViolation($constraint->maxGreaterThanMollieMaxMessage) + ->setParameter('%amount%', $mollieMaxAmount) + ->atPath("[{$key}]." . self::AMOUNT_LIMITS_FIELD . '.' . self::MAXIMUM_FIELD) + ->addViolation(); + } + } + } + + } +} diff --git a/src/Validator/Constraints/MollieGatewayConfigValidatorType.php b/src/Validator/Constraints/MollieGatewayConfigValidatorType.php new file mode 100644 index 00000000..de05bee7 --- /dev/null +++ b/src/Validator/Constraints/MollieGatewayConfigValidatorType.php @@ -0,0 +1,25 @@ +paymentCheckoutOrderResolver->resolve(); - /** @var PaymentInterface $payment */ + /** @var PaymentInterface|null $payment */ $payment = $order->getPayments()->last(); + if(null === $payment || false === $payment) + { + return; + } + /** @var ?PaymentMethodInterface $paymentMethod */ $paymentMethod = $payment->getMethod(); if (null === $paymentMethod) { diff --git a/tests/Application/templates/bundles/SyliusAdminBundle/PaymentMethod/_mollieMethodsForm.html.twig b/tests/Application/templates/bundles/SyliusAdminBundle/PaymentMethod/_mollieMethodsForm.html.twig index 2ce6f0e9..7787c381 100644 --- a/tests/Application/templates/bundles/SyliusAdminBundle/PaymentMethod/_mollieMethodsForm.html.twig +++ b/tests/Application/templates/bundles/SyliusAdminBundle/PaymentMethod/_mollieMethodsForm.html.twig @@ -162,7 +162,11 @@ {{ 'sylius_mollie_plugin.ui.payment_fee'|trans }} {{ form_row(methodForm.paymentSurchargeFee, { 'attr': { 'class': 'js-onboardingWizard-paymentFee' } }) }} - +

+ {{ 'sylius_mollie_plugin.ui.min_max'|trans }} +

+
{{ 'sylius_mollie_plugin.form.amount_limit_help'|trans }}
+ {{ form_row(methodForm.amountLimits, { 'attr': { 'class': 'js-onboardingWizard-minMax fields' } }) }}
{% else %} diff --git a/tests/Application/templates/bundles/SyliusShopBundle/Checkout/SelectPayment/_payment.html.twig b/tests/Application/templates/bundles/SyliusShopBundle/Checkout/SelectPayment/_payment.html.twig index 9d499a93..0bdc623f 100644 --- a/tests/Application/templates/bundles/SyliusShopBundle/Checkout/SelectPayment/_payment.html.twig +++ b/tests/Application/templates/bundles/SyliusShopBundle/Checkout/SelectPayment/_payment.html.twig @@ -10,6 +10,8 @@ {% if form.method.vars.choices[key].data.gatewayConfig.factoryName in [mollieFactoryName, mollieRecurringFactoryName] %} {% if not isRendered %} {{ form_row(form.details.cartToken) }} + {{ form_row(form.details.saveCardInfo) }} + {{ form_row(form.details.useSavedCards) }} {% set isRendered = true %} {% endif %} {% include '@SyliusShop/Checkout/SelectPayment/_choiceMollie.html.twig' with {