diff --git a/composer.json b/composer.json index e29c5c2c..c5b70cd2 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,8 @@ "commerceguys/addressing": "0.8.*", "symfony/cache": "~3.1.0", "thelia/colissimows-module": "^1.1", - "thelia/colissimo-label-module": "~0.3.2" + "thelia/colissimo-label-module": "~0.3.2", + "thelia/stripe-payment-module": "~2.0.4" }, "require-dev": { "fzaninotto/faker": "1.5.*", diff --git a/composer.lock b/composer.lock index 9e6f5c2d..2a999666 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9792ca0006636137328111bcecd57a36", + "content-hash": "5713db4d71a29f2a4aeca5a61aa6e072", "packages": [ { "name": "commerceguys/addressing", @@ -1199,6 +1199,62 @@ ], "time": "2014-11-23T20:37:11+00:00" }, + { + "name": "stripe/stripe-php", + "version": "v6.43.1", + "source": { + "type": "git", + "url": "https://github.com/stripe/stripe-php.git", + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/42fcdaf99c44bb26937223f8eae1f263491d5ab8", + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "1.*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": "~2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Stripe\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "description": "Stripe PHP Library", + "homepage": "https://stripe.com/", + "keywords": [ + "api", + "payment processing", + "stripe" + ], + "time": "2019-08-29T16:56:12+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v5.4.1", @@ -3522,6 +3578,34 @@ "description": "Number management library", "time": "2015-11-05T15:52:55+00:00" }, + { + "name": "thelia/stripe-payment-module", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/thelia-modules/StripePayment.git", + "reference": "50b758551bfee2208f2c196cc552790688f9e084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thelia-modules/StripePayment/zipball/50b758551bfee2208f2c196cc552790688f9e084", + "reference": "50b758551bfee2208f2c196cc552790688f9e084", + "shasum": "" + }, + "require": { + "stripe/stripe-php": "6.*", + "thelia/installer": "~1.1" + }, + "type": "thelia-module", + "extra": { + "installer-name": "StripePayment" + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0+" + ], + "time": "2020-06-03T13:40:26+00:00" + }, { "name": "wsdltophp/package-colissimo-postage", "version": "1.0.0", diff --git a/core/vendor/composer/autoload_psr4.php b/core/vendor/composer/autoload_psr4.php index c584decf..444d2b4d 100644 --- a/core/vendor/composer/autoload_psr4.php +++ b/core/vendor/composer/autoload_psr4.php @@ -46,6 +46,7 @@ return array( 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), 'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'), 'Symfony\\Cmf\\Component\\Routing\\' => array($vendorDir . '/symfony-cmf/routing'), + 'Stripe\\' => array($vendorDir . '/stripe/stripe-php/lib'), 'SoapClient\\' => array($vendorDir . '/wsdltophp/package-colissimo-postage/SoapClient'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'Giggsey\\Locale\\' => array($vendorDir . '/giggsey/locale/src'), diff --git a/core/vendor/composer/autoload_static.php b/core/vendor/composer/autoload_static.php index 753652c3..904c149f 100644 --- a/core/vendor/composer/autoload_static.php +++ b/core/vendor/composer/autoload_static.php @@ -73,6 +73,7 @@ class ComposerStaticInit60933c160e6e784f12d951b85ffd7bf5 'Symfony\\Component\\Cache\\' => 24, 'Symfony\\Component\\BrowserKit\\' => 29, 'Symfony\\Cmf\\Component\\Routing\\' => 30, + 'Stripe\\' => 7, 'SoapClient\\' => 11, ), 'P' => @@ -261,6 +262,10 @@ class ComposerStaticInit60933c160e6e784f12d951b85ffd7bf5 array ( 0 => __DIR__ . '/..' . '/symfony-cmf/routing', ), + 'Stripe\\' => + array ( + 0 => __DIR__ . '/..' . '/stripe/stripe-php/lib', + ), 'SoapClient\\' => array ( 0 => __DIR__ . '/..' . '/wsdltophp/package-colissimo-postage/SoapClient', diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json index e597bf90..aea80352 100644 --- a/core/vendor/composer/installed.json +++ b/core/vendor/composer/installed.json @@ -1296,7 +1296,8 @@ "homepage": "https://github.com/sebastianbergmann/php-token-stream/", "keywords": [ "tokenizer" - ] + ], + "abandoned": true }, { "name": "phpunit/phpunit", @@ -2227,6 +2228,64 @@ "stack" ] }, + { + "name": "stripe/stripe-php", + "version": "v6.43.1", + "version_normalized": "6.43.1.0", + "source": { + "type": "git", + "url": "https://github.com/stripe/stripe-php.git", + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/42fcdaf99c44bb26937223f8eae1f263491d5ab8", + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "1.*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": "~2.8" + }, + "time": "2019-08-29T16:56:12+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Stripe\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "description": "Stripe PHP Library", + "homepage": "https://stripe.com/", + "keywords": [ + "api", + "payment processing", + "stripe" + ] + }, { "name": "swiftmailer/swiftmailer", "version": "v5.4.1", @@ -4684,6 +4743,36 @@ ], "description": "Number management library" }, + { + "name": "thelia/stripe-payment-module", + "version": "2.0.4", + "version_normalized": "2.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/thelia-modules/StripePayment.git", + "reference": "50b758551bfee2208f2c196cc552790688f9e084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thelia-modules/StripePayment/zipball/50b758551bfee2208f2c196cc552790688f9e084", + "reference": "50b758551bfee2208f2c196cc552790688f9e084", + "shasum": "" + }, + "require": { + "stripe/stripe-php": "6.*", + "thelia/installer": "~1.1" + }, + "time": "2020-06-03T13:40:26+00:00", + "type": "thelia-module", + "extra": { + "installer-name": "StripePayment" + }, + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0+" + ] + }, { "name": "wsdltophp/package-colissimo-postage", "version": "1.0.0", diff --git a/local/modules/StripePayment/Classes/StripePaymentException.php b/local/modules/StripePayment/Classes/StripePaymentException.php new file mode 100644 index 00000000..633ef048 --- /dev/null +++ b/local/modules/StripePayment/Classes/StripePaymentException.php @@ -0,0 +1,13 @@ + + */ +class StripePaymentException extends \Exception +{ + +} \ No newline at end of file diff --git a/local/modules/StripePayment/Classes/StripePaymentLog.php b/local/modules/StripePayment/Classes/StripePaymentLog.php new file mode 100644 index 00000000..f89d5dd0 --- /dev/null +++ b/local/modules/StripePayment/Classes/StripePaymentLog.php @@ -0,0 +1,60 @@ + + */ +class StripePaymentLog +{ + const EMERGENCY = 'EMERGENCY'; + const ALERT = 'ALERT'; + const CRITICAL = 'CRITICAL'; + const ERROR = 'ERROR'; + const WARNING = 'WARNING'; + const NOTICE = 'NOTICE'; + const INFO = 'INFO'; + const DEBUG = 'DEBUG'; + const LOGCLASS = "\\Thelia\\Log\\Destination\\TlogDestinationFile"; + + /** @var Tlog $log */ + protected $log; + + /** + * Log a message + * + * @param string $message Message + * @param string $severity EMERGENCY|ALERT|CRITICAL|ERROR|WARNING|NOTICE|INFO|DEBUG + * @param string $category Category + */ + public function logText($message, $severity = 'ALERT', $category = 'stripe') + { + $this->setTLogStripe(); + $msg = "$category.$severity: $message"; + $this->log->info($msg); + // Back to previous state + $this->getBackToPreviousState(); + } + + /** + * @return Tlog + */ + protected function setTLogStripe() + { + /* + * Write Log + */ + $this->log = Tlog::getInstance(); + $this->log->setDestinations(self::LOGCLASS); + $this->log->setConfig(self::LOGCLASS, 0, THELIA_ROOT . "log" . DS . "log-stripe.txt"); + } + + protected function getBackToPreviousState() + { + $this->log->setDestinations("\\Thelia\\Log\\Destination\\TlogDestinationRotatingFile"); + } +} \ No newline at end of file diff --git a/local/modules/StripePayment/Config/config.xml b/local/modules/StripePayment/Config/config.xml new file mode 100644 index 00000000..ff983ab9 --- /dev/null +++ b/local/modules/StripePayment/Config/config.xml @@ -0,0 +1,24 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/local/modules/StripePayment/Config/module.xml b/local/modules/StripePayment/Config/module.xml new file mode 100644 index 00000000..8365f823 --- /dev/null +++ b/local/modules/StripePayment/Config/module.xml @@ -0,0 +1,24 @@ + + + StripePayment\StripePayment + + Stripe + + + Stripe + + + en_US + fr_FR + + 2.0.4 + + Etienne Perriere + eperriere@openstudio.fr + + payment + 2.2.0 + other + diff --git a/local/modules/StripePayment/Config/routing.xml b/local/modules/StripePayment/Config/routing.xml new file mode 100644 index 00000000..a4fbfaba --- /dev/null +++ b/local/modules/StripePayment/Config/routing.xml @@ -0,0 +1,28 @@ + + + + StripePayment:StripePaymentConfig:default + + + StripePayment:StripePaymentConfig:save + + + StripePayment:StripeCustomer:default + + + StripePayment:StripeCustomer:create + + + StripePayment:StripeCustomer:update + + + StripePayment:StripeCustomer:processUpdate + + + StripePayment:StripeWebHooks:listen + .* + + + StripePayment:StripeCustomer:delete + + diff --git a/local/modules/StripePayment/Config/schema.xml b/local/modules/StripePayment/Config/schema.xml new file mode 100644 index 00000000..e216f239 --- /dev/null +++ b/local/modules/StripePayment/Config/schema.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/local/modules/StripePayment/Controller/Base/StripePaymentConfigController.php b/local/modules/StripePayment/Controller/Base/StripePaymentConfigController.php new file mode 100644 index 00000000..9376bcdd --- /dev/null +++ b/local/modules/StripePayment/Controller/Base/StripePaymentConfigController.php @@ -0,0 +1,77 @@ +checkAuth([AdminResources::MODULE], ["stripepayment"], AccessManager::VIEW)) { + return $response; + } + + return $this->render("stripepayment-configuration"); + } + + public function saveAction() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], ["stripepayment"], AccessManager::UPDATE)) { + return $response; + } + + $baseForm = $this->createForm("stripepayment.configuration"); + + $errorMessage = null; + + try { + $form = $this->validateForm($baseForm); + $data = $form->getData(); + StripePayment::setConfigValue(StripePaymentConfigValue::ENABLED, is_bool($data["enabled"]) ? (int) ($data["enabled"]) : $data["enabled"]); + StripePayment::setConfigValue(StripePaymentConfigValue::STRIPE_ELEMENT, is_bool($data["stripe_element"]) ? (int) ($data["stripe_element"]) : $data["stripe_element"]); + StripePayment::setConfigValue(StripePaymentConfigValue::ONE_CLICK_PAYMENT, is_bool($data["one_click_payment"]) ? (int) ($data["one_click_payment"]) : $data["one_click_payment"]); + StripePayment::setConfigValue(StripePaymentConfigValue::SECRET_KEY, is_bool($data["secret_key"]) ? (int) ($data["secret_key"]) : $data["secret_key"]); + StripePayment::setConfigValue(StripePaymentConfigValue::PUBLISHABLE_KEY, is_bool($data["publishable_key"]) ? (int) ($data["publishable_key"]) : $data["publishable_key"]); + StripePayment::setConfigValue(StripePaymentConfigValue::WEBHOOKS_KEY, is_bool($data["webhooks_key"]) ? (int) ($data["webhooks_key"]) : $data["webhooks_key"]); + StripePayment::setConfigValue(StripePaymentConfigValue::SECURE_URL, is_bool($data["secure_url"]) ? (int) ($data["secure_url"]) : $data["secure_url"]); + } catch (FormValidationException $ex) { + // Invalid data entered + $errorMessage = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + // Any other error + $errorMessage = $this->getTranslator()->trans('Sorry, an error occurred: %err', ['%err' => $ex->getMessage()], [], StripePayment::MESSAGE_DOMAIN); + } + + if (null !== $errorMessage) { + // Mark the form as with error + $baseForm->setErrorMessage($errorMessage); + + // Send the form and the error to the parser + $this->getParserContext() + ->addForm($baseForm) + ->setGeneralError($errorMessage) + ; + } else { + $this->getParserContext() + ->set("success", true) + ; + } + + return $this->defaultAction(); + } +} diff --git a/local/modules/StripePayment/Controller/StripePaymentConfigController.php b/local/modules/StripePayment/Controller/StripePaymentConfigController.php new file mode 100644 index 00000000..9e394b1c --- /dev/null +++ b/local/modules/StripePayment/Controller/StripePaymentConfigController.php @@ -0,0 +1,17 @@ + + */ +class StripePaymentController extends BasePaymentModuleController +{ + /** + * Return a module identifier used to calculate the name of the log file, + * and in the log messages. + * + * @return string the module code + */ + protected function getModuleCode() + { + return 'StripePayment'; + } +} \ No newline at end of file diff --git a/local/modules/StripePayment/Controller/StripeWebHooksController.php b/local/modules/StripePayment/Controller/StripeWebHooksController.php new file mode 100644 index 00000000..4bf5370e --- /dev/null +++ b/local/modules/StripePayment/Controller/StripeWebHooksController.php @@ -0,0 +1,141 @@ +logText(serialize($event)); + + // Handle the event + switch ($event->type) { + case 'checkout.session.completed': + /** @var Session $sessionCompleted */ + $sessionCompleted = $event->data->object; + $this->handleSessionCompleted($sessionCompleted); + break; + case 'payment_intent.succeeded': + // Needed to wait for order to be created (Stripe is faster than Thelia) + sleep(5); + /** @var Session $sessionCompleted */ + $paymentId = $event->data->object->id; + $this->handlePaymentIntentSuccess($paymentId); + break; + case 'payment_intent.payment_failed': + // Needed to wait for order to be created (Stripe is faster than Thelia) + sleep(5); + /** @var Session $sessionCompleted */ + $paymentId = $event->data->object->id; + $this->handlePaymentIntentFail($paymentId); + break; + default: + // Unexpected event type + (new StripePaymentLog())->logText('Unexpected event type'); + + return new Response('Unexpected event type', 400); + } + + return new Response('Success', 200); + } catch (\UnexpectedValueException $e) { + // Invalid payload + (new StripePaymentLog())->logText($e->getMessage()); + return new Response('Invalid payload', 400); + } catch (SignatureVerification $e) { + return new Response($e->getMessage(), 400); + } catch (\Exception $e) { + return new Response($e->getMessage(), 404); + } + } + + return new Response('Bad request', 400); + } + + protected function handleSessionCompleted(Session $sessionCompleted) + { + $order = OrderQuery::create() + ->findOneByRef($sessionCompleted->client_reference_id); + + if (null === $order) { + throw new \Exception("Order with reference $sessionCompleted->client_reference_id not found"); + } + + $this->setOrderToPaid($order); + } + + protected function handlePaymentIntentSuccess($paymentId) + { + $order = OrderQuery::create() + ->findOneByTransactionRef($paymentId); + + if (null === $order) { + throw new \Exception("Order with transaction ref $paymentId not found"); + } + + $this->setOrderToPaid($order); + } + + protected function handlePaymentIntentFail($paymentId) + { + $order = OrderQuery::create() + ->findOneByTransactionRef($paymentId); + + if (null === $order) { + throw new \Exception("Order with transaction ref $paymentId not found"); + } + + $this->setOrderToCanceled($order); + } + + protected function setOrderToPaid($order) + { + $paidStatusId = OrderStatusQuery::create() + ->filterByCode('paid') + ->select('ID') + ->findOne(); + + $event = new OrderEvent($order); + $event->setStatus($paidStatusId); + $this->getDispatcher()->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event); + } + + protected function setOrderToCanceled($order) + { + $canceledStatusId = OrderStatusQuery::create() + ->filterByCode('canceled') + ->select('ID') + ->findOne(); + + $event = new OrderEvent($order); + $event->setStatus($canceledStatusId); + $this->getDispatcher()->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event); + } +} diff --git a/local/modules/StripePayment/EventListeners/CartEventListener.php b/local/modules/StripePayment/EventListeners/CartEventListener.php new file mode 100644 index 00000000..cffe01b6 --- /dev/null +++ b/local/modules/StripePayment/EventListeners/CartEventListener.php @@ -0,0 +1,205 @@ +request = $request; + $this->dispatcher = $dispatcher; + $this->taxEngine = $taxEngine; + } + + public static function getSubscribedEvents() + { + $events = [ + TheliaEvents::CART_RESTORE_CURRENT => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_CREATE_NEW => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_ADDITEM => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_DELETEITEM => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_UPDATEITEM => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_CLEAR => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CHANGE_DEFAULT_CURRENCY => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::ORDER_SET_POSTAGE => [ "createOrUpdatePaymentIntent", 64 ] + ]; + + return $events; + } + + public function createOrUpdatePaymentIntent(ActionEvent $event) + { + Stripe::setApiKey(StripePayment::getConfigValue('secret_key')); + + /** @var Session $session */ + $session = $this->request->getSession(); + + $paymentIntentValues = $this->getPaymentIntentValues($event); + + if (false === $paymentIntentValues) { + return; + } + + if ( + $session->has(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY) + && + null !== $paymentId = $session->get(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY) + ) + { + + $payment = PaymentIntent::update( + $paymentId, + $paymentIntentValues + ); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, $payment->client_secret); + + return; + } + + /** @var PaymentIntent $payment */ + $payment = PaymentIntent::create($paymentIntentValues); + + $session->set(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY, $payment->id); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, $payment->client_secret); + return; + } + + + protected function getPaymentIntentValues(ActionEvent $event) + { + /** @var Session $session */ + $session = $this->request->getSession(); + $currency = $session->getCurrency(); + + $data = $this->getCartAndOrderFromEvent($event); + + if (false === $data) { + return false; + } + + /** @var Cart $cart */ + $cart = $data['cart']; + + /** @var Order $order */ + $order = $data['order']; + + $postageAmount = floatval($order->getPostage()); + + $country = $this->taxEngine->getDeliveryCountry(); + + $cartAmount = floatval($cart->getTaxedAmount($country)); + + $totalAmount = ($postageAmount + $cartAmount) * 100; + + if (!$totalAmount > 0) { + return false; + } + + $values = [ + 'amount' => intval(round($totalAmount)), + 'currency' => strtolower($currency->getCode()) + ]; + + if (null !== $stripeCustomerId = $this->getStripeCustomerId($session)) { + $values['customer'] = $stripeCustomerId; + } + + return $values; + } + + protected function getStripeCustomerId(Session $session) + { + if (null === $session->getCustomerUser()) { + return null; + } + + if (!$session->has(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY)) { + /** @var Customer $customer */ + $customer = $session->getCustomerUser(); + $email = $customer->getEmail(); + + $stripeCustomer = \Stripe\Customer::create([ + 'email' => $email + ]); + + $session->set(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY, $stripeCustomer->id); + } + + return $session->get(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY); + } + + protected function getCartAndOrderFromEvent(ActionEvent $event) + { + /** @var Session $session */ + $session = $this->request->getSession(); + + if ($event instanceof CartRestoreEvent) { + return [ + 'cart' => $event->getCart(), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof CartCreateEvent) { + return [ + 'cart' => $event->getCart(), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof CartEvent) { + return [ + 'cart' => $event->getCart(), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof CurrencyChangeEvent) { + return [ + 'cart' => $session->getSessionCart($this->dispatcher), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof OrderEvent) { + return [ + 'cart' => $session->getSessionCart($this->dispatcher), + 'order' => $event->getOrder() + ]; + } + + return false; + } +} \ No newline at end of file diff --git a/local/modules/StripePayment/Form/Base/StripePaymentConfigForm.php b/local/modules/StripePayment/Form/Base/StripePaymentConfigForm.php new file mode 100644 index 00000000..036fa8d2 --- /dev/null +++ b/local/modules/StripePayment/Form/Base/StripePaymentConfigForm.php @@ -0,0 +1,207 @@ +formBuilder attribute : + * + * $this->formBuilder->add("name", "text") + * ->add("email", "email", array( + * "attr" => array( + * "class" => "field" + * ), + * "label" => "email", + * "constraints" => array( + * new \Symfony\Component\Validator\Constraints\NotBlank() + * ) + * ) + * ) + * ->add('age', 'integer'); + * + * @return null + */ + protected function buildForm() + { + $translationKeys = $this->getTranslationKeys(); + $fieldsIdKeys = $this->getFieldsIdKeys(); + + $this->addEnabledField($translationKeys, $fieldsIdKeys); + $this->addStripeElementField($translationKeys, $fieldsIdKeys); + $this->addOneClickPaymentField($translationKeys, $fieldsIdKeys); + $this->addSecretKeyField($translationKeys, $fieldsIdKeys); + $this->addPublishableKeyField($translationKeys, $fieldsIdKeys); + $this->addWebhooksKeyField($translationKeys, $fieldsIdKeys); + $this->addSecureUrlField($translationKeys, $fieldsIdKeys); + } + + protected function addEnabledField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("enabled", "checkbox", array( + "label" => $this->readKey("enabled", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("enabled", $fieldsIdKeys), + "help" => $this->readKey("help.enabled", $translationKeys) + ], + "required" => false, + "constraints" => array( + ), + "value" => StripePayment::getConfigValue(StripePaymentConfigValue::ENABLED, false), + )) + ; + } + + protected function addStripeElementField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("stripe_element", "checkbox", array( + "label" => $this->readKey("stripe_element", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("stripeelementch", $fieldsIdKeys), + "help" => $this->readKey("help.stripe_element", $translationKeys) + ], + "required" => false, + "constraints" => array( + ), + "value" => StripePayment::getConfigValue(StripePaymentConfigValue::STRIPE_ELEMENT, false), + )) + ; + } + + protected function addOneClickPaymentField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("one_click_payment", "checkbox", array( + "label" => $this->readKey("one_click_payment", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("one_click_payment", $fieldsIdKeys) + ], + "required" => false, + "constraints" => array( + ), + "value" => StripePayment::getConfigValue(StripePaymentConfigValue::ONE_CLICK_PAYMENT, false), + )) + ; + } + + protected function addSecretKeyField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("secret_key", "text", array( + "label" => $this->readKey("secret_key", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("secret_key", $fieldsIdKeys), + "help" => $this->readKey("help.secret_key", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePaymentConfigValue::SECRET_KEY), + )) + ; + } + + protected function addPublishableKeyField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("publishable_key", "text", array( + "label" => $this->readKey("publishable_key", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("publishable_key", $fieldsIdKeys), + "help" => $this->readKey("help.publishable_key", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePaymentConfigValue::PUBLISHABLE_KEY), + )) + ; + } + + protected function addWebhooksKeyField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("webhooks_key", "text", array( + "label" => $this->readKey("webhooks_key", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("webhooks_key", $fieldsIdKeys), + "help" => $this->readKey("help.webhooks_key", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePaymentConfigValue::WEBHOOKS_KEY), + )) + ; + } + protected function addSecureUrlField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("secure_url", "text", array( + "label" => $this->readKey("secure_url", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("secure_url", $fieldsIdKeys), + "help" => $this->readKey("help.secure_url", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePaymentConfigValue::SECURE_URL), + )) + ; + } + + public function getName() + { + return static::FORM_NAME; + } + + public function readKey($key, array $keys, $default = '') + { + if (isset($keys[$key])) { + return $keys[$key]; + } + + return $default; + } + + public function getTranslationKeys() + { + return array(); + } + + public function getFieldsIdKeys() + { + return array( + "enabled" => "enabled", + "secret_key" => "secret_key", + "publishable_key" => "publishable_key", + "webhooks_key" => "webhooks_key", + "secure_url" => "secure_url" + ); + } +} diff --git a/local/modules/StripePayment/Form/StripePaymentConfigForm.php b/local/modules/StripePayment/Form/StripePaymentConfigForm.php new file mode 100644 index 00000000..979ce2e3 --- /dev/null +++ b/local/modules/StripePayment/Form/StripePaymentConfigForm.php @@ -0,0 +1,33 @@ + $this->translator->trans("Activate payment with stripe ?", [], StripePayment::MESSAGE_DOMAIN), + "stripe_element" => $this->translator->trans("Activate Element ?", [], StripePayment::MESSAGE_DOMAIN), + "one_click_payment" => $this->translator->trans("Activate one click payment ?", [], StripePayment::MESSAGE_DOMAIN), + "secret_key" => $this->translator->trans("Your secret key", [], StripePayment::MESSAGE_DOMAIN), + "publishable_key" => $this->translator->trans("Your publishable key (test or live)", [], StripePayment::MESSAGE_DOMAIN), + "webhooks_key" => $this->translator->trans("Your webhooks key", [], StripePayment::MESSAGE_DOMAIN), + "secure_url" => $this->translator->trans("Your chain of char for secure return webhook", [], StripePayment::MESSAGE_DOMAIN), + "help.enabled" => $this->translator->trans("Do you want to activate Stripe Payment", [], StripePayment::MESSAGE_DOMAIN), + "help.stripe_element" => $this->translator->trans("Element is the embedded and customizable payment form", [], StripePayment::MESSAGE_DOMAIN), + "help.secret_key" => $this->translator->trans("You can see all your keys in your Stripe dashboard. Also note that you can place your test or your live API keys", [], StripePayment::MESSAGE_DOMAIN), + ); + } +} diff --git a/local/modules/StripePayment/Hook/StripePaymentHook.php b/local/modules/StripePayment/Hook/StripePaymentHook.php new file mode 100644 index 00000000..f82f2654 --- /dev/null +++ b/local/modules/StripePayment/Hook/StripePaymentHook.php @@ -0,0 +1,74 @@ + + */ +class StripePaymentHook extends BaseHook +{ + protected $request; + + protected $taxEngine; + + public function __construct(Request $request, TaxEngine $taxEngine) + { + $this->request = $request; + $this->taxEngine = $taxEngine; + } + + public function includeStripe(HookRenderEvent $event) + { + if(StripePayment::getConfigValue('stripe_element')){ + $publicKey = StripePayment::getConfigValue('publishable_key'); + $clientSecret = $this->request->getSession()->get(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY); + $currency = strtolower($this->request->getSession()->getCurrency()->getCode()); + $country = $this->taxEngine->getDeliveryCountry()->getIsoalpha2(); + $event->add($this->render( + 'assets/js/stripe-js.html', + [ + 'stripe_module_id' => $this->getModule()->getModuleId(), + 'public_key' => $publicKey, + 'oneClickPayment' => StripePayment::getConfigValue(StripePaymentConfigValue::ONE_CLICK_PAYMENT, false), + 'clientSecret' => $clientSecret, + 'currency' => $currency, + 'country' => $country + ] + )); + } + } + + public function declareStripeOnClickEvent(HookRenderEvent $event) + { + if(StripePayment::getConfigValue('stripe_element')){ + $publicKey = StripePayment::getConfigValue('publishable_key'); + $event->add($this->render( + 'assets/js/order-invoice-after-js-include.html', + [ + 'stripe_module_id' => $this->getModule()->getModuleId(), + 'public_key' => $publicKey + ] + )); + } + } + + public function includeStripeJsV3(HookRenderEvent $event) + { + $event->add(''); + } + + public function onMainHeadBottom(HookRenderEvent $event) + { + $content = $this->addCSS('assets/css/styles.css'); + $event->add($content); + } +} \ No newline at end of file diff --git a/local/modules/StripePayment/I18n/backOffice/default/en_US.php b/local/modules/StripePayment/I18n/backOffice/default/en_US.php new file mode 100644 index 00000000..115c80f5 --- /dev/null +++ b/local/modules/StripePayment/I18n/backOffice/default/en_US.php @@ -0,0 +1,12 @@ + 'Configuration correctly saved', + 'Configure stripepayment' => 'Configure stripepayment', + 'Home' => 'Home', + 'Modules' => 'Modules', + 'StripePayment configuration' => 'StripePayment configuration', + 'The configuration value enabled' => 'The configuration value enabled', + 'The configuration value publishable_key' => 'The configuration value publishable_key', + 'The configuration value secret_key' => 'The configuration value secret_key', +); diff --git a/local/modules/StripePayment/I18n/backOffice/default/fr_FR.php b/local/modules/StripePayment/I18n/backOffice/default/fr_FR.php new file mode 100644 index 00000000..7fc731d3 --- /dev/null +++ b/local/modules/StripePayment/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,17 @@ + 'Configuration correctement sauvegardée', + 'Configure stripepayment' => 'Configurer Stripe', + 'Home' => 'Accueil', + 'Modules' => 'Modules', + 'StripePayment configuration' => 'Configuration du module Stripe', + 'The configuration value enabled' => 'La valeur de configuration "enabled"', + 'The configuration value publishable_key' => 'La valeur de configuration "publishable_key"', + 'The configuration value secret_key' => 'La valeur de configuration "secret_key"', + 'The configuration value secure_url' => 'La valeur de configuration secure_url', + 'The configuration value webhooks_key whsec_...' => 'La valeur de configuration webhooks_key whsec_...', + 'Webhooks endpoint url' => 'URL d\'endpoint WebHook', + 'Webhooks event to activate' => 'Événements WebHook à activer', + 'active stripe element' => 'Activer Stripe Element', +); diff --git a/local/modules/StripePayment/I18n/email/default/en_US.php b/local/modules/StripePayment/I18n/email/default/en_US.php new file mode 100644 index 00000000..a3bd1cb6 --- /dev/null +++ b/local/modules/StripePayment/I18n/email/default/en_US.php @@ -0,0 +1,13 @@ + 'Dear customer,', + 'Payment is confirmed for your order' => 'Payment is confirmed for your order', + 'Reference %ref' => 'Reference: %ref', + 'Thank you again for your purchase.' => 'Thank you again for your purchase!', + 'Thank you for your order!' => 'Thank you for your order!', + 'The %name team.' => 'The %name team.', + 'This is a confirmation of the payment of your order %order on %name.' => 'This is a confirmation of the payment of your order %order on %name.', + 'Your invoice is now available in your customer account on' => 'Your invoice is now available in your customer account on', + 'Your invoice is now available in your customer account on %site' => 'Your invoice is now available in your customer account on %site.', +); diff --git a/local/modules/StripePayment/I18n/email/default/fr_FR.php b/local/modules/StripePayment/I18n/email/default/fr_FR.php new file mode 100644 index 00000000..2b922e51 --- /dev/null +++ b/local/modules/StripePayment/I18n/email/default/fr_FR.php @@ -0,0 +1,13 @@ + 'Cher client,', + 'Payment is confirmed for your order' => 'Le paiement de votre commande est confirmé', + 'Reference %ref' => 'Référence de commande : %ref', + 'Thank you again for your purchase.' => 'Merci encore pour cet achat !', + 'Thank you for your order!' => 'Merci pour votre commande !', + 'The %name team.' => 'L\'équipe %name.', + 'This is a confirmation of the payment of your order %order on %name.' => 'Ce message confirme le paiement de votre commande n° %order sur %name.', + 'Your invoice is now available in your customer account on' => 'Votre facture est maintenant disponible sur votre compte sur ', + 'Your invoice is now available in your customer account on %site' => 'Votre facture est maintenant disponible sur votre compte sur %site. ', +); diff --git a/local/modules/StripePayment/I18n/en_US.php b/local/modules/StripePayment/I18n/en_US.php new file mode 100644 index 00000000..88eb9c9d --- /dev/null +++ b/local/modules/StripePayment/I18n/en_US.php @@ -0,0 +1,21 @@ + 'Activated ?', + 'An error occurred during payment.' => 'An error occurred during payment.', + 'An error occurred with Stripe.' => 'An error occurred with Stripe.', + 'Authentication with Stripe failed. Please contact administrators.' => 'Authentication with Stripe failed. Please contact administrators.', + 'Do you want to activate Stripe Payment' => 'Do you want to activate Stripe Payment', + 'Invalid parameters were supplied to Stripe.' => 'Invalid parameters were supplied to Stripe.', + 'Network communication failed.' => 'Network communication failed.', + 'Payment confirmation of your order {$order_ref} on %store_name' => 'Payment confirmation of your order {$order_ref} on %store_name', + 'Payment confirmation on %store_name' => 'Payment confirmation on %store_name', + 'Sorry, an error occurred: %err' => 'Sorry, an error occurred: %err', + 'Stripe library isn\'t installed' => 'Stripe library isn\'t installed', + 'The payment mean does not have the same amount as your cart. Please reload and try again.' => 'The payment mean does not have the same amount as your cart. Please reload and try again.', + 'Too many requests too quickly.' => 'Too many requests too quickly.', + 'You can see all your keys in your Stripe dashboard. Also note that you can place your test or your live API keys' => 'You can see all your keys in your Stripe dashboard. Also note that you have to place your test and your live API keys', + 'Your card has been declined.' => 'Your card has been declined.', + 'Your publishable key (test or live)' => 'Your publishable key (test or live)', + 'Your secret key' => 'Your secret key', +); diff --git a/local/modules/StripePayment/I18n/fr_FR.php b/local/modules/StripePayment/I18n/fr_FR.php new file mode 100644 index 00000000..c2655677 --- /dev/null +++ b/local/modules/StripePayment/I18n/fr_FR.php @@ -0,0 +1,29 @@ + 'Activer Element ?', + 'Activate payment with stripe ?' => 'Activer le paiement avec Stripe ?', + 'An error occurred during payment.' => 'Une erreur est survenue lors du paiement.', + 'An error occurred with Stripe.' => 'Une erreur est survenue lors du paiement avec Stripe.', + 'Authentication with Stripe failed. Please contact administrators.' => 'Des paramètres invalides ont été envoyés à Stripe.', + 'Discount' => 'Remise', + 'Do you want to activate Stripe Payment' => 'Voulez-vous activer Stripe ?', + 'Element is the embedded and customizable payment form' => 'Element est un formulaire intégrer et personlisable.', + 'Invalid parameters were supplied to Stripe.' => 'Une erreur liée au réseau empêche la communication avec Stripe.', + 'Network communication failed.' => 'L\'authentification auprès de Stripe a échoué. Merci de contacter les administrateurs du site.', + 'Payment confirmation for Stripe Payment' => 'Confirmation de paiement par Stripe', + 'Payment confirmation of your order {$order_ref} on {$store_name}' => 'Confirmation de paiement pour votre commande {$order_ref} sur {$store_name}', + 'Sorry, an error occurred: %err' => 'Désolé, une erreur est survenue : %err', + 'Stripe library is missing.' => 'La libraire Stripe est manquante', + 'Stripe version is greater than max version (< %version). Current version: %curVersion.' => 'La version de la libraire Stripe est plus haute qua la version maximum ( < %version). Version actuelle %curVersion.', + 'Stripe version is lower than min version (%version). Current version: %curVersion.' => 'La version de la libraire Stripe est plus basse qua la version minimum ( > %version). Version actuelle %curVersion.', + 'The payment mean does not have the same amount as your cart. Please reload and try again.' => 'Le moyen de paiement n\'indique pas le même montant que votre panier. Rechargez la pager et réessayez.', + 'Too many requests too quickly.' => 'Trop de requêtes en peu de temps.', + 'Total' => 'Total', + 'You can see all your keys in your Stripe dashboard. Also note that you can place your test or your live API keys' => 'Vos clés sont disponibles dans votre compte sur la plateforme d\'administration Stripe. Veuillez noter que vous devez renseigner ici vos clés publique et privée.', + 'Your card has been declined.' => 'Votre carte bancaire a été refusée.', + 'Your chain of char for secure return webhook' => 'Une chaine de caractère pour sécuriser le retour des Webhooks', + 'Your publishable key (test or live)' => 'Votre clé publique', + 'Your secret key' => 'Votre clé secrète', + 'Your webhooks key' => 'Votre clé Webhooks ?', +); diff --git a/local/modules/StripePayment/I18n/frontOffice/default/fr_FR.php b/local/modules/StripePayment/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 00000000..855d244d --- /dev/null +++ b/local/modules/StripePayment/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,6 @@ + 'Ou entrer les détails de votre carte de crédit', + 'Quick pay' => 'Paiement rapide', +); diff --git a/local/modules/StripePayment/LICENSE.txt b/local/modules/StripePayment/LICENSE.txt new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/local/modules/StripePayment/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/local/modules/StripePayment/Model/Config/Base/StripePaymentConfigValue.php b/local/modules/StripePayment/Model/Config/Base/StripePaymentConfigValue.php new file mode 100644 index 00000000..7b497a20 --- /dev/null +++ b/local/modules/StripePayment/Model/Config/Base/StripePaymentConfigValue.php @@ -0,0 +1,29 @@ +/local/modules/``` directory and be sure that the name of the module is StripePayment. +* Install the Stripe PHP library : + * add "stripe/stripe-php" to your composer.json file with command : `composer require stripe/stripe-php:"6.*"` + * or download the library from and install it in your `core/vendor` directory +* Activate it in your Thelia administration panel + + +### Composer + +Add it in your main thelia composer.json file: + +``` +composer require thelia/stripe-payment-module ~2.0.0 +``` + +### Configuration + +Enter your Stripe keys (*secret* and *public*) available on your [Stripe dashboard](https://dashboard.stripe.com/). + +Put your Stripe account in live mode. + +Then activate the Stripe in the module configuration panel. + +Activate the webhooks in stripe dashboard with the url specified in Thelia Back-office Stripe configuration, +and add events listed in Thelia Back-office Stripe configuration. + +### Logs + +Stripe error logs are stored in a specific file located in the log folder. diff --git a/local/modules/StripePayment/Resource/images/module/stripe.png b/local/modules/StripePayment/Resource/images/module/stripe.png new file mode 100644 index 00000000..20b7de7b Binary files /dev/null and b/local/modules/StripePayment/Resource/images/module/stripe.png differ diff --git a/local/modules/StripePayment/StripePayment.php b/local/modules/StripePayment/StripePayment.php new file mode 100644 index 00000000..98e780f8 --- /dev/null +++ b/local/modules/StripePayment/StripePayment.php @@ -0,0 +1,542 @@ + + */ +class StripePayment extends AbstractPaymentModule +{ + const MESSAGE_DOMAIN = "stripepayment"; + const CONFIRMATION_MESSAGE_NAME = "stripe_confirm_payment"; + const STRIPE_VERSION_MIN = "3.0.0"; + const STRIPE_VERSION_MAX = "7.0.0"; + + const PAYMENT_INTENT_ID_SESSION_KEY = 'payment_intent_id'; + const PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY = 'payment_intent_customer_id'; + const PAYMENT_INTENT_SECRET_SESSION_KEY = 'payment_intent_secret'; + + public function preActivation(ConnectionInterface $con = null) + { + // Check if Stripe API is present + try { + $this->checkApi(); + } catch (\Exception $ex) { + throw $ex; + } + + return true; + } + + public function postActivation(ConnectionInterface $con = null) + { + // Module image + $moduleModel = $this->getModuleModel(); + + if (! $moduleModel->isModuleImageDeployed($con)) { + $this->deployImageFolder($moduleModel, sprintf('%s'.DS.'Resource'.DS.'images'.DS.'module', __DIR__), $con); + } + + $this->createMailMessage(); + } + + public function createMailMessage() + { + // Create payment confirmation message from templates, if not already defined + if (null === MessageQuery::create()->findOneByName(self::CONFIRMATION_MESSAGE_NAME)) { + + $languages = LangQuery::create()->find(); + + $message = new Message(); + $message + ->setName(self::CONFIRMATION_MESSAGE_NAME) + ->setHtmlTemplateFileName(self::CONFIRMATION_MESSAGE_NAME.'.html') + ->setTextTemplateFileName(self::CONFIRMATION_MESSAGE_NAME.'.txt') + ; + + foreach ($languages as $language) { + /** @var Lang $language */ + $locale = $language->getLocale(); + $message + ->setLocale($locale) + ->setTitle( + Translator::getInstance()->trans( + "Payment confirmation for Stripe Payment", + [], + self::MESSAGE_DOMAIN, + $locale + ) + ) + ->setSubject( + Translator::getInstance()->trans( + 'Payment confirmation of your order {$order_ref} on {$store_name}', + [], + self::MESSAGE_DOMAIN, + $locale + ) + ) + ; + } + + $message->save(); + } + } + + public function checkApi() + { + try { + $ReflectedClass = new \ReflectionClass('Stripe\Stripe'); + } catch (\Exception $ex) { + throw new \Exception( + Translator::getInstance()->trans( + "Stripe library is missing.", + [], + self::MESSAGE_DOMAIN + ) + ); + } + + $stripeVersion = \Stripe\Stripe::VERSION; + + if (version_compare(self::STRIPE_VERSION_MIN, $stripeVersion) == 1) { + throw new \Exception( + Translator::getInstance()->trans( + "Stripe version is lower than min version (%version). Current version: %curVersion.", + [ + '%version' => self::STRIPE_VERSION_MIN, + '%curVersion' => $stripeVersion + ], + self::MESSAGE_DOMAIN + ) + ); + } + + if (version_compare(self::STRIPE_VERSION_MAX, $stripeVersion) < 1) { + throw new \Exception( + Translator::getInstance()->trans( + "Stripe version is greater than max version (< %version). Current version: %curVersion.", + [ + '%version' => self::STRIPE_VERSION_MAX, + '%curVersion' => $stripeVersion + ], + self::MESSAGE_DOMAIN + ) + ); + } + } + + /** + * + * Method used by payment gateway. + * + * If this method return a \Thelia\Core\HttpFoundation\Response instance, this response is send to the + * browser. + * + * In many cases, it's necessary to send a form to the payment gateway. On your response you can return this form already + * completed, ready to be sent + * + * @param \Thelia\Model\Order $order processed order + * @return null|\Thelia\Core\HttpFoundation\Response + */ + public function pay(Order $order) + { + if (!$this->isValidPayment()) { + throw new Exception("Your connection is not secured. Check that 'https' is present at the beginning of the site's address."); + } + + return $this->doPay($order); + } + + protected function doPay(Order $order) + { + Stripe::setApiKey(StripePayment::getConfigValue('secret_key')); + $session = $this->getRequest()->getSession(); + + try { + + if(StripePayment::getConfigValue('stripe_element')){ + $order->setTransactionRef($session->get(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY)) + ->save(); + $session->set(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY, null); + + return; + }else{ + $session->set(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY, null); + + // Create the session on Stripe's servers - this will charge the user's order and save session id into order transaction reference + return $this->createStripeSession($order); + } + + } catch(\Stripe\Error\Card $e) { + // The card has been declined + // FIXME Translate message here + $logMessage = sprintf( + 'Error paying order %d with Stripe. Card declined. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Your card has been declined.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Error\RateLimit $e) { + // Too many requests made to the API too quickly + $logMessage = sprintf( + 'Error paying order %d with Stripe. Too many requests. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Too many requests too quickly.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Error\InvalidRequest $e) { + // Invalid parameters were supplied to Stripe's API + $logMessage = sprintf( + 'Error paying order %d with Stripe. Invalid parameters. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Invalid parameters were supplied to Stripe.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Error\Authentication $e) { + // Authentication with Stripe's API failed + // (maybe you changed API keys recently) + $logMessage = sprintf( + 'Error paying order %d with Stripe. Authentication failed: API key changed? Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Authentication with Stripe failed. Please contact administrators.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Error\ApiConnection $e) { + // Network communication with Stripe failed + $logMessage = sprintf( + 'Error paying order %d with Stripe. Network communication failed. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Network communication failed.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Error\Base $e) { + // Display a very generic error to the user + $logMessage = sprintf( + 'Error paying order %d with Stripe. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'An error occurred with Stripe.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (StripePaymentException $e) { + // Amount shown to the user by Stripe & order amount are not equal + $logMessage = sprintf( + 'Error paying order %d with Stripe. Amounts are different. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = $e->getMessage(); + } catch (\Exception $e) { + // Something else happened, completely unrelated to Stripe + $logMessage = sprintf( + 'Error paying order %d with Stripe but maybe unrelated with it. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'An error occurred during payment.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } + + if ($logMessage !== NULL) { + (new StripePaymentLog())->logText($logMessage); + + return new RedirectResponse( + URL::getInstance()->absoluteUrl("/order/failed/".$order->getId()."/".$userMessage) + ); + } + + return new Response(); + } + + public function createStripeSession(OrderModel $order) + { + /* Impossible d'ajouter une ligne spécifique pour la remise, cette partie est mise de côté en attendant que stripe ajoute cette possibilité + + $lineItems = $this->prepareLineItems($order); + + */ + + $currency = $order->getCurrency(); + + if (null === $currency) { + $currency = $this->getRequest()->getSession()->getCurrency(); + } + + $lineItems[] = [ + 'name'=> Translator::getInstance()->trans('Total', [], StripePayment::MESSAGE_DOMAIN ), + 'description' => null, + 'quantity'=> 1, + 'currency' => strtolower($currency->getCode()), + 'amount' => round($order->getTotalAmount(), 2) * 100 + ]; + + if(empty($lineItems)){ + throw new \Exception("Sorry, your cart is empty. There's nothing to pay."); + } + + $session = Session::create([ + 'customer_email' => $order->getCustomer()->getEmail(), + 'client_reference_id' => $order->getRef(), + 'payment_method_types' => ['card'], + 'line_items' => $lineItems, + 'success_url' => URL::getInstance()->absoluteUrl('/order/placed/' . $order->getId()), + 'cancel_url' => URL::getInstance()->absoluteUrl('/order/failed/' . $order->getId() . '/error'), + ]); + + $order->setTransactionRef($session->payment_intent)->save(); + + /** @var ParserInterface $parser */ + $parser = $this->getContainer()->get("thelia.parser"); + + $parser->setTemplateDefinition( + $parser->getTemplateHelper()->getActiveFrontTemplate() + ); + + $renderedTemplate = $parser->render( + "stripe-paiement.html", + [ + 'checkout_session_id' => $session->id, + 'public_key' => StripePayment::getConfigValue('publishable_key') + ] + ); + + return Response::create($renderedTemplate); + } + + /** + * + * This method is call on Payment loop. + * + * If you return true, the payment method will be display + * If you return false, the payment method will not be display + * + * @return boolean + */ + public function isValidPayment() + { + return ( ($this->isDevEnvironment() || $this->isSslEnabled()) && $this->getConfigValue('enabled') ); + } + + /** + * Return true if the current environment is in Dev mode + * + * @return bool + */ + protected function isDevEnvironment() + { + return 'dev' == $this->getContainer()->getParameter('kernel.environment'); + } + + /** + * return true if SSL is enabled + * + * @return bool + */ + protected function isSslEnabled() + { + return $this->getRequest()->isSecure(); + } + + public function checkOrderAmount(OrderModel $order, $stripeAmount) + { + $orderAmount = $order->getTotalAmount() * 100; + + if (strval($stripeAmount) != strval($orderAmount)) { + throw new StripePaymentException(Translator::getInstance() + ->trans( + 'The payment mean does not have the same amount as your cart. Please reload and try again.', + [], + StripePayment::MESSAGE_DOMAIN + ) + ); + } + } + + protected function prepareLineItems(Order $order, $currency) + { + $stripeAmount = 0; + $lineItems = []; + + $baseSourceFilePath = ConfigQuery::read('images_library_path'); + if ($baseSourceFilePath === null) { + $baseSourceFilePath = THELIA_LOCAL_DIR . 'media' . DS . 'images'; + } else { + $baseSourceFilePath = THELIA_ROOT . $baseSourceFilePath; + } + if(null !== $orderProducts = OrderProductQuery::create()->filterByOrderId($order->getId())->joinOrderProductTax('opt', Criteria::LEFT_JOIN)->withColumn('SUM(`opt`.AMOUNT)', 'TOTAL_TAX')->withColumn('SUM(`opt`.PROMO_AMOUNT)', 'TOTAL_PROMO_TAX')->groupById()->find()){ + foreach ($orderProducts as $orderProduct) { + $description=''; + if(null !== $orderProductAttributeCombinations = OrderProductAttributeCombinationQuery::create()->filterByOrderProductId($orderProduct->getId())->find()){ + foreach ($orderProductAttributeCombinations as $orderProductAttributeCombination) { + if($description) $description .= ', '; + $description .= $orderProductAttributeCombination->getAttributeTitle() . ' ' . $orderProductAttributeCombination->getAttributeAvTitle(); + } + } + $images=array(); + if(null !== $product = ProductQuery::create()->filterByRef($orderProduct->getProductRef())->findOne()){ + if(null !== $productImages = ProductImageQuery::create()->filterByProductId($product->getId())->filterByVisible(1)->orderBy('position')->find()){ + foreach ($productImages as $productImage) { + // Put source image file path + $sourceFilePath = sprintf( + '%s/%s/%s', + $baseSourceFilePath, + 'product', + $productImage->getFile() + ); + + // Create image processing event + $event = new ImageEvent(); + $event->setSourceFilepath($sourceFilePath); + $event->setCacheSubdirectory('product'); + $width=100; + try { + // Dispatch image processing event + $event->setWidth($width); + $order->getDispatcher()->dispatch(TheliaEvents::IMAGE_PROCESS, $event); + $images[]=$event->getFileUrl(); + } catch (\Exception $ex) { + // Ignore the result and log an error + Tlog::getInstance()->addError(sprintf("Failed to process image in image loop: %s", $ex->getMessage())); + } + } + } + } + if($orderProduct->getWasInPromo()){ + $amount = $orderProduct->getPromoPrice() + $orderProduct->getVirtualColumn('TOTAL_PROMO_TAX'); + }else{ + $amount = $orderProduct->getPrice() + $orderProduct->getVirtualColumn('TOTAL_TAX'); + } + + $stripeAmount += $amount * $orderProduct->getQuantity() * 100; + $lineItems[] = [ + 'name' => $orderProduct->getTitle(), + 'description' => $description, + 'images' => $images, + 'amount' => $amount*100, + 'currency' => $currency, + 'quantity' => $orderProduct->getQuantity(), + ]; + } + } + if ($order->getPostage()){ + if (null !== $module = ModuleQuery::create()->findPk($order->getDeliveryModuleId())){ + $locale = $this->getRequest()->getLocale(); + if ($locale == 'en') { + $locale = 'en_US'; + } + $module->setLocale($locale); + + if (!$module->getTitle()) { + $module->setLocale('fr_FR'); + } + $lineItems[] = ['name'=> $module->getTitle(), 'description' => $module->getChapo(), 'quantity'=> 1, 'currency' => $currency, 'amount' => ($order->getPostage()*100)]; + $stripeAmount += $order->getPostage() * 100; + } + } + + if($order->getDiscount() > 0){ + $description=null; + if(null !== $orderCoupons = OrderCouponQuery::create()->filterByOrderId($order->getId())->find()){ + foreach($orderCoupons as $orderCoupon){ + if($description)$description .= ', '; + $description .= $orderCoupon->getTitle(); + } + } + $lineItems[] = ['name'=> Translator::getInstance()->trans('Discount', [], StripePayment::MESSAGE_DOMAIN ), 'description' => $description, 'quantity'=> 1, 'currency' => $currency, 'amount' => -($order->getDiscount()*100)]; + $stripeAmount -= $order->getDiscount() * 100; + } + + $this->checkOrderAmount($order, $stripeAmount); + + return $lineItems; + } + + + /** + * if you want, you can manage stock in your module instead of order process. + * Return false to decrease the stock when order status switch to pay + * + * @return bool + */ + public function manageStockOnCreation() + { + return false; + } +} diff --git a/local/modules/StripePayment/composer.json b/local/modules/StripePayment/composer.json new file mode 100644 index 00000000..6cf93cea --- /dev/null +++ b/local/modules/StripePayment/composer.json @@ -0,0 +1,12 @@ +{ + "name": "thelia/stripe-payment-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1", + "stripe/stripe-php": "6.*" + }, + "extra": { + "installer-name": "StripePayment" + } +} \ No newline at end of file diff --git a/local/modules/StripePayment/templates/backOffice/default/stripepayment-configuration.html b/local/modules/StripePayment/templates/backOffice/default/stripepayment-configuration.html new file mode 100644 index 00000000..a9b7a536 --- /dev/null +++ b/local/modules/StripePayment/templates/backOffice/default/stripepayment-configuration.html @@ -0,0 +1,197 @@ +{extends file="admin-layout.tpl"} + +{block name="no-return-functions"} + {$admin_current_location = 'modules'} +{/block} + +{block name="page-title"}{intl d="stripepayment.bo.default" l='StripePayment configuration'}{/block} + +{block name="check-resource"}admin.module{/block} +{block name="check-access"}view{/block} +{block name="check-module"}StripePayment{/block} + +{block name="main-content"} +
+ + +
+
+ {intl l="Configure stripepayment" d="stripepayment.bo.default"} +
+ +
+
+ {if $success} +
+ {intl l="Configuration correctly saved" d="stripepayment.bo.default"} +
+ {/if} + + {form name="stripepayment.configuration"} + + {include "includes/inner-form-toolbar.html" hide_flags = 1 close_url={url path='/admin/modules'}} +
+ + {form_field form=$form field="success_url"} + + {/form_field} + + {form_hidden_fields form=$form} + + {form_field form=$form field="enabled"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + {form_field form=$form field="stripe_element"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + {form_field form=$form field="one_click_payment"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + {form_field form=$form field="secret_key"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help nofilter} + {/if} +
+ {/form_field} + {form_field form=$form field="publishable_key"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="webhooks_key"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="secure_url"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + +
+ + +
+ +
+ +
    +
  • payment_intent.payment_failed
  • +
  • payment_intent.succeeded
  • +
  • checkout.session.completed
  • +
+
+ {include "includes/inner-form-toolbar.html" hide_flags = 1 close_url={url path='/admin/modules'} page_bottom=1} + + {/form} +
+
+
+
+{/block} + +{block name="javascript-initialization"} +{/block} diff --git a/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.html b/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.html new file mode 100644 index 00000000..b57d7550 --- /dev/null +++ b/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.html @@ -0,0 +1,34 @@ +{extends file="email-layout.tpl"} + +{* Open in browser *} +{block name="browser"}{/block} + +{* No big image header *} +{block name="image-header"}{/block} + +{* No pre-header *} +{block name="pre-header"}{/block} + +{* Subject *} +{block name="email-subject"}Payment confirmation {$store_name}{/block} + +{* Title *} +{block name="email-title"}{/block} + +{* Content *} +{block name="email-content"} +

{$store_name}

+ +

{intl l="Payment is confirmed for your order" d="stripepayment.email.default"}

+ +

{intl l="Reference %ref" ref={$order_ref} d="stripepayment.email.default"}

+ +

+ {intl l="Your invoice is now available in your customer account on" d="stripepayment.email.default"} + {$store_name}. +

+ +

{intl l="Thank you for your order!" d="stripepayment.email.default"}

+ +

{intl l="The %name team." name={$store_name} d="stripepayment.email.default"}

+{/block} diff --git a/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.txt b/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.txt new file mode 100644 index 00000000..1e3c8660 --- /dev/null +++ b/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.txt @@ -0,0 +1,9 @@ +{intl l="Dear customer," d="stripepayment.email.default"} + +{intl l="This is a confirmation of the payment of your order %order on %name." order={$order_ref} name={$store_name} d="stripepayment.email.default"} + +{intl l="Your invoice is now available in your customer account on %site" site={$store_url} d="stripepayment.email.default"} + +{intl l="Thank you again for your purchase." d="stripepayment.email.default"} + +{intl l="The %name team." name={$store_name} d="stripepayment.email.default"} diff --git a/local/modules/StripePayment/templates/frontOffice/default/assets/css/styles.css b/local/modules/StripePayment/templates/frontOffice/default/assets/css/styles.css new file mode 100644 index 00000000..87bff2d1 --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default/assets/css/styles.css @@ -0,0 +1,61 @@ +/** + * The CSS shown here will not be introduced in the Quickstart guide, but shows + * how you can use CSS to style your Element's container. + */ +.payment { + margin-bottom: 20px; +} +.stripe-payment { + width: 80%; + margin: auto; + text-align: center; +} +.stripe-payment .payment{ + background-color: #f5f5f5; + border-radius: 5px; +} +.stripe-payment .payment-label { + font-size: 20px; + font-weight: 500; +} +#payment-request-button { + margin-top: 10px; +} +#card-element { + box-sizing: border-box; + + height: 40px; + + padding: 10px 12px; + + border: 1px solid transparent; + border-radius: 4px; + background-color: white; + + box-shadow: 0 1px 3px 0 #e6ebf1; + -webkit-transition: box-shadow 150ms ease; + transition: box-shadow 150ms ease; +} + +#card-element--focus { + box-shadow: 0 1px 3px 0 #cfd7df; +} + +#card-element--invalid { + border-color: #fa755a; +} + +#card-element--webkit-autofill { + background-color: #fefde5 !important; +} +#card-errors, #payment-request-errors { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; + border-radius: .25rem; + padding: .75rem 1.25rem; + text-align: center; +} +#card-errors.hidden, #payment-request-errors.hidden { + display: none; +} \ No newline at end of file diff --git a/local/modules/StripePayment/templates/frontOffice/default/assets/js/order-invoice-after-js-include.html b/local/modules/StripePayment/templates/frontOffice/default/assets/js/order-invoice-after-js-include.html new file mode 100755 index 00000000..58040e79 --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default/assets/js/order-invoice-after-js-include.html @@ -0,0 +1,166 @@ + diff --git a/local/modules/StripePayment/templates/frontOffice/default/assets/js/stripe-js.html b/local/modules/StripePayment/templates/frontOffice/default/assets/js/stripe-js.html new file mode 100755 index 00000000..720ee8e5 --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default/assets/js/stripe-js.html @@ -0,0 +1,29 @@ + + +
+ {if $oneClickPayment} +
+ {intl l="Quick pay" d="stripepayment.fo.default"} +
+ +
+ +
+ + {/if} +
+ {if $oneClickPayment} + {intl l="Or enter card details" d="stripepayment.fo.default"} + {/if} +
+ +
+
+ +
diff --git a/local/modules/StripePayment/templates/frontOffice/default/stripe-paiement.html b/local/modules/StripePayment/templates/frontOffice/default/stripe-paiement.html new file mode 100644 index 00000000..1654133a --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default/stripe-paiement.html @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/local/modules/StripePayment/templates/frontOffice/default2020/assets/css/styles.css b/local/modules/StripePayment/templates/frontOffice/default2020/assets/css/styles.css new file mode 100644 index 00000000..87bff2d1 --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default2020/assets/css/styles.css @@ -0,0 +1,61 @@ +/** + * The CSS shown here will not be introduced in the Quickstart guide, but shows + * how you can use CSS to style your Element's container. + */ +.payment { + margin-bottom: 20px; +} +.stripe-payment { + width: 80%; + margin: auto; + text-align: center; +} +.stripe-payment .payment{ + background-color: #f5f5f5; + border-radius: 5px; +} +.stripe-payment .payment-label { + font-size: 20px; + font-weight: 500; +} +#payment-request-button { + margin-top: 10px; +} +#card-element { + box-sizing: border-box; + + height: 40px; + + padding: 10px 12px; + + border: 1px solid transparent; + border-radius: 4px; + background-color: white; + + box-shadow: 0 1px 3px 0 #e6ebf1; + -webkit-transition: box-shadow 150ms ease; + transition: box-shadow 150ms ease; +} + +#card-element--focus { + box-shadow: 0 1px 3px 0 #cfd7df; +} + +#card-element--invalid { + border-color: #fa755a; +} + +#card-element--webkit-autofill { + background-color: #fefde5 !important; +} +#card-errors, #payment-request-errors { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; + border-radius: .25rem; + padding: .75rem 1.25rem; + text-align: center; +} +#card-errors.hidden, #payment-request-errors.hidden { + display: none; +} \ No newline at end of file diff --git a/local/modules/StripePayment/templates/frontOffice/default2020/assets/js/order-invoice-after-js-include.html b/local/modules/StripePayment/templates/frontOffice/default2020/assets/js/order-invoice-after-js-include.html new file mode 100755 index 00000000..58040e79 --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default2020/assets/js/order-invoice-after-js-include.html @@ -0,0 +1,166 @@ + diff --git a/local/modules/StripePayment/templates/frontOffice/default2020/assets/js/stripe-js.html b/local/modules/StripePayment/templates/frontOffice/default2020/assets/js/stripe-js.html new file mode 100755 index 00000000..720ee8e5 --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default2020/assets/js/stripe-js.html @@ -0,0 +1,29 @@ + + +
+ {if $oneClickPayment} +
+ {intl l="Quick pay" d="stripepayment.fo.default"} +
+ +
+ +
+ + {/if} +
+ {if $oneClickPayment} + {intl l="Or enter card details" d="stripepayment.fo.default"} + {/if} +
+ +
+
+ +
diff --git a/local/modules/StripePayment/templates/frontOffice/default2020/stripe-paiement.html b/local/modules/StripePayment/templates/frontOffice/default2020/stripe-paiement.html new file mode 100644 index 00000000..1654133a --- /dev/null +++ b/local/modules/StripePayment/templates/frontOffice/default2020/stripe-paiement.html @@ -0,0 +1,16 @@ + + \ No newline at end of file