Install the Marketplace Order Invoice Documents feature
Edit on GitHubThis document describes how to install the Marketplace order invoice documents feature.
Spryker does not generate, validate, or assume legal responsibility for invoices or credit notes. The seller remains the invoice issuer and is responsible for the document’s tax and legal compliance, including its format under applicable national e-invoicing mandates.
Spryker’s role is limited to forwarding the document links from the seller, making them available to the correct buyer or seller.
Spryker does not transmit documents to national e-invoicing or clearance networks such as Peppol, KSeF, or SdI. Transmission remains the responsibility of the seller or their e-invoicing provider.
The Order confirmation / invoice notification email feature is not compliant with the latest EU e-invoicing requirements.
Install feature core
Follow the steps below to install the feature core.
Prerequisites
Install the required features:
| NAME | VERSION | INSTALLATION GUIDE |
|---|---|---|
| Spryker Core | 202608.0 | Install the Spryker Core feature |
| Order Invoice Documents | 202608.0 | Install the Order Invoice Documents feature |
| Marketplace Order Management | 202608.0 | Install the Marketplace Order Management feature |
| Marketplace Order Management + Merchant Portal | 202608.0 | Install the Marketplace Order Management + Merchant Portal feature |
| Marketplace Merchant Portal Core | 202608.0 | Install the Marketplace Merchant Portal Core feature |
1) Install the required modules
Install the required modules using Composer:
composer require spryker/merchant-sales-invoice:"^1.0.0" spryker/sales-invoice:"^1.7.0" spryker/sales-invoice-extension:"^1.1.0" spryker/sales-merchant-portal-gui:"^4.3.0" --update-with-dependencies
Make sure the following modules have been installed:
| MODULE | EXPECTED DIRECTORY |
|---|---|
| MerchantSalesInvoice | vendor/spryker/merchant-sales-invoice |
| SalesInvoice | vendor/spryker/sales-invoice |
| SalesInvoiceExtension | vendor/spryker/sales-invoice-extension |
| SalesMerchantPortalGui | vendor/spryker/sales-merchant-portal-gui |
2) Set up database schema and transfer objects
The module adds the merchant reference of an invoice to the spy_sales_order_invoice table. Apply the database changes and generate the transfer changes:
console propel:install
console transfer:generate
Make sure the following changes have been applied to the database:
| DATABASE ENTITY | TYPE | EVENT |
|---|---|---|
| spy_sales_order_invoice.merchant_reference | column | created |
| spy_sales_order_invoice-unique-sales_order-merchant_reference | unique key | created |
Make sure the following transfer objects have been generated:
| TRANSFER | TYPE | EVENT | PATH |
|---|---|---|---|
| OrderInvoice.merchantReference | property | created | src/Generated/Shared/Transfer/OrderInvoiceTransfer |
| OrderInvoice.merchant | property | created | src/Generated/Shared/Transfer/OrderInvoiceTransfer |
| MerchantOrder.orderInvoices | property | created | src/Generated/Shared/Transfer/MerchantOrderTransfer |
3) Set up the OMS process
The invoice of a merchant is generated when the merchant’s items enter the shipped by merchant state. Add the MarketplaceInvoice subprocess to the marketplace state machine and replace the direct transition from shipped by merchant to delivered with the transition through the new invoice generated state.
- Create the subprocess:
config/Zed/oms/MarketplaceSubprocess/MarketplaceInvoice01.xml
<?xml version="1.0"?>
<statemachine
xmlns="spryker:oms-01"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="spryker:oms-01 http://static.spryker.com/oms-01.xsd"
>
<process name="MarketplaceInvoice">
<states>
<state name="invoice generated" reserved="true" display="oms.state.shipped-by-merchant"/>
</states>
<transitions>
<transition happy="true">
<source>shipped by merchant</source>
<target>invoice generated</target>
<event>invoice-generate</event>
</transition>
<transition happy="true">
<source>invoice generated</source>
<target>delivered</target>
<event>deliver</event>
</transition>
</transitions>
<events>
<event name="invoice-generate" onEnter="true" command="MerchantInvoice/Generate"/>
</events>
</process>
</statemachine>
- Include the subprocess in the marketplace process and remove the direct
shipped by merchant→deliveredtransition:
config/Zed/oms/MarketplacePayment01.xml
<process name="MarketplacePayment01" main="true">
<subprocesses>
<process>MarketplaceInvoice</process>
</subprocesses>
</process>
<process name="MarketplaceInvoice" file="MarketplaceSubprocess/MarketplaceInvoice01.xml"/>
- Optional: to get one invoice per merchant from the manual
invoice-generateevent of a non-marketplace payment process too, switch its command toMerchantInvoice/Generate:
config/Zed/oms/DummySubprocess/DummyInvoice01.xml
<event name="invoice-generate" manual="true" command="MerchantInvoice/Generate"/>
- Register the OMS command:
| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
|---|---|---|---|
| GenerateMerchantOrderInvoicesCommandPlugin | Generates one invoice per merchant of the order items of the OMS event and one invoice for the items without a merchant. | Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\Oms |
src/Pyz/Zed/Oms/OmsDependencyProvider.php
<?php
namespace Pyz\Zed\Oms;
use Spryker\Zed\Kernel\Container;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\Oms\GenerateMerchantOrderInvoicesCommandPlugin;
use Spryker\Zed\Oms\Communication\Plugin\Oms\Command\CommandCollection;
use Spryker\Zed\Oms\OmsDependencyProvider as SprykerOmsDependencyProvider;
class OmsDependencyProvider extends SprykerOmsDependencyProvider
{
/**
* @param \Spryker\Zed\Kernel\Container $container
*
* @return \Spryker\Zed\Kernel\Container
*/
protected function extendCommandPlugins(Container $container): Container
{
$container->extend(static::COMMAND_PLUGINS, function (CommandCollection $commandCollection) {
$commandCollection->add(new GenerateMerchantOrderInvoicesCommandPlugin(), 'MerchantInvoice/Generate');
return $commandCollection;
});
return $container;
}
}
- Rebuild the OMS process cache:
console oms:process-cache:warm-up
Open Administration > OMS in the Back Office and make sure the MarketplacePayment01 process shows the invoice generated state between shipped by merchant and delivered.
4) Set up behavior
- Register the plugins:
| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
|---|---|---|---|
| MerchantReferenceOrderInvoiceBeforeSavePlugin | Stores the merchant reference shared by the items of the invoice; NULL for the items without a merchant. |
Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice | |
| MerchantOrderInvoiceExistenceCheckStrategyPlugin | Reports an existing invoice per order and merchant reference instead of per order. | Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice | |
| MerchantOrderInvoiceRenderOrderExpanderPlugin | Narrows the rendered order to the items, expenses, and totals of the invoice’s merchant and adds the merchant to the invoice; recalculates the totals for the invoice of the items without a merchant. | Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice | |
| MerchantOrderInvoicesExpanderPlugin | Adds OrderInvoice.merchant to every loaded invoice with a merchant reference. |
Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice | |
| OrderInvoicesMerchantOrderExpanderPlugin | Adds the invoices of the merchant to MerchantOrder.orderInvoices for the Merchant Portal order page. |
Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\MerchantSalesOrder | |
| MerchantSalesInvoiceMerchantAclRuleExpanderPlugin | Grants merchant users access to the merchant-sales-invoice bundle. |
Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\AclMerchantPortal | |
| MerchantOrderInvoiceSalesListBlockRendererPlugin | Renders the Documents block of the Back Office order page with a Merchant column; replaces OrderInvoiceSalesListBlockRendererPlugin. |
Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\Sales |
src/Pyz/Zed/SalesInvoice/SalesInvoiceDependencyProvider.php
<?php
namespace Pyz\Zed\SalesInvoice;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice\MerchantOrderInvoiceExistenceCheckStrategyPlugin;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice\MerchantOrderInvoiceRenderOrderExpanderPlugin;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice\MerchantOrderInvoicesExpanderPlugin;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\SalesInvoice\MerchantReferenceOrderInvoiceBeforeSavePlugin;
use Spryker\Zed\SalesInvoice\SalesInvoiceDependencyProvider as SprykerSalesInvoiceDependencyProvider;
class SalesInvoiceDependencyProvider extends SprykerSalesInvoiceDependencyProvider
{
/**
* @return array<\Spryker\Zed\SalesInvoiceExtension\Dependency\Plugin\OrderInvoiceBeforeSavePluginInterface>
*/
protected function getOrderInvoiceBeforeSavePlugins(): array
{
return [
new MerchantReferenceOrderInvoiceBeforeSavePlugin(),
];
}
/**
* @return array<\Spryker\Zed\SalesInvoiceExtension\Dependency\Plugin\OrderInvoicesExpanderPluginInterface>
*/
protected function getOrderInvoicesExpanderPlugins(): array
{
return [
new MerchantOrderInvoicesExpanderPlugin(),
];
}
/**
* @return array<\Spryker\Zed\SalesInvoiceExtension\Dependency\Plugin\OrderInvoiceExistenceCheckStrategyPluginInterface>
*/
protected function getOrderInvoiceExistenceCheckStrategyPlugins(): array
{
return [
new MerchantOrderInvoiceExistenceCheckStrategyPlugin(),
];
}
/**
* @return array<\Spryker\Zed\SalesInvoiceExtension\Dependency\Plugin\OrderInvoiceRenderOrderExpanderPluginInterface>
*/
protected function getOrderInvoiceRenderOrderExpanderPlugins(): array
{
return [
new MerchantOrderInvoiceRenderOrderExpanderPlugin(),
];
}
}
src/Pyz/Zed/MerchantSalesOrder/MerchantSalesOrderDependencyProvider.php
<?php
namespace Pyz\Zed\MerchantSalesOrder;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\MerchantSalesOrder\OrderInvoicesMerchantOrderExpanderPlugin;
use Spryker\Zed\MerchantSalesOrder\MerchantSalesOrderDependencyProvider as SprykerMerchantSalesOrderDependencyProvider;
class MerchantSalesOrderDependencyProvider extends SprykerMerchantSalesOrderDependencyProvider
{
/**
* @return array<\Spryker\Zed\MerchantSalesOrderExtension\Dependency\Plugin\MerchantOrderExpanderPluginInterface>
*/
protected function getMerchantOrderExpanderPlugins(): array
{
return [
new OrderInvoicesMerchantOrderExpanderPlugin(),
];
}
}
src/Pyz/Zed/AclMerchantPortal/AclMerchantPortalDependencyProvider.php
<?php
namespace Pyz\Zed\AclMerchantPortal;
use Spryker\Zed\AclMerchantPortal\AclMerchantPortalDependencyProvider as SprykerAclMerchantPortalDependencyProvider;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\AclMerchantPortal\MerchantSalesInvoiceMerchantAclRuleExpanderPlugin;
class AclMerchantPortalDependencyProvider extends SprykerAclMerchantPortalDependencyProvider
{
/**
* @return array<\Spryker\Zed\AclMerchantPortalExtension\Dependency\Plugin\MerchantAclRuleExpanderPluginInterface>
*/
protected function getMerchantAclRuleExpanderPlugins(): array
{
return [
new MerchantSalesInvoiceMerchantAclRuleExpanderPlugin(),
];
}
}
src/Pyz/Zed/Sales/SalesDependencyProvider.php
<?php
namespace Pyz\Zed\Sales;
use Spryker\Zed\MerchantSalesInvoice\Communication\Plugin\Sales\MerchantOrderInvoiceSalesListBlockRendererPlugin;
use Spryker\Zed\Sales\SalesDependencyProvider as SprykerSalesDependencyProvider;
class SalesDependencyProvider extends SprykerSalesDependencyProvider
{
/**
* @return array<\Spryker\Zed\SalesExtension\Dependency\Plugin\SalesDetailBlockRendererPluginInterface>
*/
protected function getSalesDetailBlockRendererPlugins(): array
{
return [
new MerchantOrderInvoiceSalesListBlockRendererPlugin(),
];
}
}
- Allow the Merchant Portal firewall to serve the invoice page. The default pattern of the Merchant Portal firewall covers only the
*-merchant-portal-guibundles; without themerchant-sales-invoicebundle in it, the Back Office firewall redirects the page to the Back Office login. Addmerchant-sales-invoiceas an alternative to theMERCHANT_PORTAL_ROUTE_PATTERNyour project already has; with the core default, the result is:
src/Pyz/Zed/SecurityMerchantPortalGui/SecurityMerchantPortalGuiConfig.php
<?php
namespace Pyz\Zed\SecurityMerchantPortalGui;
use Spryker\Zed\SecurityMerchantPortalGui\SecurityMerchantPortalGuiConfig as SprykerSecurityMerchantPortalGuiConfig;
class SecurityMerchantPortalGuiConfig extends SprykerSecurityMerchantPortalGuiConfig
{
/**
* @var string
*/
protected const MERCHANT_PORTAL_ROUTE_PATTERN = '^/((.+)-merchant-portal-gui|merchant-sales-invoice)/';
}
- Rebuild the caches and synchronize the ACL entities, so that existing merchant users get the new bundle rule:
console cache:class-resolver:build
console router:cache:warm-up:merchant-portal
console translator:generate-cache
console acl-entity:synchronize
console cache:empty-all
Place a marketplace order with items of two merchants, ship the items of one merchant in the Merchant Portal, and open the order in the Back Office. Make sure the Documents block lists one invoice for that merchant with the merchant’s name in the Merchant column. Ship the items of the second merchant and make sure a second invoice appears. Open the merchant order in the Merchant Portal and make sure the Documents card lists only the invoice of that merchant and its PDF link opens the invoice page with the merchant’s items and totals.
5) Print the merchant requisites on the invoice
The invoice of a merchant carries the merchant in invoice.merchant. Print the merchant’s requisites in your invoice template next to the operator’s data:
src/Pyz/Zed/SalesInvoice/Presentation/Invoice/invoice.twig
{% set merchant = invoice.merchant is defined ? invoice.merchant : null %}
{% set merchantProfile = merchant and merchant.merchantProfile ? merchant.merchantProfile : null %}
{% set merchantAddress = merchantProfile and merchantProfile.addressCollection is not empty ? merchantProfile.addressCollection | first : null %}
{% if merchant %}
<strong>{{ merchant.name }}</strong>
{% if merchant.registrationNumber %}
<div class="text-small">{{ 'order_invoice.invoice_template.merchant.registration_number' | trans }} {{ merchant.registrationNumber }}</div>
{% endif %}
{% if merchantAddress %}
<div class="text-small">
{{ [merchantAddress.address1, merchantAddress.address2, merchantAddress.address3] | filter(line => line) | join(' ') }}<br>
{{ merchantAddress.zipCode }} {{ merchantAddress.city }}<br>
{{ merchantAddress.countryName }}
</div>
{% endif %}
{% endif %}
Append the glossary key and import it:
data/import/common/common/glossary.csv
order_invoice.invoice_template.merchant.registration_number,Registration number:,en_US
order_invoice.invoice_template.merchant.registration_number,Registernummer:,de_DE
console data:import glossary
console twig:cache:warmer
Open the invoice of a merchant and make sure the document shows the merchant’s name, registration number, and address, and lists only that merchant’s items with the merchant order totals.
Install feature frontend
Follow the steps below to install the feature frontend.
Prerequisites
Install the required features:
| NAME | VERSION | INSTALLATION GUIDE |
|---|---|---|
| Order Invoice Documents | 202608.0 | Install the Order Invoice Documents feature |
| Marketplace Merchant | 202608.0 | Install the Marketplace Merchant feature |
1) Set up widgets
The Storefront row of an invoice shows its merchant through the SoldByMerchantWidget of the MerchantWidget module. Register the widget if it is not registered yet:
| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
|---|---|---|---|
| SoldByMerchantWidget | Shows the merchant of the invoice in the Documents section of the order details page. | SprykerShop\Yves\MerchantWidget\Widget |
src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php
<?php
namespace Pyz\Yves\ShopApplication;
use SprykerShop\Yves\MerchantWidget\Widget\SoldByMerchantWidget;
use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;
class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{
/**
* @return array<string>
*/
protected function getGlobalWidgets(): array
{
return [
SoldByMerchantWidget::class,
];
}
}
console cache:class-resolver:build
console twig:cache:warmer
Log in as the customer of a marketplace order with generated invoices and open the order in Customer Account > Order History. Make sure the Documents section lists one row per invoice and every row of a merchant’s invoice shows the merchant’s name.
Thank you!
For submitting the form