Sylius: Get started

Available payment methods

Mollie for Sylius offers a wide range of payment methods that your customers can use to pay during checkout:

Alma, Apple Pay (incl. Apple Pay Direct), Bancontact, Billie, Credit/Debit cards, EPS, iDEAL; iDEAL in3, Klarna, PayPal, Przelewy24, KBC Payment Button, Belfius Pay Button, SEPA Bank Transfer, Vouchers.

For information on how to select the best payment methods for your business, refer to the Mollie Payment methods guide.

Features

Mollie for Sylius offers the following features to effectively manage your online payments:

  • Customize the payment experience
  • Localize your payment methods
  • Offer and manage subscriptions
  • Manage orders and payments

Install and connect

Enhance your store's payment options with Mollie.

πŸ“˜

Note

This user guide explains how to install, configure and use the standard Mollie integra- tion with Sylius. For information on how to customize your integration, read Additional resources for developers↗.

What you need to do in advance:

  • Create and successfully log in to your Mollie account.
  • Make sure that you use these package versions:
    • php 7.2 or 8.0
    • mollie/mollie-api-php 2.0
    • sylius/admin-order-creation-plugin 0.12, 0.13 or 0.14
    • sylius/refund-plugin 1.0
    • sylius 1.9.0, 1.10.0, 1.11.0 or 1.12.0
  • Install Composer.
  • Ensure that you have NPM and Node installed.

Install

Install the back-end:

  1. Install the Refund plugin:

πŸ“˜

Note

  • Make sure that wkhtmltopdf is installed.
  • Set the tool path in the .env file using the WKHTMLTOPDF_PATH and
    WKHTMLTOIMAGE_PATH variables.
 composer require sylius/refund-plugin
  1. Install Mollie for Sylius:
composer require mollie/sylius-plugin --no-scripts -w
  1. Update the GatewayConfig entity class with the following code: (with annotations)
<?php

declare(strict_types=1);

namespace App\Entity\Payment;

use Doctrine\ORM\Mapping as ORM;
use SyliusMolliePlugin\Entity\GatewayConfigInterface;
use SyliusMolliePlugin\Entity\GatewayConfigTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Sylius\Bundle\PayumBundle\Model\GatewayConfig as BaseGatewayConfig;

/**
 * @ORM\Entity
 * @ORM\Table(name="sylius_gateway_config")
 */
class GatewayConfig extends BaseGatewayConfig implements GatewayConfigInterface
{
    use GatewayConfigTrait;

    /**
     * @var ArrayCollection
     * @ORM\OneToMany(
     *     targetEntity="SyliusMolliePlugin\Entity\MollieGatewayConfig",
     *     mappedBy="gateway",
     *     orphanRemoval=true,
     *     cascade={"all"}
     * )
     */
    protected $mollieGatewayConfig;

    public function __construct()
    {
        parent::__construct();

        $this->mollieGatewayConfig = new ArrayCollection();
    }
}

You can find more annotation examples under the tests/Application/src/Entity/* path.

If you don't use annotations, you can also define new Entity mapping inside your src/Resources/config/doctrine directory:

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                  http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"
>
    <mapped-superclass name="App\Entity\Payment\GatewayConfig" table="sylius_gateway_config">
        <one-to-many field="mollieGatewayConfig" target-entity="SyliusMolliePlugin\Entity\MollieGatewayConfig" mapped-by="gateway" orphan-removal="true">
            <cascade>
                <cascade-all />
            </cascade>
        </one-to-many>
    </mapped-superclass>
</doctrine-mapping>

For an example, check tests/Application/src/Resources/config/doctrine/GatewayConfig.orm.xml file.

Override the GatewayConfig resource:

# config/packages/_sylius.yaml
...

sylius_payum:
    resources:
        gateway_config:
          classes:
              model: App\Entity\Payment\GatewayConfig
  1. Update the Order entity class: (with annotations)
<?php

declare(strict_types=1);

namespace App\Entity\Order;

use SyliusMolliePlugin\Entity\OrderInterface;
use SyliusMolliePlugin\Entity\AbandonedEmailOrderTrait;
use SyliusMolliePlugin\Entity\RecurringOrderTrait;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\Order as BaseOrder;
use Sylius\Component\Core\Model\OrderItemInterface;

/**
 * @ORM\Entity
 * @ORM\Table(name="sylius_order")
 */
class Order extends BaseOrder implements OrderInterface
{
    use AbandonedEmailOrderTrait;
    use RecurringOrderTrait;

    /**
     * @var bool
     * @ORM\Column(type="boolean", name="abandoned_email")
     */
    protected $abandonedEmail = false;

    /**
     * @var ?int
     * @ORM\Column(type="integer", name="recurring_sequence_index", nullable=true)
     */
    protected $recurringSequenceIndex;

    /**
     * @var MollieSubscriptionInterface|null
     * @ORM\ManyToOne(targetEntity="SyliusMolliePlugin\Entity\MollieSubscription")
     * @ORM\JoinColumn(name="subscription_id", fieldName="subscription", onDelete="RESTRICT")
     */
    protected $subscription = null;

    public function getRecurringItems(): Collection
    {
        return $this
            ->items
            ->filter(function (OrderItemInterface $orderItem) {
                $variant = $orderItem->getVariant();

                return $variant !== null
                    && true === $variant->isRecurring();
            })
            ;
    }

    public function getNonRecurringItems(): Collection
    {
        return $this
            ->items
            ->filter(function (OrderItemInterface $orderItem) {
                $variant = $orderItem->getVariant();

                return $variant !== null
                    && false === $variant->isRecurring();
            })
            ;
    }

    public function hasRecurringContents(): bool
    {
        return 0 < $this->getRecurringItems()->count();
    }

    public function hasNonRecurringContents(): bool
    {
        return 0 < $this->getNonRecurringItems()->count();
    }
}

If you don't use annotations, you can also define new Entity mapping inside your src/Resources/config/doctrine directory.

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                  http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"
>
    <mapped-superclass name="App\Entity\Order\Order" table="sylius_order">
        <field name="abandonedEmail" type="boolean" column="abandoned_email"/>
    </mapped-superclass>
</doctrine-mapping>

Override Order resource:

# config/packages/_sylius.yaml
...

sylius_order:
    resources:
        order:
            classes:
                model: App\Entity\Order\Order
  1. Update the Product entity class with the following code:
<?php

declare(strict_types=1);

namespace App\Entity\Product;

use Doctrine\ORM\Mapping as ORM;
use SyliusMolliePlugin\Entity\ProductInterface;
use SyliusMolliePlugin\Entity\ProductTrait;
use Sylius\Component\Core\Model\Product as BaseProduct;

/**
 * @ORM\Entity
 * @ORM\Table(name="sylius_product")
 */
class Product extends BaseProduct implements ProductInterface
{
    use ProductTrait;

    /**
     * @ORM\ManyToOne(targetEntity="SyliusMolliePlugin\Entity\ProductType")
     * @ORM\JoinColumn(name="product_type_id", fieldName="productType", onDelete="SET NULL")
     */
    protected $productType;
}

If you don't use annotations, you can also define new Entity mapping inside your src/Resources/config/doctrine directory.

<?xml version="1.0" encoding="UTF-8" ?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                                  http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"
                  xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping"
>
    <entity name="App\Entity\Product\Product" table="sylius_product">
        <many-to-one
            field="productType"
            target-entity="SyliusMolliePlugin\Entity\ProductType"
        >
            <join-column
                name="product_type_id"
                on-delete="SET NULL"
            />
        </many-to-one>
    </entity>
</doctrine-mapping>

Override Product resource:

# config/packages/_sylius.yaml
...

sylius_product:
        resources:
            product:
                classes:
                    model: App\Entity\Product\Product
  1. Update the ProductVariant entity class with the following code:
<?php

declare(strict_types=1);

namespace App\Entity\Product;

use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\ProductVariant as BaseProductVariant;
use Sylius\Component\Product\Model\ProductVariantTranslationInterface;

/**
 * @ORM\Entity
 * @ORM\Table(name="sylius_product_variant")
 */
class ProductVariant extends BaseProductVariant
{
    use RecurringProductVariantTrait;

    protected function createTranslation(): ProductVariantTranslationInterface
    {
        return new ProductVariantTranslation();
    }
    
    public function getName(): ?string
    {
        return parent::getName() ?: $this->getProduct()->getName();
    }
}

Add RecurringProductVariantTrait implementation:

<?php

declare(strict_types=1);

namespace App\Entity\Product;


use Doctrine\ORM\Mapping as ORM;

trait RecurringProductVariantTrait
{
    /**
     * @var bool
     * @ORM\Column(type="boolean", name="recurring", nullable="false", options={"default":0})
     */
    private bool $recurring = false;

    /**
     * @var ?int
     * @ORM\Column(type="integer", name="recurring_times", nullable="true")
     */
    private ?int $times = null;

    /**
     * @var ?string
     * @ORM\Column(type="string", name="recurring_interval", nullable="true")
     */
    private ?string $interval = null;

    public function isRecurring(): bool
    {
        return $this->recurring;
    }

    public function setRecurring(bool $recurring): void
    {
        $this->recurring = $recurring;
    }

    public function getTimes(): ?int
    {
        return $this->times;
    }

    public function setTimes(?int $times): void
    {
        $this->times = $times;
    }

    public function getInterval(): ?string
    {
        return $this->interval;
    }

    public function setInterval(?string $interval): void
    {
        $this->interval = $interval;
    }
}

If you don't use annotations, you can also define new Entity mapping inside your src/Resources/config/doctrine directory.

<?xml version="1.0" encoding="UTF-8" ?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                                  http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"
                  xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping"
>
    <mapped-superclass name="App\Entity\Product\ProductVariant" table="sylius_product_variant">
        <field name="recurring" type="boolean" column="recurring">
            <options>
                <option name="default">0</option>
            </options>
        </field>
        <field name="times" type="integer" column="recurring_times" nullable="true"/>
        <field name="interval" column="recurring_interval" nullable="true"/>
    </mapped-superclass>
</doctrine-mapping>

Override ProductVariant resource:

# config/packages/_sylius.yaml
...

sylius_product:
        resources:
          product_variant:
                classes:
                    model: App\Entity\Product\ProductVariant
  1. Ensure that the plugin dependency is added to your config/bundles.php file:
# config/bundles.php

return [
    ...
    SyliusMolliePlugin\SyliusMolliePlugin::class => ['all' => true],
];
  1. Import required config in your config/packages/_sylius.yaml file:
# config/packages/_sylius.yaml

imports:
    ...
    - { resource: "@SyliusMolliePlugin/Resources/config/config.yaml" }
  1. Add state machine configuration in config/packages/_sylius.yaml:
# config/packages/_sylius.yaml

winzou_state_machine:
  sylius_order_checkout:
    transitions:
      complete:
        from: [cart, addressed, shipping_selected, shipping_skipped, payment_selected, payment_skipped]
        to: completed
  1. Add image directory parameter in config/packages/_sylius.yaml:
# config/packages/_sylius.yaml

   parameters:
       images_dir: "/media/image/"
  1. Import the routing in your config/routes.yaml file:
# config/routes.yaml

sylius_mollie_plugin:
    resource: "@SyliusMolliePlugin/Resources/config/routing.yaml"
  1. Update your database
    Apply migration to your database: (this is for the plugin fresh installation only)
    bin/console doctrine:migrations:migrate
    

In case if you are updating from older version of plugin (versions < 5.0), you will need to run the following commands before running migrate command.

bin/console doctrine:migrations:version --add --range-from='SyliusMolliePlugin\Migrations\Version20200513092722' --range-to='SyliusMolliePlugin\Migrations\Version20220211040328'
bin/console doctrine:migrations:execute --up 'SyliusMolliePlugin\Migrations\Version20231225151033'

After running all the above-mentioned commands, run migrate command:

bin/console doctrine:migrations:migrate
  1. Copy Sylius templates overridden in plugin to your templates directory (e.g templates/bundles/):
mkdir -p templates/bundles/SyliusAdminBundle/
mkdir -p templates/bundles/SyliusShopBundle/
mkdir -p templates/bundles/SyliusUiBundle/
mkdir -p templates/bundles/SyliusRefundPlugin/

cp -R vendor/mollie/sylius-plugin/tests/Application/templates/bundles/SyliusAdminBundle/* templates/bundles/SyliusAdminBundle/
cp -R vendor/mollie/sylius-plugin/tests/Application/templates/bundles/SyliusShopBundle/* templates/bundles/SyliusShopBundle/
cp -R vendor/mollie/sylius-plugin/tests/Application/templates/bundles/SyliusUiBundle/* templates/bundles/SyliusUiBundle/
cp -R vendor/mollie/sylius-plugin/tests/Application/templates/bundles/SyliusRefundPlugin/* templates/bundles/SyliusRefundPlugin/
  1. Install assets:
bin/console assets:install

Note: If you are running it on production, add the -e prod flag to this command.

  1. Add the payment link cronjob:
* * * * * /usr/bin/php /path/to/bin/console mollie:send-payment-link
  1. Download the domain validation file and place it on your server at:
public/.well-known/apple-developer-merchantid-domain-association

Install the front-end

  1. Installing assets without webpack
    If you're not using webpack, you can install assets via:
    bin/console assets:install
    

And then import these already built assets into shop/admin _scripts and _styles .html.twig files: For example: templates/bundles/SyliusAdminBundle/_scripts.html.twig using:

{{ asset('public/bundles/syliusmollieplugin/mollie/admin.css') }}
{{ asset('public/bundles/syliusmollieplugin/mollie/admin.js') }}
{{ asset('public/bundles/syliusmollieplugin/mollie/shop.css') }}
{{ asset('public/bundles/syliusmollieplugin/mollie/shop.js') }}

These assets are located in:

public/bundles/syliusmollieplugin/mollie/admin.css
public/bundles/syliusmollieplugin/mollie/admin.js
public/bundles/syliusmollieplugin/mollie/shop.css
public/bundles/syliusmollieplugin/mollie/shop.js
  1. Importing pre-built assets without webpack
    Another way is to import already built assets directly from mollie source files:
vendor/mollie/sylius-plugin/src/Resources/public/mollie/admin.css
vendor/mollie/sylius-plugin/src/Resources/public/mollie/admin.js
vendor/mollie/sylius-plugin/src/Resources/public/mollie/shop.css
vendor/mollie/sylius-plugin/src/Resources/public/mollie/shop.js
  1. Using webpack
    Require webpack bundle with composer:
composer require symfony/webpack-encore-bundle

Ensure the following configuration is present in config/packages/webpack_encore.yaml:

webpack_encore:
    output_path: "%kernel.project_dir%/public/build"
    builds:
        mollie-admin: "%kernel.project_dir%/public/build/admin"
        mollie-shop: "%kernel.project_dir%/public/build/shop"
    script_attributes:
        defer: false

framework:
    assets:
        json_manifest_path: '%kernel.project_dir%/public/build/admin/manifest.json'

Ensure that mollie-shop-entry and mollie-admin-entry are present in webpackconfig.js:

Encore.addEntry(
    'mollie-shop-entry',
    path.resolve(
      __dirname,
      'vendor/mollie/sylius-plugin/src/Resources/assets/shop/entry.js'
    )
)

Encore.addEntry(
    'mollie-admin-entry',
    path.resolve(
        __dirname,
        'vendor/mollie/sylius-plugin/src/Resources/assets/admin/entry.js'
    )
)

If you are using Sylius version <= 1.11 ensure that Node version 12 is currently used, otherwise Node version 14 should be used:

nvm install 12
nvm use 12

Ensure you have the following packages installed:

yarn add babel-preset-env bazinga-translator intl-messageformat lodash.get [email protected] [email protected] webpack-notifier
yarn add --dev @babel/[email protected] @babel/[email protected] @babel/[email protected] @symfony/[email protected]

Run gulp:

yarn run gulp

Build the front-end assets:

yarn install
yarn build
yarn encore production

Update the scheme, since webpack and asset require new tables that are not in the migrations:

php bin/console doctrine:schema:update --force

Connect

Mollie gives you two API keys to authenticate Sylius requests:

  • Test key: Use this key when setting up and testing Mollie.
  • Live key: Use this key after Mollie has approved your account and you are ready to accept live payments.

To connect with Mollie, follow these steps:

  1. Log in to your Mollie Dashboard.
  2. Select the organization in the top-left corner of the dashboard's home page.

❗️

Important

To comply with UK regulations, UK-based merchants have to select a UK-registered organization to ensure that they use the correct API keys.

  1. Go to More > Developers > API keys.
  2. Copy the API keys.
  3. Log in to Sylius.
  4. Open Configuration > Payment methods.
  5. Select +Create.
  6. Select Mollie or Mollie subscriptions.
    • Mollie: Payment methods for products purchased on a non-recurring basis
    • Mollie subscriptions: Payment methods for products purchased on a recurring basis
  7. Add a payment gateway code to identify the Mollie payment methods.
  8. In the Gateway configuration section, select Test from the drop-down menu.
  9. Paste the API keys into their respective fields.
  10. Select +Create.
  11. Select Edit next to the gateway.
  12. Select Load methods.
  13. Select Save changes.