En un artículo pasado, vimos cómo podemos implementar una calculadora asociada a los gastos de envío en Sylius. En este artículo vamos a ver cómo podemos asociar un gasto adicional al pedido en función del método de pago seleccionado. El caso de uso surge por la necesidad de añadir un método de pago “a contra reembolso”, el cual que requiere añadir un gasto adicional para el cliente.
Para conseguir nuestro objetivo, nos vamos a basar en el uso de OrderProcessors. Los OrderProcessor
son responsables de manipular los pedidos para aplicar diferentes ajustes predefinidos u otras modificaciones basadas en el estado del pedido. Con esta estrategia, podemos por ejemplo, aplicar descuentos por volumen o añadir impuestos adicionales. En nuestro caso, vamos a configurar un coste asociado al Método de Pago dado de alta que se sumará al pedido como un coste adicional.
Modificando el modelo PaymentMethod de Sylius
Para ello, lo primero que debemos de hacer es modificar el modelo PaymentMethod
para añadirle un campo adicional. Para ello extendemos del modelo base que nos proporciona el framework Sylius\Component\Core\Model\PaymentMethod
y añadimos nuestro campo price.
<?php namespace AppBundle\Entity; use Sylius\Component\Core\Model\PaymentMethod as BasePaymentMethod; /** * Class Product * @package AppBundle\Entity */ class PaymentMethod extends BasePaymentMethod { /** * @var int */ protected $price; /** * @return int */ public function getPrice(): ?int { return $this->price; } /** * @param int $price */ public function setPrice(?int $price): void { $this->price = $price; } }
Una vez incluido nuestro campo lo añadimos a los metadatos de doctrine:
AppBundle\Entity\PaymentMethod: type: entity table: sylius_payment_method fields: price: type: integer nullable: true
Para profundizar en la customización de los modelos, os recomiendo este apartado de la documentación. Una vez tenemos el modelo añadido, necesitamos añadir la opción en el panel de administración para añadir el coste asociado al método de pago. Para ello, vamos a extender el formulario utilizando un AbstractTypeExtension
de Symfony tratando de extender el formulario SyliusPaymentMethodType
. A este formulario añadiremos el campo adicional price.
<?php declare(strict_types=1); namespace AppBundle\Form\Extension; use Sylius\Bundle\MoneyBundle\Form\Type\MoneyType; use Sylius\Bundle\PaymentBundle\Form\Type\PaymentMethodType as SyliusPaymentMethodType; use Sylius\Bundle\ResourceBundle\Form\EventSubscriber\AddCodeFormSubscriber; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Type; /** * Class PaymentMethodTypeExtension * @package AppBundle\Form\Extension */ class PaymentMethodTypeExtension extends AbstractTypeExtension { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->addEventSubscriber(new AddCodeFormSubscriber()) ->add( 'price', MoneyType::class, [ 'required'=> false, 'label' => 'Precio', 'constraints' => [ new Type(['type' => 'integer', 'groups' => ['sylius']]), ], ] ); } /** * {@inheritdoc} */ public function getExtendedType() { return SyliusPaymentMethodType::class; } }
Una vez implementada la clase, debemos de darla de alta en nuestro fichero services.yml
app.form.extension.type.payment_method: class: AppBundle\Form\Extension\PaymentMethodTypeExtension tags: - { name: form.type_extension, extended_type: Sylius\Bundle\PaymentBundle\Form\Type\PaymentMethodType }
Para modificar la vista del panel de administración debemos de adaptar el la vista modificando el twig app/Resources/SyliusAdminBundle/views/PaymentMethod/_form.html.twig
añadiendo al final del documento la renderización de nuestro nuevo campo:
<div class="ui segment"> <h4 class="ui dividing header">Coste Adicional</h4> {{ form_row(form.price) }} </div>
Utilizando OrderProcessor
Como hemos comentado al principio, vamos a ayudarnos del concepto OrderProcessor para añadir nuestro gasto en el pedido. Para ello, vamos a añadir al pedido un elemento Adjustment
en el caso de que el método de pago elegido durante la creación del pedido tenga un coste asociado.
namespace AppBundle\Order\Processor; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Order\Model\AdjustmentInterface as BaseAdjustmentInterface; use Sylius\Component\Order\Model\OrderInterface as BaseOrderInterface; use Sylius\Component\Order\Processor\OrderProcessorInterface; use Sylius\Component\Resource\Factory\FactoryInterface; /** * Class PaymentChargesProcessor * @package AppBundle\Order\Processor */ final class PaymentChargesProcessor implements OrderProcessorInterface { /** * @var FactoryInterface */ private $adjustmentFactory; /** * @param FactoryInterface $adjustmentFactory */ public function __construct( FactoryInterface $adjustmentFactory ) { $this->adjustmentFactory = $adjustmentFactory; } /** * @param BaseOrderInterface $order */ public function process(BaseOrderInterface $order): void { assert($order instanceof OrderInterface); $order->removeAdjustments(); foreach ($order->getPayments() as $payment) { if (!$price = $payment->getMethod()->getPrice()) { continue; } $adjustment = $this->adjustmentFactory->createNew(); assert($adjustment instanceof BaseAdjustmentInterface); $adjustment->setType('adjustment'); $adjustment->setAmount($price); $adjustment->setLabel($payment->getMethod() !== null ? $payment->getMethod()->getName() : null); $adjustment->setOriginCode($payment->getMethod() !== null ? $payment->getMethod()->getCode() : null); $adjustment->setNeutral(false); $order->addAdjustment($adjustment); } } }
El concepto Adjustment
en Sylius nos permite hacer ajustes en el total de nuestro pedido. Los ajustes habituales se pueden dividir en tres grupos: ajustes de promoción, ajustes de envío y ajustes de impuestos. Hay que tener en cuenta que los ajustes pueden ser positivos (cargos) o negativos (descuentos).
Finalmente, para que el OrderProcessor
empiece a funcionar, debemos de darlo de alta con el tag sylius.order_processor
order.processor.payment_charges: class: AppBundle\Order\Processor\PaymentChargesProcessor arguments: - '@sylius.factory.adjustment' tags: - name: sylius.order_processor priority: 0
Los OrderProcessor
se añaden a un servicio de Sylius CompositeOrderProcessor
que incluye todos los processors del sistema priorizados. Este servicio es capaz de ejecutarlos en el orden adecuado. Por defecto, la ejecución de estos OrderProcessors
se ejecutan en casos particulares, por ejemplo cuando se añaden o eliminan elementos en el carrito de compra.
Como nuestra necesidad es que este cálculo se aplique cuando se asocia un método de pago a un pedido, se ha desarrollado un Subscriber asociado al evento sylius.order.post_payment
, el cual es ejecutado justo después de asociar el método de pago, de esta manera recalculamos los ajustes necesarios:
<?php namespace AppBundle\Subscriber; use Sylius\Component\Order\Processor\CompositeOrderProcessor; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Class PostPaymentSubscriber * @package AppBundle\Subsriber */ class PostPaymentSubscriber implements EventSubscriberInterface { /** * @var CompositeOrderProcessor */ private $processor; public function __construct(CompositeOrderProcessor $processor) { $this->processor = $processor; } /** * @return array */ public static function getSubscribedEvents() { return [ 'sylius.order.post_payment'=> 'onPostPayment', ]; } /** * @param Event $event */ public function onPostPayment(Event $event) { $subject = $event->getSubject(); $this->processor->process($subject); } }
Conclusión
Como resumen de lo visto a lo largo del articulo, hemos modificado el modelo PaymentMethod del framework y hemos modificado el formulario del panel de administración para que el usuario configure el coste asociado al método de pago.
Una vez realizado estos pasos, hemos añadido un OrderProcessor que se encargue de añadir el coste asociado al PaymentMethod, para que se ejecute cuando haya algún cambio en el método de pago del pedido, usamos el servicio CompositeOrderProcessor dentro de nuestro Subscriber al eventosylius.order.post_payment.
Espero que este artículo pueda resultar de utilidad y os animo a comentar alternativas a este método y vuestra experiencia manipulando el coste de un pedido en Sylius.