The problem

Managing shipments in any eCommerce app is something that may be tricky. There are many shipping providers, and each of them has probably its API format, which allows us to provide shipment data and book a courier. To make this process more straightforward and generic, we decided to create an abstract layer for Sylius platform-based applications, which allows you to write just a simple API call and configuration form for the specific shipping provider. The workflow is quite simple – configure proper data that are needed to export a shipment, like access key or pickup hour, book a courier for an order with one click and get shipping label file if any was received from the API. The implementation limits to write a shipping provider gateway configuration form, one event listener, and web services access layer.

Installation

Install package via composer:

$ composer require bitbag/shipping-export-plugin

Enable plugin in your AppKernel.php:

final class AppKernel extends Kernel
{
    /**
     * {@inheritdoc}
     */
    public function registerBundles()
    {
        return array_merge(parent::registerBundles(), [
            ...

            new BitBagSyliusShippingExportPluginBitBagSyliusShippingExportPlugin(),
        ]);
    }
}

Import config in app/config/config.yml:

imports:
    ...

    - { resource: "@BitBagSyliusShippingExportPlugin/Resources/config/config.yml" }

Import routing in you app/config/routing.yml:

bitbag_shipping_export_plugin:
    resource: "@BitBagSyliusShippingExportPlugin/Resources/config/routing.yml"
    prefix: /admin

Now when you go to the admin dashboard page, two new menu items should appear under “Sales” and “Configuration” named “Export shipping data” and “Shipping gateways”. Shipments to export will appear once someone will place an order with shipping method which has a configured shipping gateway.

Update your database either with migrations or $ bin/console doctrine:schema:update –force  command.

Shipping gateway configuration form

Example form type definition:

namespace AppBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentValidatorConstraintsNotBlank;

final class FrankMartinShippingGatewayType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('iban', TextType::class, [
                'label' => 'IBAN',
                'constraints' => [
                    new NotBlank([
                        'message' => 'IBAN number cannot be blank.',
                        'groups' => 'bitbag',
                    ])
                ],
            ])
            ->add('address', TextType::class, [
                'label' => 'Address',
                'constraints' => [
                    new NotBlank([
                        'message' => 'Address cannot be blank.',
                        'groups' => 'bitbag',
                    ])
                ],
            ])
        ;
    }
}

Service definition:

services:
    app.form.type.frank_martin_shipping_gateway:
        class: AppBundleFormTypeFrankMartinShippingGatewayType
        tags:
            - { name: bitbag.shipping_gateway_configuration_type, type: "frank_martin_shipping_gateway", label: "Transporter Gateway" }

Shipping export event listener

Basic event listener definition:

namespace AppBundleEventListener;

use BitBagShippingExportPluginEventExportShipmentEvent;

final class FrankMartinShippingExportEventListener
{
    /**
     * @param ExportShipmentEvent $event
     */
    public function exportShipment(ExportShipmentEvent $event)
    {
        $shippingExport = $event->getShippingExport();
        $shippingGateway = $shippingExport->getShippingGateway();

        if ($shippingGateway->getCode() !== 'frank_martin_shipping_gateway') {
            return;
        }

        if (false) {
            $event->addErrorFlash(); // Add an error notification

            return;
        }

        $event->addSuccessFlash(); // Add success notification
        $event->saveShippingLabel("Some label content received from external API", 'pdf'); // Save label
        $event->exportShipment(); // Mark shipment as "Exported"
    }
}

Service definition:

services:
    ...
    app.event_listener.frank_martin_shipping_export:
    class: AppBundleEventListenerFrankMartinShippingExportEventListener
    tags:
        - { name: kernel.event_listener, event: 'bitbag.export_shipment', method: exportShipment }

Plugin parameters:

parameters:
    bitbag.shipping_gateway.validation_groups: ['bitbag']
    bitbag.shipping_labels_path: '%kernel.root_dir%/../shipping_labels'

Final thoughts

You can read more use-cases of this plugin in feature files which can be found in this plugin directory. Contributors are always welcomed! You can check out our GitHub profile where more Sylius plugins you may love can be found.