Integrate the Vertex app
Edit on GitHubAfter you have integrated the ACP connector module for tax calculation, you can integrate the Vertex app.
Spryker doesn’t have the same data model as Vertex, which is necessary for accurate tax calculations. Therefore, the integration requires project developers to add some missing information to the Quote object before sending a calculation request.
The following diagram shows the data flow of the tax calculation request from the Spryker Cart to the Vertex API.
To integrate Vertex, follow these steps.
1. Configure Vertex-specific metadata transfers
Define specific Vertex Tax metadata transfers and extend other transfers with them:
<?xml version="1.0"?>
<transfers xmlns="spryker:transfer-01"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="spryker:transfer-01
http://static.spryker.com/transfer-01.xsd">
<!-- Calculable Object is extended with SaleTaxMetadata -->
<transfer name="CalculableObject">
<property name="taxMetadata" type="SaleTaxMetadata" strict="true"/>
</transfer>
<!-- Order is extended with SaleTaxMetadata -->
<transfer name="Order">
<property name="taxMetadata" type="SaleTaxMetadata" strict="true"/>
</transfer>
<!-- Expense is extended with ItemTaxMetadata -->
<transfer name="Expense">
<property name="taxMetadata" type="ItemTaxMetadata" strict="true"/>
</transfer>
<!-- Item is extended with ItemTaxMetadata -->
<transfer name="Item">
<property name="taxMetadata" type="ItemTaxMetadata" strict="true"/>
</transfer>
<!-- Product Option is extended with ItemTaxMetadata -->
<transfer name="ProductOption">
<property name="taxMetadata" type="ItemTaxMetadata" strict="true"/>
</transfer>
<!-- Sales Tax Metadata. It contains Vertex tax metadata which is related to Order or Quote in general. -->
<transfer name="SaleTaxMetadata" strict="true">
<property name="seller" type="array" associative="true" singular="_"/>
<property name="customer" type="array" associative="true" singular="_"/>
</transfer>
<!-- Items Tax Metadata. It contains Vertex tax metadata which is related to Item.-->
<transfer name="ItemTaxMetadata" strict="true">
<property name="product" type="array" associative="true" singular="_"/>
<property name="flexibleFields" type="array" associative="true" singular="_"/>
</transfer>
</transfers>
SaleTaxMetadata
and ItemTaxMetadata
are designed to be equal to the Vertex Tax Calculation API request body. You can extend them as you need, following to the Vertex API structure.
-
SaleTaxMetadata
equals the Invoicing/Quotation request payload, excluding LineItems. -
ItemTaxMetadata
equals the Line Item API payload.
2. Implement Vertex-specific metadata extender plugins
There are several types of expander plugins you have to introduce. As a starting point, you can take examples provided by Spryker in the tax-app-vertex module. The plugins in this module are for development purposes. The data in the TaxMetaData fields has to be collected from the project database or other sources such as external ERP.
Configure the Customer Class Code Expander plugins
The following code sample shows how to introduce the following expander plugin:
Pyz/Zed/{YourDesiredModule}/Communication/Plugin/Order/OrderCustomerWithVertexCodeExpanderPlugin.php
<?php
namespace Pyz\Zed\{YourDesiredModule}\Communication\Plugin\Order;
use Generated\Shared\Transfer\OrderTransfer;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\TaxAppExtension\Dependency\Plugin\OrderTaxAppExpanderPluginInterface;
class OrderCustomerWithVertexCodeExpanderPlugin extends AbstractPlugin implements OrderTaxAppExpanderPluginInterface
{
/**
* @param \Generated\Shared\Transfer\OrderTransfer $orderTransfer
*
* @return \Generated\Shared\Transfer\OrderTransfer
*/
public function expand(OrderTransfer $orderTransfer): OrderTransfer
{
$orderTransfer->getTaxMetadata()->setCustomer(['customerCode' => ['classCode' => 'vertex-customer-code']]);
return $orderTransfer;
}
}
The following code sample shows how to introduce the following expander plugin:
Pyz/Zed/{YourDesiredModule}/Communication/Plugin/Quote/CalculableObjectCustomerWithVertexCodeExpanderPlugin.php
<?php
namespace Spryker\Zed\{YourDesiredModule}\Communication\Plugin\Quote;
use Generated\Shared\Transfer\CalculableObjectTransfer;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\TaxAppExtension\Dependency\Plugin\CalculableObjectTaxAppExpanderPluginInterface;
class CalculableObjectCustomerWithVertexCodeExpanderPlugin extends AbstractPlugin implements CalculableObjectTaxAppExpanderPluginInterface
{
/**
* @param \Generated\Shared\Transfer\CalculableObjectTransfer $calculableObjectTransfer
*
* @return \Generated\Shared\Transfer\CalculableObjectTransfer
*/
public function expand(CalculableObjectTransfer $calculableObjectTransfer): CalculableObjectTransfer
{
$calculableObjectTransfer->getTaxMetadata()->setCustomer(['customerCode' => ['classCode' => 'vertex-customer-code']]);
return $calculableObjectTransfer;
}
}
Configure the Customer Exemption Certificate Expander plugins
- Configure the Customer Exemption Certificate Expander plugin for order:
Pyz/Zed/{YourDesiredModule}/Communication/Plugin/Order/OrderCustomerWithVertexExemptionCertificateExpanderPlugin.php
- Configure the Customer Exemption Certificate Expander plugin for quote:
Pyz/Zed/{YourDesiredModule}/Communication/Plugin/Quote/CalculableObjectCustomerWithVertexExemptionCertificateExpanderPlugin.php
The following code sample shows how to implement the previously mentioned plugins:
// ...
class OrderCustomerWithVertexExemptionCertificateExpanderPlugin extends AbstractPlugin implements CalculableObjectTaxAppExpanderPluginInterface
{
/**
* @param \Generated\Shared\Transfer\CalculableObjectTransfer $calculableObjectTransfer
*
* @return \Generated\Shared\Transfer\CalculableObjectTransfer
*/
public function expand(CalculableObjectTransfer $calculableObjectTransfer): CalculableObjectTransfer
{
$calculableObjectTransfer->getTaxMetadata()->setCustomer(['exemptionCertificate' => ['exemptionCertificateNumber' => 'vertex-exemption-certificate-number']]);
return $calculableObjectTransfer;
}
}
Configure the Product Class Code Expander plugins
Configure the Product Class Code Expander plugin for order items:
Pyz/Zed/{YourDesiredModule}/Communication/Plugin/Order/OrderItemVertexProductClassCodeExpanderPlugin.php
Configure the Product Class Code Expander plugin for quote items:
Pyz/Zed/{YourDesiredModule}/Communication/Plugin/Quote/CalculableObjectItemWithVertexProductClassCodeExpanderPlugin.php
The contents of both plugins are similar:
// ...
class ItemWithVertexClassCodeExpanderPlugin extends AbstractPlugin implements CalculableObjectTaxAppExpanderPluginInterface
{
/**
* @param \Generated\Shared\Transfer\CalculableObjectTransfer $calculableObjectTransfer
*
* @return \Generated\Shared\Transfer\CalculableObjectTransfer
*/
public function expand(CalculableObjectTransfer $calculableObjectTransfer): CalculableObjectTransfer
{
foreach ($calculableObjectTransfer->getItems() as $itemTransfer) {
$itemTransfer->getTaxMetadata()->setProduct(['productClass' => 'vertex-product-class-code']);
}
return $calculableObjectTransfer;
}
}
You must use the same Product Class Code extension for all product options and other order expenses. From Vertex’s perspective, it considers each of them as a separate item for tax calculation. For guidance on where to place them, refer to the definition of transfers in Configure Vertex-specific Metadata Transfers.
Configure the flexible fields extension
Configure the flexible files extension as in the following example:
class ItemWithFlexibleFieldsExpanderPlugin extends AbstractPlugin implements CalculableObjectTaxAppExpanderPluginInterface
{
// ...
/**
* @param \CalculableObjectTransfer $calculableObjectTransfer
*
* @return \CalculableObjectTransfer
*/
public function expand(CalculableObjectTransfer $calculableObjectTransfer): CalculableObjectTransfer
{
foreach ($calculableObjectTransfer->getItems() as $itemTransfer) {
$itemTransfer
->getTaxMetadata()
->setFlexibleFields(
[
'flexibleCodeFields' => [
[
'fieldId' => 1,
'value' => 'vertex-flexible-code-value',
],
],
],
);
}
return $calculableObjectTransfer;
}
}
3. Configure the Tax App dependency provider
After the Tax App dependency provider configuration, the plugin stack should look similar to this one:
namespace Pyz\Zed\TaxApp;
// The following plugins are for Marketplace only.
use Spryker\Zed\MerchantProfile\Communication\Plugin\TaxApp\MerchantProfileAddressCalculableObjectTaxAppExpanderPlugin;
use Spryker\Zed\MerchantProfile\Communication\Plugin\TaxApp\MerchantProfileAddressOrderTaxAppExpanderPlugin;
use Spryker\Zed\ProductOfferAvailability\Communication\Plugin\TaxApp\ProductOfferAvailabilityCalculableObjectTaxAppExpanderPlugin;
use Spryker\Zed\ProductOfferAvailability\Communication\Plugin\TaxApp\ProductOfferAvailabilityOrderTaxAppExpanderPlugin;
class TaxAppDependencyProvider extends SprykerTaxAppDependencyProvider
{
/**
* @return array<\Spryker\Zed\TaxAppExtension\Dependency\Plugin\CalculableObjectTaxAppExpanderPluginInterface>
*/
protected function getCalculableObjectTaxAppExpanderPlugins(): array
{
return [
# This plugin stack is responsible for expansion of CalculableObjectTransfer based on present fields. Add your custom implemented expander plugins here following the example in `spryker/tax-app-vertex` module.
// The following plugins are for Marketplace only.
# This plugin is expanding CalculableObjectTransfer object with merchant address information.
new MerchantProfileAddressCalculableObjectTaxAppExpanderPlugin(),
# This plugin is expanding CalculableObjectTransfer object with product offer availability information.
new ProductOfferAvailabilityCalculableObjectTaxAppExpanderPlugin(),
];
}
/**
* @return array<\Spryker\Zed\TaxAppExtension\Dependency\Plugin\OrderTaxAppExpanderPluginInterface>
*/
protected function getOrderTaxAppExpanderPlugins(): array
{
return [
# This plugin stack is responsible for expansion of OrderTransfer based on present fields. Add your custom implemented expander plugins here following the example in `spryker/tax-app-vertex` module.
// The following plugins are for Marketplace only.
# This plugin is expanding OrderTransfer object with merchant address information.
new MerchantProfileAddressOrderTaxAppExpanderPlugin(),
# This plugin is expanding OrderTransfer object with product offer availability information.
new ProductOfferAvailabilityOrderTaxAppExpanderPlugin(),
];
}
}
4. Configure Product Offer Stock dependency provider (Marketplace only)
After you have configured the Product Offer Stock dependency provider for Marketplace, the plugin stack should look similar to this one:
namespace Pyz\Zed\ProductOfferStock;
use Spryker\Zed\ProductOfferStock\ProductOfferStockDependencyProvider as SprykerProductOfferStockDependencyProvider;
use Spryker\Zed\StockAddress\Communication\Plugin\Stock\StockAddressStockTransferProductOfferStockExpanderPlugin;
class ProductOfferStockDependencyProvider extends SprykerProductOfferStockDependencyProvider
{
/**
* @return array<\Spryker\Zed\ProductOfferStockExtension\Dependency\Plugin\StockTransferProductOfferStockExpanderPluginInterface>
*/
protected function getStockTransferExpanderPluginCollection(): array
{
return [
# This plugin is providing warehouse address to StockTransfer objects which is used during tax calculation.
new StockAddressStockTransferProductOfferStockExpanderPlugin(),
];
}
}
5. Add translations
Append glossary according to your configuration:
src/data/import/glossary.csv
cart.total.tax_total.calculated_at_checkout,an der Kasse berechnet,de_DE
cart.total.tax_total.calculated_at_checkout,calculated at checkout,en_US
To import data, run the following command:
console data:import glossary
Reference: Quote/Order object to Vertex API mapping
While developing custom plugins for deeper integration with your projects, you may need to know how Spryker data is mapped to Vertex API requests.
The following table reflects the mapping of the Spryker Quote/Order transfer object to the Vertex API request format.
QuoteTransfer/OrderTransfer object properties | Vertex API field | Comment |
---|---|---|
Current date (Y-m-d) | documentDate | |
QuoteTransfer.uuid / OrderTransfer.orderReference / new Uuid4 (if quote.uuid is not present) | documentNumber | |
QuoteTransfer.uuid / OrderTransfer.orderReference / new Uuid4 (if quote.uuid is not present) | transactionId | |
- | transactionType | Always SALE . |
- | saleMessageType | Depends on the type of operation, INVOICE or QUOTATION . |
taxMetadata | Mapped over the final request 1:1 | Metadata is supposed to follow the structure of the Vertex API request. |
taxMetadata.seller.company | seller.company | Required by Vertex from the legal point of view. |
items[].sku | lineItems[].lineItemId; lineItems[].product.value; lineItems[].vendorSku | lineItems[].lineItemId can be changed if there are multiple items with the same SKU in the request. |
items[].shipment.shippingAddress | lineItems[].customer.destination | |
billingAddress | lineItems[].customer.administrativeDestination | |
items[].merchantStockAddresses | lineItems[].seller.physicalOrigin | Multiple addresses are mapped to multiple items in the Vertex PBC and Vertex API lineItems[]. |
items[].merchantProfileAddress | lineItems[].seller.administrativeOrigin | |
items[].unitDiscountAmountFullAggregation | lineItems[].discount.discountValue | Prices are converted from the Spryker’s cent-based format to the Vertex decimal format. |
- | lineItems[].discount.discountType | Always DiscountAmount . Spryker stores discount based on amount, so there is no need for percentage-based discounts. |
items[].unitPrice (either GROSS or NET depending on the currently selected mode) | lineItems[].unitPrice | Prices are converted from Spryker’s cent-based format to Vertex decimal format. |
items[].merchantStockAddresses.quantityToShip | lineItems[].quantity.value | If quantityToShip is less than quantity requested in cart - in this case this item will be mapped to multiple items in the Vertex API. |
- | lineItems[].quantity.unitOfMeasure | Always EA (“each”). Other units of measure are not supported yet. |
items[].taxMetadata | Mapped over specific lineItem 1:1 | Metadata is supposed to follow the structure of the Vertex API request. For lineItems it is mapped over each corresponding item based on lineItemId. |
items[].taxMetadata.seller.company | lineItems[].seller.company | Required by Vertex from the legal point of view. |
expenses (only for expenses with type SHIPMENT_EXPENSE_TYPE ) |
lineItems | Shipments are treated just like products in Vertex - it’s all a line item. |
expenses.hash | lineItems[].lineItemId | |
expenses.shipment.shipmentAddress | lineItems[].customer.destination | |
billingAddress | lineItems[].customer.administrativeDestination | |
expenses.sumPrice (either GROSS or NET depending on currently selected mode) | lineItems[].extendedPrice | |
expenses.sumDiscountAmountAggregation | lineItems[].discount.discountValue | Prices are converted from the Spryker’s cent-based format to the Vertex decimal format. |
- | lineItems[].discount.discountType | Always DiscountAmount . Spryker stores discount based on amount, so there is no need to use percentage-based discounts. |
Location mapping
Spryker | Vertex | Comment |
---|---|---|
address1 | streetAddress1 | |
address2 | streetAddress2 | Should be either not empty or null |
city | city | |
state | mainDivision | Should be either not empty or null |
zipCode | postalCode | |
country.iso2Code | country |
Next step
Thank you!
For submitting the form