diff --git a/local/modules/Maintenance/Config/config.xml b/local/modules/Maintenance/Config/config.xml new file mode 100644 index 00000000..ca9e91a6 --- /dev/null +++ b/local/modules/Maintenance/Config/config.xml @@ -0,0 +1,31 @@ + + + + + +
+ + + + + %kernel.debug% + + + + + + + + + + + + + + + + + + diff --git a/local/modules/Maintenance/Config/module.xml b/local/modules/Maintenance/Config/module.xml new file mode 100644 index 00000000..cc16e066 --- /dev/null +++ b/local/modules/Maintenance/Config/module.xml @@ -0,0 +1,24 @@ + + + Maintenance\Maintenance + + Maintenance mode + + + Mode Maintenance + + + en_US + fr_FR + + 0.8 + + Nicolas Léon, Franck Allimant + nicolas@omnitic.com, thelia@cqfdev.fr + + classic + 2.3.0 + prod + diff --git a/local/modules/Maintenance/Config/routing.xml b/local/modules/Maintenance/Config/routing.xml new file mode 100644 index 00000000..a0117844 --- /dev/null +++ b/local/modules/Maintenance/Config/routing.xml @@ -0,0 +1,23 @@ + + + + + + Maintenance\Controller\MaintenanceController::displayMaintenance + + + + Maintenance\Controller\MaintenanceController::displayMaintenance + + + + Maintenance\Controller\MaintenanceAdminController::indexAction + + + + Maintenance\Controller\MaintenanceAdminController::configureAction + + + diff --git a/local/modules/Maintenance/Controller/MaintenanceAdminController.php b/local/modules/Maintenance/Controller/MaintenanceAdminController.php new file mode 100644 index 00000000..59bf92cf --- /dev/null +++ b/local/modules/Maintenance/Controller/MaintenanceAdminController.php @@ -0,0 +1,67 @@ + + * @author Benjamin Perche + */ +class MaintenanceAdminController extends BaseAdminController +{ + + public function configureAction() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], ['Maintenance'], AccessManager::UPDATE)) { + return $response; + } + + $m_form = new MaintenanceSettingsForm($this->getRequest()); + + $error_message = null; + + try { + $form = $this->validateForm($m_form, "post"); + + $data = $form->getData(); + + ConfigQuery::write('com.omnitic.maintenance_mode', (bool) $data['maintenance_mode']); + ConfigQuery::write('com.omnitic.maintenance_template_name', $data['maintenance_template_name']); + ConfigQuery::write('com.omnitic.maintenance_message', $data['maintenance_message']); + ConfigQuery::write('com.omnitic.maintenance_allowed_ips', preg_replace("/\s/", '', $data['maintenance_allowed_ips'])); + + } catch (FormValidationException $e) { + $error_message = $this->createStandardFormValidationErrorMessage($e); + } catch (\Exception $e) { + $error_message = $e->getMessage(); + } + + if ($error_message !== null) { + $m_form->setErrorMessage($error_message); + + $this->getParserContext() + ->addForm($m_form) + ->setGeneralError($error_message) + ; + } + + return $this->generateRedirect(URL::getInstance()->absoluteUrl('/admin/module/Maintenance')); + } +} diff --git a/local/modules/Maintenance/Controller/MaintenanceController.php b/local/modules/Maintenance/Controller/MaintenanceController.php new file mode 100644 index 00000000..88a477ef --- /dev/null +++ b/local/modules/Maintenance/Controller/MaintenanceController.php @@ -0,0 +1,24 @@ + + */ +class MaintenanceController extends BaseFrontController +{ + public function displayMaintenance() + { + $tplName = ConfigQuery::read("com.omnitic.maintenance_template_name"); + + if (empty($tplName)) { + $tplName = "maintenance"; + } + + return $this->render("maintenance/$tplName"); + } +} diff --git a/local/modules/Maintenance/EventListener/MaintenanceListener.php b/local/modules/Maintenance/EventListener/MaintenanceListener.php new file mode 100644 index 00000000..e09a58f8 --- /dev/null +++ b/local/modules/Maintenance/EventListener/MaintenanceListener.php @@ -0,0 +1,109 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Maintenance\EventListener; + +use BackOfficePath\BackOfficePath; +use Maintenance\Controller\MaintenanceController; +use Maintenance\Maintenance; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; + +use Thelia\Core\HttpKernel\Exception\RedirectException; +use Thelia\Model\ConfigQuery; +use Thelia\Model\ModuleQuery; +use Thelia\Tools\URL; + +/** + * Class MaintenanceListener + * @package Maintenance\EventListener + * @author Benjamin Perche + * @author Nicolas Léon + */ +class MaintenanceListener implements EventSubscriberInterface +{ + protected $debugMode; + + /** + * MaintenanceListener constructor. + * + * @param $debugMode + */ + public function __construct($debugMode) + { + $this->debugMode = $debugMode; + } + + /* + * Displays the maintenance page according to Admin settings + * + * @params FilterResponseEvent $event + * + */ + public function setMaintenanceView(GetResponseEvent $event) + { + $maintenance_mode = ConfigQuery::read('com.omnitic.maintenance_mode'); + + if ($maintenance_mode) { + /** + * @var \Thelia\Core\HttpFoundation\Request + */ + $request = $event->getRequest(); + + // Check that the current request ip address is in the white list + $allowed_ips = explode(',', ConfigQuery::read('com.omnitic.maintenance_allowed_ips')); + $allowed_ips[] = '127.0.0.1'; + $current_ip = $request->server->get('REMOTE_ADDR'); + $path = $request->getPathInfo(); + + if ($path !== '/maintenance') { + // Check that we're not in debug mode + if (! $this->debugMode) { + // Check that we're not an allowed ip address + if (!in_array($current_ip, $allowed_ips)) { + // Check that we're not an admin user + if ($request->getSession()->getAdminUser() === null) { + // Check that we're not accessing admin pages + if (!preg_match("#^/admin#i", $path)) { + throw new RedirectException(URL::getInstance()->absoluteUrl("/maintenance")); + } + } + } + } + } + } + } + + /** + * Returns an array of event names this subscriber wants to listen to. + */ + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => ["setMaintenanceView", 128] + ); + } + +} diff --git a/local/modules/Maintenance/Form/MaintenanceSettingsForm.php b/local/modules/Maintenance/Form/MaintenanceSettingsForm.php new file mode 100644 index 00000000..c281c841 --- /dev/null +++ b/local/modules/Maintenance/Form/MaintenanceSettingsForm.php @@ -0,0 +1,110 @@ + + * @author Benjamin Perche + */ +class MaintenanceSettingsForm extends BaseForm +{ + protected function buildForm() + { + $translator = Translator::getInstance(); + + $this->formBuilder + ->add('maintenance_mode', 'checkbox', array( + 'label' => $translator->trans( + "Put the store in maintenance mode", + [], + Maintenance::MESSAGE_DOMAIN + ), + 'label_attr' => [ + 'for' => 'maintenance_mode', + 'help' => $this->translator->trans( + 'Lorsque cette case est cochée, votre boutique n\'est plus accessible à vos clients.', + [], + Maintenance::MESSAGE_DOMAIN + ) + ], + 'required' => false, + )) + ->add('maintenance_template_name', 'text', array( + 'label' => $translator->trans( + "Template name", + [], + Maintenance::MESSAGE_DOMAIN + ), + 'label_attr' => array( + 'for' => 'maintenance_template_name', + 'help' => $this->translator->trans( + 'This is the name of the HTML template displayed to your customers. The module provides the following templates : "maintenance", "light" and "simple", but feel free to make your own template, and put its name here.', + [], + Maintenance::MESSAGE_DOMAIN + ) + ), + 'required' => true, + 'constraints' => array( + new NotBlank(), + ) + )) + ->add('maintenance_message', 'textarea', array( + 'label' => $translator->trans( + "Reminder message", + [], + Maintenance::MESSAGE_DOMAIN + ), + 'label_attr' => array( + 'for' => 'maintenance_message', + 'help' => $this->translator->trans( + 'This message will be displayed to your customers.', + [], + Maintenance::MESSAGE_DOMAIN + ) + ), + "required" => true, + "constraints" => array( + new NotBlank(), + ) + )) + ->add('maintenance_allowed_ips', 'text', array( + 'label' => $translator->trans( + "Authorized IP address(es)", + [], + Maintenance::MESSAGE_DOMAIN + ), + 'label_attr' => array( + 'for' => 'allowed_ips', + 'help' => $this->translator->trans( + 'Enter here a comma separated list of the IP addresses that will be allowed to access the shop when maintenance mode is enabled. Your IP address is currently %ip.', + [ "%ip" => $this->getRequest()->getClientIp()], + Maintenance::MESSAGE_DOMAIN + ) + ), + "required" => false, + // "constraints" => array( + // new NotBlank(), + // ) + )) + ; + } + + public function getName() + { + return 'admin_maintenance_settings'; + } +} diff --git a/local/modules/Maintenance/Hook/HookManager.php b/local/modules/Maintenance/Hook/HookManager.php new file mode 100644 index 00000000..f9db364b --- /dev/null +++ b/local/modules/Maintenance/Hook/HookManager.php @@ -0,0 +1,53 @@ + + */ +class HookManager extends BaseHook +{ + public function onMainBodyTop(HookRenderEvent $event) + { + if (ConfigQuery::read('com.omnitic.maintenance_mode')) { + $event->add($this->render("maintenance/maintenance_warning.html")); + } + } + + public function onMainTopMenuTools(HookRenderBlockEvent $event) + { + $event->add( + [ + 'id' => 'tools_menu_tags', + 'class' => '', + 'url' => URL::getInstance()->absoluteUrl('/admin/module/Maintenance'), + 'title' => $this->translator->trans("Mode maintenance", [], Maintenance::MESSAGE_DOMAIN) + ] + ); + } + + public function onModuleConfigure(HookRenderEvent $event) + { + $event->add($this->render("maintenance/module_configuration.html")); + } +} diff --git a/local/modules/Maintenance/I18n/backOffice/default/en_US.php b/local/modules/Maintenance/I18n/backOffice/default/en_US.php new file mode 100644 index 00000000..04734510 --- /dev/null +++ b/local/modules/Maintenance/I18n/backOffice/default/en_US.php @@ -0,0 +1,11 @@ + 'When the maintenance mode is active, your store is cloded, displaying a message of your choice to your visitors.', + 'Maintenance mode' => 'Maintenance mode', + 'Preview' => 'Preview', + 'Save changes' => 'Save changes', + 'Save your changes to get proper preview !' => 'Be sure to saveyour changes to get the selected preview !', + 'Sur cette page vous pouvez activer ou désactiver le mode maintenance, configurer les accès autorisés et régler les paramètres d\'affichage.' => 'On this page you can activate or deactivate the maintenance mode, configure the allowed accesses and adjust the display parameters.', + 'Vous conservez toujours un accès à la boutique si vous etes connecté en tant qu\'administrateur, ou accédez à la boutique en mode développement. Dans ce cas, un rappel du mode maintenance est affiché sur la boutique. Vous pouvez le modifier dans le template frontOffice/default/maintenance/maintenance_warning.html.' => 'You keep an access to the store if you are logged as an administrator, or access the store in development mode. In this case, a reminder of maintenance mode is displayed on the shop. You can change it in the frontOffice/default/maintenance/maintenance_warning.html template.', +); diff --git a/local/modules/Maintenance/I18n/backOffice/default/fr_FR.php b/local/modules/Maintenance/I18n/backOffice/default/fr_FR.php new file mode 100644 index 00000000..6013e11e --- /dev/null +++ b/local/modules/Maintenance/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,11 @@ + 'Le mode maintenance permet de fermer temporairement votre boutique, en affichant un message de votre choix à vos visiteurs.', + 'Maintenance mode' => 'Mode maintenance', + 'Preview' => 'Prévisualiser', + 'Save changes' => 'Enregistrer', + 'Save your changes to get proper preview !' => 'Enregistrez les changements pour afficher la vue choisie !', + 'Sur cette page vous pouvez activer ou désactiver le mode maintenance, configurer les accès autorisés et régler les paramètres d\'affichage.' => 'Sur cette page vous pouvez activer ou désactiver le mode maintenance, configurer les accès autorisés et régler les paramètres d\'affichage.', + 'Vous conservez toujours un accès à la boutique si vous etes connecté en tant qu\'administrateur, ou accédez à la boutique en mode développement. Dans ce cas, un rappel du mode maintenance est affiché sur la boutique. Vous pouvez le modifier dans le template frontOffice/default/maintenance/maintenance_warning.html.' => 'Vous conservez toujours un accès à la boutique si vous etes connecté en tant qu\'administrateur, ou accédez à la boutique en mode développement. Dans ce cas, un rappel du mode maintenance est affiché sur la boutique. Vous pouvez le modifier dans le template frontOffice/default/maintenance/maintenance_warning.html.', +); diff --git a/local/modules/Maintenance/I18n/en_US.php b/local/modules/Maintenance/I18n/en_US.php new file mode 100644 index 00000000..b13aecec --- /dev/null +++ b/local/modules/Maintenance/I18n/en_US.php @@ -0,0 +1,13 @@ + 'Allowed IP address(es)', + 'Enter here a comma separated list of the IP addresses that will be allowed to access the shop when maintenance mode is enabled. Your IP address is currently %ip.' => 'Enter here a comma separated list of the IP addresses that will be allowed to access the shop when maintenance mode is enabled. Your IP address is currently %ip.', + 'Lorsque cette case est cochée, votre boutique n\'est plus accessible à vos clients.' => 'When this box is checked, your store is closed', + 'Mode maintenance' => 'Maintenance mode', + 'Put the store in maintenance mode' => 'Put the store in maintenance mode', + 'Reminder message' => 'Reminder message', + 'Template name' => 'Template name', + 'This is the name of the HTML template displayed to your customers. The module provides the following templates : "maintenance", "light" and "simple", but feel free to make your own template, and put its name here.' => 'This is the name of the HTML template displayed to your customers. The module provides the following templates : "maintenance", "light" and "simple", but feel free to make your own template, and put its name here.', + 'This message will be displayed to your customers.' => 'This message will be displayed to your customers.', +); diff --git a/local/modules/Maintenance/I18n/fr_FR.php b/local/modules/Maintenance/I18n/fr_FR.php new file mode 100644 index 00000000..8db759ff --- /dev/null +++ b/local/modules/Maintenance/I18n/fr_FR.php @@ -0,0 +1,13 @@ + 'Addresses IP autorisées', + 'Enter here a comma separated list of the IP addresses that will be allowed to access the shop when maintenance mode is enabled. Your IP address is currently %ip.' => 'Indiquez ici la liste des adresses IP (séparées par des virgules) qui seront autorisées à voir la boutique lorsque le mode maintenance est actif. Votre adresse IP est %ip', + 'Lorsque cette case est cochée, votre boutique n\'est plus accessible à vos clients.' => 'Lorsque cette case est cochée, votre boutique n\'est plus accessible à vos clients.', + 'Mode maintenance' => 'Mode maintenance', + 'Put the store in maintenance mode' => 'Afficher la page de maintenance', + 'Reminder message' => 'Message d\'attente', + 'Template name' => 'Nom du template', + 'This is the name of the HTML template displayed to your customers. The module provides the following templates : "maintenance", "light" and "simple", but feel free to make your own template, and put its name here.' => 'Il s\'agit du nom du template HTML qui sera affiché aux visiteurs de la boutique. Le modulke vous propose les templates "simple", "light" et "maintenance", mais vous pouvez tout à fait créer le votre dans Maintenance/templates/frontOffice/default/maintenance et placer son nom ici.', + 'This message will be displayed to your customers.' => 'Ce message sera présenté aux visiteurs de la boutique', +); diff --git a/local/modules/Maintenance/I18n/frontOffice/default/fr_FR.php b/local/modules/Maintenance/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 00000000..aa196b37 --- /dev/null +++ b/local/modules/Maintenance/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,5 @@ + 'Le mode maintenance est activé !', +); diff --git a/local/modules/Maintenance/Maintenance.php b/local/modules/Maintenance/Maintenance.php new file mode 100644 index 00000000..b531258a --- /dev/null +++ b/local/modules/Maintenance/Maintenance.php @@ -0,0 +1,65 @@ + 0, + 'com.omnitic.maintenance_template_name' => 'maintenance', + 'com.omnitic.maintenance_message' => 'Nous mettons à jour notre boutique. Revenez nous voir dans quelques minutes.', + 'com.omnitic.maintenance_allowed_ips' => '', + ]; + + /* + * Install the default module settings + * + */ + public function postActivation(ConnectionInterface $con = null) + { + foreach ($this->settings as $setting_name => $value) { + $setting = ConfigQuery::read($setting_name); + + if (empty($setting)) { + ConfigQuery::write($setting_name, $value, null, 1); + } + } + + } + + /* + * Delete module data on module destroy + * + * + */ + public function destroy(ConnectionInterface $con = null, $deleteModuleData = false) + { + foreach ($this->settings as $setting_name => $value) { + $setting = ConfigQuery::create()->findOneByName($setting_name); + if ($setting !== null) { + $setting->delete(); + } + } + + } + + public function getCode() + { + return 'Maintenance'; + } + +} diff --git a/local/modules/Maintenance/Readme.md b/local/modules/Maintenance/Readme.md new file mode 100644 index 00000000..a61e1730 --- /dev/null +++ b/local/modules/Maintenance/Readme.md @@ -0,0 +1,89 @@ +# Maintenance mode module for Thelia 2 (en_US) + +This module allow the store owner to put the store in maintenance mode while performing changes to the store. + +## How to install + +Download the .zip file of this module or create a git submodule into your project like this : + +``` +cd /path-to-thelia +git submodule add https://github.com/nicolasleon/Maintenance.git local/modules/Mainteance +``` + +To install the module, copy the folder Maintenance into your ```modules/``` directory (install_dir/local/modules). + +Next, go to your Thelia admin panel and activate the module. + + +## Configuration + +You can manage the store maintenance mode settings by clicking the "Configure" button on the modules list. + +Under the Configure tab you can set the following options: + +**Put the store in maintenance mode**: Check the box to put the store in maintenance mode. + +**Maintenance page name**: the name of the template to be displayed when the maintenance mode is active (See the provided templates in /templates/fontOffice/module_maintenance folder of the module). + +**Reminder message**: a message to display in the store front when the maintenance mode is active. + +**Allowed ips**: Ip addresses that are allowed to see the store in maintenance mode is active (e.g.: "212.127.1.5, 192.135.0.1") + + +## How to use + +Click on "Configure" button and check "Put the store in maintenance mode". +Define the message displayed to the shop visitors in Reminder message field. + +The maintenance templates are stored in Maintenance/templates/frontOffice/default/maintenance folder. There are 3 samples maintenance templates provided (maintenance, simple and light) with the modules. Feel free to customize them to best match your store design. + +Save you settings. The store is now in maintenance mode. + +Any store admin can still accesss the store in maintenance mode (They will see the reminder message at the top of the page). The sotre remains visible if accessed in developement mode (you know, index_dev.php) + + +# Module Mode Maintenance pour Thelia 2 (fr_FR) + +Ce module permet au ecommerçant de mettre la boutique en mode maintenance pendant la mise à jour de cette dernière. + +## Installation + +Téléchargez le fichier zip du module ou créez un submodule Git dans votre projet comme ceci : + +``` +cd /dossier-thelia +git submodule add https://github.com/nicolasleon/Maintenance.git local/modules/Mainteance +``` + +Pour installer le module, copiez le dossier Maintenance dans le répertoire /local/modules situé à la racine de votre dossier Thelia (mon-dossier-thelia/local/modules). + +Activez le module dans l'interface d'administration Thelia. + + +## Configuration + +Vous pouvez régler les paramètres du mode maintenance de la boutique en cliquant sur le bouton "Configuration" du module. + +Les paramètres disponibles sont les suivants : + +**Afficher la page de maintenance**: cochez cette case pour passer la boutique en mode maintenance. + +**Nom du template**: le nom du template que les visiteurs verront quand la boutique est en mode maintenance (le template fourni maintenance.html pourra êtreSee the provided maintenance.html in templates/fontOffice/default folder of the module). + +**Message d'attente**: Un message à afficher sur la page de maintenance quand la boutique est en maintenance. + +**Adresses ip autorisées**: Liste des adresses ip pouvant accéder à la boutique quand celle-ci est en maintenance ("212.127.1.5, 192.135.0.1"). + +## Utilisation + +Réglez les paramètres du module, cochez la case "Afficher le mode maintenance". +Click on "Configure" button and check "Put the store in maintenance mode". +Define the message displayed to the shop visitors in Reminder message field. + +Les templates du mode maintenance sont définis dans Maintenance/templates/frontOffice/default/maintenance. N'hésitez pas à personnaliser les 3 (maintenance, simple et light) exemples fournis pour mieux correspondre au design de votre boutique, ou a créer le vôtre. + +Enregistrez vos paramètres. La boutique est en mode maintenance. Pour quittez le mode maintenance décochez la case et enregistrez votre configuration. + +Dans le mode maintenance, les utilisateurs connectés au back office Thelia accèdent normalement à la boutique (un message est affiché en haut des pages rappelle que le mode maintenance est activé). +Vous pouvez toujours accéder à la boutique en mode développement (vous savez, index_dev.php). diff --git a/local/modules/Maintenance/composer.json b/local/modules/Maintenance/composer.json new file mode 100644 index 00000000..d8d5dd00 --- /dev/null +++ b/local/modules/Maintenance/composer.json @@ -0,0 +1,11 @@ +{ + "name": "vlopes/maintenance-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "Maintenance" + } +} \ No newline at end of file diff --git a/local/modules/Maintenance/templates/backOffice/default/maintenance/module_configuration.html b/local/modules/Maintenance/templates/backOffice/default/maintenance/module_configuration.html new file mode 100644 index 00000000..6be90bfa --- /dev/null +++ b/local/modules/Maintenance/templates/backOffice/default/maintenance/module_configuration.html @@ -0,0 +1,56 @@ +
+
+
+
+ {intl d="maintenance.fo.default" l="Maintenance mode"} +
+
+ +
+
+
+
+

{intl d="maintenance.fo.default" l="Le mode maintenance permet de fermer temporairement votre boutique, en affichant un message de votre choix à vos visiteurs."}

+

{intl d="maintenance.fo.default" l="Sur cette page vous pouvez activer ou désactiver le mode maintenance, configurer les accès autorisés et régler les paramètres d'affichage."}

+

{intl d="maintenance.fo.default" l="Vous conservez toujours un accès à la boutique si vous etes connecté en tant qu'administrateur, ou accédez à la boutique en mode développement. Dans ce cas, un rappel du mode maintenance est affiché sur la boutique. Vous pouvez le modifier dans le template frontOffice/default/maintenance/maintenance_warning.html."}

+
+ + {form name="admin_maintenance_settings"} + + {if $form_error} +
{$form_error_message}
+ {/if} + + {form_hidden_fields} + + {render_form_field field="maintenance_mode" value={config key="com.omnitic.maintenance_mode"}} + +
+
+ {custom_render_form_field field="maintenance_template_name"} + + {/custom_render_form_field} +
+ +
+ {render_form_field field="maintenance_allowed_ips" value={config key="com.omnitic.maintenance_allowed_ips"}} +
+
+ + {render_form_field field="maintenance_message" value={config key="com.omnitic.maintenance_message"} extra_class="wysiwyg"} + +
+ +
+ + {/form} +
+
+
+
+
diff --git a/local/modules/Maintenance/templates/frontOffice/default/maintenance/light.html b/local/modules/Maintenance/templates/frontOffice/default/maintenance/light.html new file mode 100644 index 00000000..19b69712 --- /dev/null +++ b/local/modules/Maintenance/templates/frontOffice/default/maintenance/light.html @@ -0,0 +1,41 @@ + + + + + + + + + {config key="store_name"} + + + + + +
+

+
{config key="com.omnitic.maintenance_message"}
+

{config key="store_name"}

+
+ + diff --git a/local/modules/Maintenance/templates/frontOffice/default/maintenance/maintenance.html b/local/modules/Maintenance/templates/frontOffice/default/maintenance/maintenance.html new file mode 100644 index 00000000..f58edd3c --- /dev/null +++ b/local/modules/Maintenance/templates/frontOffice/default/maintenance/maintenance.html @@ -0,0 +1,40 @@ + + + + + + + + + + {config key="store_name"} + + + + +
+

{config key="store_name"}

+
{config key="com.omnitic.maintenance_message"}
+
+ + diff --git a/local/modules/Maintenance/templates/frontOffice/default/maintenance/maintenance_warning.html b/local/modules/Maintenance/templates/frontOffice/default/maintenance/maintenance_warning.html new file mode 100644 index 00000000..5606645e --- /dev/null +++ b/local/modules/Maintenance/templates/frontOffice/default/maintenance/maintenance_warning.html @@ -0,0 +1,3 @@ +
+ {intl l="Maintenance mode is active" d="maintenance.fo.default"} +
diff --git a/local/modules/Maintenance/templates/frontOffice/default/maintenance/simple.html b/local/modules/Maintenance/templates/frontOffice/default/maintenance/simple.html new file mode 100644 index 00000000..a3366687 --- /dev/null +++ b/local/modules/Maintenance/templates/frontOffice/default/maintenance/simple.html @@ -0,0 +1,39 @@ + + + + + + + + + {config key="store_name"} + + + + + +
+

{config key="store_name"}

+
{config key="com.omnitic.maintenance_message"}
+

+
+ + diff --git a/local/modules/PayPlugModule/Command/TreatOrderMultiPaymentCommand.php b/local/modules/PayPlugModule/Command/TreatOrderMultiPaymentCommand.php new file mode 100755 index 00000000..e1f5626f --- /dev/null +++ b/local/modules/PayPlugModule/Command/TreatOrderMultiPaymentCommand.php @@ -0,0 +1,49 @@ +setName("payplug:treat:multi_payment") + ->setDescription("Treat multi payment order"); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->initRequest(); + $dispatcher = $this->getDispatcher(); + $today = (new \DateTime())->setTime(0,0,0,0); + + $todayPlannedOrderPayments = OrderPayPlugMultiPaymentQuery::create() + ->filterByPaidAt(null, Criteria::ISNULL) + ->filterByPlannedAt($today) + ->find(); + + /** @var OrderPayPlugMultiPayment $todayPlannedOrderPayment */ + foreach ($todayPlannedOrderPayments as $todayPlannedOrderPayment) { + $output->writeln($todayPlannedOrderPayment->getId()); + + $order = $todayPlannedOrderPayment->getOrder(); + $paymentEvent = @(new PayPlugPaymentEvent())->buildFromOrder($order) + ->setAmount($todayPlannedOrderPayment->getAmount()) + ->setPaymentMethod($todayPlannedOrderPayment->getPaymentMethod()) + ->setInitiator("MERCHANT"); + + $dispatcher->dispatch(PayPlugPaymentEvent::CREATE_PAYMENT_EVENT, $paymentEvent); + + $todayPlannedOrderPayment->setPaymentId($paymentEvent->getPaymentId()) + ->save(); + } + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Config/config.xml b/local/modules/PayPlugModule/Config/config.xml new file mode 100755 index 00000000..47aa5c80 --- /dev/null +++ b/local/modules/PayPlugModule/Config/config.xml @@ -0,0 +1,77 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local/modules/PayPlugModule/Config/module.xml b/local/modules/PayPlugModule/Config/module.xml new file mode 100755 index 00000000..e097efdb --- /dev/null +++ b/local/modules/PayPlugModule/Config/module.xml @@ -0,0 +1,32 @@ + + + PayPlugModule\PayPlugModule + + PayPlug payment module + https://www.payplug.com + + + Module de paiement PayPlug + https://www.payplug.com/fr + + payplug.png + images + + en_US + fr_FR + + 1.0.5 + + + Vincent Lopes-Vicente + vlopes@openstudio.fr + + + payment + 2.4.0 + other + 0 + 0 + diff --git a/local/modules/PayPlugModule/Config/routing.xml b/local/modules/PayPlugModule/Config/routing.xml new file mode 100755 index 00000000..6b9805ab --- /dev/null +++ b/local/modules/PayPlugModule/Config/routing.xml @@ -0,0 +1,28 @@ + + + + + + PayPlugModule\Controller\Admin\ConfigurationController::viewAction + + + PayPlugModule\Controller\Admin\ConfigurationController::saveAction + + + PayPlugModule\Controller\Admin\OrderController::refundAction + + + PayPlugModule\Controller\Admin\OrderController::captureAction + + + + PayPlugModule\Controller\NotificationController::entryPoint + + + + PayPlugModule\Controller\CardController::deleteCurrentCustomerCard + + + diff --git a/local/modules/PayPlugModule/Config/schema.xml b/local/modules/PayPlugModule/Config/schema.xml new file mode 100755 index 00000000..312441d3 --- /dev/null +++ b/local/modules/PayPlugModule/Config/schema.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
diff --git a/local/modules/PayPlugModule/Config/sqldb.map b/local/modules/PayPlugModule/Config/sqldb.map new file mode 100755 index 00000000..63a93baa --- /dev/null +++ b/local/modules/PayPlugModule/Config/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +thelia.sql=thelia diff --git a/local/modules/PayPlugModule/Config/thelia.sql b/local/modules/PayPlugModule/Config/thelia.sql new file mode 100644 index 00000000..e0710c73 --- /dev/null +++ b/local/modules/PayPlugModule/Config/thelia.sql @@ -0,0 +1,97 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- order_pay_plug_data +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `order_pay_plug_data`; + +CREATE TABLE `order_pay_plug_data` +( + `id` INTEGER NOT NULL, + `amount_refunded` DECIMAL(16,6), + `need_capture` TINYINT DEFAULT 0, + `capture_expire_at` DATETIME, + `captured_at` DATETIME, + PRIMARY KEY (`id`), + CONSTRAINT `order_pay_plug_data_fk_19ea48` + FOREIGN KEY (`id`) + REFERENCES `order` (`id`) + ON UPDATE CASCADE + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- pay_plug_card +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `pay_plug_card`; + +CREATE TABLE `pay_plug_card` +( + `uuid` VARCHAR(150) NOT NULL, + `customer_id` INTEGER, + `brand` VARCHAR(255), + `last_4` VARCHAR(255), + `expire_month` INTEGER, + `expire_year` INTEGER, + PRIMARY KEY (`uuid`), + INDEX `pay_plug_card_fi_7e8f3e` (`customer_id`), + CONSTRAINT `pay_plug_card_fk_7e8f3e` + FOREIGN KEY (`customer_id`) + REFERENCES `customer` (`id`) + ON UPDATE CASCADE + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- order_pay_plug_multi_payment +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `order_pay_plug_multi_payment`; + +CREATE TABLE `order_pay_plug_multi_payment` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `order_id` INTEGER NOT NULL, + `amount` DECIMAL(16,6), + `is_first_payment` TINYINT DEFAULT 0, + `planned_at` DATETIME, + `payment_method` VARCHAR(255), + `payment_id` VARCHAR(255), + `paid_at` DATETIME, + `amount_refunded` DECIMAL(16,6) DEFAULT 0, + PRIMARY KEY (`id`,`order_id`), + INDEX `order_pay_plug_multi_payment_fi_75704f` (`order_id`), + CONSTRAINT `order_pay_plug_multi_payment_fk_75704f` + FOREIGN KEY (`order_id`) + REFERENCES `order` (`id`) + ON UPDATE CASCADE + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- pay_plug_module_delivery_type +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `pay_plug_module_delivery_type`; + +CREATE TABLE `pay_plug_module_delivery_type` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `module_id` INTEGER, + `delivery_type` VARCHAR(255), + PRIMARY KEY (`id`), + INDEX `fi_pay_plug_module_delivery_type_module_id` (`module_id`), + CONSTRAINT `fk_pay_plug_module_delivery_type_module_id` + FOREIGN KEY (`module_id`) + REFERENCES `module` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/local/modules/PayPlugModule/Config/update/1.0.2.sql b/local/modules/PayPlugModule/Config/update/1.0.2.sql new file mode 100644 index 00000000..30368bbb --- /dev/null +++ b/local/modules/PayPlugModule/Config/update/1.0.2.sql @@ -0,0 +1,9 @@ +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +ALTER TABLE `order_pay_plug_multi_payment` ADD COLUMN `amount_refunded` DECIMAL(16,6) DEFAULT 0; +ALTER TABLE `order_pay_plug_multi_payment` DROP COLUMN `refunded_at`; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/local/modules/PayPlugModule/Config/update/1.0.3.sql b/local/modules/PayPlugModule/Config/update/1.0.3.sql new file mode 100644 index 00000000..34e635ef --- /dev/null +++ b/local/modules/PayPlugModule/Config/update/1.0.3.sql @@ -0,0 +1,27 @@ +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + + +-- --------------------------------------------------------------------- +-- pay_plug_module_delivery_type +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `pay_plug_module_delivery_type`; + +CREATE TABLE `pay_plug_module_delivery_type` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `module_id` INTEGER, + `delivery_type` VARCHAR(255), + PRIMARY KEY (`id`), + INDEX `fi_pay_plug_module_delivery_type_module_id` (`module_id`), + CONSTRAINT `fk_pay_plug_module_delivery_type_module_id` + FOREIGN KEY (`module_id`) + REFERENCES `module` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/local/modules/PayPlugModule/Controller/Admin/ConfigurationController.php b/local/modules/PayPlugModule/Controller/Admin/ConfigurationController.php new file mode 100755 index 00000000..07325493 --- /dev/null +++ b/local/modules/PayPlugModule/Controller/Admin/ConfigurationController.php @@ -0,0 +1,72 @@ +container->get('payplugmodule_order_status_service'); + $orderStatusesService->initAllStatuses(); + $deliveryModuleFormFields = ConfigurationForm::getDeliveryModuleFormFields(); + + return $this->render( + "PayPlugModule/configuration", + compact('deliveryModuleFormFields') + ); + } + + public function saveAction() + { + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'PayPlugModule', AccessManager::UPDATE)) { + return $response; + } + + $form = $this->createForm('payplugmodule_configuration_form'); + + try { + $data = $this->validateForm($form)->getData(); + + foreach ($data as $key => $value) { + if (in_array($key, PayPlugConfigValue::getConfigKeys())) { + PayPlugModule::setConfigValue($key, $value); + } + + $explodedKey = explode(':', $key); + if ($explodedKey[0] === ConfigurationForm::DELIVERY_MODULE_TYPE_KEY_PREFIX) { + $moduleId = $explodedKey[1]; + $payPlugModuleDeliveryType = PayPlugModuleDeliveryTypeQuery::create()->filterByModuleId($moduleId)->findOneOrCreate(); + $payPlugModuleDeliveryType->setDeliveryType($value) + ->save(); + } + } + + } catch (\Exception $e) { + $this->setupFormErrorContext( + Translator::getInstance()->trans( + "Error", + [], + PayPlugModule::DOMAIN_NAME + ), + $e->getMessage(), + $form + ); + return $this->viewAction(); + } + + return $this->generateSuccessRedirect($form); + } + +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Controller/Admin/OrderController.php b/local/modules/PayPlugModule/Controller/Admin/OrderController.php new file mode 100755 index 00000000..fe92b88c --- /dev/null +++ b/local/modules/PayPlugModule/Controller/Admin/OrderController.php @@ -0,0 +1,84 @@ +checkAuth(array(AdminResources::MODULE), 'PayPlugModule', AccessManager::UPDATE)) { + return $response; + } + + $form = $this->createForm('payplugmodule_order_action_form_refund'); + + try { + $data = $this->validateForm($form)->getData(); + $order = OrderQuery::create() + ->findOneById($data['order_id']); + + $amountToRefund = (int)($data['refund_amount'] * 100); + + /** @var PaymentService $paymentService */ + $paymentService = $this->container->get('payplugmodule_payment_service'); + $paymentService->doOrderRefund($order, $amountToRefund); + } catch (\Exception $e) { + $this->setupFormErrorContext( + Translator::getInstance()->trans( + "Error", + [], + PayPlugModule::DOMAIN_NAME + ), + $e->getMessage(), + $form + ); + } + + // Sleep to let time for PayPlug to send validation + sleep(2); + $url = $this->retrieveSuccessUrl($form); + return $this->generateRedirect($url.'#orderPayPlug'); + } + + public function captureAction() + { + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'PayPlugModule', AccessManager::UPDATE)) { + return $response; + } + + $form = $this->createForm('payplugmodule_order_action_form'); + + try { + $data = $this->validateForm($form)->getData(); + $order = OrderQuery::create() + ->findOneById($data['order_id']); + + /** @var PaymentService $paymentService */ + $paymentService = $this->container->get('payplugmodule_payment_service'); + $paymentService->doOrderCapture($order); + } catch (\Exception $e) { + $this->setupFormErrorContext( + Translator::getInstance()->trans( + "Error", + [], + PayPlugModule::DOMAIN_NAME + ), + $e->getMessage(), + $form + ); + } + + // Sleep to let time for PayPlug to send validation + sleep(2); + $url = $this->retrieveSuccessUrl($form); + return $this->generateRedirect($url.'#orderPayPlug'); + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Controller/CardController.php b/local/modules/PayPlugModule/Controller/CardController.php new file mode 100755 index 00000000..7332bb09 --- /dev/null +++ b/local/modules/PayPlugModule/Controller/CardController.php @@ -0,0 +1,22 @@ +getSession()->getCustomerUser()->getId(); + + if (null !== $card = PayPlugCardQuery::create()->findOneByCustomerId($customerId)) { + $card->delete(); + } + + return $this->generateRedirect($this->getSession()->getReturnToUrl()); + } + +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Controller/NotificationController.php b/local/modules/PayPlugModule/Controller/NotificationController.php new file mode 100755 index 00000000..91fe4dce --- /dev/null +++ b/local/modules/PayPlugModule/Controller/NotificationController.php @@ -0,0 +1,28 @@ +container->get('payplugmodule_payment_service'); + Tlog::getInstance()->addAlert('Notification received'); + Tlog::getInstance()->addAlert($request->getContent()); + + $notificationResource = $paymentService->getNotificationResource($request); + + $notificationEvent = new UnknownNotificationEvent($notificationResource); + $this->dispatch(UnknownNotificationEvent::UNKNOWN_NOTIFICATION_EVENT, $notificationEvent); + + return new Response(); + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Event/Notification/PaymentNotificationEvent.php b/local/modules/PayPlugModule/Event/Notification/PaymentNotificationEvent.php new file mode 100755 index 00000000..6ee23d4f --- /dev/null +++ b/local/modules/PayPlugModule/Event/Notification/PaymentNotificationEvent.php @@ -0,0 +1,37 @@ +resource = $resource; + } + + /** + * @return Payment + */ + public function getResource(): Payment + { + return $this->resource; + } + + /** + * @param Payment $resource + * @return PaymentNotificationEvent + */ + public function setResource(Payment $resource): PaymentNotificationEvent + { + $this->resource = $resource; + return $this; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Event/Notification/RefundNotificationEvent.php b/local/modules/PayPlugModule/Event/Notification/RefundNotificationEvent.php new file mode 100755 index 00000000..0e016634 --- /dev/null +++ b/local/modules/PayPlugModule/Event/Notification/RefundNotificationEvent.php @@ -0,0 +1,37 @@ +resource = $resource; + } + + /** + * @return Refund + */ + public function getResource(): Refund + { + return $this->resource; + } + + /** + * @param Refund $resource + * @return RefundNotificationEvent + */ + public function setResource(Refund $resource): RefundNotificationEvent + { + $this->resource = $resource; + return $this; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Event/Notification/UnknownNotificationEvent.php b/local/modules/PayPlugModule/Event/Notification/UnknownNotificationEvent.php new file mode 100755 index 00000000..6219d785 --- /dev/null +++ b/local/modules/PayPlugModule/Event/Notification/UnknownNotificationEvent.php @@ -0,0 +1,37 @@ +resource = $resource; + } + + /** + * @return IVerifiableAPIResource + */ + public function getResource(): IVerifiableAPIResource + { + return $this->resource; + } + + /** + * @param IVerifiableAPIResource $resource + * @return UnknownNotificationEvent + */ + public function setResource(IVerifiableAPIResource $resource): UnknownNotificationEvent + { + $this->resource = $resource; + return $this; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Event/PayPlugPaymentEvent.php b/local/modules/PayPlugModule/Event/PayPlugPaymentEvent.php new file mode 100755 index 00000000..7a5542f0 --- /dev/null +++ b/local/modules/PayPlugModule/Event/PayPlugPaymentEvent.php @@ -0,0 +1,1605 @@ + [ + 'type' => 'integer', + 'access' => 'getFormattedAmount', + 'accessParameters' => [ + 'amount' + ] + ], + 'authorized_amount' => [ + 'type' => 'integer', + 'access' => 'getFormattedAmount', + 'accessParameters' => [ + 'authorized_amount' + ] + ], + 'auto_capture' => [ + 'type' => 'boolean', + 'required' => false, + 'access' => 'autoCapture' + ], + 'allow_save_card' => [ + 'type' => 'boolean', + 'access' => 'allowSaveCard' + ], + 'payment_method' => [ + 'type' => 'string', + 'access' => 'paymentMethod' + ], + 'initiator' => [ + 'type' => 'string', + ], + 'save_card' => [ + 'type' => 'boolean', + 'access' => 'forceSaveCard' + ], + 'currency' => [ + 'type' => 'string', + 'required' => true + ], + 'billing' => [ + 'type' => 'nested', + 'required' => true, + 'parameters' => [ + 'title' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'billingTitle' + ], + 'first_name' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'billingFirstName' + ], + 'last_name' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'billingLastName' + ], + 'email' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'billingEmail' + ], + 'mobile_phone_number' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'billingMobilePhone' + ], + 'landline_phone_number' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'billingLandLinePhone' + ], + 'address1' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'billingAddress1' + ], + 'address2' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'billingAddress2' + ], + 'postcode' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'billingPostcode' + ], + 'company_name' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'billingCompany' + ], + 'city' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'billingCity' + ], + 'state' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'billingState' + ], + 'country' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'billingCountry' + ], + 'language' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'billingLanguage' + ] + ] + ], + 'shipping' => [ + 'type' => 'nested', + 'required' => true, + 'parameters' => [ + 'title' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'shippingTitle' + ], + 'first_name' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'shippingFirstName' + ], + 'last_name' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'shippingLastName' + ], + 'email' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'shippingEmail' + ], + 'mobile_phone_number' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'shippingMobilePhone' + ], + 'landline_phone_number' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'shippingLandLinePhone' + ], + 'address1' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'shippingAddress1' + ], + 'address2' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'shippingAddress2' + ], + 'postcode' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'shippingPostcode' + ], + 'company_name' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'shippingCompany' + ], + 'city' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'shippingCity' + ], + 'state' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'shippingState' + ], + 'country' => [ + 'type' => 'string', + 'required' => true, + 'access' => 'shippingCountry' + ], + 'language' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'shippingLanguage' + ], + 'delivery_type' => [ + 'type' => 'choice', + 'required' => true, + 'access' => 'shippingDeliveryType', + 'options' => [ + 'BILLING', + 'VERIFIED', + 'NEW', + 'SHIP_TO_STORE', + 'DIGITAL_GOODS', + 'TRAVEL_OR_EVENT', + 'OTHER' + ] + ] + ] + ], + 'hosted_payment' => [ + 'type' => 'nested', + 'parameters' => [ + 'return_url' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'hostedPaymentReturnUrl' + ], + 'cancel_url' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'hostedPaymentCancelUrl' + ], + 'sent_by' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'hostedPaymentSentBy' + ] + ] + ], + 'notification_url' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'notificationUrl' + ], + 'payment_context' => [ + 'type' => 'nested', + 'required' => false, + 'parameters' => [ + 'cart' => [ + 'type' => 'array', + 'access' => 'products', + 'item_definition' => [ + 'brand' => [ + 'type' => "string", + 'required' => true, + 'access' => 'getBrand' + ], + 'expected_delivery_date' => [ + 'type' => "string", + 'required' => true, + 'access' => "getExpectedDeliveryDate" + ], + 'delivery_label' => [ + 'type' => "string", + 'required' => true, + 'access' => "getDeliveryLabel" + ], + 'delivery_type' => [ + 'type' => "string", + 'required' => true, + 'access' => "getDeliveryType" + ], + 'merchant_item_id' => [ + 'type' => "string", + 'required' => true, + 'access' => "getMerchantItemId" + ], + 'name' => [ + 'type' => "string", + 'required' => true, + 'access' => "getName" + ], + 'price' => [ + 'type' => "integer", + 'required' => true, + 'access' => "getPrice" + ], + 'quantity' => [ + 'type' => "integer", + 'required' => true, + 'access' => "getQuantity" + ], + 'total_amount' => [ + 'type' => "integer", + 'required' => true, + 'access' => "getTotalAmount" + ] + ] + ] + ] + ], + 'metadata' => [ + 'type' => 'nested', + 'parameters' => [ + 'customer_id' => [ + 'type' => 'string', + 'required' => false, + 'access' => 'customerId' + ] + ] + ], + ]; + + /** + * @var string + */ + protected $paymentId; + + /** + * @var string + */ + protected $paymentUrl; + + /** + * @var boolean + */ + protected $isPaid; + + /** + * @var Order + */ + protected $order; + + /** + * @var integer + */ + protected $amount; + + /** + * @var boolean + */ + protected $capture = false; + + /** + * @var boolean + */ + protected $autoCapture = false; + + /** + * @var boolean + */ + protected $allowSaveCard = false; + + /** + * @var boolean + */ + protected $forceSaveCard = false; + + /** + * @var string + */ + protected $paymentMethod; + + /** + * @var string + */ + protected $initiator; + + /** + * @var string + */ + protected $currency; + + /** + * @var string + */ + protected $customerId; + + /** + * @var string + */ + protected $billingPersonTitle; + + /** + * @var string + */ + protected $billingFirstName; + + /** + * @var string + */ + protected $billingLastName; + + /** + * @var string + */ + protected $billingMobilePhone; + + /** + * @var string + */ + protected $billingLandLinePhone; + + /** + * @var string + */ + protected $billingEmail; + + /** + * @var string + */ + protected $billingAddress1; + + /** + * @var string + */ + protected $billingAddress2; + + /** + * @var string + */ + protected $billingPostcode; + + /** + * @var string + */ + protected $billingCompany; + + /** + * @var string + */ + protected $billingCity; + + /** + * @var string + */ + protected $billingState; + + /** + * @var string + */ + protected $billingCountry; + + /** + * @var string + */ + protected $billingLanguage; + + /** + * @var string + */ + protected $shippingPersonTitle; + + /** + * @var string + */ + protected $shippingFirstName; + + /** + * @var string + */ + protected $shippingLastName; + + /** + * @var string + */ + protected $shippingMobilePhone; + + /** + * @var string + */ + protected $shippingLandLinePhone; + + /** + * @var string + */ + protected $shippingEmail; + + /** + * @var string + */ + protected $shippingAddress1; + + /** + * @var string + */ + protected $shippingAddress2; + + /** + * @var string + */ + protected $shippingPostcode; + + /** + * @var string + */ + protected $shippingCompany; + + /** + * @var string + */ + protected $shippingCity; + + /** + * @var string + */ + protected $shippingState; + + /** + * @var string + */ + protected $shippingCountry; + + /** + * @var string + */ + protected $shippingLanguage; + + /** + * @var string + */ + protected $shippingDeliveryType = 'BILLING'; + + /** + * @var string + */ + protected $hostedPaymentReturnUrl; + + /** + * @var string + */ + protected $hostedPaymentCancelUrl; + + /** + * @var string + */ + protected $hostedPaymentSentBy; + + /** + * @var PayPlugProduct[] + */ + protected $products; + + /** + * @var string + */ + protected $notificationUrl; + + public function buildFromOrder(Order $order) + { + $this->order = $order; + + if (null !== $customer = $order->getCustomer()) { + $this->setCustomerId($customer->getId()) + ->setBillingEmail($customer->getEmail()) + ->setShippingEmail($customer->getEmail()); + } + + if (null !== $order) { + // Avoid php bad int cast https://www.php.net/manual/fr/function.intval.php#60793 + $orderAmount = $order->getTotalAmount() * 100; + $this->setAmount(intval("$orderAmount")) + ->setCurrency($order->getCurrency()->getCode()); + + $this->setPayPlugAddress($order->getOrderAddressRelatedByInvoiceOrderAddressId(), 'Billing'); + $this->setPayPlugAddress($order->getOrderAddressRelatedByDeliveryOrderAddressId(), 'Shipping'); + + $langCode = $order->getLang()->getCode(); + $this->setBillingLanguage($langCode) + ->setShippingLanguage($langCode); + + $this->setHostedPaymentReturnUrl(URL::getInstance()->absoluteUrl('/order/placed/'.$order->getId())) + ->setHostedPaymentCancelUrl(URL::getInstance()->absoluteUrl('/order/failed/'.$order->getId().'/-')) + ->setNotificationUrl(URL::getInstance()->absoluteUrl('/payplug/notification')); + + // If payment is done retrieve his id on transaction ref + $this->setPaymentId($order->getTransactionRef()); + + $payPlugDeliveryType = PayPlugModuleDeliveryTypeQuery::create() + ->filterByModuleId($order->getDeliveryModuleId()) + ->findOne(); + + foreach ($order->getOrderProducts() as $orderProduct) { + $this->addProduct((new PayPlugProduct())->buildFromOrderProduct($orderProduct, $payPlugDeliveryType)); + } + } + + return $this; + } + + public function getFormattedPaymentParameters() + { + $this->checkParametersReadyForPayment($this::PARAMETER_DEFINITIONS); + $formattedParameters = []; + $this->buildArrayParameters($this::PARAMETER_DEFINITIONS, $formattedParameters); + + return $formattedParameters; + } + + protected function buildArrayParameters($parameterDefinitions, &$array, $parentKey = null, $target = null) + { + if (null === $target) { + $target = $this; + } + + foreach ($parameterDefinitions as $key => $parameterDefinition) { + $access = isset($parameterDefinition['access']) ? $parameterDefinition['access'] : $key; + + if ($parameterDefinition['type'] === 'array') { + $targetArray = isset($array[$parentKey]) ? $array[$parentKey] : $array; + $targetArray[$key] = []; + + foreach ($target->{$access} as $item) { + $childArray = []; + $this->buildArrayParameters($parameterDefinition['item_definition'], $childArray, $key, $item); + $targetArray[$key][] = $childArray; + } + + if (!empty($targetArray[$key])) { + $array = $targetArray; + } + continue; + } + + if ($parameterDefinition['type'] === 'nested') { + $targetArray = isset($array[$parentKey]) ? $array[$parentKey] : $array; + $targetArray[$key] = []; + + $this->buildArrayParameters($parameterDefinition['parameters'], $targetArray[$key], $key); + if (!empty($targetArray[$key])) { + $array = $targetArray; + } + continue; + } + + $value = null; + $value = property_exists($target, $access) ? $target->{$access} :null; + if (null === $value && method_exists($target, $access)) { + $parameters = isset($parameterDefinition['accessParameters']) ? $parameterDefinition['accessParameters'] : []; + $value = call_user_func([$target, $access], ...$parameters); + } + + // If still null or empty no need to fill this parameter + if (null == $value) { + continue; + } + + $array[$key] = $value; + } + } + + protected function checkParametersReadyForPayment(array $parameterDefinitions, $target = null) + { + if (null === $target) { + $target = $this; + } + + foreach ($parameterDefinitions as $key => $parameterDefinition) { + $access = isset($parameterDefinition['access']) ? $parameterDefinition['access'] : $key; + + if ($parameterDefinition['type'] === 'array') { + foreach ($target->{$access} as $item) { + $this->checkParametersReadyForPayment($parameterDefinition['item_definition'], $item); + } + continue; + } + + if ($parameterDefinition['type'] === 'nested') { + $this->checkParametersReadyForPayment($parameterDefinition['parameters'], $target); + continue; + } + + $value = property_exists($target, $access) ? $target->{$access} :null; + if (null === $value && method_exists($target, $access)) { + $parameters = isset($parameterDefinition['accessParameters']) ? $parameterDefinition['accessParameters'] : []; + $value = call_user_func([$target, $access], ...$parameters); + } + + if (isset($parameterDefinition['required']) && $parameterDefinition['required'] && $value == null) { + throw new \Exception(Translator::getInstance()->trans("Invalid payment parameter, %parameter should not be null or empty.", ['%parameter' => $key], PayPlugModule::DOMAIN_NAME)); + } + } + } + + public function setPayPlugAddress($addressData, $addressType) + { + $this + ->{'set'.$addressType.'FirstName'}($addressData->getFirstname()) + ->{'set'.$addressType.'LastName'}($addressData->getLastname()) + ->{'set'.$addressType.'Address1'}($addressData->getAddress1()) + ->{'set'.$addressType.'Postcode'}($addressData->getZipcode()) + ->{'set'.$addressType.'City'}($addressData->getCity()) + ->{'set'.$addressType.'Country'}($addressData->getCountry()->getIsoalpha2()); + + if (null !== $addressData->getAddress2()) { + $this->{'set'.$addressType.'Address2'}($addressData->getAddress2()); + } + + if (null !== $state = $addressData->getState()) { + $this->{'set'.$addressType.'State'}($state->getIsocode()); + } + + if (null !== $addressData->getCellphone()) { + $internationalPhoneNumber = $this->formatPhoneNumber($addressData->getCellphone(), $addressData->getCountry()->getIsoalpha2()); + $this->{'set'.$addressType.'MobilePhone'}($internationalPhoneNumber); + } + + if (null !== $addressData->getPhone()) { + $internationalPhoneNumber = $this->formatPhoneNumber($addressData->getPhone(), $addressData->getCountry()->getIsoalpha2()); + $this->{'set'.$addressType.'LandLinePhone'}($internationalPhoneNumber); + } + + if (null !== $addressData->getCompany()) { + $this->{'set'.$addressType.'Company'}($addressData->getCompany()); + } + } + + protected function getFormattedAmount($key) + { + if ((!$this->capture && $key === "amount") || ($this->capture && $key === "authorized_amount")) { + return $this->amount; + } + + return null; + } + + protected function formatPhoneNumber($phoneNumber, $countryCode) + { + try { + $phoneUtil = PhoneNumberUtil::getInstance(); + $phoneNumber = $phoneUtil->parse($phoneNumber, $countryCode); + + return $phoneUtil->format($phoneNumber, PhoneNumberFormat::INTERNATIONAL); + } catch (\Exception $exception) { + return null; + } + } + + /** + * @return string + */ + public function getPaymentId() + { + return $this->paymentId; + } + + /** + * @param string $paymentId + * @return PayPlugPaymentEvent + */ + public function setPaymentId(string $paymentId = null): PayPlugPaymentEvent + { + $this->paymentId = $paymentId; + return $this; + } + + /** + * @return string + */ + public function getPaymentUrl() + { + return $this->paymentUrl; + } + + /** + * @param string $paymentUrl + * @return PayPlugPaymentEvent + */ + public function setPaymentUrl(string $paymentUrl): PayPlugPaymentEvent + { + $this->paymentUrl = $paymentUrl; + return $this; + } + + /** + * @return bool + */ + public function isPaid() + { + return $this->isPaid; + } + + /** + * @param bool $isPaid + * @return PayPlugPaymentEvent + */ + public function setIsPaid(bool $isPaid): PayPlugPaymentEvent + { + $this->isPaid = $isPaid; + return $this; + } + + /** + * @return Order + */ + public function getOrder(): Order + { + return $this->order; + } + + /** + * @param Order $order + * @return PayPlugPaymentEvent + */ + public function setOrder(Order $order): PayPlugPaymentEvent + { + $this->order = $order; + return $this; + } + + /** + * @return int + */ + public function getAmount() + { + return $this->amount; + } + + /** + * @param int $amount + * @return PayPlugPaymentEvent + */ + public function setAmount(int $amount): PayPlugPaymentEvent + { + $this->amount = $amount; + return $this; + } + + /** + * @return bool + */ + public function isCapture() + { + return $this->capture; + } + + /** + * @param bool $capture + * @return PayPlugPaymentEvent + */ + public function setCapture(bool $capture): PayPlugPaymentEvent + { + $this->capture = $capture; + return $this; + } + + /** + * @return bool + */ + public function isAutoCapture() + { + return $this->autoCapture; + } + + /** + * @param bool $autoCapture + * @return PayPlugPaymentEvent + */ + public function setAutoCapture(bool $autoCapture): PayPlugPaymentEvent + { + $this->autoCapture = $autoCapture; + return $this; + } + + /** + * @return bool + */ + public function isAllowSaveCard() + { + return $this->allowSaveCard; + } + + /** + * @param bool $allowSaveCard + * @return PayPlugPaymentEvent + */ + public function setAllowSaveCard(bool $allowSaveCard): PayPlugPaymentEvent + { + $this->allowSaveCard = $allowSaveCard; + + return $this; + } + + /** + * @return bool + */ + public function isForceSaveCard() + { + return $this->forceSaveCard; + } + + /** + * @param bool $forceSaveCard + * @return PayPlugPaymentEvent + */ + public function setForceSaveCard(bool $forceSaveCard): PayPlugPaymentEvent + { + $this->forceSaveCard = $forceSaveCard; + + return $this; + } + + /** + * @return string + */ + public function getPaymentMethod() + { + return $this->paymentMethod; + } + + /** + * @param string $paymentMethod + * @return PayPlugPaymentEvent + */ + public function setPaymentMethod(string $paymentMethod = null): PayPlugPaymentEvent + { + $this->paymentMethod = $paymentMethod; + return $this; + } + + /** + * @return string + */ + public function getInitiator() + { + return $this->initiator; + } + + /** + * @param string $initiator + * @return PayPlugPaymentEvent + */ + public function setInitiator(string $initiator): PayPlugPaymentEvent + { + $this->initiator = $initiator; + return $this; + } + + /** + * @return string + */ + public function getCurrency() + { + return $this->currency; + } + + /** + * @param string $currency + * @return PayPlugPaymentEvent + */ + public function setCurrency(string $currency): PayPlugPaymentEvent + { + $this->currency = $currency; + return $this; + } + + /** + * @return string + */ + public function getCustomerId() + { + return $this->customerId; + } + + /** + * @param string $customerId + * @return PayPlugPaymentEvent + */ + public function setCustomerId(string $customerId): PayPlugPaymentEvent + { + $this->customerId = $customerId; + return $this; + } + + /** + * @return string + */ + public function getBillingPersonTitle() + { + return $this->billingPersonTitle; + } + + /** + * @param string $billingPersonTitle + * @return PayPlugPaymentEvent + */ + public function setBillingPersonTitle(string $billingPersonTitle): PayPlugPaymentEvent + { + $this->billingPersonTitle = $billingPersonTitle; + return $this; + } + + /** + * @return string + */ + public function getBillingFirstName() + { + return $this->billingFirstName; + } + + /** + * @param string $billingFirstName + * @return PayPlugPaymentEvent + */ + public function setBillingFirstName(string $billingFirstName): PayPlugPaymentEvent + { + $this->billingFirstName = $billingFirstName; + return $this; + } + + /** + * @return string + */ + public function getBillingLastName() + { + return $this->billingLastName; + } + + /** + * @param string $billingLastName + * @return PayPlugPaymentEvent + */ + public function setBillingLastName(string $billingLastName): PayPlugPaymentEvent + { + $this->billingLastName = $billingLastName; + return $this; + } + + /** + * @return string + */ + public function getBillingMobilePhone() + { + return $this->billingMobilePhone; + } + + /** + * @param string $billingMobilePhone + * @return PayPlugPaymentEvent + */ + public function setBillingMobilePhone(string $billingMobilePhone): PayPlugPaymentEvent + { + $this->billingMobilePhone = $billingMobilePhone; + return $this; + } + + /** + * @return string + */ + public function getBillingLandLinePhone() + { + return $this->billingLandLinePhone; + } + + /** + * @param string $billingLandLinePhone + * @return PayPlugPaymentEvent + */ + public function setBillingLandLinePhone(string $billingLandLinePhone): PayPlugPaymentEvent + { + $this->billingLandLinePhone = $billingLandLinePhone; + return $this; + } + + /** + * @return string + */ + public function getBillingEmail() + { + return $this->billingEmail; + } + + /** + * @param string $billingEmail + * @return PayPlugPaymentEvent + */ + public function setBillingEmail(string $billingEmail): PayPlugPaymentEvent + { + $this->billingEmail = $billingEmail; + return $this; + } + + /** + * @return string + */ + public function getBillingAddress1() + { + return $this->billingAddress1; + } + + /** + * @param string $billingAddress1 + * @return PayPlugPaymentEvent + */ + public function setBillingAddress1(string $billingAddress1): PayPlugPaymentEvent + { + $this->billingAddress1 = $billingAddress1; + return $this; + } + + /** + * @return string + */ + public function getBillingAddress2() + { + return $this->billingAddress2; + } + + /** + * @param string $billingAddress2 + * @return PayPlugPaymentEvent + */ + public function setBillingAddress2(string $billingAddress2): PayPlugPaymentEvent + { + $this->billingAddress2 = $billingAddress2; + return $this; + } + + /** + * @return string + */ + public function getBillingPostcode() + { + return $this->billingPostcode; + } + + /** + * @param string $billingPostcode + * @return PayPlugPaymentEvent + */ + public function setBillingPostcode(string $billingPostcode): PayPlugPaymentEvent + { + $this->billingPostcode = $billingPostcode; + return $this; + } + + /** + * @return string|null + */ + public function getBillingCompany() + { + return $this->billingCompany; + } + + /** + * @param string $billingCompany + * @return PayPlugPaymentEvent + */ + public function setBillingCompany(string $billingCompany): PayPlugPaymentEvent + { + $this->billingCompany = $billingCompany; + return $this; + } + + /** + * @return string + */ + public function getBillingCity() + { + return $this->billingCity; + } + + /** + * @param string $billingCity + * @return PayPlugPaymentEvent + */ + public function setBillingCity(string $billingCity): PayPlugPaymentEvent + { + $this->billingCity = $billingCity; + return $this; + } + + /** + * @return string + */ + public function getBillingState() + { + return $this->billingState; + } + + /** + * @param string $billingState + * @return PayPlugPaymentEvent + */ + public function setBillingState(string $billingState): PayPlugPaymentEvent + { + $this->billingState = $billingState; + return $this; + } + + /** + * @return string + */ + public function getBillingCountry() + { + return $this->billingCountry; + } + + /** + * @param string $billingCountry + * @return PayPlugPaymentEvent + */ + public function setBillingCountry(string $billingCountry): PayPlugPaymentEvent + { + $this->billingCountry = $billingCountry; + return $this; + } + + /** + * @return string + */ + public function getBillingLanguage() + { + return $this->billingLanguage; + } + + /** + * @param string $billingLanguage + * @return PayPlugPaymentEvent + */ + public function setBillingLanguage(string $billingLanguage): PayPlugPaymentEvent + { + $this->billingLanguage = $billingLanguage; + return $this; + } + + /** + * @return string + */ + public function getShippingPersonTitle() + { + return $this->shippingPersonTitle; + } + + /** + * @param string $shippingPersonTitle + * @return PayPlugPaymentEvent + */ + public function setShippingPersonTitle(string $shippingPersonTitle): PayPlugPaymentEvent + { + $this->shippingPersonTitle = $shippingPersonTitle; + return $this; + } + + /** + * @return string + */ + public function getShippingFirstName() + { + return $this->shippingFirstName; + } + + /** + * @param string $shippingFirstName + * @return PayPlugPaymentEvent + */ + public function setShippingFirstName(string $shippingFirstName): PayPlugPaymentEvent + { + $this->shippingFirstName = $shippingFirstName; + return $this; + } + + /** + * @return string + */ + public function getShippingLastName() + { + return $this->shippingLastName; + } + + /** + * @param string $shippingLastName + * @return PayPlugPaymentEvent + */ + public function setShippingLastName(string $shippingLastName): PayPlugPaymentEvent + { + $this->shippingLastName = $shippingLastName; + return $this; + } + + /** + * @return string + */ + public function getShippingMobilePhone() + { + return $this->shippingMobilePhone; + } + + /** + * @param string $shippingMobilePhone + * @return PayPlugPaymentEvent + */ + public function setShippingMobilePhone(string $shippingMobilePhone): PayPlugPaymentEvent + { + $this->shippingMobilePhone = $shippingMobilePhone; + return $this; + } + + /** + * @return string + */ + public function getShippingLandLinePhone() + { + return $this->shippingLandLinePhone; + } + + /** + * @param string $shippingLandLinePhone + * @return PayPlugPaymentEvent + */ + public function setShippingLandLinePhone(string $shippingLandLinePhone): PayPlugPaymentEvent + { + $this->shippingLandLinePhone = $shippingLandLinePhone; + return $this; + } + + /** + * @return string + */ + public function getShippingEmail() + { + return $this->shippingEmail; + } + + /** + * @param string $shippingEmail + * @return PayPlugPaymentEvent + */ + public function setShippingEmail(string $shippingEmail): PayPlugPaymentEvent + { + $this->shippingEmail = $shippingEmail; + return $this; + } + + /** + * @return string + */ + public function getShippingAddress1() + { + return $this->shippingAddress1; + } + + /** + * @param string $shippingAddress1 + * @return PayPlugPaymentEvent + */ + public function setShippingAddress1(string $shippingAddress1): PayPlugPaymentEvent + { + $this->shippingAddress1 = $shippingAddress1; + return $this; + } + + /** + * @return string + */ + public function getShippingAddress2() + { + return $this->shippingAddress2; + } + + /** + * @param string $shippingAddress2 + * @return PayPlugPaymentEvent + */ + public function setShippingAddress2(string $shippingAddress2): PayPlugPaymentEvent + { + $this->shippingAddress2 = $shippingAddress2; + return $this; + } + + /** + * @return string + */ + public function getShippingPostcode() + { + return $this->shippingPostcode; + } + + /** + * @param string $shippingPostcode + * @return PayPlugPaymentEvent + */ + public function setShippingPostcode(string $shippingPostcode): PayPlugPaymentEvent + { + $this->shippingPostcode = $shippingPostcode; + return $this; + } + + /** + * @return string|null + */ + public function getShippingCompany() + { + return $this->shippingCompany; + } + + /** + * @param string $shippingCompany + * @return PayPlugPaymentEvent + */ + public function setShippingCompany(string $shippingCompany): PayPlugPaymentEvent + { + $this->shippingCompany = $shippingCompany; + return $this; + } + + /** + * @return string + */ + public function getShippingCity() + { + return $this->shippingCity; + } + + /** + * @param string $shippingCity + * @return PayPlugPaymentEvent + */ + public function setShippingCity(string $shippingCity): PayPlugPaymentEvent + { + $this->shippingCity = $shippingCity; + return $this; + } + + /** + * @return string + */ + public function getShippingState() + { + return $this->shippingState; + } + + /** + * @param string $shippingState + * @return PayPlugPaymentEvent + */ + public function setShippingState(string $shippingState): PayPlugPaymentEvent + { + $this->shippingState = $shippingState; + return $this; + } + + /** + * @return string + */ + public function getShippingCountry() + { + return $this->shippingCountry; + } + + /** + * @param string $shippingCountry + * @return PayPlugPaymentEvent + */ + public function setShippingCountry(string $shippingCountry): PayPlugPaymentEvent + { + $this->shippingCountry = $shippingCountry; + return $this; + } + + /** + * @return string + */ + public function getShippingLanguage() + { + return $this->shippingLanguage; + } + + /** + * @param string $shippingLanguage + * @return PayPlugPaymentEvent + */ + public function setShippingLanguage(string $shippingLanguage): PayPlugPaymentEvent + { + $this->shippingLanguage = $shippingLanguage; + return $this; + } + + /** + * @return string + */ + public function getShippingDeliveryType() + { + return $this->shippingDeliveryType; + } + + /** + * @param string $shippingDeliveryType + * @return PayPlugPaymentEvent + */ + public function setShippingDeliveryType(string $shippingDeliveryType): PayPlugPaymentEvent + { + $this->shippingDeliveryType = $shippingDeliveryType; + return $this; + } + + /** + * @return string + */ + public function getHostedPaymentReturnUrl() + { + return $this->hostedPaymentReturnUrl; + } + + /** + * @param string $hostedPaymentReturnUrl + * @return PayPlugPaymentEvent + */ + public function setHostedPaymentReturnUrl(string $hostedPaymentReturnUrl = null): PayPlugPaymentEvent + { + $this->hostedPaymentReturnUrl = $hostedPaymentReturnUrl; + return $this; + } + + /** + * @return string + */ + public function getHostedPaymentCancelUrl() + { + return $this->hostedPaymentCancelUrl; + } + + /** + * @param string $hostedPaymentCancelUrl + * @return PayPlugPaymentEvent + */ + public function setHostedPaymentCancelUrl(string $hostedPaymentCancelUrl = null): PayPlugPaymentEvent + { + $this->hostedPaymentCancelUrl = $hostedPaymentCancelUrl; + return $this; + } + + /** + * @return string + */ + public function getHostedPaymentSentBy() + { + return $this->hostedPaymentSentBy; + } + + /** + * @param string $hostedPaymentSentBy + * @return PayPlugPaymentEvent + */ + public function setHostedPaymentSentBy(string $hostedPaymentSentBy): PayPlugPaymentEvent + { + $this->hostedPaymentSentBy = $hostedPaymentSentBy; + return $this; + } + + /** + * @return PayPlugProduct[] + */ + public function getProducts(): array + { + return $this->products; + } + + /** + * @param PayPlugProduct[] $products + * @return PayPlugPaymentEvent + */ + public function setProducts(array $products): PayPlugPaymentEvent + { + $this->products = $products; + return $this; + } + + /** + * @param PayPlugProduct $product + * @return PayPlugPaymentEvent + */ + public function addProduct(PayPlugProduct $product): PayPlugPaymentEvent + { + $this->products[] = $product; + return $this; + } + + /** + * @return string + */ + public function getNotificationUrl() + { + return $this->notificationUrl; + } + + /** + * @param string $notificationUrl + * @return PayPlugPaymentEvent + */ + public function setNotificationUrl(string $notificationUrl): PayPlugPaymentEvent + { + $this->notificationUrl = $notificationUrl; + return $this; + } +} diff --git a/local/modules/PayPlugModule/Event/PayPlugProduct.php b/local/modules/PayPlugModule/Event/PayPlugProduct.php new file mode 100644 index 00000000..042ba9c0 --- /dev/null +++ b/local/modules/PayPlugModule/Event/PayPlugProduct.php @@ -0,0 +1,249 @@ +trans( + 'Unknown', + [], + PayPlugModule::DOMAIN_NAME + ) + ); + + $deliveryType = $payPlugModuleDeliveryType !== null ? $payPlugModuleDeliveryType->getDeliveryType() : 'carrier'; + // Brand can't be find from order product but it's required so set store name as brand or "Unknown" + $this->setBrand($storeName); + $this->setExpectedDeliveryDate(date('Y-m-d')); + $this->setDeliveryLabel($storeName); + $this->setDeliveryType($deliveryType); + $this->setMerchantItemId($orderProduct->getId()); + $this->setName($orderProduct->getTitle()); + + $orderProductTaxes = $orderProduct->getOrderProductTaxes(); + $tax = array_reduce( + iterator_to_array($orderProductTaxes), + function ($accumulator, OrderProductTax $orderProductTax) { + return $accumulator + $orderProductTax->getAmount(); + }, + 0 + ); + $promoTax = array_reduce( + iterator_to_array($orderProductTaxes), + function ($accumulator, OrderProductTax $orderProductTax) { + return $accumulator + $orderProductTax->getPromoAmount(); + }, + 0 + ); + + $taxedPrice = round((float) $orderProduct->getPrice() + $tax, 2); + $taxedPromoPrice = round((float) $orderProduct->getPromoPrice() + $promoTax, 2); + + $price = $orderProduct->getWasInPromo() ? $taxedPromoPrice : $taxedPrice; + $this->setPrice($price * 100); + $this->setQuantity($orderProduct->getQuantity()); + $this->setTotalAmount($price * $orderProduct->getQuantity() * 100); + + return $this; + } + + /** + * @return string + */ + public function getBrand() + { + return $this->brand; + } + + /** + * @param string $brand + * @return PayPlugProduct + */ + public function setBrand(string $brand): PayPlugProduct + { + $this->brand = $brand; + return $this; + } + + /** + * @return string + */ + public function getExpectedDeliveryDate() + { + return $this->expectedDeliveryDate; + } + + /** + * @param string $expectedDeliveryDate + * @return PayPlugProduct + */ + public function setExpectedDeliveryDate(string $expectedDeliveryDate): PayPlugProduct + { + $this->expectedDeliveryDate = $expectedDeliveryDate; + return $this; + } + + /** + * @return string + */ + public function getDeliveryLabel() + { + return $this->deliveryLabel; + } + + /** + * @param string $deliveryLabel + * @return PayPlugProduct + */ + public function setDeliveryLabel(string $deliveryLabel): PayPlugProduct + { + $this->deliveryLabel = $deliveryLabel; + return $this; + } + + /** + * @return string + */ + public function getDeliveryType() + { + return $this->deliveryType; + } + + /** + * @param string $deliveryType + * @return PayPlugProduct + */ + public function setDeliveryType(string $deliveryType): PayPlugProduct + { + $this->deliveryType = $deliveryType; + return $this; + } + + /** + * @return string + */ + public function getMerchantItemId() + { + return $this->merchantItemId; + } + + /** + * @param string $merchantItemId + * @return PayPlugProduct + */ + public function setMerchantItemId(string $merchantItemId): PayPlugProduct + { + $this->merchantItemId = $merchantItemId; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + * @return PayPlugProduct + */ + public function setName(string $name): PayPlugProduct + { + $this->name = $name; + return $this; + } + + /** + * @return int + */ + public function getPrice() + { + return $this->price; + } + + /** + * @param int $price + * @return PayPlugProduct + */ + public function setPrice(int $price): PayPlugProduct + { + $this->price = $price; + return $this; + } + + /** + * @return int + */ + public function getQuantity() + { + return $this->quantity; + } + + /** + * @param int $quantity + * @return PayPlugProduct + */ + public function setQuantity(int $quantity): PayPlugProduct + { + $this->quantity = $quantity; + return $this; + } + + /** + * @return int + */ + public function getTotalAmount() + { + return $this->totalAmount; + } + + /** + * @param int $totalAmount + * @return PayPlugProduct + */ + public function setTotalAmount(int $totalAmount): PayPlugProduct + { + $this->totalAmount = $totalAmount; + return $this; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/EventListener/ConfirmationEmailListener.php b/local/modules/PayPlugModule/EventListener/ConfirmationEmailListener.php new file mode 100755 index 00000000..bfbb97f1 --- /dev/null +++ b/local/modules/PayPlugModule/EventListener/ConfirmationEmailListener.php @@ -0,0 +1,86 @@ + + */ +class ConfirmationEmailListener implements EventSubscriberInterface +{ + /** + * @var MailerFactory + */ + protected $mailer; + + public function __construct(MailerFactory $mailer) + { + $this->mailer = $mailer; + } + + /** + * @param OrderEvent $event + * + * @throws \Exception if the message cannot be loaded. + */ + public function sendConfirmationEmail(OrderEvent $event) + { + if (PayPlugModule::getConfigValue('send_confirmation_message_only_if_paid')) { + // We send the order confirmation email only if the order is paid + $order = $event->getOrder(); + + if (! $order->isPaid() && $order->getPaymentModuleId() == PayPlugModule::getModuleId()) { + $event->stopPropagation(); + } + } + } + + /* + * Check if we are the order payment module, and if order new status is paid, send a confirmation email to the customer. + * + * @param OrderEvent $event + * @param $eventName + * @param EventDispatcherInterface $dispatcher + * @throws \Propel\Runtime\Exception\PropelException + */ + public function updateStatus(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher) + { + $order = $event->getOrder(); + + if ($order->isPaid() && $order->getPaymentModuleId() == PayPlugModule::getModuleId()) { + // Send confirmation email if required. + if (PayPlugModule::getConfigValue('send_confirmation_message_only_if_paid')) { + $dispatcher->dispatch(TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL, $event); + } + + Tlog::getInstance()->debug("Confirmation email sent to customer " . $order->getCustomer()->getEmail()); + } + } + + public static function getSubscribedEvents() + { + return array( + TheliaEvents::ORDER_UPDATE_STATUS => array('updateStatus', 128), + TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL => array('sendConfirmationEmail', 129) + ); + } +} diff --git a/local/modules/PayPlugModule/EventListener/FormExtend/OrderFormListener.php b/local/modules/PayPlugModule/EventListener/FormExtend/OrderFormListener.php new file mode 100755 index 00000000..2d621251 --- /dev/null +++ b/local/modules/PayPlugModule/EventListener/FormExtend/OrderFormListener.php @@ -0,0 +1,70 @@ +request = $requestStack->getCurrentRequest(); + } + + public function addMultiPaymentField(TheliaFormEvent $event) + { + if (!PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_ENABLED)) { + return; + } + + $event->getForm()->getFormBuilder() + ->add( + self::PAY_PLUG_MULTI_PAYMENT_FIELD_NAME, + CheckboxType::class + ); + + } + + public function checkMultiPaymentSelected(OrderEvent $event) + { + $this->request->getSession()->set(self::PAY_PLUG_MULTI_PAYMENT_FIELD_NAME, 0); + $formData = $this->request->get(self::THELIA_CUSTOMER_ORDER_PAYMENT_FROM_NAME); + + if (!isset($formData[self::PAY_PLUG_MULTI_PAYMENT_FIELD_NAME]) || 0 == $formData[self::PAY_PLUG_MULTI_PAYMENT_FIELD_NAME]) { + return; + } + + $this->request->getSession()->set(self::PAY_PLUG_MULTI_PAYMENT_FIELD_NAME, 1); + } + + /** + * Returns an array of event names this subscriber wants to listen to. + * + * @return array The event names to listen to + * + * @api + */ + public static function getSubscribedEvents() + { + return array( + TheliaEvents::FORM_AFTER_BUILD.'.'.self::THELIA_CUSTOMER_ORDER_PAYMENT_FROM_NAME => array('addMultiPaymentField', 64), + TheliaEvents::ORDER_SET_PAYMENT_MODULE => array('checkMultiPaymentSelected', 64) + ); + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/EventListener/NotificationListener.php b/local/modules/PayPlugModule/EventListener/NotificationListener.php new file mode 100755 index 00000000..f0b7c046 --- /dev/null +++ b/local/modules/PayPlugModule/EventListener/NotificationListener.php @@ -0,0 +1,209 @@ +dispatcher = $dispatcher; + $this->orderStatusService = $orderStatusService; + } + + public function handleUnknownNotification(UnknownNotificationEvent $event) + { + $resource = $event->getResource(); + switch(true) { + case $resource instanceof Payment: + $paymentNotificationEvent = new PaymentNotificationEvent($resource); + $this->dispatcher->dispatch(PaymentNotificationEvent::PAYMENT_NOTIFICATION_EVENT, $paymentNotificationEvent); + break; + case $resource instanceof Refund: + $refundNotificationEvent = new RefundNotificationEvent($resource); + $this->dispatcher->dispatch(RefundNotificationEvent::REFUND_NOTIFICATION_EVENT, $refundNotificationEvent); + break; + } + } + + public function handlePaymentNotification(PaymentNotificationEvent $event) + { + $transactionRef = $event->getResource()->id; + if (!$transactionRef) { + return null; + } + + $order = OrderQuery::create() + ->filterByPaymentModuleId(PayPlugModule::getModuleId()) + ->filterByTransactionRef($transactionRef) + ->findOne(); + + if (null === $order) { + return; + } + + $orderPayPlugData = OrderPayPlugDataQuery::create() + ->findOneById($order->getId()); + + if (null === $orderPayPlugData) { + return; + } + + $paymentResource = $event->getResource(); + + $orderStatusId = OrderStatusQuery::getCancelledStatus()->getId(); + + $orderPayPlugMultiPayment = OrderPayPlugMultiPaymentQuery::create() + ->findByOrderId($order->getId()); + + // Multi payment is really different + if ($orderPayPlugMultiPayment->count() > 0) { + $this->handleMultiPaymentNotification($paymentResource, $order, $orderPayPlugData, $orderPayPlugMultiPayment); + return; + } + + if ($orderPayPlugData->getNeedCapture()) { + // Handle differed payment + if ($paymentResource->is_paid) { + $orderPayPlugData->setCapturedAt((new \DateTime())) + ->save(); + // Don't update status on capture + $orderStatusId = null; + } elseif ($paymentResource->authorization->authorized_at) { + $orderStatusId = PayPlugModule::getConfigValue(PayPlugConfigValue::DIFFERED_PAYMENT_AUTHORIZED_CAPTURE_STATUS); + $orderPayPlugData->setCaptureExpireAt($paymentResource->authorization->expires_at) + ->save(); + } + } elseif ($paymentResource->is_paid) { + // Handle classic payment + $orderStatusId = OrderStatusQuery::getPaidStatus()->getId(); + } + + if (null !== $orderStatusId) { + $event = (new OrderEvent($order)) + ->setStatus($orderStatusId); + $this->dispatcher->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event); + } + + if (null !== $paymentResource->card->id) { + $cardData = $paymentResource->card; + $cardExist = PayPlugCardQuery::create() + ->filterByCustomerId($order->getCustomerId()) + ->filterByUuid($cardData->id) + ->findOne(); + + if (null === $cardExist) { + (new PayPlugCard()) + ->setUuid($cardData->id) + ->setCustomerId($order->getCustomerId()) + ->setBrand($cardData->brand) + ->setLast4($cardData->last4) + ->setExpireMonth($cardData->exp_month) + ->setExpireYear($cardData->exp_year) + ->save(); + } + } + } + + protected function handleMultiPaymentNotification($paymentResource, Order $order, OrderPayPlugData $orderPayPlugData, Collection $orderMultiPayments) + { + /** @var OrderPayPlugMultiPayment $orderMultiPayment */ + foreach ($orderMultiPayments as $orderMultiPayment) { + $orderMultiPayment->setPaymentMethod($paymentResource->card->id); + + if ($paymentResource->id === $orderMultiPayment->getPaymentId()) { + $orderStatusId = OrderStatusQuery::getCancelledStatus()->getId(); + + if ($paymentResource->is_paid) { + $orderMultiPayment->setPaidAt(new \DateTime()); + $orderStatusId = OrderStatusQuery::getPaidStatus()->getId(); + } + + // Update order status only for first payment + if ($orderMultiPayment->getIsFirstPayment()) { + $event = (new OrderEvent($order)) + ->setStatus($orderStatusId); + $this->dispatcher->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event); + } + } + + $orderMultiPayment->save(); + } + } + + public function handleRefundNotification(RefundNotificationEvent $event) + { + $transactionRef = $event->getResource()->payment_id; + if (!$transactionRef) { + return; + } + + $order = OrderQuery::create() + ->filterByPaymentModuleId(PayPlugModule::getModuleId()) + ->filterByTransactionRef($transactionRef) + ->findOne(); + + $multiPayment = OrderPayPlugMultiPaymentQuery::create() + ->findOneByPaymentId($transactionRef); + + if (null !== $multiPayment) { + $multiPayment->setAmountRefunded((int)$multiPayment->getAmountRefunded() + $event->getResource()->amount) + ->save(); + $order = $multiPayment->getOrder(); + } + + if (null === $order) { + return; + } + + $orderPayPlugData = OrderPayPlugDataQuery::create() + ->findOneById($order->getId()); + + $orderPayPlugData->setAmountRefunded((int)$orderPayPlugData->getAmountRefunded() + $event->getResource()->amount) + ->save(); + + $event = (new OrderEvent($order)) + ->setStatus(OrderStatusQuery::getRefundedStatus()->getId()); + $this->dispatcher->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event); + } + + /** + * @inheritDoc + */ + public static function getSubscribedEvents() + { + return [ + UnknownNotificationEvent::UNKNOWN_NOTIFICATION_EVENT => ['handleUnknownNotification', 128], + PaymentNotificationEvent::PAYMENT_NOTIFICATION_EVENT => ['handlePaymentNotification', 128], + RefundNotificationEvent::REFUND_NOTIFICATION_EVENT => ['handleRefundNotification', 128] + ]; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/EventListener/OrderListener.php b/local/modules/PayPlugModule/EventListener/OrderListener.php new file mode 100755 index 00000000..32aac194 --- /dev/null +++ b/local/modules/PayPlugModule/EventListener/OrderListener.php @@ -0,0 +1,59 @@ +paymentService = $paymentService; + } + + public function onOrderUpdateStatus(OrderEvent $event) + { + $order = $event->getOrder(); + + $orderPayPlugData = OrderPayPlugDataQuery::create() + ->findOneById($order->getId()); + + if (null === $orderPayPlugData || false == $orderPayPlugData->getNeedCapture()) { + return; + } + + $this->handleCapture($event, $orderPayPlugData); + } + + protected function handleCapture(OrderEvent $event, OrderPayPlugData $orderPayPlugData) + { + // If already captured do nothing + if (null !== $orderPayPlugData->getCapturedAt()) { + return; + } + + // If new status is not trigger status do nothing + if (PayPlugModule::getConfigValue(PayPlugConfigValue::DIFFERED_PAYMENT_TRIGGER_CAPTURE_STATUS) != $event->getStatus()) { + return; + } + + $this->paymentService->doOrderCapture($event->getOrder()); + } + + public static function getSubscribedEvents() + { + return [ + TheliaEvents::ORDER_UPDATE_STATUS => ['onOrderUpdateStatus', 64] + ]; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/EventListener/PaymentListener.php b/local/modules/PayPlugModule/EventListener/PaymentListener.php new file mode 100755 index 00000000..bb7839c1 --- /dev/null +++ b/local/modules/PayPlugModule/EventListener/PaymentListener.php @@ -0,0 +1,208 @@ +orderStatusService = $orderStatusService; + } + + /** + * Send refund to PayPlug, set payment url and id to event + * + * @param PayPlugPaymentEvent $paymentEvent + * @throws \Payplug\Exception\ConfigurationNotSetException + */ + public function createPayment(PayPlugPaymentEvent $paymentEvent) + { + try { + $parameters = $paymentEvent->getFormattedPaymentParameters(); + $payPlugPayment = Payment::create($parameters); + + $paymentEvent->setPaymentId($payPlugPayment->id) + ->setIsPaid($payPlugPayment->is_paid) + ->setPaymentUrl($payPlugPayment->hosted_payment->payment_url); + + } catch (PayplugException $exception) { + throw new \Exception($this->formatErrorMessage($exception), 0, $exception); + } + } + + /** + * Send refund to PayPlug + * + * @param PayPlugPaymentEvent $paymentEvent + * @throws \Payplug\Exception\ConfigurationNotSetException + * @throws \Payplug\Exception\InvalidPaymentException + */ + public function createRefund(PayPlugPaymentEvent $paymentEvent) + { + try { + $payPlugPayment = Payment::retrieve($paymentEvent->getPaymentId()); + $data = null; + if ($paymentEvent->getAmount()) { + $data = ['amount' => $paymentEvent->getAmount()]; + } + $payPlugPayment->refund($data); + } catch (PayplugException $exception) { + throw new \Exception($this->formatErrorMessage($exception), 0, $exception); + } + } + + /** + * Send capture to PayPlug + * + * @param PayPlugPaymentEvent $paymentEvent + * @throws \Payplug\Exception\ConfigurationNotSetException + */ + public function createCapture(PayPlugPaymentEvent $paymentEvent) + { + try { + $payPlugPayment = Payment::retrieve($paymentEvent->getPaymentId()); + + $paymentCapture = $payPlugPayment->capture(); + } catch (PayplugException $exception) { + throw new \Exception($this->formatErrorMessage($exception), 0, $exception); + } + } + + /** + * Dispatch create payment for an order and set payment id to order transaction ref + * + * @param PayPlugPaymentEvent $paymentEvent + */ + public function orderPayment(PayPlugPaymentEvent $paymentEvent) + { + $this->dispatcher->dispatch(PayPlugPaymentEvent::CREATE_PAYMENT_EVENT, $paymentEvent); + + $order = $paymentEvent->getOrder(); + + $orderPayPlugData = (new OrderPayPlugData()) + ->setId($order->getId()); + + if ($paymentEvent->isCapture()) { + $orderPayPlugData->setNeedCapture(1); + } + + $orderPayPlugData->save(); + + $orderEvent = new OrderEvent($paymentEvent->getOrder()); + $orderEvent->setTransactionRef($paymentEvent->getPaymentId()); + $this->dispatcher->dispatch(TheliaEvents::ORDER_UPDATE_TRANSACTION_REF, $orderEvent); + } + + /** + * Dispatch refund for an order + * + * @param PayPlugPaymentEvent $paymentEvent + * @throws \Payplug\Exception\ConfigurationNotSetException + * @throws \Payplug\Exception\InvalidPaymentException + */ + public function orderRefund(PayPlugPaymentEvent $paymentEvent) + { + $multiPayments = OrderPayPlugMultiPaymentQuery::create() + ->findByOrderId($paymentEvent->getOrder()->getId()); + + if (count($multiPayments) > 0) { + $amountToRefund = $paymentEvent->getAmount(); + foreach ($multiPayments as $multiPayment) { + if ($amountToRefund <= 0) { + continue; + } + + // If already refunded => do nothing + if ($multiPayment->getAmountRefunded() >= $multiPayment->getAmount()) { + continue; + } + + // If not paid => cancel it + if ($multiPayment->getPaidAt() === null) { + $multiPayment->setPlannedAt(null) + ->save(); + continue; + } + + $currentPaymentAmountToRefund = $multiPayment->getAmount()-$multiPayment->getAmountRefunded(); + $currentPaymentAmountToRefund = $currentPaymentAmountToRefund > $amountToRefund ? $amountToRefund : $currentPaymentAmountToRefund; + // Else refund it + $refundPaymentEvent = clone $paymentEvent; + $refundPaymentEvent->setPaymentId($multiPayment->getPaymentId()) + ->setAmount($currentPaymentAmountToRefund); + $this->dispatcher->dispatch(PayPlugPaymentEvent::CREATE_REFUND_EVENT, $refundPaymentEvent); + $amountToRefund = $amountToRefund - $currentPaymentAmountToRefund; + } + return; + } + + $this->dispatcher->dispatch(PayPlugPaymentEvent::CREATE_REFUND_EVENT, $paymentEvent); + } + + /** + * Dispatch capture for an order + * + * @param PayPlugPaymentEvent $paymentEvent + * @throws \Payplug\Exception\ConfigurationNotSetException + * @throws \Payplug\Exception\InvalidPaymentException + */ + public function orderCapture(PayPlugPaymentEvent $paymentEvent) + { + $this->dispatcher->dispatch(PayPlugPaymentEvent::CREATE_CAPTURE_EVENT, $paymentEvent); + } + + protected function formatErrorMessage(PayplugException $exception) + { + $response = json_decode($exception->getHttpResponse(), true); + + $details = ""; + + + if (isset($response['details'])) { + $details = implode(' -', array_map( + function ($v, $k) { + $errors = []; + foreach ($v as $field => $error) { + $errors[] = "$field : $error"; + } + return " [$k] ".implode(";", $errors)." ."; + }, + $response['details'], + array_keys($response['details']) + )); + } + + return $response['message'] . $details; + } + + /** + * @inheritDoc + */ + public static function getSubscribedEvents() + { + return [ + PayPlugPaymentEvent::CREATE_PAYMENT_EVENT => ['createPayment', 128], + PayPlugPaymentEvent::CREATE_REFUND_EVENT => ['createRefund', 128], + PayPlugPaymentEvent::CREATE_CAPTURE_EVENT => ['createCapture', 128], + PayPlugPaymentEvent::ORDER_PAYMENT_EVENT => ['orderPayment', 128], + PayPlugPaymentEvent::ORDER_REFUND_EVENT => ['orderRefund', 128], + PayPlugPaymentEvent::ORDER_CAPTURE_EVENT => ['orderCapture', 128] + ]; + } +} diff --git a/local/modules/PayPlugModule/Form/ConfigurationForm.php b/local/modules/PayPlugModule/Form/ConfigurationForm.php new file mode 100755 index 00000000..178c2881 --- /dev/null +++ b/local/modules/PayPlugModule/Form/ConfigurationForm.php @@ -0,0 +1,255 @@ +find(); + + $orderStatusChoices = []; + foreach ($orderStatuses as $orderStatus) { + $orderStatusChoices[$orderStatus->getId()] = $orderStatus->getTitle(); + } + + /** @var OrderStatusService $orderStatusesService */ + $orderStatusesService = $this->container->get('payplugmodule_order_status_service'); + + $this->formBuilder + ->add( + PayPlugConfigValue::OFFER, + ChoiceType::class, + [ + 'choices' => [ + 'starter' => 'Starter', + 'pro' => 'Pro', + 'premium' => 'Premium', + ], + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::OFFER), + "label"=> Translator::getInstance()->trans("Select your PayPlug offer", [], PayPlugModule::DOMAIN_NAME), + "required" => true + ] + ) + ->add( + PayPlugConfigValue::PAYMENT_ENABLED, + CheckboxType::class, + [ + "data" => !!PayPlugModule::getConfigValue(PayPlugConfigValue::PAYMENT_ENABLED, false), + "label"=> Translator::getInstance()->trans("Enable payment by PayPlug", [], PayPlugModule::DOMAIN_NAME), + "required" => false + ] + ) + ->add( + PayPlugConfigValue::API_MODE, + ChoiceType::class, + [ + 'choices' => [ + 'live' => 'Live', + 'test' => 'Test', + ], + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::API_MODE), + "label"=> Translator::getInstance()->trans("Choose API mode", [], PayPlugModule::DOMAIN_NAME), + "required" => true + ] + ) + ->add( + PayPlugConfigValue::LIVE_API_KEY, + TextType::class, + [ + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::LIVE_API_KEY), + "label"=> Translator::getInstance()->trans("Live API secret key", [], PayPlugModule::DOMAIN_NAME), + "label_attr" => ['help' => Translator::getInstance()->trans("Look here %link", ['%link' => "Api configuration"], PayPlugModule::DOMAIN_NAME)], + "required" => true + ] + ) + ->add( + PayPlugConfigValue::TEST_API_KEY, + TextType::class, + [ + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::TEST_API_KEY), + "label"=> Translator::getInstance()->trans("Test API secret key", [], PayPlugModule::DOMAIN_NAME), + "label_attr" => ['help' => Translator::getInstance()->trans("Look here %link", ['%link' => "Api configuration"], PayPlugModule::DOMAIN_NAME)], + "required" => true + ] + ) + ->add( + PayPlugConfigValue::PAYMENT_PAGE_TYPE, + ChoiceType::class, + [ + 'choices' => [ + 'hosted_page' => Translator::getInstance()->trans("Hosted page", [], PayPlugModule::DOMAIN_NAME), + 'lightbox' => Translator::getInstance()->trans("Lightbox", [], PayPlugModule::DOMAIN_NAME), + // Todo implement payplug JS + //'payplug_js' => Translator::getInstance()->trans("Payplug.js", [], PayPlugModule::DOMAIN_NAME) + ], + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::PAYMENT_PAGE_TYPE), + "label"=> Translator::getInstance()->trans("Payment page type", [], PayPlugModule::DOMAIN_NAME), + "label_attr" => ['help' => Translator::getInstance()->trans("Hosted page will redirect your customer to a payment page / Lightbox will open a payment pop up in your website.", [], PayPlugModule::DOMAIN_NAME)], + "required" => true + ] + ) + ->add( + PayPlugConfigValue::ONE_CLICK_PAYMENT_ENABLED, + CheckboxType::class, + [ + "data" => !!PayPlugModule::getConfigValue(PayPlugConfigValue::ONE_CLICK_PAYMENT_ENABLED), + "label"=> Translator::getInstance()->trans("Enable one click payment", [], PayPlugModule::DOMAIN_NAME), + "label_attr" => ['help' => Translator::getInstance()->trans("This will allow your customer to save their card fo future order.", [], PayPlugModule::DOMAIN_NAME)], + "required" => false + ] + ) + ->add( + PayPlugConfigValue::MULTI_PAYMENT_ENABLED, + CheckboxType::class, + [ + "data" => !!PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_ENABLED), + "label"=> Translator::getInstance()->trans("Enabled multi-payment", [], PayPlugModule::DOMAIN_NAME), + "label_attr" => ['help' => Translator::getInstance()->trans("Enable payment in 2,3 or 4 times", [], PayPlugModule::DOMAIN_NAME)], + "required" => false + ] + ) + ->add( + PayPlugConfigValue::MULTI_PAYMENT_TIMES, + ChoiceType::class, + [ + 'choices' => [ + '2' => Translator::getInstance()->trans("2 times", [], PayPlugModule::DOMAIN_NAME), + '3' => Translator::getInstance()->trans("3 times", [], PayPlugModule::DOMAIN_NAME), + '4' => Translator::getInstance()->trans("4 times", [], PayPlugModule::DOMAIN_NAME) + ], + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_TIMES), + "label"=> Translator::getInstance()->trans("Payment in ", [], PayPlugModule::DOMAIN_NAME), + "required" => false + ] + ) + ->add( + PayPlugConfigValue::MULTI_PAYMENT_MINIMUM, + TextType::class, + [ + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_MINIMUM), + "label"=> Translator::getInstance()->trans("Minimum amount ", [], PayPlugModule::DOMAIN_NAME), + "required" => false + ] + ) + ->add( + PayPlugConfigValue::MULTI_PAYMENT_MAXIMUM, + TextType::class, + [ + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_MAXIMUM), + "label"=> Translator::getInstance()->trans("Maximum amount ", [], PayPlugModule::DOMAIN_NAME), + "required" => false + ] + ) + ->add( + PayPlugConfigValue::DIFFERED_PAYMENT_ENABLED, + CheckboxType::class, + [ + "data" => !!PayPlugModule::getConfigValue(PayPlugConfigValue::DIFFERED_PAYMENT_ENABLED), + "label"=> Translator::getInstance()->trans("Enabled differed payment", [], PayPlugModule::DOMAIN_NAME), + "label_attr" => ['help' => Translator::getInstance()->trans("Trigger the payment on order status change (max : 7 days after)", [], PayPlugModule::DOMAIN_NAME)], + "required" => false + ] + ) + ->add( + PayPlugConfigValue::DIFFERED_PAYMENT_AUTHORIZED_CAPTURE_STATUS, + ChoiceType::class, + [ + 'choices' => $orderStatusChoices, + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::DIFFERED_PAYMENT_AUTHORIZED_CAPTURE_STATUS, $orderStatusesService->findOrCreateAuthorizedCaptureOrderStatus()->getId()), + "label"=> Translator::getInstance()->trans("Which status to set when a capture is authorized", [], PayPlugModule::DOMAIN_NAME), + "required" => false + ] + ) + ->add( + PayPlugConfigValue::DIFFERED_PAYMENT_TRIGGER_CAPTURE_STATUS, + ChoiceType::class, + [ + 'choices' => $orderStatusChoices, + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::DIFFERED_PAYMENT_TRIGGER_CAPTURE_STATUS), + "label"=> Translator::getInstance()->trans("Capture the payment after order get the status", [], PayPlugModule::DOMAIN_NAME), + "required" => false + ] + ) + ->add( + PayPlugConfigValue::DIFFERED_PAYMENT_CAPTURE_EXPIRED_STATUS, + ChoiceType::class, + [ + 'choices' => $orderStatusChoices, + "data" => PayPlugModule::getConfigValue(PayPlugConfigValue::DIFFERED_PAYMENT_CAPTURE_EXPIRED_STATUS, $orderStatusesService->findOrCreateExpiredCaptureOrderStatus()->getId()), + "label"=> Translator::getInstance()->trans("What status to set on expired capture ", [], PayPlugModule::DOMAIN_NAME), + "required" => false + ] + ) + ->add( + PayPlugConfigValue::SEND_CONFIRMATION_MESSAGE_ONLY_IF_PAID, + CheckboxType::class, + [ + "data" => !!PayPlugModule::getConfigValue(PayPlugConfigValue::SEND_CONFIRMATION_MESSAGE_ONLY_IF_PAID), + "label"=> Translator::getInstance()->trans("Send order confirmation on payment success", [], PayPlugModule::DOMAIN_NAME), + "label_attr" => ['help' => Translator::getInstance()->trans("If checked, the order confirmation message is sent to the customer only when the payment is successful. The order notification is always sent to the shop administrator", [], PayPlugModule::DOMAIN_NAME)], + "required" => false + ] + ) + ; + + foreach (self::getDeliveryModuleFormFields() as $deliveryModuleFormField) + { + $this->formBuilder + ->add( + $deliveryModuleFormField['name'], + ChoiceType::class, + [ + 'required' => false, + 'label' => $deliveryModuleFormField['moduleCode'], + 'choices' => [ + 'carrier' => Translator::getInstance()->trans('Carrier ', [], PayPlugModule::DOMAIN_NAME), + 'storepickup' => Translator::getInstance()->trans('Store pick up ', [], PayPlugModule::DOMAIN_NAME), + 'networkpickup' => Translator::getInstance()->trans('Network pick up ', [], PayPlugModule::DOMAIN_NAME), + 'travelpickup' => Translator::getInstance()->trans('Travel pick up ', [], PayPlugModule::DOMAIN_NAME), + 'edelivery' => Translator::getInstance()->trans('E-Delivery ', [], PayPlugModule::DOMAIN_NAME) + ], + 'data' => $deliveryModuleFormField['value'] + ] + ); + } + } + + public static function getDeliveryModuleFormFields() + { + $deliveryModules = ModuleQuery::create() + ->filterByType(BaseModule::DELIVERY_MODULE_TYPE) + ->find(); + + return array_map(function (Module $deliveryModule) { + $oneyModuleDeliveryType = PayPlugModuleDeliveryTypeQuery::create()->filterByModuleId($deliveryModule->getId())->findOne(); + return [ + 'name' => self::DELIVERY_MODULE_TYPE_KEY_PREFIX.':'.$deliveryModule->getId(), + 'moduleCode' => $deliveryModule->getCode(), + 'value' => $oneyModuleDeliveryType !== null ? $oneyModuleDeliveryType->getDeliveryType() : null + ]; + }, iterator_to_array($deliveryModules)); + } + + public function getName() + { + return "payplugmodule_configuration_form"; + } +} diff --git a/local/modules/PayPlugModule/Form/OrderActionForm.php b/local/modules/PayPlugModule/Form/OrderActionForm.php new file mode 100755 index 00000000..f2bd46c2 --- /dev/null +++ b/local/modules/PayPlugModule/Form/OrderActionForm.php @@ -0,0 +1,23 @@ +formBuilder + ->add( + 'order_id', + TextType::class + ); + } + + public function getName() + { + return "payplugmodule_order_action_form"; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Form/OrderRefundForm.php b/local/modules/PayPlugModule/Form/OrderRefundForm.php new file mode 100644 index 00000000..b0c7b794 --- /dev/null +++ b/local/modules/PayPlugModule/Form/OrderRefundForm.php @@ -0,0 +1,24 @@ +formBuilder + ->add( + 'refund_amount', + TextType::class + ); + } + + public function getName() + { + return parent::getName().'_refund'; + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Hook/BackHookManager.php b/local/modules/PayPlugModule/Hook/BackHookManager.php new file mode 100755 index 00000000..c737de33 --- /dev/null +++ b/local/modules/PayPlugModule/Hook/BackHookManager.php @@ -0,0 +1,65 @@ +filterByPaymentModuleId(PayPlugModule::getModuleId()) + ->filterById($event->getArgument('order_id')) + ->findOne(); + + if (null === $order) { + return; + } + + /** @var OrderPayPlugData $orderPayPlugData */ + $orderPayPlugData = OrderPayPlugDataQuery::create() + ->findOneById($order->getId()); + + if (null === $orderPayPlugData) { + return; + } + + $orderPayPlugMultiPayments = OrderPayPlugMultiPaymentQuery::create() + ->filterByOrderId($order->getId()) + ->find() + ->toArray(null, false,TableMap::TYPE_CAMELNAME); + + $isPaid = !in_array($order->getOrderStatus()->getCode(), [OrderStatus::CODE_NOT_PAID, OrderStatus::CODE_CANCELED]); + $event->add( + $this->render( + 'PayPlugModule/order_pay_plug.html', + array_merge( + $event->getArguments(), + [ + 'isPaid' => $isPaid, + 'currency' => $order->getCurrency()->getSymbol() + ], + $orderPayPlugData->toArray(TableMap::TYPE_CAMELNAME), + [ + 'multiPayments' => $orderPayPlugMultiPayments + ] + ) + ) + ); + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Hook/FrontHookManager.php b/local/modules/PayPlugModule/Hook/FrontHookManager.php new file mode 100755 index 00000000..d3ce80d7 --- /dev/null +++ b/local/modules/PayPlugModule/Hook/FrontHookManager.php @@ -0,0 +1,95 @@ +taxEngine = $taxEngine; + } + + public function onOrderInvoiceAfterJsInclude(HookRenderEvent $event) + { + $payPlugModuleId = PayPlugModule::getModuleId(); + if (PayPlugModule::getConfigValue(PayPlugConfigValue::PAYMENT_PAGE_TYPE) === "lightbox") { + $event->add($this->render( + 'PayPlugModule/order-invoice-after-js-include.html', + compact('payPlugModuleId') + )); + } + } + + public function onOrderInvoicePaymentExtra(HookRenderEvent $event) + { + if ((int)$event->getArgument('module') !== PayPlugModule::getModuleId()) { + return; + } + + $this->displayOneClickPayment($event); + $this->displayMultiPayment($event); + } + + protected function displayMultiPayment(HookRenderEvent $event) + { + if (!PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_ENABLED)) { + return; + } + + $nTimes = PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_TIMES); + $minimumAmount = PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_MINIMUM); + $maximumAmount = PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_MAXIMUM); + + /** @var Country $country */ + $country = $this->taxEngine->getDeliveryCountry(); + + $cart = $this->getSession()->getSessionCart(); + $cartAmount = $cart->getTaxedAmount($country); + if ($cartAmount <= $minimumAmount || $cartAmount >= $maximumAmount) { + return; + } + + $event->add( + $this->render( + 'PayPlugModule/multi-payment.html', + compact("nTimes") + ) + ); + } + + protected function displayOneClickPayment(HookRenderEvent $event) + { + if (!PayPlugModule::getConfigValue(PayPlugConfigValue::ONE_CLICK_PAYMENT_ENABLED)) { + return; + } + + $customerId = $this->getSession()->getCustomerUser()->getId(); + + $payPlugCard = PayPlugCardQuery::create() + ->findOneByCustomerId($customerId); + + if (null === $payPlugCard) { + return; + } + + $event->add( + $this->render( + 'PayPlugModule/one-click-payment.html', + [ + 'last4' => $payPlugCard->getLast4() + ] + ) + ); + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/I18n/backOffice/default/fr_FR.php b/local/modules/PayPlugModule/I18n/backOffice/default/fr_FR.php new file mode 100644 index 00000000..b7d76c17 --- /dev/null +++ b/local/modules/PayPlugModule/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,38 @@ + 'Remboursé', + 'Adjust amount' => 'Ajuster le montant', + 'Amount' => 'Montant', + 'Canceled' => 'Annulé', + 'Capture order' => 'Déclencher le paiement de cette commande', + 'Differed payment' => 'Paiement différé ', + 'Do you really want to capture this order ?' => 'Voulez vous vraiment déclencher le paiement de cette commande ?', + 'Do you really want to refund this order ?' => 'Voulez vous vraiment rembourser cette commande ?', + 'Force the capture for this order' => 'Forcer le déclenchement du paiement', + 'Multi payment' => 'Paiement en plusieurs fois', + 'Order without shipping' => 'La commande sans les frais de port', + 'Paid at' => 'Payé le ', + 'PayPlug API configuration' => 'Configuration API PayPlug', + 'PayPlug basic payment configuration' => 'Configuration de paiement basique', + 'PayPlug delivery type configuration' => 'Configuration des types de livraison PayPlug', + 'PayPlug module configuration' => 'PayPlug module configuration', + 'PayPlug offer' => 'Offre PayPlug', + 'PayPlug order data' => 'Données de commande PayPlug', + 'PayPlug premium offer configuration' => 'Configuration de paiement offre Premium', + 'PayPlug pro offer configuration' => 'Configuration de paiement offre Pro', + 'Payment capture will expire on ' => 'L\'empreinte de paiement expirera le ', + 'Payment n° ' => 'Paiement n° ', + 'Payment was captured at ' => 'Paiement déclenché le ', + 'Payments in several times are not guaranteed, a default may occur on future due dates.' => 'Les paiements en plusieurs fois ne sont pas garantis. Un défaut de paiement peut survenir lors des échéances futures.', + 'Planned at' => 'Prévu le ', + 'Please select a Pay Plug delivery type correspondance for each delivery modules' => 'Veuillez choisir un type de livraison PayPlug sur chaques modules de livraison', + 'Products' => 'Produits', + 'Refund' => 'Remboursement', + 'Refund order' => 'Rembourser la commande ', + 'Refunded at' => 'Remboursé le ', + 'Save' => 'Enregistrer', + 'The entire order' => 'La commande totale', + 'To trigger the payments on planned date please add a daily cron on this Thelia command ' => 'Pour déclencher les paiements programmés veuillez ajouter cette commande Thelia a un cron journalier ', + 'What do you want refund' => 'Que voulez-vous rembourser', +); diff --git a/local/modules/PayPlugModule/I18n/en_US.php b/local/modules/PayPlugModule/I18n/en_US.php new file mode 100755 index 00000000..fd56ad6b --- /dev/null +++ b/local/modules/PayPlugModule/I18n/en_US.php @@ -0,0 +1,34 @@ + '2 times', + '3 times' => '3 times', + '4 times' => '4 times', + 'Capture the payment after order get the status' => 'Capture the payment after order get the status', + 'Choose API mode' => 'Choose API mode', + 'Enable one click payment' => 'Enable one click payment', + 'Enable payment by PayPlug' => 'Enable payment by PayPlug', + 'Enable payment in 2,3 or 4 times' => 'Enable payment in 2,3 or 4 times', + 'Enabled differed payment' => 'Enabled differed payment', + 'Enabled multi-payment' => 'Enabled multi-payment', + 'Error' => 'Error', + 'Hosted page' => 'Hosted page', + 'Hosted page will redirect your customer to a payment page / Lightbox will open a payment pop up in your website.' => 'Hosted page will redirect your customer to a payment page / Lightbox will open a payment pop up in your website.', + 'If checked, the order confirmation message is sent to the customer only when the payment is successful. The order notification is always sent to the shop administrator' => 'If checked, the order confirmation message is sent to the customer only when the payment is successful. The order notification is always sent to the shop administrator', + 'Invalid payment parameter, %parameter should not be null or empty.' => 'Invalid payment parameter, %parameter should not be null or empty.', + 'Lightbox' => 'Lightbox', + 'Live API secret key' => 'Live API secret key', + 'Look here %link' => 'Look here %link', + 'Maximum amount ' => 'Maximum amount ', + 'Minimum amount ' => 'Minimum amount ', + 'Payment in ' => 'Payment in ', + 'Payment page type' => 'Payment page type', + 'Payplug.js' => 'Payplug.js', + 'Select your PayPlug offer' => 'Select your PayPlug offer', + 'Send order confirmation on payment success' => 'Send order confirmation on payment success', + 'Test API secret key' => 'Test API secret key', + 'This will allow your customer to save their card fo future order.' => 'This will allow your customer to save their card fo future order.', + 'Trigger the payment on order status change (max : 7 days after)' => 'Trigger the payment on order status change (max : 7 days after)', + 'What status to set on expired capture ' => 'What status to set on expired capture ', + 'Which status to set when a capture is authorized' => 'Which status to set when a capture is authorized', +); diff --git a/local/modules/PayPlugModule/I18n/fr_FR.php b/local/modules/PayPlugModule/I18n/fr_FR.php new file mode 100755 index 00000000..4d6edb65 --- /dev/null +++ b/local/modules/PayPlugModule/I18n/fr_FR.php @@ -0,0 +1,39 @@ + '2 fois', + '3 times' => '3 fois', + '4 times' => '4 fois', + 'Capture the payment after order get the status' => 'Déclenché le paiement lorsque la commande as le statut', + 'Carrier ' => 'Transporteur', + 'Choose API mode' => 'Choisissez le mode de l\'API', + 'E-Delivery ' => 'Livraison en ligne', + 'Enable one click payment' => 'Activer le paiement en un clic', + 'Enable payment by PayPlug' => 'Activer le paiement par PayPlug', + 'Enable payment in 2,3 or 4 times' => 'Activer le paiement en 2, 3 ou 4 fois.', + 'Enabled differed payment' => 'Activer le paiement différé', + 'Enabled multi-payment' => 'Activer le paiement en plusieurs fois', + 'Error' => 'Erreur', + 'Hosted page' => 'Hosted page', + 'Hosted page will redirect your customer to a payment page / Lightbox will open a payment pop up in your website.' => 'Hosted page redirige le client sur une page de paiement / Lightbox ouvre une pop-up de paiement sur votre site.', + 'If checked, the order confirmation message is sent to the customer only when the payment is successful. The order notification is always sent to the shop administrator' => 'Si la case est cochée, le message de confirmation de commande est envoyé au client seulement quand le paiment est réussi. La notification de commande est toujours envoyée à l\'administrateur du site', + 'Invalid payment parameter, %parameter should not be null or empty.' => 'Paramètre de paiement invalide, %parameter ne doit pas être nulle ou vide.', + 'Lightbox' => 'Lightbox', + 'Live API secret key' => 'Live API secret key', + 'Look here %link' => 'Regardez ici %link', + 'Maximum amount ' => 'Montant maximum', + 'Minimum amount ' => 'Montant minimum', + 'Network pick up ' => 'Point retrait', + 'Payment in ' => 'Paiement en ', + 'Payment page type' => 'Type de la page de paiement', + 'Payplug.js' => 'Payplug.js', + 'Select your PayPlug offer' => 'Séléctionner votre offre PayPlug', + 'Send order confirmation on payment success' => 'Confirmation de commande si le paiement réussit', + 'Store pick up ' => 'Retrait en magasin', + 'Test API secret key' => 'Test API secret key', + 'This will allow your customer to save their card fo future order.' => 'Cela permettra a vos clients d\'enregistrer leur carte pour leur commande futur.', + 'Trigger the payment on order status change (max : 7 days after)' => 'Déclencher le paiement au changement de statut (max : 7 jour après)', + 'Unknown' => 'Inconnue', + 'What status to set on expired capture ' => 'Quel statut pour les commandes dont l\'empreinte a expirée ', + 'Which status to set when a capture is authorized' => 'Quel staut mettre quand l\'empreinte est autorisé', +); diff --git a/local/modules/PayPlugModule/I18n/frontOffice/default/fr_FR.php b/local/modules/PayPlugModule/I18n/frontOffice/default/fr_FR.php new file mode 100755 index 00000000..6303a0d4 --- /dev/null +++ b/local/modules/PayPlugModule/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,9 @@ + 'Une carte de paiement finissant par %last4 as été enregitrée précédemment a votre demande. Le paiement de cette commande seras fait automatiquement après un clic sur le bouton "Etape suivante".', + 'One click payment is available !' => 'Paiement en un clic disponible !', + 'Pay in %n times without fees.' => 'Paiement en %n fois sans frais.', + 'To clear your payment card details, please click here' => 'Pour supprimer ces informations de paiement, cliquez ici', + 'You will be asked to save your payment card data.' => 'Ils vous sera demandé d\'enregistrer votre carte bancaire.', +); diff --git a/local/modules/PayPlugModule/Model/OrderPayPlugData.php b/local/modules/PayPlugModule/Model/OrderPayPlugData.php new file mode 100755 index 00000000..00b3f7bc --- /dev/null +++ b/local/modules/PayPlugModule/Model/OrderPayPlugData.php @@ -0,0 +1,20 @@ +getConfigValue('is_initialized', false)) { + $database = new Database($con); + $database->insertSql(null, [__DIR__ . "/Config/thelia.sql"]); + } + } + + public function update($currentVersion, $newVersion, ConnectionInterface $con = null) + { + $finder = Finder::create() + ->name('*.sql') + ->depth(0) + ->sortByName() + ->in(__DIR__ . DS . 'Config' . DS . 'update'); + + $database = new Database($con); + + /** @var \SplFileInfo $file */ + foreach ($finder as $file) { + if (version_compare($currentVersion, $file->getBasename('.sql'), '<')) { + $database->insertSql(null, [$file->getPathname()]); + } + } + } + + public function isValidPayment() + { + /** @var PaymentService $paymentService */ + $paymentService = $this->container->get('payplugmodule_payment_service'); + return $paymentService->isPayPlugAvailable(); + } + + /** + * @inheritDoc + */ + public function pay(Order $order) + { + try { + /** @var PaymentService $paymentService */ + $paymentService = $this->container->get('payplugmodule_payment_service'); + + $slice = 1; + + $isMultiPayment = $this->getRequest()->getSession()->get(OrderFormListener::PAY_PLUG_MULTI_PAYMENT_FIELD_NAME, 0); + if ($isMultiPayment) { + $orderTotalAmount = $order->getTotalAmount(); + + $minAmount = PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_MINIMUM); + $maxAmount = PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_MAXIMUM); + + if ($minAmount <= $orderTotalAmount && $maxAmount >= $orderTotalAmount) { + $slice = PayPlugModule::getConfigValue(PayPlugConfigValue::MULTI_PAYMENT_TIMES); + } + } + + $payment = $paymentService->sendOrderPayment( + $order, + PayPlugModule::getConfigValue(PayPlugConfigValue::DIFFERED_PAYMENT_ENABLED, false), + PayPlugModule::getConfigValue(PayPlugConfigValue::ONE_CLICK_PAYMENT_ENABLED, false), + $slice + ); + + $forceRedirect = false; + if (true === $payment['isPaid']) { + $forceRedirect = true; + $payment['url'] = URL::getInstance()->absoluteUrl('/order/placed/'.$order->getId()); + } + + if ($this->getRequest()->isXmlHttpRequest()) { + return new JsonResponse( + [ + 'paymentUrl' => $payment['url'], + 'forceRedirect' => $forceRedirect + ] + ); + } + } catch (\Exception $exception) { + if ($this->getRequest()->isXmlHttpRequest()) { + return new JsonResponse(['error' => $exception->getMessage()], 400); + } + return RedirectResponse::create(URL::getInstance()->absoluteUrl('error')); + } + + return new RedirectResponse($payment['url']); + } + + public function getHooks() + { + return [ + [ + "type" => TemplateDefinition::BACK_OFFICE, + "code" => "payplugmodule.configuration.bottom", + "title" => [ + "en_US" => "Bottom of PayPlug configuration page", + "fr_FR" => "Bas de la page de configuration PayPlug", + ], + "block" => false, + "active" => true, + ] + ]; + } + +} diff --git a/local/modules/PayPlugModule/Readme.md b/local/modules/PayPlugModule/Readme.md new file mode 100755 index 00000000..47b25a67 --- /dev/null +++ b/local/modules/PayPlugModule/Readme.md @@ -0,0 +1,17 @@ +# Pay Plug Module + +Thelia module for the payment solution PayPlug https://www.payplug.com + +## Installation + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/pay-plug-module:~1.0.0 +``` + +## Usage + +Go to module configuration page select the offer you have with PayPlug (starter, pro or premium) and you will see the options who corresponding to your offer. diff --git a/local/modules/PayPlugModule/Service/OrderStatusService.php b/local/modules/PayPlugModule/Service/OrderStatusService.php new file mode 100755 index 00000000..1d208341 --- /dev/null +++ b/local/modules/PayPlugModule/Service/OrderStatusService.php @@ -0,0 +1,103 @@ +dispatcher = $dispatcher; + } + + /** + * @var EventDispatcherInterface + */ + protected $dispatcher; + + public function initAllStatuses() + { + $this->findOrCreateRefundPendingOrderStatus(); + $this->findOrCreateAuthorizedCaptureOrderStatus(); + $this->findOrCreateExpiredCaptureOrderStatus(); + } + + /** + * @return \Thelia\Model\OrderStatus + */ + public function findOrCreateRefundPendingOrderStatus() + { + $refundPendingOrderStatus = OrderStatusQuery::create() + ->findOneByCode($this::REFUND_PENDING_ORDER_STATUS_CODE); + + if (null !== $refundPendingOrderStatus) { + return $refundPendingOrderStatus; + } + + $refundPendingOrderStatusEvent = (new OrderStatusCreateEvent()) + ->setCode(self::REFUND_PENDING_ORDER_STATUS_CODE) + ->setColor("#A7A7A7") + ->setLocale('en_US') + ->setTitle('Refund pending'); + + $this->dispatcher->dispatch(TheliaEvents::ORDER_STATUS_CREATE, $refundPendingOrderStatusEvent); + + return $refundPendingOrderStatusEvent->getOrderStatus(); + } + + /** + * @return \Thelia\Model\OrderStatus + */ + public function findOrCreateAuthorizedCaptureOrderStatus() + { + $authorizedCaptureOrderStatus = OrderStatusQuery::create() + ->findOneByCode($this::AUTHORIZED_CAPTURE_ORDER_STATUS_CODE); + + if (null !== $authorizedCaptureOrderStatus) { + return $authorizedCaptureOrderStatus; + } + + $authorizedCaptureOrderStatus = (new OrderStatusCreateEvent()) + ->setCode(self::AUTHORIZED_CAPTURE_ORDER_STATUS_CODE) + ->setColor("#71ED71") + ->setLocale('en_US') + ->setTitle('Authorized capture'); + + $this->dispatcher->dispatch(TheliaEvents::ORDER_STATUS_CREATE, $authorizedCaptureOrderStatus); + + return $authorizedCaptureOrderStatus->getOrderStatus(); + } + + /** + * @return \Thelia\Model\OrderStatus + */ + public function findOrCreateExpiredCaptureOrderStatus() + { + $expiredCaptureOrderStatus = OrderStatusQuery::create() + ->findOneByCode($this::EXPIRED_CAPTURE_ORDER_STATUS_CODE); + + if (null !== $expiredCaptureOrderStatus) { + return $expiredCaptureOrderStatus; + } + + $expiredCaptureOrderStatus = (new OrderStatusCreateEvent()) + ->setCode(self::EXPIRED_CAPTURE_ORDER_STATUS_CODE) + ->setColor("#4B4B4B") + ->setLocale('en_US') + ->setTitle('Expired capture'); + + $this->dispatcher->dispatch(TheliaEvents::ORDER_STATUS_CREATE, $expiredCaptureOrderStatus); + + return $expiredCaptureOrderStatus->getOrderStatus(); + } + +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/Service/PaymentService.php b/local/modules/PayPlugModule/Service/PaymentService.php new file mode 100755 index 00000000..51f8d199 --- /dev/null +++ b/local/modules/PayPlugModule/Service/PaymentService.php @@ -0,0 +1,152 @@ +dispatcher = $dispatcher; + self::initAuth(); + } + + public function isPayPlugAvailable() + { + if (!PayPlugModule::getConfigValue(PayPlugConfigValue::PAYMENT_ENABLED, false)) { + return false; + } + + // Check API availability + try { + Payment::listPayments(1); + } catch (\Exception $exception) { + return false; + } + + return true; + } + + /** + * @param Order $order + * @return array + * @throws \Propel\Runtime\Exception\PropelException + */ + public function sendOrderPayment( + Order $order, + bool $capture = false, + bool $allowSaveCard = false, + int $paymentSlice = 1 + ) { + $paymentEvent = (new PayPlugPaymentEvent()) + ->buildFromOrder($order) + ->setCapture($capture) + ->setAllowSaveCard($allowSaveCard); + + if (null !== $card = PayPlugCardQuery::create()->findOneByCustomerId($order->getCustomerId())) { + $paymentEvent->setPaymentMethod($card->getUuid()) + ->setInitiator('PAYER') + ->setAllowSaveCard(false); + } + + $firstPayment = null; + if ($paymentSlice > 1) { + $totalAmount = $paymentEvent->getAmount(); + $firstAmount = round($totalAmount / $paymentSlice) + $totalAmount % $paymentSlice; + $paymentEvent->setForceSaveCard(true) + ->setAllowSaveCard(false) + ->setPaymentMethod(null) + ->setAmount($firstAmount); + $today = (new \DateTime())->setTime(0,0,0,0); + + $firstPayment = (new OrderPayPlugMultiPayment()) + ->setAmount($paymentEvent->getAmount()) + ->setOrder($order) + ->setPlannedAt($today) + ->setPaymentId($paymentEvent->getPaymentId()) + ->setIsFirstPayment(true); + $firstPayment->save(); + + for ($paymentCount = 1; $paymentCount < $paymentSlice ; $paymentCount++) { + $paymentDay = (clone $today)->add((new \DateInterval('P'.intval($paymentCount * 30).'D'))); + $multiPayment = (new OrderPayPlugMultiPayment()) + ->setAmount(round($totalAmount / $paymentSlice)) + ->setOrder($order) + ->setPlannedAt($paymentDay); + $multiPayment->save(); + } + } + + $this->dispatcher->dispatch(PayPlugPaymentEvent::ORDER_PAYMENT_EVENT, $paymentEvent); + + if (null !== $firstPayment) { + $firstPayment->setPaymentId($paymentEvent->getPaymentId()) + ->save(); + + } + + $isPaid = $paymentEvent->isPaid(); + + // If one click payment consider it as isPaid (redirect to order/placed) + if (!$isPaid && $paymentEvent->isCapture() && null !== $paymentEvent->getPaymentMethod()) { + $isPaid = true; + } + + return [ + 'id' => $paymentEvent->getPaymentId(), + 'url' => $paymentEvent->getPaymentUrl(), + 'isPaid' => $isPaid + ]; + } + + public function doOrderCapture(Order $order) + { + $paymentEvent = (new PayPlugPaymentEvent()) + ->buildFromOrder($order); + + $this->dispatcher->dispatch(PayPlugPaymentEvent::ORDER_CAPTURE_EVENT, $paymentEvent); + } + + public function doOrderRefund(Order $order, int $amountRefund = null) + { + $paymentEvent = (new PayPlugPaymentEvent()) + ->buildFromOrder($order); + + if (null !== $amountRefund) { + $paymentEvent->setAmount($amountRefund); + } + + $this->dispatcher->dispatch(PayPlugPaymentEvent::ORDER_REFUND_EVENT, $paymentEvent); + } + + public function getNotificationResource(Request $request) + { + return Notification::treat($request->getContent()); + } + + public function initAuth() + { + return Payplug::init( + [ + 'secretKey' => PayPlugConfigValue::getApiKey(), + 'apiVersion' => '2019-08-06' + ] + ); + } +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/composer.json b/local/modules/PayPlugModule/composer.json new file mode 100755 index 00000000..31f9a7e6 --- /dev/null +++ b/local/modules/PayPlugModule/composer.json @@ -0,0 +1,13 @@ +{ + "name": "thelia/pay-plug-module", + "license": "LGPL-3.0-or-later", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1", + "payplug/payplug-php": "^3.2", + "giggsey/libphonenumber-for-php": "^8.12" + }, + "extra": { + "installer-name": "PayPlugModule" + } +} diff --git a/local/modules/PayPlugModule/images/payplug.png b/local/modules/PayPlugModule/images/payplug.png new file mode 100755 index 00000000..2535a63c Binary files /dev/null and b/local/modules/PayPlugModule/images/payplug.png differ diff --git a/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/assets/logo-payplug.png b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/assets/logo-payplug.png new file mode 100755 index 00000000..fbc631cc Binary files /dev/null and b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/assets/logo-payplug.png differ diff --git a/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/configuration.html b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/configuration.html new file mode 100755 index 00000000..1f2cd9fe --- /dev/null +++ b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/configuration.html @@ -0,0 +1,408 @@ +{extends file="admin-layout.tpl"} + +{block name="after-bootstrap-css"} + +{/block} + +{block name="no-return-functions"} +{$admin_current_location = 'module'} +{/block} + +{block name="page-title"}{intl l='PayPlug module configuration' d='payplugmodule.bo.default'}{/block} + +{block name="check-resource"}admin.module{/block} +{block name="check-access"}view{/block} +{block name="check-module"}PayPlug{/block} + +{block name="main-content"} + +
+
+ {form name="payplugmodule_configuration_form"} + + {form_hidden_fields form=$form} + + {if $form_error} +
{$form_error_message}
+ {/if} + + {form_field form=$form field='success_url'} + + {/form_field} + +
+
+
+ Payment configuration +
+
+
+

{intl l="PayPlug offer" d='payplugmodule.bo.default'}

+
+
+ {form_field form=$form field="offer"} +
+ +
+ {$inputOfferName = {$name}} + {foreach from=$choices item=choice} + value}checked{/if} /> + + {/foreach} + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+
+
+

{intl l="PayPlug API configuration" d='payplugmodule.bo.default'}

+
+
+ {form_field form=$form field="api_mode"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+ {form_field form=$form field='live_api_key'} + + + {if ! empty($label_attr.help)} + {$label_attr.help nofilter} + {/if} + {/form_field} +
+
+ {form_field form=$form field='test_api_key'} + + + {if ! empty($label_attr.help)} + {$label_attr.help nofilter} + {/if} + {/form_field} +
+
+
+
+

{intl l="PayPlug basic payment configuration" d='payplugmodule.bo.default'}

+
+
+ {form_field form=$form field="payment_enabled"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+ {form_field form=$form field="payment_page_type"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+ {render_form_field field="send_confirmation_message_only_if_paid"} +
+
+
+
+

{intl l="PayPlug pro offer configuration" d='payplugmodule.bo.default'}

+
+
+ {form_field form=$form field="one_click_payment_enabled"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+ {form_field form=$form field="multi_payment_enabled"} + {$inputMultiPaymentName = $name} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+
+ {intl l="Payments in several times are not guaranteed, a default may occur on future due dates." d='payplugmodule.bo.default'} +
+
+ {form_field form=$form field='multi_payment_minimum'} + + + {/form_field} +
+
+ {form_field form=$form field='multi_payment_maximum'} + + + {/form_field} +
+
+ {form_field form=$form field="multi_payment_times"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+

+ {intl l="To trigger the payments on planned date please add a daily cron on this Thelia command " d='payplugmodule.bo.default'} + php Thelia payplug:treat:multi_payment +

+
+
+
+
+
+

{intl l="PayPlug premium offer configuration" d='payplugmodule.bo.default'}

+
+
+ {form_field form=$form field="differed_payment_enabled"} + {$inputDifferedPaymentName = $name} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+
+ {form_field form=$form field="differed_payment_authorized_capture_status"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+ {form_field form=$form field="differed_payment_trigger_capture_status"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+ {form_field form=$form field="differed_payment_capture_expired_status"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} +
+
+
+
+
+

{intl l="PayPlug delivery type configuration" d='payplugmodule.bo.default'}

+

{intl l="Please select a Pay Plug delivery type correspondance for each delivery modules" d='payplugmodule.bo.default'}

+
+
+ {foreach from=$deliveryModuleFormFields item="deliveryModuleFormField"} +
+ {form_field form=$form field=$deliveryModuleFormField['name']} + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} + {/form_field} +
+ {/foreach} +
+
+
+
+
+
+ +
+
+
+ + {/form} +
+ {hook name="payplugmodule.configuration.bottom"} +
+{/block} + +{block name="javascript-initialization"} + +{/block} + +{block name="javascript-last-call"} + +{/block} diff --git a/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/order_pay_plug.html b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/order_pay_plug.html new file mode 100644 index 00000000..f39cf030 --- /dev/null +++ b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/order_pay_plug.html @@ -0,0 +1,168 @@ +
+
+

{intl l="PayPlug order data" d='payplugmodule.bo.default'}

+
+
+ {form name="payplugmodule_order_action_form_refund"} + {if $form_error} +
{$form_error_message}
+ {/if} + {/form} + {form name="payplugmodule_order_action_form"} + {if $form_error} +
{$form_error_message}
+ {/if} + {/form} +
+ + + {if $amountRefunded || $isPaid} + + + + + + {/if} + {if $needCapture} + + + {if null == $capturedAt} + + + {else} + + + {/if} + + {/if} + {if count($multiPayments) > 0} + + + + + {/if} + +
{intl l="Refund" d='payplugmodule.bo.default'} + {loop type="order" name="refund-order-amount" id=$order_id customer="*" with_prev_next_info="true" backend_context="1"} + {$orderTotalAmount = $TOTAL_TAXED_AMOUNT} + {$orderTotalAmountWithoutShipping = $TOTAL_TAXED_AMOUNT-$POSTAGE} + {/loop} + {if $amountRefunded} +
{format_money number=$amountRefunded/100} {intl l=" refunded" d='payplugmodule.bo.default'}
+ {/if} + {if $orderTotalAmount > ($amountRefunded/100) } +
+
+ + +
+
+ +
+ + + {currency attr="symbol"} +
+
+ +
+ {/if} +
{intl l="Differed payment" d='payplugmodule.bo.default'}{intl l="Payment capture will expire on " d='payplugmodule.bo.default'}{format_date date=$captureExpireAt} + + {intl l="Force the capture for this order" d='payplugmodule.bo.default'} + + + {intl l="Payment was captured at " d='payplugmodule.bo.default'}{format_date date=$capturedAt} +
{intl l="Multi payment" d='payplugmodule.bo.default'} + + + + + + + + + {$paymentCount = 1} + {foreach from=$multiPayments item=multiPayment} + + + + + + + + {$paymentCount = $paymentCount + 1} + {/foreach} +
{intl l="Payment n° " d='payplugmodule.bo.default'}{intl l="Amount" d='payplugmodule.bo.default'}{intl l="Planned at" d='payplugmodule.bo.default'}{intl l="Paid at" d='payplugmodule.bo.default'}{intl l="Refunded at" d='payplugmodule.bo.default'}
{$paymentCount}{format_money number=$multiPayment['amount']/100}{if $multiPayment['plannedAt']}{format_date date=$multiPayment['plannedAt']}{else}{intl l="Canceled" d='payplugmodule.bo.default'}{/if}{if $multiPayment['paidAt']}{format_date date=$multiPayment['paidAt']}{/if}{if $multiPayment['refundedAt']}{format_date date=$multiPayment['refundedAt']}{/if}
+
+
+ +{capture "refund_dialog"} + {form name="payplugmodule_order_action_form_refund"} + {form_hidden_fields form=$form} + {if $form_error} +
{$form_error_message}
+ {/if} + {form_field form=$form field='success_url'} + + {/form_field} + {form_field form=$form field="order_id"} + + {/form_field} + {form_field form=$form field="refund_amount"} + + {/form_field} + {/form} +{/capture} + +{include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "order_refund_dialog" + dialog_title = {intl l="Refund order" d="payplugmodule.bo.default"} + dialog_message = {intl l="Do you really want to refund this order ?" d="payplugmodule.bo.default"} + +form_method = "POST" +form_action = {url path='/admin/payplugmodule/order/refund'} +form_content = {$smarty.capture.refund_dialog nofilter} +} + +{capture "capture_dialog"} + {form name="payplugmodule_order_action_form"} + {form_hidden_fields form=$form} + {if $form_error} +
{$form_error_message}
+ {/if} + {form_field form=$form field='success_url'} + + {/form_field} + {form_field form=$form field="order_id"} + + {/form_field} + {/form} +{/capture} + +{include +file = "includes/generic-confirm-dialog.html" + +dialog_id = "order_capture_dialog" +dialog_title = {intl l="Capture order" d="payplugmodule.bo.default"} +dialog_message = {intl l="Do you really want to capture this order ?" d="payplugmodule.bo.default"} + +form_method = "POST" +form_action = {url path='/admin/payplugmodule/order/capture'} +form_content = {$smarty.capture.capture_dialog nofilter} +} \ No newline at end of file diff --git a/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/order_pay_plug.js b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/order_pay_plug.js new file mode 100644 index 00000000..93f4b036 --- /dev/null +++ b/local/modules/PayPlugModule/templates/backOffice/default/PayPlugModule/order_pay_plug.js @@ -0,0 +1,21 @@ +var inputAmountRefund = $('#input_amount_refund'); +var maxAmountRefund = parseFloat(inputAmountRefund.attr('max')); + +$("#quick_select_amount_refund").change(function (e) { + var amountToRefund = 0; + var selected = $(e.target).val(); + + if (null !== selected) { + amountToRefund = selected.reduce(function (a, c) { + return parseFloat(a) + parseFloat(c); + }); + } + + amountToRefund = amountToRefund > maxAmountRefund ? maxAmountRefund : amountToRefund; + inputAmountRefund.val(amountToRefund).trigger('change'); +}); + +inputAmountRefund.change(function (e) { + var amountToRefund = $(e.target).val() > maxAmountRefund ? maxAmountRefund : $(e.target).val(); + $("#refund_amount").val(amountToRefund); +}); \ No newline at end of file diff --git a/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/multi-payment.html b/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/multi-payment.html new file mode 100755 index 00000000..4229e305 --- /dev/null +++ b/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/multi-payment.html @@ -0,0 +1,8 @@ +{form_field form=$form field='pay_plug_multi_payment'} +

+ + +
+ {intl l='You will be asked to save your payment card data.' n={url path='/payplug/card/delete'} d='payplugmodule.fo.default'} +

+{/form_field} \ No newline at end of file diff --git a/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/one-click-payment.html b/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/one-click-payment.html new file mode 100755 index 00000000..60e31463 --- /dev/null +++ b/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/one-click-payment.html @@ -0,0 +1,6 @@ +

{intl l='One click payment is available !' d='payplugmodule.fo.default'}

+

+ {intl l='A payment card ending by %last4 has been previously saved at your request. Payment for your order will be made immediately after clicking the "Next Step" button.' last4=$last4 d='payplugmodule.fo.default'} +
+ {intl l='To clear your payment card details, please click here' url={url path='/payplug/card/delete'} d='payplugmodule.fo.default'}. +

diff --git a/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/order-invoice-after-js-include.html b/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/order-invoice-after-js-include.html new file mode 100755 index 00000000..512fa424 --- /dev/null +++ b/local/modules/PayPlugModule/templates/frontOffice/default/PayPlugModule/order-invoice-after-js-include.html @@ -0,0 +1,34 @@ + + + \ No newline at end of file diff --git a/local/modules/Recettes/Config/config.xml b/local/modules/Recettes/Config/config.xml new file mode 100644 index 00000000..adaf1e24 --- /dev/null +++ b/local/modules/Recettes/Config/config.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local/modules/Recettes/Config/config_dev.xml b/local/modules/Recettes/Config/config_dev.xml new file mode 100644 index 00000000..adaf1e24 --- /dev/null +++ b/local/modules/Recettes/Config/config_dev.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local/modules/Recettes/Config/config_prod.xml b/local/modules/Recettes/Config/config_prod.xml new file mode 100644 index 00000000..adaf1e24 --- /dev/null +++ b/local/modules/Recettes/Config/config_prod.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local/modules/Recettes/Config/config_test.xml b/local/modules/Recettes/Config/config_test.xml new file mode 100644 index 00000000..adaf1e24 --- /dev/null +++ b/local/modules/Recettes/Config/config_test.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local/modules/Recettes/Config/module.xml b/local/modules/Recettes/Config/module.xml new file mode 100644 index 00000000..1acc4471 --- /dev/null +++ b/local/modules/Recettes/Config/module.xml @@ -0,0 +1,43 @@ + + + Recettes\Recettes + + Automatically generated module - please update module.xml file + + + + Module généré automatiquement - éditez le fichier module.xml + + + + + en_US + fr_FR + + + + + + + + + classic + + 2.4.3 + other + 0 + 0 + diff --git a/local/modules/Recettes/Config/routing.xml b/local/modules/Recettes/Config/routing.xml new file mode 100644 index 00000000..075d4c35 --- /dev/null +++ b/local/modules/Recettes/Config/routing.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/local/modules/Recettes/Config/schema.xml b/local/modules/Recettes/Config/schema.xml new file mode 100644 index 00000000..509bcfee --- /dev/null +++ b/local/modules/Recettes/Config/schema.xml @@ -0,0 +1,25 @@ + + + + diff --git a/local/modules/Recettes/I18n/en_US.php b/local/modules/Recettes/I18n/en_US.php new file mode 100644 index 00000000..0b4fa142 --- /dev/null +++ b/local/modules/Recettes/I18n/en_US.php @@ -0,0 +1,4 @@ + 'The displayed english string', +); diff --git a/local/modules/Recettes/I18n/fr_FR.php b/local/modules/Recettes/I18n/fr_FR.php new file mode 100644 index 00000000..37086245 --- /dev/null +++ b/local/modules/Recettes/I18n/fr_FR.php @@ -0,0 +1,4 @@ + 'La traduction française de la chaine', +); diff --git a/local/modules/Recettes/Readme.md b/local/modules/Recettes/Readme.md new file mode 100644 index 00000000..d415305e --- /dev/null +++ b/local/modules/Recettes/Readme.md @@ -0,0 +1,55 @@ +# Recettes + +Add a short description here. You can also add a screenshot if needed. + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is Recettes. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require your-vendor/recettes-module:~1.0 +``` + +## Usage + +Explain here how to use your module, how to configure it, etc. + +## Hook + +If your module use one or more hook, fill this part. Explain which hooks are used. + + +## Loop + +If your module declare one or more loop, describe them here like this : + +[loop name] + +### Input arguments + +|Argument |Description | +|--- |--- | +|**arg1** | describe arg1 with an exemple. | +|**arg2** | describe arg2 with an exemple. | + +### Output arguments + +|Variable |Description | +|--- |--- | +|$VAR1 | describe $VAR1 variable | +|$VAR2 | describe $VAR2 variable | + +### Exemple + +Add a complete exemple of your loop + +## Other ? + +If you have other think to put, feel free to complete your readme as you want. diff --git a/local/modules/Recettes/Recettes.php b/local/modules/Recettes/Recettes.php new file mode 100644 index 00000000..9cac1ca3 --- /dev/null +++ b/local/modules/Recettes/Recettes.php @@ -0,0 +1,28 @@ + +
+ {render_form_field field='titre'} + {render_form_field field='difficulte'} + {render_form_field field='temps_preparation'} + {render_form_field field='temps_cuisson'} + {render_form_field field='ingredients'} + {render_form_field field="description" extra_class="wysiwyg"} +
+ \ No newline at end of file diff --git a/templates/backOffice/custom/content-edit.html b/templates/backOffice/custom/content-edit.html index fd82dfab..a197ae47 100644 --- a/templates/backOffice/custom/content-edit.html +++ b/templates/backOffice/custom/content-edit.html @@ -9,6 +9,15 @@ {block name="page-title"}{intl l='Edit content'}{/block} +{assign var="recette" value=false} +{loop name="my_folder_path" type="folder-path" visible="*" folder=$folder_id} + {if $TITLE|strstr:"recettes"} + {assign var="recette" value=true} + {/if} +{/loop} + + + {block name="main-content"}
@@ -83,6 +92,9 @@
  • {intl l="Documents"}
  • {$smarty.capture.content_tab_tab nofilter}
  • {intl l="Modules"}
  • + {if $recette} +
  • {intl l="Recette"}
  • + {/if}
    @@ -212,6 +224,12 @@ {* ugly fix : {hook name="content.tab-content" id="{$content_id}" view="content"} *} {include file="includes/module-tab-content.html" hook="content.tab-content" location="content-edit" id="{$content_id}" view="content"}
    + + {if $recette} +
    + {hook name="hook-recette" content_id=$content_id} +
    + {/if}
    diff --git a/templates/backOffice/custom/includes/module-block.html b/templates/backOffice/custom/includes/module-block.html index 8b8bd4d7..b4c71421 100644 --- a/templates/backOffice/custom/includes/module-block.html +++ b/templates/backOffice/custom/includes/module-block.html @@ -158,6 +158,8 @@ {$icon nofilter}{intl l="Shipping zones"} {/if} + {* TheCoreDev le 31/03/2021 : y'avait un bogue sur l'affichage du bouton Configurer *} + {$buttons = []} {if $EXISTS} {if $CONFIGURABLE == 1 && $ACTIVE} diff --git a/templates/backOffice/custom/template.xml b/templates/backOffice/custom/template.xml index 40cad776..99a7c094 100644 --- a/templates/backOffice/custom/template.xml +++ b/templates/backOffice/custom/template.xml @@ -3,10 +3,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://thelia.net/schema/dic/template http://thelia.net/schema/dic/template/template-1_0.xsd"> - Template back office par défaut + Template back office personnalisé - Default back-office template + Custom back-office template default @@ -33,10 +33,9 @@ 1.0.0 - Thelia team - thelia.net - contact@thelia.net - thelia.net + Laurent LE CORRE + TheCoreDev + laurent@thecoredev.fr 2.4.3 diff --git a/templates/frontOffice/custom/content.html b/templates/frontOffice/custom/content.html index c574f5ed..31e2dadd 100644 --- a/templates/frontOffice/custom/content.html +++ b/templates/frontOffice/custom/content.html @@ -25,6 +25,8 @@ {/if} {/block} +{assign var="recette" value=false} + {* Breadcrumb *} {block name='no-return-functions' append} {if $content_id} @@ -32,6 +34,11 @@ {loop type="content" name="content-breadcrumb" id=$content_id limit="1"} {loop name="folder_path" type="folder-path" folder={$DEFAULT_FOLDER}} {$breadcrumbs[] = ['title' => {$TITLE}, 'url'=> {$URL nofilter}]} + + {if $TITLE|strstr:"recettes"} + {assign var="recette" value=true} + {/if} + {/loop} {$breadcrumbs[] = ['title' => {$TITLE}, 'url'=> {$URL nofilter}]} {/loop} @@ -62,7 +69,9 @@ {/if} + {if $recette} {$images[0]['title']} + {/if} {if $DESCRIPTION}
    @@ -70,7 +79,9 @@
    {/if} + {if $recette} {$images[1]['title']} + {/if} {ifloop rel="blog.document"}