How to customize the refund form? ================================= .. note:: This cookbook describes customization of a feature available only with `Sylius/RefundPlugin `_ installed. A refund form is the form in which, as an Administrator, you can specify the exact amounts of money that will be refunded to a Customer. Why would you customize the refund form? ---------------------------------------- Refund Plugin provides a generic solution for refunding orders, it is enough for a basic refund but many shops need more custom functionalities. For example, one may need to add refund payments scheduling, as they may be paid once a month. How to add a field to the refund form? -------------------------------------- The refund form is a form used to create the Refund Payment, thus in order to add a field to this form, you need to first add it to the Refund Payment's model. Refunds are processed with such a flow: ``command -> handler -> event -> listener``, and this flow we will also need to customize in order to process the data from the new field. In this customization, we will be extending the refund form with a ``scheduledAt`` field, which might be used then for scheduling the payments in the payment gateway. **1. Add the custom field to the Refund Payment:** Extended refund payment should look like this: .. code-block:: php scheduledAt; } public function setScheduledAt(\DateTimeInterface $scheduledAt): void { $this->scheduledAt = $scheduledAt; } } It should implement a new interface: .. code-block:: php **3. Adjust the ``RefundUnits`` command:** We want the refund payments to be created with our extra ``scheduledAt`` date, therefore we need to provide this data in command, We will extend the ``RefundUnits`` command from Refund Plugin and add the new value: .. code-block:: php scheduledAt = $scheduledAt; } public function getScheduledAt(): ?\DateTimeInterface { return $this->scheduledAt; } public function setScheduledAt(?\DateTimeInterface $scheduledAt): void { $this->scheduledAt = $scheduledAt; } } **4. Update the ``RefundUnitsCommandCreator``:** The controller related to the refund form dispatches the ``RefundUnits`` command, and there is a service that creates a command from request, so we need to overwrite the ``Sylius\RefundPlugin\Creator\RefundUnitsCommandCreator``: .. code-block:: php refundUnitsConverter = $refundUnitsConverter; } public function fromRequest(Request $request): BaseRefundUnits { Assert::true($request->attributes->has('orderNumber'), 'Refunded order number not provided'); $units = $this->refundUnitsConverter->convert( $request->request->has('sylius_refund_units') ? $request->request->all()['sylius_refund_units'] : [], RefundType::orderItemUnit(), OrderItemUnitRefund::class ); $shipments = $this->refundUnitsConverter->convert( $request->request->has('sylius_refund_shipments') ? $request->request->all()['sylius_refund_shipments'] : [], RefundType::shipment(), ShipmentRefund::class ); if (count($units) === 0 && count($shipments) === 0) { throw InvalidRefundAmount::withValidationConstraint('sylius_refund.at_least_one_unit_should_be_selected_to_refund'); } /** @var string $comment */ $comment = $request->request->get('sylius_refund_comment', ''); // here we need to return the new RefundUnits command, with new data return new RefundUnits( $request->attributes->get('orderNumber'), $units, $shipments, (int) $request->request->get('sylius_refund_payment_method'), $comment, new \DateTime($request->request->get('sylius_scheduled_at')) ); } } And register the new service: .. code-block:: yaml # config/services.yaml Sylius\RefundPlugin\Creator\RefundUnitsCommandCreatorInterface: class: App\Creator\RefundUnitsCommandCreator arguments: - '@Sylius\RefundPlugin\Converter\RefundUnitsConverterInterface' **5. Modify the ``RefundUnitsHandler``:** Now, when we have a new command, we also need to overwrite the related command handler: .. code-block:: php orderUnitsRefunder = $orderUnitsRefunder; $this->orderShipmentsRefunder = $orderShipmentsRefunder; $this->eventBus = $eventBus; $this->orderRepository = $orderRepository; $this->refundUnitsCommandValidator = $refundUnitsCommandValidator; } public function __invoke(RefundUnits $command): void { $this->refundUnitsCommandValidator->validate($command); $orderNumber = $command->orderNumber(); /** @var OrderInterface $order */ $order = $this->orderRepository->findOneByNumber($orderNumber); $refundedTotal = 0; $refundedTotal += $this->orderUnitsRefunder->refundFromOrder($command->units(), $orderNumber); $refundedTotal += $this->orderShipmentsRefunder->refundFromOrder($command->shipments(), $orderNumber); /** @var string|null $currencyCode */ $currencyCode = $order->getCurrencyCode(); Assert::notNull($currencyCode); $this->eventBus->dispatch(new UnitsRefunded( $orderNumber, $command->units(), $command->shipments(), $command->paymentMethodId(), $refundedTotal, $currencyCode, $command->comment(), $command->getScheduledAt() )); } } And register it: .. code-block:: yaml # config/services.yaml Sylius\RefundPlugin\CommandHandler\RefundUnitsHandler: class: App\CommandHandler\RefundUnitsHandler arguments: - '@Sylius\RefundPlugin\Refunder\OrderItemUnitsRefunder' - '@Sylius\RefundPlugin\Refunder\OrderShipmentsRefunder' - '@sylius.event_bus' - '@sylius.repository.order' - '@Sylius\RefundPlugin\Validator\RefundUnitsCommandValidatorInterface' tags: - { name: messenger.message_handler, bus: sylius.command_bus } **6. Modify the ``UnitsReturned`` event:** In previous command handler we are dispatching a new event so now we need to create this event and related event handler: event: .. code-block:: php scheduledAt = $scheduledAt; } public function getScheduledAt(): \DateTimeInterface { return $this->scheduledAt; } } And process manager to handle the new event: .. code-block:: php orderFullyRefundedStateResolver = $orderFullyRefundedStateResolver; $this->relatedPaymentIdProvider = $relatedPaymentIdProvider; $this->refundPaymentFactory = $refundPaymentFactory; $this->orderRepository = $orderRepository; $this->paymentMethodRepository = $paymentMethodRepository; $this->entityManager = $entityManager; $this->eventBus = $eventBus; } public function next(UnitsRefunded $unitsRefunded): void { /** @var OrderInterface|null $order */ $order = $this->orderRepository->findOneByNumber($unitsRefunded->orderNumber()); Assert::notNull($order); /** @var PaymentMethodInterface|null $paymentMethod */ $paymentMethod = $this->paymentMethodRepository->find($unitsRefunded->paymentMethodId()); Assert::notNull($paymentMethod); /** @var AppRefundPaymentInterface $refundPayment */ $refundPayment = $this->refundPaymentFactory->createWithData( $order, $unitsRefunded->amount(), $unitsRefunded->currencyCode(), RefundPaymentInterface::STATE_NEW, $paymentMethod ); $refundPayment->setScheduledAt($unitsRefunded->getScheduledAt()); $this->entityManager->persist($refundPayment); $this->entityManager->flush(); $this->eventBus->dispatch(new RefundPaymentGenerated( $refundPayment->getId(), $unitsRefunded->orderNumber(), $unitsRefunded->amount(), $unitsRefunded->currencyCode(), $unitsRefunded->paymentMethodId(), $this->relatedPaymentIdProvider->getForRefundPayment($refundPayment) )); $this->orderFullyRefundedStateResolver->resolve($unitsRefunded->orderNumber()); } } And register it: .. code-block:: yaml Sylius\RefundPlugin\ProcessManager\RefundPaymentProcessManager: class: App\ProcessManager\RefundPaymentProcessManager arguments: - '@Sylius\RefundPlugin\StateResolver\OrderFullyRefundedStateResolverInterface' - '@Sylius\RefundPlugin\Provider\RelatedPaymentIdProviderInterface' - '@sylius_refund.factory.refund_payment' - '@sylius.repository.order' - '@sylius.repository.payment_method' - '@doctrine.orm.default_entity_manager' - '@sylius.event_bus' tags: - {name: sylius_refund.units_refunded.process_step, priority: 50} **7. Display the new field on the refund payment:** And as the last step, we need to overwrite the template ``_refundPayments.html.twig`` from Refund Plugin. Copy the entire ``_refundPayments.html.twig`` to ``templates/bundles/SyliusRefundPlugin/Order/Admin/_refundPayments.html.twig``: .. code-block:: bash mkdir -p templates/bundles/SyliusRefundPlugin/Order/Admin cp vendor/sylius/refund-plugin/src/Resources/views/Order/Admin/_refundPayments.html.twig templates/bundles/SyliusRefundPlugin/Order/Admin/ And replace ``header`` with: .. code-block:: twig
{{ refund_payment.paymentMethod }} {% if refund_payment.scheduledAt is not null %} (Payment should be made in {{ refund_payment.scheduledAt|date('Y-M-d') }}) {% endif %}
And that's it, we have a new field on Refund Payment with a "scheduled at" date (when admin/payment gateway should make the payment), in your application, you probably will add crone to automatize it.