Important
You are browsing the documentation for version 3.1 of OroCommerce, OroCRM and OroPlatform, which is no longer maintained. Read version 5.1 (the latest LTS version) of the Oro documentation to get up-to-date information.
See our Release Process documentation for more information on the currently supported and upcoming releases.
Shipping Methods¶
This topic describes how to add a custom shipping method to your OroCommerce-based store.
It is recommended to manage shipping methods through integrations. Therefore, to create a new shipping method:
Implement an integration for the shipping method
Implement the shipping method itself
Usually, a shipping method has several services to provide a flexible choice of price and delivery time. As an example, we will implement the “Fast Shipping” method — a simple method that requires just the minimum set of options to operate. It will have two services (types): “With present” and “Without present”. Thus, at the end of the topic, you will have the understanding of what steps are necessary to add a workable shipment method and the basic template that you can further extend when the need arises.
Create a Bundle¶
First, create and enable the FastShippingBundle bundle for your shipping method as described in the How to create a new bundle topic:
In the /src/ACME/Bundle/FastShippingBundle/ directory of your application, create class ACMEFastShippingBundle.php:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle;
4
5use ACME\Bundle\FastShippingBundle\DependencyInjection\FastShippingExtension;
6use Symfony\Component\HttpKernel\Bundle\Bundle;
7
8/**
9 * The FastShipping bundle class.
10 */
11class ACMEFastShippingBundle extends Bundle
12{
13 /**
14 * {@inheritDoc}
15 */
16 public function getContainerExtension()
17 {
18 if (!$this->extension) {
19 $this->extension = new FastShippingExtension();
20 }
21
22 return $this->extension;
23 }
24}
Tip
The body of your class can be empty if you use regular case in the name of your organization (i.e. Acme or ACME in our example). getExtension()
is necessary when you use uppercase, as Symfony treats uppercase letters in the organization prefix as separate words when creating aliases.
To enable the bundle, create Resources/config/oro/bundles.yml in the same directory, with the following content:
1bundles:
2 # Set a priority higher than the priority of the ShippingBundle (i.e. 200) to ensure that all dependencies
3 # from the ShippingBundle are loaded before the dependent methods of your bundle.
4 - { name: ACME\Bundle\FastShippingBundle\ACMEFastShippingBundle, priority: 200 }
Hint
To fully enable a bundle, you need to regenerate the application cache. However, to save time, you can do it after creation of the shipping integration.
Tip
All the files and subdirectories mentioned in the following sections are to be added to the /src/ACME/Bundle/FastShippingBundle/ directory of your application (referred to as <bundle_root>).
Create a Shipping Integration¶
Create an Entity to Store the Shipping Method Settings¶
Define an entity that to store the configuration settings of the shipping method in the database. To do this, create <bundle_root>/Entity/FastShippingSettings.php:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Entity;
4
5use Doctrine\Common\Collections\ArrayCollection;
6use Doctrine\Common\Collections\Collection;
7use Doctrine\ORM\Mapping as ORM;
8use Oro\Bundle\IntegrationBundle\Entity\Transport;
9use Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue;
10use Symfony\Component\HttpFoundation\ParameterBag;
11
12/**
13 * Entity with settings for Fast Shipping integration
14 *
15 * @ORM\Entity
16 */
17class FastShippingSettings extends Transport
18{
19 /**
20 * @var Collection|LocalizedFallbackValue[]
21 *
22 * @ORM\ManyToMany(
23 * targetEntity="Oro\Bundle\LocaleBundle\Entity\LocalizedFallbackValue",
24 * cascade={"ALL"},
25 * orphanRemoval=true
26 * )
27 * @ORM\JoinTable(
28 * name="acme_fast_ship_transport_label",
29 * joinColumns={
30 * @ORM\JoinColumn(name="transport_id", referencedColumnName="id", onDelete="CASCADE")
31 * },
32 * inverseJoinColumns={
33 * @ORM\JoinColumn(name="localized_value_id", referencedColumnName="id", onDelete="CASCADE", unique=true)
34 * }
35 * )
36 */
37 private $labels;
38
39 /**
40 * @var ParameterBag
41 */
42 private $settings;
43
44 public function __construct()
45 {
46 $this->labels = new ArrayCollection();
47 }
48
49 /**
50 * @return Collection|LocalizedFallbackValue[]
51 */
52 public function getLabels(): Collection
53 {
54 return $this->labels;
55 }
56
57 /**
58 * @param LocalizedFallbackValue $label
59 *
60 * @return FastShippingSettings
61 */
62 public function addLabel(LocalizedFallbackValue $label): FastShippingSettings
63 {
64 if (!$this->labels->contains($label)) {
65 $this->labels->add($label);
66 }
67
68 return $this;
69 }
70
71 /**
72 * @param LocalizedFallbackValue $label
73 *
74 * @return FastShippingSettings
75 */
76 public function removeLabel(LocalizedFallbackValue $label): FastShippingSettings
77 {
78 if ($this->labels->contains($label)) {
79 $this->labels->removeElement($label);
80 }
81
82 return $this;
83 }
84
85 /**
86 * @return ParameterBag
87 */
88 public function getSettingsBag(): ParameterBag
89 {
90 if (null === $this->settings) {
91 $this->settings = new ParameterBag([]);
92 }
93
94 return $this->settings;
95 }
96}
As you can see from the code above, the only necessary parameter defined for the FastShipping shipping method is the label
parameter.
Important
When naming DB columns, make sure that the name does not exceed 31 symbols. Pay attention to the acme_fast_ship_transport_label
name in the following extract:
1 * @ORM\JoinTable(
2 * name="acme_fast_ship_transport_label",
3 * joinColumns={
4 * @ORM\JoinColumn(name="transport_id", referencedColumnName="id", onDelete="CASCADE")
5 * },
6 * inverseJoinColumns={
7 * @ORM\JoinColumn(name="localized_value_id", referencedColumnName="id", onDelete="CASCADE", unique=true)
8 * }
9 * )
Create a User Interface Form for the Shipping Method Integration¶
When you add an integration via the user interface of the back-office, a form that contains the integration settings appears. In this step, implement the form. To do this, create <bundle_root>/Form/Type/FastShippingTransportSettingsType.php:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Form\Type;
4
5use ACME\Bundle\FastShippingBundle\Entity\FastShippingSettings;
6use Oro\Bundle\LocaleBundle\Form\Type\LocalizedFallbackValueCollectionType;
7use Symfony\Component\Form\AbstractType;
8use Symfony\Component\Form\FormBuilderInterface;
9use Symfony\Component\OptionsResolver\OptionsResolver;
10use Symfony\Component\Validator\Constraints\NotBlank;
11
12/**
13 * Form type for Fast Shipping integration settings
14 */
15class FastShippingTransportSettingsType extends AbstractType
16{
17 private const BLOCK_PREFIX = 'acme_fast_shipping_settings';
18
19 /**
20 * {@inheritDoc}
21 */
22 public function buildForm(FormBuilderInterface $builder, array $options)
23 {
24 $builder
25 ->add(
26 'labels',
27 LocalizedFallbackValueCollectionType::class,
28 [
29 'label' => 'acme.fast_shipping.settings.labels.label',
30 'required' => true,
31 'entry_options' => ['constraints' => [new NotBlank()]],
32 ]
33 );
34 }
35
36 /**
37 * {@inheritDoc}
38 */
39 public function configureOptions(OptionsResolver $resolver)
40 {
41 $resolver->setDefaults([
42 'data_class' => FastShippingSettings::class
43 ]);
44 }
45
46 /**
47 * {@inheritDoc}
48 */
49 public function getBlockPrefix()
50 {
51 return self::BLOCK_PREFIX;
52 }
53}
Add Translations for the Form Texts¶
To present the information on the user interface in a user-friendly way, add translations for the shipping method settings’ names. To do this, create <bundle_root>/Resources/translations/messages.en.yml:
1acme:
2 fast_shipping:
3 settings:
4 labels:
5 label: 'Label'
This defines the name of the field that contains the label.
Create the Integration Channel Type¶
When you select the type of the integration on the user interface, you will see the integration name and the icon that you define in this step.
To implement a channel type, create <bundle_root>/Integration/FastShippingChannelType.php:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Integration;
4
5use Oro\Bundle\IntegrationBundle\Provider\ChannelInterface;
6use Oro\Bundle\IntegrationBundle\Provider\IconAwareIntegrationInterface;
7
8/**
9 * Integration channel type for Fast Shipping integration
10 */
11class FastShippingChannelType implements ChannelInterface, IconAwareIntegrationInterface
12{
13 /**
14 * {@inheritDoc}
15 */
16 public function getLabel(): string
17 {
18 return 'acme.fast_shipping.channel_type.label';
19 }
20
21 /**
22 * {@inheritDoc}
23 */
24 public function getIcon(): string
25 {
26 return 'bundles/acmefastshipping/img/fast-shipping-logo.png';
27 }
28}
Add an Icon for the Integration¶
To add an icon:
Save the file to the <bundle_root>/Resources/public/img directory.
Install assets:
1bin/console assets:install --symlink
To make sure that the icon is accessible for the web interface, check if it appears (as a copy or a symlink depending on the settings selected during the application installation) in the /public/bundles/acmefastshipping/img directory of your application.
Create the Integration Transport¶
Transport is generally responsible for how the data is obtained from the external system. While the Fast Shipping method does not interact with external systems, you still need to define transport and implement all methods of the TransportInterface for the integration to work properly. To add transport, create <bundle_root>/Integration/FastShippingTransport.php:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Integration;
4
5use ACME\Bundle\FastShippingBundle\Entity\FastShippingSettings;
6use ACME\Bundle\FastShippingBundle\Form\Type\FastShippingTransportSettingsType;
7use Oro\Bundle\IntegrationBundle\Entity\Transport;
8use Oro\Bundle\IntegrationBundle\Provider\TransportInterface;
9use Symfony\Component\HttpFoundation\ParameterBag;
10
11/**
12 * Transport for Fast Shipping integration
13 */
14class FastShippingTransport implements TransportInterface
15{
16 /** @var ParameterBag */
17 protected $settings;
18
19 /**
20 * @param Transport $transportEntity
21 */
22 public function init(Transport $transportEntity)
23 {
24 $this->settings = $transportEntity->getSettingsBag();
25 }
26
27 /**
28 * {@inheritDoc}
29 */
30 public function getSettingsFormType(): string
31 {
32 return FastShippingTransportSettingsType::class;
33 }
34
35 /**
36 * {@inheritDoc}
37 */
38 public function getSettingsEntityFQCN(): string
39 {
40 return FastShippingSettings::class;
41 }
42
43 /**
44 * {@inheritDoc}
45 */
46 public function getLabel(): string
47 {
48 return 'acme.fast_shipping.transport.label';
49 }
50}
Create a Configuration File for the Service Container¶
To start using a service container for your bundle, first create the bundle configuration file <bundle_root>/Resources/config/services.yml.
Add the Channel Type and Transport to the Services Container¶
To register the channel type and transport, append the following key-values to <bundle_root>/Resources/config/services.yml:
1parameters:
2 acme_fast_shipping.integration.type: 'fast_shipping'
3
4services:
5 acme_fast_shipping.integration.channel:
6 class: 'ACME\Bundle\FastShippingBundle\Integration\FastShippingChannelType'
7 public: false
8 tags:
9 - { name: oro_integration.channel, type: '%acme_fast_shipping.integration.type%' }
10
11 acme_fast_shipping.integration.transport:
12 class: 'ACME\Bundle\FastShippingBundle\Integration\FastShippingTransport'
13 public: false
14 tags:
15 - { name: oro_integration.transport, type: '%acme_fast_shipping.integration.type%', channel_type: '%acme_fast_shipping.integration.type%' }
Set up Services with DependencyInjection¶
To set up services, load your configuration file (services.yml) using the DependencyInjection component. For this, create <bundle_root>/DependencyInjection/FastShippingExtension.php with the following content:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\DependencyInjection;
4
5use Symfony\Component\Config\FileLocator;
6use Symfony\Component\DependencyInjection\ContainerBuilder;
7use Symfony\Component\DependencyInjection\Loader;
8use Symfony\Component\HttpKernel\DependencyInjection\Extension;
9
10class FastShippingExtension extends Extension
11{
12 /**
13 * @param array $configs
14 * @param ContainerBuilder $container
15 *
16 * @throws \Exception
17 */
18 public function load(array $configs, ContainerBuilder $container)
19 {
20 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
21 $loader->load('services.yml');
22 }
23
24 /**
25 * @return string
26 */
27 public function getAlias()
28 {
29 return 'acme_fast_shipping';
30 }
31}
Add Translations for the Channel Type and Transport¶
The channel type and, in general, transport labels also appear on the user interface (you will not see the transport label for Fast Shipping). Provide translations for them by appending the <bundle_root>/Resources/translations/messages.en.yml. Now, the messages.en.yml content must look as follows:
1acme:
2 fast_shipping:
3 settings:
4 labels:
5 label: 'Label'
6 transport:
7 label: 'Fast Shipping'
8 channel_type:
9 label: 'Fast Shipping'
Add an Installer¶
An installer ensures that upon the application installation, the database will contain the entity that you defined within your bundle.
Follow the instructions provided in the How to generate an installer topic to apply the changes without migration and generate an installer file based on the current schema of the DB.
Note
If you have not performed the steps mentioned in How to generate an installer, because you already have the installer file, then make sure to run the php bin/console oro:migration:load --force
command to apply the changes from the file.
After you complete the process, you will have the <bundle_root>/Migrations/Schema/FastShippingBundleInstaller.php class with the following content:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Migrations\Schema;
4
5use Doctrine\DBAL\Schema\Schema;
6use Oro\Bundle\MigrationBundle\Migration\Installation;
7use Oro\Bundle\MigrationBundle\Migration\QueryBag;
8
9/**
10 * @SuppressWarnings(PHPMD.TooManyMethods)
11 * @SuppressWarnings(PHPMD.ExcessiveClassLength)
12 */
13class FastShippingBundleInstaller implements Installation
14{
15 /**
16 * {@inheritDoc}
17 */
18 public function getMigrationVersion()
19 {
20 return 'v1_0';
21 }
22
23 /**
24 * {@inheritDoc}
25 */
26 public function up(Schema $schema, QueryBag $queries)
27 {
28 /** Tables generation **/
29 $this->createAcmeFastShipTransportLabelTable($schema);
30
31 /** Foreign keys generation **/
32 $this->addAcmeFastShipTransportLabelForeignKeys($schema);
33 }
34
35 /**
36 * Create acme_fast_ship_transport_label table
37 *
38 * @param Schema $schema
39 */
40 protected function createAcmeFastShipTransportLabelTable(Schema $schema)
41 {
42 $table = $schema->createTable('acme_fast_ship_transport_label');
43 $table->addColumn('transport_id', 'integer', []);
44 $table->addColumn('localized_value_id', 'integer', []);
45 $table->setPrimaryKey(['transport_id', 'localized_value_id']);
46 $table->addUniqueIndex(['localized_value_id'], 'UNIQ_15E6E6F3EB576E89');
47 $table->addIndex(['transport_id'], 'IDX_15E6E6F39909C13F', []);
48 }
49
50 /**
51 * Add acme_fast_ship_transport_label foreign keys.
52 *
53 * @param Schema $schema
54 */
55 protected function addAcmeFastShipTransportLabelForeignKeys(Schema $schema)
56 {
57 $table = $schema->getTable('acme_fast_ship_transport_label');
58 $table->addForeignKeyConstraint(
59 $schema->getTable('oro_integration_transport'),
60 ['transport_id'],
61 ['id'],
62 ['onDelete' => 'CASCADE', 'onUpdate' => null]
63 );
64 $table->addForeignKeyConstraint(
65 $schema->getTable('oro_fallback_localization_val'),
66 ['localized_value_id'],
67 ['id'],
68 ['onDelete' => 'CASCADE', 'onUpdate' => null]
69 );
70 }
71}
Check That the Integration is Created Successfully¶
Clear the application cache:
1bin/console cache:clear
Note
If you are working in a production environment, you have to use the
--env=prod
parameter with the command.Open the user interface and check that the changes have applied and you can add an integration of the Fast Shipping type. Note that at this point you are not yet able to add this shipping method to a shipping rule.
Implement a Shipping Method¶
Now implement the shipping method itself using the following steps:
Implement the Main Method¶
To implement the main method, create the <bundle_root>/Method/FastShippingMethod.php class that implements two standard interfaces \Oro\Bundle\ShippingBundle\Method\ShippingMethodInterface
and \Oro\Bundle\ShippingBundle\Method\ShippingMethodIconAwareInterface
:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Method;
4
5use Oro\Bundle\ShippingBundle\Method\ShippingMethodIconAwareInterface;
6use Oro\Bundle\ShippingBundle\Method\ShippingMethodInterface;
7use Oro\Bundle\ShippingBundle\Method\ShippingMethodTypeInterface;
8use Symfony\Component\Form\Extension\Core\Type\HiddenType;
9
10/**
11 * Fast Shipping method class
12 */
13class FastShippingMethod implements ShippingMethodInterface, ShippingMethodIconAwareInterface
14{
15 /**
16 * @var array
17 */
18 private $types;
19
20 /**
21 * @var string
22 */
23 private $label;
24
25 /**
26 * @var string|null
27 */
28 private $icon;
29
30 /**
31 * @var string
32 */
33 private $identifier;
34
35 /**
36 * @var bool
37 */
38 private $enabled;
39
40 /**
41 * @param string $identifier
42 * @param string $label
43 * @param string|null $icon
44 * @param bool $enabled
45 * @param array $types
46 */
47 public function __construct(string $identifier, string $label, ?string $icon, bool $enabled, array $types)
48 {
49 $this->identifier = $identifier;
50 $this->label = $label;
51 $this->icon = $icon;
52 $this->enabled = $enabled;
53 $this->types = $types;
54 }
55
56 /**
57 * {@inheritDoc}
58 */
59 public function getIdentifier(): string
60 {
61 return $this->identifier;
62 }
63
64 /**
65 * {@inheritDoc}
66 */
67 public function isGrouped(): bool
68 {
69 return true;
70 }
71
72 /**
73 * {@inheritDoc}
74 */
75 public function isEnabled(): bool
76 {
77 return $this->enabled;
78 }
79
80 /**
81 * {@inheritDoc}
82 */
83 public function getLabel(): string
84 {
85 return $this->label;
86 }
87
88 /**
89 * {@inheritDoc}
90 */
91 public function getIcon(): ?string
92 {
93 return $this->icon;
94 }
95
96 /**
97 * {@inheritDoc}
98 */
99 public function getTypes(): array
100 {
101 return $this->types;
102 }
103
104 /**
105 * {@inheritDoc}
106 */
107 public function getType($type): ?ShippingMethodTypeInterface
108 {
109 if (array_key_exists($type, $this->types)) {
110 return $this->types[$type];
111 }
112
113 return null;
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 public function getOptionsConfigurationFormType(): string
120 {
121 return HiddenType::class;
122 }
123
124 /**
125 * {@inheritDoc}
126 */
127 public function getSortOrder(): int
128 {
129 return 150;
130 }
131}
The methods are the following:
getIdentifier
— Provides a unique identifier of the shipping method in the scope of the Oro application.getLabel
— Returns the shipping method’s label that appears on the shipping rule edit page. It can also be a Symfony translated message.getIcon
— Returns the icon that appears on the shipping rule edit page.isEnabled
— Defines, whether the integration of the shipping method is enabled by default.isGrouped
— Defines how shipping method’s types appear in the shipping method configuration on the user interface. If set totrue
, the types appear in the table where each line contains the Active check box that enables users to enable individual shipping method types for a particular shipping method configuration.getSortOrder
— Defines the order in which shipping methods appear on the user interface. For example, in the following screenshot, the Flat rate sort order is lower than the UPS sort order:getType
— Returns the selected shipping method type based on the type identifier.getTypes
— Returns a set of the shipping method types.getOptionsConfigurationFormType
— Returns the user interface form with the configuration options. The form appears on the shipping rule edit page. If the method returnsHiddenType::class
, the form does not appear.
Add the Shipping Method Identifier Generator to the Services Container¶
Append the following lines to <bundle_root>/Resources/config/services.yml:
1 acme_fast_shipping.method.identifier_generator.method:
2 parent: oro_integration.generator.prefixed_identifier_generator
3 arguments:
4 - '%acme_fast_shipping.integration.type%'
Create a Factory for the Shipping Method¶
This factory generates an individual configuration set for each instance of the integration of the Fast Shipping type. In our case, it also contains the method createTypes() that generates the services (types) of the fast shipping type and assigns them labels.
Create the <bundle_root>/Factory/FastShippingMethodFromChannelFactory.php class with the following content:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Factory;
4
5use ACME\Bundle\FastShippingBundle\Entity\FastShippingSettings;
6use ACME\Bundle\FastShippingBundle\Method\FastShippingMethod;
7use ACME\Bundle\FastShippingBundle\Method\FastShippingMethodType;
8use Oro\Bundle\IntegrationBundle\Entity\Channel;
9use Oro\Bundle\IntegrationBundle\Generator\IntegrationIdentifierGeneratorInterface;
10use Oro\Bundle\IntegrationBundle\Provider\IntegrationIconProviderInterface;
11use Oro\Bundle\LocaleBundle\Helper\LocalizationHelper;
12use Oro\Bundle\ShippingBundle\Method\Factory\IntegrationShippingMethodFactoryInterface;
13use Symfony\Component\Translation\TranslatorInterface;
14
15/**
16 * Factory that creates shipping method from the channel
17 */
18class FastShippingMethodFromChannelFactory implements IntegrationShippingMethodFactoryInterface
19{
20 /**
21 * @var IntegrationIdentifierGeneratorInterface
22 */
23 private $identifierGenerator;
24
25 /**
26 * @var LocalizationHelper
27 */
28 private $localizationHelper;
29
30 /**
31 * @var TranslatorInterface
32 */
33 private $translator;
34
35 /**
36 * @var IntegrationIconProviderInterface
37 */
38 private $integrationIconProvider;
39
40 /**
41 * FastShippingMethodFromChannelFactory constructor.
42 * @param IntegrationIdentifierGeneratorInterface $identifierGenerator
43 * @param LocalizationHelper $localizationHelper
44 * @param TranslatorInterface $translator
45 * @param IntegrationIconProviderInterface $integrationIconProvider
46 */
47 public function __construct(
48 IntegrationIdentifierGeneratorInterface $identifierGenerator,
49 LocalizationHelper $localizationHelper,
50 TranslatorInterface $translator,
51 IntegrationIconProviderInterface $integrationIconProvider
52 ) {
53 $this->identifierGenerator = $identifierGenerator;
54 $this->localizationHelper = $localizationHelper;
55 $this->translator = $translator;
56 $this->integrationIconProvider = $integrationIconProvider;
57 }
58
59 /**
60 * @param Channel $channel
61 *
62 * @return FastShippingMethod
63 */
64 public function create(Channel $channel): FastShippingMethod
65 {
66 $id = $this->identifierGenerator->generateIdentifier($channel);
67 $label = $this->getChannelLabel($channel);
68 $icon = $this->getIcon($channel);
69 $types = $this->createTypes();
70
71 return new FastShippingMethod($id, $label, $icon, $channel->isEnabled(), $types);
72 }
73
74 /**
75 * @param Channel $channel
76 *
77 * @return string
78 */
79 private function getChannelLabel(Channel $channel): string
80 {
81 /** @var FastShippingSettings $transport */
82 $transport = $channel->getTransport();
83
84 return (string) $this->localizationHelper->getLocalizedValue($transport->getLabels());
85 }
86
87 /**
88 * @param Channel $channel
89 *
90 * @return string|null
91 */
92 private function getIcon(Channel $channel): ?string
93 {
94 return $this->integrationIconProvider->getIcon($channel);
95 }
96
97 /**
98 * @return array
99 */
100 private function createTypes(): array
101 {
102 $withoutPresentLabel = $this->translator
103 ->trans('acme.fast_shipping.method.processing_type.without_present.label');
104 $withPresentLabel = $this->translator
105 ->trans('acme.fast_shipping.method.processing_type.with_present.label');
106
107 $withoutPresent = new FastShippingMethodType($withoutPresentLabel, false);
108 $withPresent = new FastShippingMethodType($withPresentLabel, true);
109
110 return [
111 $withoutPresent->getIdentifier() => $withoutPresent,
112 $withPresent->getIdentifier() => $withPresent,
113 ];
114 }
115}
Add the Shipping Method Factory to the Services Container¶
To register the shipping method factory, append the following key-values to <bundle_root>/Resources/config/services.yml under the services section:
1 acme_fast_shipping.factory.method:
2 class: 'ACME\Bundle\FastShippingBundle\Factory\FastShippingMethodFromChannelFactory'
3 public: false
4 arguments:
5 - '@acme_fast_shipping.method.identifier_generator.method'
6 - '@oro_locale.helper.localization'
7 - '@translator'
8 - '@oro_integration.provider.integration_icon'
Create a Shipping Method Provider¶
For this, add the <bundle_root>/Method/FastShippingMethodProvider.php class with the following content:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Method;
4
5use Oro\Bundle\EntityBundle\ORM\DoctrineHelper;
6use Oro\Bundle\ShippingBundle\Method\Factory\IntegrationShippingMethodFactoryInterface;
7use Oro\Bundle\ShippingBundle\Method\Provider\Integration\ChannelShippingMethodProvider;
8
9/**
10 * Provides methods for getting shipping method object by name
11 */
12class FastShippingMethodProvider extends ChannelShippingMethodProvider
13{
14 /**
15 * {@inheritDoc}
16 */
17 public function __construct(
18 $channelType,
19 DoctrineHelper $doctrineHelper,
20 IntegrationShippingMethodFactoryInterface $methodFactory
21 ) {
22 parent::__construct($channelType, $doctrineHelper, $methodFactory);
23 }
24}
Add the Shipping Method Provider to the Services Container¶
Append the following lines to <bundle_root>/Resources/config/services.yml under the services section:
1 acme_fast_shipping.method.provider:
2 class: 'ACME\Bundle\FastShippingBundle\Method\FastShippingMethodProvider'
3 arguments:
4 - '%acme_fast_shipping.integration.type%'
5 - '@oro_entity.doctrine_helper'
6 - '@acme_fast_shipping.factory.method'
7 tags:
8 - { name: oro_shipping_method_provider }
9 - { name: doctrine.orm.entity_listener, entity: 'Oro\Bundle\IntegrationBundle\Entity\Channel', event: postLoad }
Create a Shipping Method Type¶
Shipping method types define different specifics of the same shipping services. For example, for Flat Rate, the type defines whether to calculate shipping price per order or per item. The Fast Shipping will have two types: “With Present” and “Without Present”.
To create a shipping method type, add the <bundle_root>/Method/FastShippingMethodType.php class with the following content:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Method;
4
5use ACME\Bundle\FastShippingBundle\Form\Type\FastShippingMethodOptionsType;
6use Oro\Bundle\CurrencyBundle\Entity\Price;
7use Oro\Bundle\ShippingBundle\Context\ShippingContextInterface;
8use Oro\Bundle\ShippingBundle\Method\ShippingMethodTypeInterface;
9
10/**
11 * Fast Shipping method type
12 * Contains logic of calculating shipping price
13 */
14class FastShippingMethodType implements ShippingMethodTypeInterface
15{
16 public const PRICE_OPTION = 'price';
17
18 private const WITHOUT_PRESENT_TYPE = 'without_present';
19 private const WITH_PRESENT_TYPE = 'with_present';
20
21 /**
22 * @var string
23 */
24 private $label;
25
26 /**
27 * @var bool
28 */
29 private $isWithPresent;
30
31 /**
32 * @param string $label
33 * @param bool $isWithPresent
34 */
35 public function __construct(string $label, bool $isWithPresent)
36 {
37 $this->label = $label;
38 $this->isWithPresent = $isWithPresent;
39 }
40
41 /**
42 * {@inheritDoc}
43 */
44 public function getIdentifier()
45 {
46 return $this->isWithPresent ? self::WITH_PRESENT_TYPE : self::WITHOUT_PRESENT_TYPE;
47 }
48
49 /**
50 * {@inheritDoc}
51 */
52 public function getLabel(): string
53 {
54 return $this->label;
55 }
56
57 /**
58 * {@inheritDoc}
59 */
60 public function getSortOrder(): int
61 {
62 return 0;
63 }
64
65 /**
66 * {@inheritDoc}
67 */
68 public function getOptionsConfigurationFormType(): string
69 {
70 return FastShippingMethodOptionsType::class;
71 }
72
73 /**
74 * {@inheritDoc}
75 */
76 public function calculatePrice(ShippingContextInterface $context, array $methodOptions, array $typeOptions): ?Price
77 {
78 $price = $typeOptions[static::PRICE_OPTION];
79
80 // Provide additional price calculation logic here if required.
81
82 return Price::create((float)$price, $context->getCurrency());
83 }
84}
getIdentifier
— Returns a unique identifier of a shipping method type in the scope of the shipping method.getLabel
— Returns the label of the shipping method type. The label appears on the shipping rule edit page in the back-office and on the storefront.getSortOrder
— Defines the order in which shipping method types appear on the user interface. For example, see the UPS shipping types below. The number that defines the sort order of the UPS Ground is lower than that of the UPS 2nd Day Air (i.e. the lower the number, the higher up the list the method type appears):getOptionsConfigurationFormType
— Returns the user interface form with the configuration options. The form appears on the shipping rule edit page. If the method returnsHiddenType::class
, the form does not appear.calculatePrice
– Contains the main logic and returns the shipping price for the given$context
.
Note
If you implement a more complicated shipping method, see OroBundleShippingBundleContextShippingContextInterface for attributes that can affect a shipping price (e.g., shipping address information or line items).
Define Translation for the Shipping Method Type¶
Provide translations by appending the <bundle_root>/Resources/translations/messages.en.yml. Now, the messages.en.yml content must look as follows:
1acme:
2 fast_shipping:
3 settings:
4 labels:
5 label: 'Label'
6 transport:
7 label: 'Fast Shipping'
8 channel_type:
9 label: 'Fast Shipping'
10 method:
11 price.label: 'Price'
12 processing_type:
13 without_present.label: 'Fast Shipping Rate Without Present'
14 with_present.label: 'Fast Shipping Rate With Present'
Create a Shipping Method Options Form¶
This form with options for a shipping method appears on the user interface of the back-office when you add the shipping method to a shipping rule. Add FastShippingMethodOptionsType.php to the <bundle_root>/Form/Type/ directory:
1<?php
2
3namespace ACME\Bundle\FastShippingBundle\Form\Type;
4
5use ACME\Bundle\FastShippingBundle\Method\FastShippingMethodType;
6use Oro\Bundle\CurrencyBundle\Rounding\RoundingServiceInterface;
7use Symfony\Component\Form\AbstractType;
8use Symfony\Component\Form\Extension\Core\Type\NumberType;
9use Symfony\Component\Form\FormBuilderInterface;
10use Symfony\Component\OptionsResolver\Exception\AccessException;
11use Symfony\Component\OptionsResolver\OptionsResolver;
12use Symfony\Component\Validator\Constraints\NotBlank;
13use Symfony\Component\Validator\Constraints\Type;
14
15/**
16 * Form type for fast shipping method options which are displayed on shipping rules page
17 */
18class FastShippingMethodOptionsType extends AbstractType
19{
20 private const BLOCK_PREFIX = 'acme_fast_shipping_options_type';
21
22 /**
23 * @var RoundingServiceInterface
24 */
25 protected $roundingService;
26
27 /**
28 * @param RoundingServiceInterface $roundingService
29 */
30 public function __construct(RoundingServiceInterface $roundingService)
31 {
32 $this->roundingService = $roundingService;
33 }
34
35 /**
36 * {@inheritDoc}
37 */
38 public function buildForm(FormBuilderInterface $builder, array $options)
39 {
40 $priceOptions = [
41 'scale' => $this->roundingService->getPrecision(),
42 'rounding_mode' => $this->roundingService->getRoundType(),
43 'attr' => ['data-scale' => $this->roundingService->getPrecision()],
44 ];
45
46 $builder
47 ->add(FastShippingMethodType::PRICE_OPTION, NumberType::class, array_merge([
48 'label' => 'acme.fast_shipping.method.price.label',
49 'constraints' => [new NotBlank(), new Type(['type' => 'numeric'])],
50 ], $priceOptions));
51 }
52
53 /**
54 * @param OptionsResolver $resolver
55 *
56 * @throws AccessException
57 */
58 public function configureOptions(OptionsResolver $resolver)
59 {
60 $resolver->setDefaults([
61 'label' => 'acme.fast_shipping.form.acme_fast_shipping_options_type.label',
62 ]);
63 }
64
65 /**
66 * {@inheritDoc}
67 */
68 public function getBlockPrefix()
69 {
70 return self::BLOCK_PREFIX;
71 }
72}
Add the Shipping Method Options Form to the Services Container¶
Append the following lines to <bundle_root>/Resources/config/services.yml under the services section:
1 acme_fast_shipping.form.type.fast_shipping_options:
2 class: 'ACME\Bundle\FastShippingBundle\Form\Type\FastShippingMethodOptionsType'
3 arguments:
4 - '@oro_currency.rounding.price_rounding_service'
5 tags:
6 - { name: form.type }
Define Translation for the Shipping Method Form Options¶
Provide translations by appending the <bundle_root>/Resources/translations/messages.en.yml. Now, the messages.en.yml content must look as follows:
1acme:
2 fast_shipping:
3 settings:
4 labels:
5 label: 'Label'
6 transport:
7 label: 'Fast Shipping'
8 channel_type:
9 label: 'Fast Shipping'
10 method:
11 price.label: 'Price'
12 processing_type:
13 without_present.label: 'Fast Shipping Rate Without Present'
14 with_present.label: 'Fast Shipping Rate With Present'
15 form:
16 acme_fast_shipping_options_type.label: 'Fast Shipping Rate'
Add a Template¶
In the shipping rules, this template is used to display the configured settings of the Fast Shipping integration.
Create the /Resources/views/method/fastShippingMethodWithOptions.html.twig file with the following content:
1{% import 'OroShippingBundle:ShippingMethodsConfigsRule:macros.html.twig' as ShipRuleMacro %}
2
3 {% spaceless %}
4 {% set methodLabel = get_shipping_method_label(methodData.identifier)|trans %}
5 {% if methodLabel|length > 0 %}
6 <li>{{ methodLabel }}
7 <ul>
8 {% endif %}
9 {% for type in methodData.types %}
10 {%- if type.enabled -%}
11 <li>{{ get_shipping_method_type_label(methodData.identifier, type.identifier)|trans }} ({{ 'acme.fast_shipping.method.price.label'|trans }}: {{ type.options['price']|oro_format_currency({'currency': currency}) }}
12 ) {{ ShipRuleMacro.renderShippingMethodDisabledFlag(methodData.identifier) }}</li>
13 {%- endif -%}
14 {% endfor %}
15 {% if methodLabel|length > 0 %}
16 </ul>
17 </li>
18 {% endif %}
19 {% endspaceless %}
Add a Check for When Users Disable Used Shipping Method Types¶
To show a notification when a user disables or removes the integration currently used in shipping rules, use the event listeners to catch the corresponding event and the event handlers.
Add Event Listeners to the System Container¶
Append the following lines to <bundle_root>/Resources/config/services.yml under the parameters and services sections:
1parameters:
2 acme_fast_shipping.admin_view.method_template: 'ACMEFastShippingBundle::method/fastShippingMethodWithOptions.html.twig'
3
4services:
5 acme_fast_shipping.event_listener.shipping_method_config_data:
6 parent: oro_shipping.admin_view.method_template.listener
7 arguments:
8 - '%acme_fast_shipping.admin_view.method_template%'
9 - '@acme_fast_shipping.method.provider'
10 tags:
11 - { name: kernel.event_listener, event: oro_shipping_method.config_data, method: onGetConfigData }
12
13 acme_fast_shipping.remove_integration_listener:
14 parent: oro_shipping.remove_integration_listener
15 arguments:
16 - '%acme_fast_shipping.integration.type%'
17 - '@acme_fast_shipping.method.identifier_generator.method'
18 - '@oro_shipping.method.event.dispatcher.method_removal'
19 tags:
20 - { name: kernel.event_listener, event: oro_integration.channel_delete, method: onRemove }
21
22 acme_fast_shipping.disable_integration_listener:
23 parent: oro_shipping.disable_integration_listener
24 arguments:
25 - '%acme_fast_shipping.integration.type%'
26 - '@acme_fast_shipping.method.identifier_generator.method'
27 - '@oro_shipping.method_disable_handler.decorator'
28 tags:
29 - { name: kernel.event_listener, event: oro_integration.channel_disable, method: onIntegrationDisable }
Add Actions¶
Create actions.yml in the <bundle_root>/Resources/config/oro/ directory:
1operations:
2 # Disable the default deactivate method for the Fast Shipping integration.
3 oro_integration_deactivate:
4 preconditions:
5 '@and':
6 - '@not_equal': [$type, '%acme_fast_shipping.integration.type%']
7
8 # Disable the default delete method for the Fast Shipping integration.
9 oro_integration_delete:
10 preconditions:
11 '@and':
12 - '@not_equal': [$type, '%acme_fast_shipping.integration.type%']
13
14 # Use the deactivate method that:
15 # a. first checks whether there are shipping rules that use the Fast Shipping method, and
16 # b. if yes, displays to a user the confirmation dialog with the notification message and the link to the list of the corresponding rules.
17 acme_fast_shipping_integration_deactivate:
18 extends: oro_integration_deactivate
19 for_all_entities: false
20 for_all_datagrids: false
21 replace:
22 - preactions
23 - preconditions
24 - frontend_options
25
26 # Filter the grid with the active shipping rules that use the Fast Shipping method and generate the link to it.
27 preactions:
28 - '@call_service_method':
29 attribute: $.actionAllowed
30 service: oro_integration.utils.edit_mode
31 method: isSwitchEnableAllowed
32 method_parameters: [$.data.editMode]
33 - '@call_service_method':
34 attribute: $.methodIdentifier
35 service: acme_fast_shipping.method.identifier_generator.method
36 method: generateIdentifier
37 method_parameters: [$.data]
38 - '@call_service_method':
39 attribute: $.linkGrid
40 service: oro_shipping.helper.filtered_datagrid_route
41 method: generate
42 method_parameters: [{'methodConfigs': $.methodIdentifier}]
43
44 # Check that the method is used in the shipping rules.
45 preconditions:
46 '@and':
47 - '@shipping_method_has_enabled_shipping_rules':
48 parameters:
49 method_identifier: $.methodIdentifier
50 - '@equal': [$type, '%acme_fast_shipping.integration.type%']
51 - '@equal': [$.actionAllowed, true]
52 - '@equal': [$.data.enabled, true]
53
54 # Show the confirmation dialog with the notification message.
55 frontend_options:
56 confirmation:
57 title: oro.shipping.integration.deactivate.title
58 okText: oro.shipping.integration.deactivate.button.okText
59 message: oro.shipping.integration.deactivate.message
60 message_parameters:
61 linkGrid: $.linkGrid
62 component: oroui/js/standart-confirmation
63
64
65 # If there are no shipping rules that use this method, deactivate without displaying to a user the confirmation dialog.
66 acme_fast_shipping_integration_deactivate_without_rules:
67 extends: acme_fast_shipping_integration_deactivate
68 for_all_entities: false
69 for_all_datagrids: false
70 replace:
71 - preconditions
72 - frontend_options
73 preconditions:
74 '@and':
75 - '@not':
76 - '@shipping_method_has_enabled_shipping_rules':
77 parameters:
78 method_identifier: $.methodIdentifier
79 - '@equal': [$type, '%acme_fast_shipping.integration.type%']
80 - '@equal': [$.actionAllowed, true]
81 - '@equal': [$.data.enabled, true]
82 frontend_options: ~
83
84 # Use the delete method that:
85 # a. first checks whether there are shipping rules that use the Fast Shipping method, and
86 # b. if yes, displays to a user the confirmation dialog with the notification message and the link to the list of the corresponding rules.
87 acme_fast_shipping_integration_delete:
88 extends: oro_integration_delete
89 for_all_entities: false
90 for_all_datagrids: false
91 replace:
92 - preactions
93 - preconditions
94 - frontend_options
95 preactions:
96 - '@call_service_method':
97 service: oro_integration.utils.edit_mode
98 method: isEditAllowed
99 method_parameters: [$.data.editMode]
100 attribute: $.actionAllowed
101 - '@call_service_method':
102 attribute: $.methodIdentifier
103 service: acme_fast_shipping.method.identifier_generator.method
104 method: generateIdentifier
105 method_parameters: [$.data]
106 - '@call_service_method':
107 attribute: $.linkGrid
108 service: oro_shipping.helper.filtered_datagrid_route
109 method: generate
110 method_parameters: [{'methodConfigs': $.methodIdentifier}]
111 preconditions:
112 '@and':
113 - '@shipping_method_has_shipping_rules':
114 parameters:
115 method_identifier: $.methodIdentifier
116 - '@equal': [$type, '%acme_fast_shipping.integration.type%']
117 - '@equal': [$.actionAllowed, true]
118 frontend_options:
119 confirmation:
120 title: oro.shipping.integration.delete.title
121 okText: oro.shipping.integration.delete.button.okText
122 message: oro.shipping.integration.delete.message
123 message_parameters:
124 linkGrid: $.linkGrid
125 component: oroui/js/standart-confirmation
126
127 acme_fast_shipping_integration_delete_without_rules:
128 extends: acme_fast_shipping_integration_delete
129 for_all_entities: false
130 for_all_datagrids: false
131 replace:
132 - preconditions
133 - frontend_options
134 preconditions:
135 '@and':
136 - '@not':
137 - '@shipping_method_has_shipping_rules':
138 parameters:
139 method_identifier: $.methodIdentifier
140 - '@equal': [$type, '%acme_fast_shipping.integration.type%']
141 - '@equal': [$.actionAllowed, true]
142 frontend_options:
143 title: oro.action.delete_entity
144 confirmation:
145 title: oro.action.delete_entity
146 message: oro.action.delete_confirm
147 message_parameters:
148 entityLabel: $name
149 component: oroui/js/delete-confirmation
Important
To enable this shipping method, you need to set up a corresponding shipping rule. Follow the Shipping Rules Configuration topic for more details.