Installation du module Stripe

This commit is contained in:
2020-09-01 19:16:54 +02:00
parent 1d513623d6
commit 16597281d2
44 changed files with 2825 additions and 3 deletions

View File

@@ -64,7 +64,8 @@
"commerceguys/addressing": "0.8.*", "commerceguys/addressing": "0.8.*",
"symfony/cache": "~3.1.0", "symfony/cache": "~3.1.0",
"thelia/colissimows-module": "^1.1", "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": { "require-dev": {
"fzaninotto/faker": "1.5.*", "fzaninotto/faker": "1.5.*",

86
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "9792ca0006636137328111bcecd57a36", "content-hash": "5713db4d71a29f2a4aeca5a61aa6e072",
"packages": [ "packages": [
{ {
"name": "commerceguys/addressing", "name": "commerceguys/addressing",
@@ -1199,6 +1199,62 @@
], ],
"time": "2014-11-23T20:37:11+00:00" "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", "name": "swiftmailer/swiftmailer",
"version": "v5.4.1", "version": "v5.4.1",
@@ -3522,6 +3578,34 @@
"description": "Number management library", "description": "Number management library",
"time": "2015-11-05T15:52:55+00:00" "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", "name": "wsdltophp/package-colissimo-postage",
"version": "1.0.0", "version": "1.0.0",

View File

@@ -46,6 +46,7 @@ return array(
'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'),
'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'), 'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'),
'Symfony\\Cmf\\Component\\Routing\\' => array($vendorDir . '/symfony-cmf/routing'), 'Symfony\\Cmf\\Component\\Routing\\' => array($vendorDir . '/symfony-cmf/routing'),
'Stripe\\' => array($vendorDir . '/stripe/stripe-php/lib'),
'SoapClient\\' => array($vendorDir . '/wsdltophp/package-colissimo-postage/SoapClient'), 'SoapClient\\' => array($vendorDir . '/wsdltophp/package-colissimo-postage/SoapClient'),
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'Giggsey\\Locale\\' => array($vendorDir . '/giggsey/locale/src'), 'Giggsey\\Locale\\' => array($vendorDir . '/giggsey/locale/src'),

View File

@@ -73,6 +73,7 @@ class ComposerStaticInit60933c160e6e784f12d951b85ffd7bf5
'Symfony\\Component\\Cache\\' => 24, 'Symfony\\Component\\Cache\\' => 24,
'Symfony\\Component\\BrowserKit\\' => 29, 'Symfony\\Component\\BrowserKit\\' => 29,
'Symfony\\Cmf\\Component\\Routing\\' => 30, 'Symfony\\Cmf\\Component\\Routing\\' => 30,
'Stripe\\' => 7,
'SoapClient\\' => 11, 'SoapClient\\' => 11,
), ),
'P' => 'P' =>
@@ -261,6 +262,10 @@ class ComposerStaticInit60933c160e6e784f12d951b85ffd7bf5
array ( array (
0 => __DIR__ . '/..' . '/symfony-cmf/routing', 0 => __DIR__ . '/..' . '/symfony-cmf/routing',
), ),
'Stripe\\' =>
array (
0 => __DIR__ . '/..' . '/stripe/stripe-php/lib',
),
'SoapClient\\' => 'SoapClient\\' =>
array ( array (
0 => __DIR__ . '/..' . '/wsdltophp/package-colissimo-postage/SoapClient', 0 => __DIR__ . '/..' . '/wsdltophp/package-colissimo-postage/SoapClient',

View File

@@ -1296,7 +1296,8 @@
"homepage": "https://github.com/sebastianbergmann/php-token-stream/", "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [ "keywords": [
"tokenizer" "tokenizer"
] ],
"abandoned": true
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
@@ -2227,6 +2228,64 @@
"stack" "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", "name": "swiftmailer/swiftmailer",
"version": "v5.4.1", "version": "v5.4.1",
@@ -4684,6 +4743,36 @@
], ],
"description": "Number management library" "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", "name": "wsdltophp/package-colissimo-postage",
"version": "1.0.0", "version": "1.0.0",

View File

@@ -0,0 +1,13 @@
<?php
namespace StripePayment\Classes;
/**
* Class StripePaymentException
* @package StripePayment\Classes
* @author Etienne Perriere - OpenStudio <eperriere@openstudio.fr>
*/
class StripePaymentException extends \Exception
{
}

View File

@@ -0,0 +1,60 @@
<?php
namespace StripePayment\Classes;
use Thelia\Log\Tlog;
/**
* Class StripePaymentLog
* @package StripePayment\Classes
* @author Etienne Perriere - OpenStudio <eperriere@openstudio.fr>
*/
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");
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://thelia.net/schema/dic/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://thelia.net/schema/dic/config http://thelia.net/schema/dic/config/thelia-1.0.xsd">
<forms>
<form name="stripepayment.configuration" class="StripePayment\Form\StripePaymentConfigForm"/>
</forms>
<hooks>
<hook id="stripepayment.hook" class="StripePayment\Hook\StripePaymentHook" scope="request">
<argument id="request" type="service"/>
<argument type="service" id="thelia.taxEngine"/>
<tag name="hook.event_listener" event="order-invoice.payment-extra" type="front" method="includeStripe"/>
<tag name="hook.event_listener" event="order-invoice.after-javascript-include" type="front" method="declareStripeOnClickEvent"/>
<tag name="hook.event_listener" event="main.after-javascript-include" type="front" method="includeStripeJsV3"/>
<tag name="hook.event_listener" event="main.head-bottom" type="front" method="onMainHeadBottom"/>
</hook>
</hooks>
<services>
<service id="stripepayment.cart.event_listener" class="StripePayment\EventListeners\CartEventListener" scope="request">
<argument id="request" type="service"/>
<argument type="service" id="event_dispatcher" />
<argument type="service" id="thelia.taxEngine"/>
<tag name="kernel.event_subscriber"/>
</service>
</services>
</config>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="http://thelia.net/schema/dic/module"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://thelia.net/schema/dic/module http://thelia.net/schema/dic/module/module-2_1.xsd">
<fullnamespace>StripePayment\StripePayment</fullnamespace>
<descriptive locale="en_US">
<title>Stripe</title>
</descriptive>
<descriptive locale="fr_FR">
<title>Stripe</title>
</descriptive>
<languages>
<language>en_US</language>
<language>fr_FR</language>
</languages>
<version>2.0.4</version>
<author>
<name>Etienne Perriere</name>
<email>eperriere@openstudio.fr</email>
</author>
<type>payment</type>
<thelia>2.2.0</thelia>
<stability>other</stability>
</module>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="stripepayment.configuration.default" path="/admin/module/StripePayment" methods="get">
<default key="_controller">StripePayment:StripePaymentConfig:default</default>
</route>
<route id="stripepayment.configuration.save" path="/admin/module/StripePayment" methods="post">
<default key="_controller">StripePayment:StripePaymentConfig:save</default>
</route>
<route id="stripepayment.stripe_customer.list" path="/admin/module/StripePayment/stripe_customer" methods="get">
<default key="_controller">StripePayment:StripeCustomer:default</default>
</route>
<route id="stripepayment.stripe_customer.create" path="/admin/module/StripePayment/stripe_customer" methods="post">
<default key="_controller">StripePayment:StripeCustomer:create</default>
</route>
<route id="stripepayment.stripe_customer.view" path="/admin/module/StripePayment/stripe_customer/edit" methods="get">
<default key="_controller">StripePayment:StripeCustomer:update</default>
</route>
<route id="stripepayment.stripe_customer.edit" path="/admin/module/StripePayment/stripe_customer/edit" methods="post">
<default key="_controller">StripePayment:StripeCustomer:processUpdate</default>
</route>
<route id="stripepayment.stripe_webhooks.listen" path="/module/StripePayment/stripe_webhook/{secure_url}/listen">
<default key="_controller">StripePayment:StripeWebHooks:listen</default>
<requirement key="secure_url">.*</requirement>
</route>
<route id="stripepayment.stripe_customer.delete" path="/admin/module/StripePayment/stripe_customer/delete" methods="post">
<default key="_controller">StripePayment:StripeCustomer:delete</default>
</route>
</routes>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<database defaultIdMethod="native" name="thelia" >
<external-schema filename="local/config/schema.xml" referenceOnly="true" />
</database>

View File

@@ -0,0 +1,77 @@
<?php
/**
* This class has been generated by TheliaStudio
* For more information, see https://github.com/thelia-modules/TheliaStudio
*/
namespace StripePayment\Controller\Base;
use StripePayment\StripePayment;
use Thelia\Controller\Admin\BaseAdminController;
use Thelia\Form\Exception\FormValidationException;
use Thelia\Core\Security\Resource\AdminResources;
use Thelia\Core\Security\AccessManager;
use StripePayment\Model\Config\StripePaymentConfigValue;
/**
* Class StripePaymentConfigController
* @package StripePayment\Controller\Base
* @author TheliaStudio
*/
class StripePaymentConfigController extends BaseAdminController
{
public function defaultAction()
{
if (null !== $response = $this->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();
}
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* This class has been generated by TheliaStudio
* For more information, see https://github.com/thelia-modules/TheliaStudio
*/
namespace StripePayment\Controller;
use StripePayment\Controller\Base\StripePaymentConfigController as BaseStripePaymentConfigController;
/**
* Class StripePaymentConfigController
* @package StripePayment\Controller
*/
class StripePaymentConfigController extends BaseStripePaymentConfigController
{
}

View File

@@ -0,0 +1,24 @@
<?php
namespace StripePayment\Controller;
use Thelia\Module\BasePaymentModuleController;
/**
* Class StripePaymentController
* @package StripePayment\Controller
* @author Etienne Perriere - OpenStudio <eperriere@openstudio.fr>
*/
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';
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace StripePayment\Controller;
use Stripe\Checkout\Session;
use Stripe\Error\SignatureVerification;
use Stripe\Stripe;
use Stripe\Webhook;
use StripePayment\Classes\StripePaymentLog;
use StripePayment\StripePayment;
use Thelia\Controller\Front\BaseFrontController;
use Thelia\Core\Event\Order\OrderEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Response;
use Thelia\Model\OrderQuery;
use Thelia\Model\OrderStatusQuery;
class StripeWebHooksController extends BaseFrontController
{
public function listenAction($secure_url)
{
if (StripePayment::getConfigValue('secure_url') == $secure_url) {
try {
Stripe::setApiKey(StripePayment::getConfigValue('secret_key'));
// You can find your endpoint's secret in your webhook settings
$endpointSecret = StripePayment::getConfigValue('webhooks_key');
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = null;
$event = Webhook::constructEvent(
$payload, $sigHeader, $endpointSecret
);
(new StripePaymentLog())->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);
}
}

View File

@@ -0,0 +1,205 @@
<?php
namespace StripePayment\EventListeners;
use Stripe\PaymentIntent;
use Stripe\Stripe;
use StripePayment\StripePayment;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Thelia\Core\Event\ActionEvent;
use Thelia\Core\Event\Cart\CartCreateEvent;
use Thelia\Core\Event\Cart\CartEvent;
use Thelia\Core\Event\Cart\CartRestoreEvent;
use Thelia\Core\Event\Currency\CurrencyChangeEvent;
use Thelia\Core\Event\Order\OrderEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Model\Cart;
use Thelia\Model\Customer;
use Thelia\Model\Order;
use Thelia\TaxEngine\TaxEngine;
class CartEventListener implements EventSubscriberInterface
{
/** @var Request */
protected $request;
/** @var EventDispatcherInterface */
protected $dispatcher;
/** @var TaxEngine */
protected $taxEngine;
function __construct(
Request $request,
EventDispatcherInterface $dispatcher,
TaxEngine $taxEngine
)
{
$this->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;
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
* This class has been generated by TheliaStudio
* For more information, see https://github.com/thelia-modules/TheliaStudio
*/
namespace StripePayment\Form\Base;
use StripePayment\StripePayment;
use Thelia\Form\BaseForm;
use StripePayment\Model\Config\StripePaymentConfigValue;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* Class StripePaymentConfigForm
* @package StripePayment\Form\Base
* @author TheliaStudio
*/
class StripePaymentConfigForm extends BaseForm
{
const FORM_NAME = "stripepayment_config_form";
/**
*
* in this function you add all the fields you need for your Form.
* Form this you have to call add method on $this->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"
);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* This class has been generated by TheliaStudio
* For more information, see https://github.com/thelia-modules/TheliaStudio
*/
namespace StripePayment\Form;
use StripePayment\StripePayment;
use StripePayment\Form\Base\StripePaymentConfigForm as BaseStripePaymentConfigForm;
/**
* Class StripePaymentConfigForm
* @package StripePayment\Form\Base
*/
class StripePaymentConfigForm extends BaseStripePaymentConfigForm
{
public function getTranslationKeys()
{
return array(
"enabled" => $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 <a target=\"_blank\" href=\"https://dashboard.stripe.com/\">Stripe dashboard</a>. Also note that you can place your test or your live API keys", [], StripePayment::MESSAGE_DOMAIN),
);
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace StripePayment\Hook;
use StripePayment\Model\Config\Base\StripePaymentConfigValue;
use StripePayment\StripePayment;
use Thelia\Core\Event\Hook\HookRenderEvent;
use Thelia\Core\Hook\BaseHook;
use Thelia\Core\HttpFoundation\Request;
use Thelia\TaxEngine\TaxEngine;
/**
* Class StripePaymentHook
* @package StripePayment\Hook
* @author Etienne Perriere - OpenStudio <eperriere@openstudio.fr>
*/
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('<script src="https://js.stripe.com/v3/"></script>');
}
public function onMainHeadBottom(HookRenderEvent $event)
{
$content = $this->addCSS('assets/css/styles.css');
$event->add($content);
}
}

View File

@@ -0,0 +1,12 @@
<?php
return array(
'Configuration correctly saved' => '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',
);

View File

@@ -0,0 +1,17 @@
<?php
return array(
'Configuration correctly saved' => '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',
);

View File

@@ -0,0 +1,13 @@
<?php
return array(
'Dear customer,' => '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.',
);

View File

@@ -0,0 +1,13 @@
<?php
return array(
'Dear customer,' => '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. ',
);

View File

@@ -0,0 +1,21 @@
<?php
return array(
'Activated ?' => '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',
);

View File

@@ -0,0 +1,29 @@
<?php
return array(
'Activate Element ?' => '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 <a target="_blank" href="https://dashboard.stripe.com/">Stripe dashboard</a>. Also note that you can place your test or your live API keys' => 'Vos clés sont disponibles dans votre compte sur la <a target="_blank" href="https://dashboard.stripe.com/">plateforme d\'administration Stripe</a>. 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 ?',
);

View File

@@ -0,0 +1,6 @@
<?php
return array(
'Or enter card details' => 'Ou entrer les détails de votre carte de crédit',
'Quick pay' => 'Paiement rapide',
);

View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
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.

View File

@@ -0,0 +1,29 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace StripePayment\Model\Config\Base;
/**
* Class StripePaymentConfigValue
* @package StripePayment\Model\Config\Base
*/
class StripePaymentConfigValue
{
const ENABLED = "enabled";
const STRIPE_ELEMENT = "stripe_element";
const ONE_CLICK_PAYMENT = "one_click_payment";
const SECRET_KEY = "secret_key";
const PUBLISHABLE_KEY = "publishable_key";
const WEBHOOKS_KEY = "webhooks_key";
const SECURE_URL = "secure_url";
}

View File

@@ -0,0 +1,24 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace StripePayment\Model\Config;
use StripePayment\Model\Config\Base\StripePaymentConfigValue as BaseStripePaymentConfigValue;
/**
* Class StripePaymentConfigValue
* @package StripePayment\Model\Config
*/
class StripePaymentConfigValue extends BaseStripePaymentConfigValue
{
}

View File

@@ -0,0 +1,43 @@
# Stripe
Thelia payment module for [Stripe](http://stripe.com).
You need a subscription to Stripe payment solution to use this module.
## Installation
Either you install StripePayment manually or via composer, the presence of Stripe API files is checked when you try to activate the module.
If the API files are absent, you can't use Stripe.
Be aware that API files are set into the core/vendor folder.
### Manually
* Copy the module into ```<thelia_root>/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 <https://github.com/stripe/stripe-php/releases> 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -0,0 +1,542 @@
<?php
namespace StripePayment;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
use Stripe\Checkout\Session;
use Stripe\Stripe;
use StripePayment\Classes\StripePaymentException;
use StripePayment\Classes\StripePaymentLog;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Thelia\Core\Event\Image\ImageEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Response;
use Thelia\Core\Template\ParserInterface;
use Thelia\Core\Translation\Translator;
use Thelia\Log\Tlog;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Lang;
use Thelia\Model\LangQuery;
use Thelia\Model\Message;
use Thelia\Model\MessageQuery;
use Thelia\Model\ModuleQuery;
use Thelia\Model\Order;
use Thelia\Model\Order as OrderModel;
use Thelia\Model\OrderCouponQuery;
use Thelia\Model\OrderProductAttributeCombinationQuery;
use Thelia\Model\OrderProductQuery;
use Thelia\Model\ProductImageQuery;
use Thelia\Model\ProductQuery;
use Thelia\Module\AbstractPaymentModule;
use Thelia\Tools\URL;
/**
* Class StripePayment
* @package StripePayment
* @author Etienne Perriere - OpenStudio <eperriere@openstudio.fr>
*/
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;
}
}

View File

@@ -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"
}
}

View File

@@ -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"}
<div class="container" id="wrapper">
<ul class="breadcrumb">
<li><a href="{url path='/admin'}">{intl l="Home" d="stripepayment.bo.default"}</a></li>
<li><a href="{url path='/admin/modules'}">{intl l="Modules" d="stripepayment.bo.default"}</a></li>
<li>{intl l="StripePayment configuration" d="stripepayment.bo.default"}</li>
</ul>
<div class="general-block-decorator">
<div class="title title-without-tabs">
{intl l="Configure stripepayment" d="stripepayment.bo.default"}
</div>
<div class="row">
<div class="col-md-12">
{if $success}
<div class="alert alert-success">
{intl l="Configuration correctly saved" d="stripepayment.bo.default"}
</div>
{/if}
{form name="stripepayment.configuration"}
<form action="{$current_url}" method="post">
{include "includes/inner-form-toolbar.html" hide_flags = 1 close_url={url path='/admin/modules'}}
<br/>
{form_field form=$form field="success_url"}
<input type="hidden" name="{$name}" value="{url path='/admin/modules'}" />
{/form_field}
{form_hidden_fields form=$form}
{form_field form=$form field="enabled"}
<div class="form-group {if $error}has-error{/if}">
<label class="control-label" for="{$label_attr.for}">
<input type="checkbox" name="{$name}" id="{$label_attr.for}" {if $value}checked{/if} placeholder="{intl l='The configuration value enabled' d="stripepayment.bo.default"}" />
{$label}
{if $required}<span class="required">*</span>{/if}
{form_error form=$form field="enabled"}
<br />
<span class="error">{$message}</span>
{/form_error}
</label>
{if ! empty($label_attr.help)}
<span class="help-block">{$label_attr.help}</span>
{/if}
</div>
{/form_field}
{form_field form=$form field="stripe_element"}
<div class="form-group {if $error}has-error{/if}">
<label class="control-label" for="{$label_attr.for}">
<input type="checkbox" name="{$name}" id="{$label_attr.for}" {if $value}checked{/if} placeholder="{intl l='active stripe element' d="stripepayment.bo.default"}" />
{$label}
{if $required}<span class="required">*</span>{/if}
{form_error form=$form field="stripe_element"}
<br />
<span class="error">{$message}</span>
{/form_error}
</label>
{if ! empty($label_attr.help)}
<span class="help-block">{$label_attr.help}</span>
{/if}
</div>
{/form_field}
{form_field form=$form field="one_click_payment"}
<div class="form-group {if $error}has-error{/if}">
<label class="control-label" for="{$label_attr.for}">
<input type="checkbox" name="{$name}" id="{$label_attr.for}" {if $value}checked{/if} placeholder="{intl l='active one click payment' d="stripepayment.bo.default"}" />
{$label}
{if $required}<span class="required">*</span>{/if}
{form_error form=$form field="stripe_element"}
<br />
<span class="error">{$message}</span>
{/form_error}
</label>
{if ! empty($label_attr.help)}
<span class="help-block">{$label_attr.help}</span>
{/if}
</div>
{/form_field}
{form_field form=$form field="secret_key"}
<div class="form-group {if $error}has-error{/if}">
<label class="control-label" for="{$label_attr.for}">
{$label}
{if $required}<span class="required">*</span>{/if}
{form_error form=$form field="secret_key"}
<br />
<span class="error">{$message}</span>
{/form_error}
</label>
<input type="text" class="form-control" name="{$name}" id="{$label_attr.for}" value="{$value}" placeholder="{intl l='The configuration value secret_key' d="stripepayment.bo.default"}" />
{if ! empty($label_attr.help)}
<span class="help-block">{$label_attr.help nofilter}</span>
{/if}
</div>
{/form_field}
{form_field form=$form field="publishable_key"}
<div class="form-group {if $error}has-error{/if}">
<label class="control-label" for="{$label_attr.for}">
{$label}
{if $required}<span class="required">*</span>{/if}
{form_error form=$form field="publishable_key"}
<br />
<span class="error">{$message}</span>
{/form_error}
</label>
<input type="text" class="form-control" name="{$name}" id="{$label_attr.for}" value="{$value}" placeholder="{intl l='The configuration value publishable_key' d="stripepayment.bo.default"}" />
{if ! empty($label_attr.help)}
<span class="help-block">{$label_attr.help}</span>
{/if}
</div>
{/form_field}
{form_field form=$form field="webhooks_key"}
<div class="form-group {if $error}has-error{/if}">
<label class="control-label" for="{$label_attr.for}">
{$label}
{if $required}<span class="required">*</span>{/if}
{form_error form=$form field="webhooks_key"}
<br />
<span class="error">{$message}</span>
{/form_error}
</label>
<input type="text" class="form-control" name="{$name}" id="{$label_attr.for}" value="{$value}" placeholder="{intl l='The configuration value webhooks_key whsec_...' d="stripepayment.bo.default"}" />
{if ! empty($label_attr.help)}
<span class="help-block">{$label_attr.help}</span>
{/if}
</div>
{/form_field}
{form_field form=$form field="secure_url"}
<div class="form-group {if $error}has-error{/if}">
<label class="control-label" for="{$label_attr.for}">
{$label}
{if $required}<span class="required">*</span>{/if}
{form_error form=$form field="secure_url"}
<br />
<span class="error">{$message}</span>
{/form_error}
</label>
<input type="text" class="form-control" name="{$name}" id="{$label_attr.for}" value="{$value}" placeholder="{intl l='The configuration value secure_url' d="stripepayment.bo.default"}" />
{if ! empty($label_attr.help)}
<span class="help-block">{$label_attr.help}</span>
{/if}
</div>
{/form_field}
<div class="form-group">
<label class="control-label" for="web_hooks_urls">{intl l='Webhooks endpoint url' d="stripepayment.bo.default"}</label>
<input id="web_hooks_urls" type="text" class="form-control disabled" disabled value="{url path="/module/StripePayment/stripe_webhook/{$value}/listen"}">
</div>
<div class="form-group">
<label>{intl l='Webhooks event to activate' d="stripepayment.bo.default"}</label>
<ul>
<li>payment_intent.payment_failed</li>
<li>payment_intent.succeeded</li>
<li>checkout.session.completed</li>
</ul>
</div>
{include "includes/inner-form-toolbar.html" hide_flags = 1 close_url={url path='/admin/modules'} page_bottom=1}
</form>
{/form}
</div>
</div>
</div>
</div>
{/block}
{block name="javascript-initialization"}
{/block}

View File

@@ -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"}
<h1 id="logotexte">{$store_name}</h1>
<h2 id="info">{intl l="Payment is confirmed for your order" d="stripepayment.email.default"}</h2>
<h3 id="commande">{intl l="Reference %ref" ref={$order_ref} d="stripepayment.email.default"}</h3>
<p id="liencompte">
{intl l="Your invoice is now available in your customer account on" d="stripepayment.email.default"}
<a href="{$store_url}">{$store_name}</a>.
</p>
<p>{intl l="Thank you for your order!" d="stripepayment.email.default"}</p>
<p>{intl l="The %name team." name={$store_name} d="stripepayment.email.default"}</p>
{/block}

View File

@@ -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"}

View File

@@ -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;
}

View File

@@ -0,0 +1,166 @@
<script>
// Create a Stripe client.
var stripe = Stripe('{$public_key}');
var stripeModuleId = '{$stripe_module_id}';
var intentSecret = '{$clientSecret}';
var totalAmount = Math.round(({cart attr="total_taxed_price"} + {order attr="postage"}) * 100);
var currency = '{$currency}';
var country = '{$country}';
var storeName = '{config key="store_name"}';
{literal}
// Create an instance of Elements.
var elements = stripe.elements();
var paymentRequest = stripe.paymentRequest({
currency: currency,
country: country,
total: {
label: storeName,
amount: totalAmount,
},
requestPayerName: true,
requestPayerEmail: true,
});
var prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
});
// Check the availability of the Payment Request API first.
paymentRequest.canMakePayment().then(function (result) {
if (result) {
prButton.mount("#payment-request-button");
}
});
paymentRequest.on('paymentmethod', function (ev) {
// Trigger HTML5 validation UI on the form if any of the inputs fail
// validation.
var plainInputsValid = true;
Array.prototype.forEach.call(form.querySelectorAll('input'), function(
input
) {
if (input.checkValidity && !input.checkValidity()) {
plainInputsValid = false;
ev.complete('fail');
return;
}
});
if (!plainInputsValid) {
triggerBrowserValidation();
ev.complete('fail');
return;
}
stripe.confirmPaymentIntent(
intentSecret,
{
payment_method: ev.paymentMethod.id,
}
).then(function (confirmResult) {
console.log("CONFIRM");
if (confirmResult.error) {
console.log("ERROR", confirmResult.error);
// Report to the browser that the payment failed, prompting it to
// re-show the payment interface, or show an error message and close
// the payment interface.
ev.complete('fail');
var paymentRequestError = document.createElement('payment-request-errors');
paymentRequestError.innerHTML = confirmResult.error.message;
paymentRequestError.classList.remove('hidden');
} else {
console.log("Success");
// Report to the browser that the confirmation was successful, prompting
// it to close the browser payment method collection interface.
ev.complete('success');
validPayment();
}
});
});
function triggerBrowserValidation() {
// The only way to trigger HTML5 form validation UI is to fake a user submit
// event.
var submit = document.createElement('input');
submit.type = 'submit';
submit.style.display = 'none';
form.appendChild(submit);
submit.click();
submit.remove();
}
// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
var style = {
base: {
color: "#32325D",
fontWeight: 500,
fontFamily: "Inter UI, Open Sans, Segoe UI, sans-serif",
fontSize: "16px",
fontSmoothing: "antialiased",
"::placeholder": {
color: "#CFD7DF"
}
},
invalid: {
color: "#E25950"
}
};
// Create an instance of the card Element.
var card = elements.create('card', {style: style});
// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.addEventListener('change', function (event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission.
var form = document.getElementById('form-cart-payment');
form.addEventListener('submit', function (event) {
var paymetnDiv = document.getElementById('payment_'+stripeModuleId);
var displayError = document.getElementById('card-errors');
if(paymetnDiv && paymetnDiv.checked) {
event.preventDefault();
stripe.handleCardPayment(
intentSecret,
card
).then( function (result) {
if (result.error) {
console.log(result.error);
displayError.innerHTML = result.error.message;
displayError.classList.remove('hidden');
// Display error.message in your UI.
} else {
console.log("Success");
validPayment();
}
});
}
});
function validPayment() {
var form = document.getElementById('form-cart-payment');
// Submit the form
form.submit();
}
{/literal}
</script>

View File

@@ -0,0 +1,29 @@
<style>
</style>
<div class="stripe-payment">
{if $oneClickPayment}
<div class="payment">
<span class="payment-label">{intl l="Quick pay" d="stripepayment.fo.default"}</span>
<div id="payment-request-button">
<!-- A Stripe Element will be inserted here. -->
</div>
<div class="hidden" id="payment-request-errors" role="alert">
</div>
</div>
{/if}
<div class="payment">
{if $oneClickPayment}
<span class="payment-label">{intl l="Or enter card details" d="stripepayment.fo.default"}</span>
{/if}
<div id="card-element">
<!-- A Stripe Element will be inserted here. -->
</div>
</div>
<div class="hidden" id="card-errors" role="alert">
</div>
</div>

View File

@@ -0,0 +1,16 @@
<script src="https://js.stripe.com/v3/"></script>
<script>
var stripe = Stripe('{$public_key}');
stripe.redirectToCheckout({
{literal}
// Make the id field from the Checkout Session creation API response
// available to this file, so you can provide it as parameter here
// instead of the {{CHECKOUT_SESSION_ID}} placeholder.
{/literal}
sessionId: '{$checkout_session_id}'
}).then(function (result) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `result.error.message`.
});
</script>

View File

@@ -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;
}

View File

@@ -0,0 +1,166 @@
<script>
// Create a Stripe client.
var stripe = Stripe('{$public_key}');
var stripeModuleId = '{$stripe_module_id}';
var intentSecret = '{$clientSecret}';
var totalAmount = Math.round(({cart attr="total_taxed_price"} + {order attr="postage"}) * 100);
var currency = '{$currency}';
var country = '{$country}';
var storeName = '{config key="store_name"}';
{literal}
// Create an instance of Elements.
var elements = stripe.elements();
var paymentRequest = stripe.paymentRequest({
currency: currency,
country: country,
total: {
label: storeName,
amount: totalAmount,
},
requestPayerName: true,
requestPayerEmail: true,
});
var prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
});
// Check the availability of the Payment Request API first.
paymentRequest.canMakePayment().then(function (result) {
if (result) {
prButton.mount("#payment-request-button");
}
});
paymentRequest.on('paymentmethod', function (ev) {
// Trigger HTML5 validation UI on the form if any of the inputs fail
// validation.
var plainInputsValid = true;
Array.prototype.forEach.call(form.querySelectorAll('input'), function(
input
) {
if (input.checkValidity && !input.checkValidity()) {
plainInputsValid = false;
ev.complete('fail');
return;
}
});
if (!plainInputsValid) {
triggerBrowserValidation();
ev.complete('fail');
return;
}
stripe.confirmPaymentIntent(
intentSecret,
{
payment_method: ev.paymentMethod.id,
}
).then(function (confirmResult) {
console.log("CONFIRM");
if (confirmResult.error) {
console.log("ERROR", confirmResult.error);
// Report to the browser that the payment failed, prompting it to
// re-show the payment interface, or show an error message and close
// the payment interface.
ev.complete('fail');
var paymentRequestError = document.createElement('payment-request-errors');
paymentRequestError.innerHTML = confirmResult.error.message;
paymentRequestError.classList.remove('hidden');
} else {
console.log("Success");
// Report to the browser that the confirmation was successful, prompting
// it to close the browser payment method collection interface.
ev.complete('success');
validPayment();
}
});
});
function triggerBrowserValidation() {
// The only way to trigger HTML5 form validation UI is to fake a user submit
// event.
var submit = document.createElement('input');
submit.type = 'submit';
submit.style.display = 'none';
form.appendChild(submit);
submit.click();
submit.remove();
}
// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
var style = {
base: {
color: "#32325D",
fontWeight: 500,
fontFamily: "Inter UI, Open Sans, Segoe UI, sans-serif",
fontSize: "16px",
fontSmoothing: "antialiased",
"::placeholder": {
color: "#CFD7DF"
}
},
invalid: {
color: "#E25950"
}
};
// Create an instance of the card Element.
var card = elements.create('card', {style: style});
// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.addEventListener('change', function (event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission.
var form = document.getElementById('form-cart-payment');
form.addEventListener('submit', function (event) {
var paymetnDiv = document.getElementById('payment_'+stripeModuleId);
var displayError = document.getElementById('card-errors');
if(paymetnDiv && paymetnDiv.checked) {
event.preventDefault();
stripe.handleCardPayment(
intentSecret,
card
).then( function (result) {
if (result.error) {
console.log(result.error);
displayError.innerHTML = result.error.message;
displayError.classList.remove('hidden');
// Display error.message in your UI.
} else {
console.log("Success");
validPayment();
}
});
}
});
function validPayment() {
var form = document.getElementById('form-cart-payment');
// Submit the form
form.submit();
}
{/literal}
</script>

View File

@@ -0,0 +1,29 @@
<style>
</style>
<div class="stripe-payment">
{if $oneClickPayment}
<div class="payment">
<span class="payment-label">{intl l="Quick pay" d="stripepayment.fo.default"}</span>
<div id="payment-request-button">
<!-- A Stripe Element will be inserted here. -->
</div>
<div class="hidden" id="payment-request-errors" role="alert">
</div>
</div>
{/if}
<div class="payment">
{if $oneClickPayment}
<span class="payment-label">{intl l="Or enter card details" d="stripepayment.fo.default"}</span>
{/if}
<div id="card-element">
<!-- A Stripe Element will be inserted here. -->
</div>
</div>
<div class="hidden" id="card-errors" role="alert">
</div>
</div>

View File

@@ -0,0 +1,16 @@
<script src="https://js.stripe.com/v3/"></script>
<script>
var stripe = Stripe('{$public_key}');
stripe.redirectToCheckout({
{literal}
// Make the id field from the Checkout Session creation API response
// available to this file, so you can provide it as parameter here
// instead of the {{CHECKOUT_SESSION_ID}} placeholder.
{/literal}
sessionId: '{$checkout_session_id}'
}).then(function (result) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `result.error.message`.
});
</script>