Search code examples
symfonystripe-paymentssylius

Sylius : Wrong total order amount passed to Stripe


I added a new processor allowing to calculate the amount of the gift card.

services:
    app.order_processing.gift_card_processor:
        class: App\OrderProcessing\GiftCardProcessor
        arguments:
            - '@sylius.factory.adjustment'
            - '@translator'
        tags:
            - { name: sylius.order_processor, priority: 5 }

<?php

declare(strict_types=1);

namespace App\OrderProcessing;

use App\Entity\Order\Adjustment;
use App\Entity\Order\Order;
use Sylius\Component\Order\Model\OrderInterface as BaseOrderInterface;
use Sylius\Component\Order\Processor\OrderProcessorInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Webmozart\Assert\Assert;

final class GiftCardProcessor implements OrderProcessorInterface
{
    private FactoryInterface $adjustmentFactory;

    private TranslatorInterface $translator;

    public function __construct(
        FactoryInterface $adjustmentFactory,
        TranslatorInterface $translator
    ) {
        $this->adjustmentFactory = $adjustmentFactory;
        $this->translator = $translator;
    }

    public function process(BaseOrderInterface $order): void
    {
        /** @var Order $order */
        Assert::isInstanceOf($order, Order::class);

        // Remove all gift card adjustments, we recalculate everything from scratch.
        $order->removeAdjustments(Adjustment::ORDER_GIFT_CARD_ADJUSTMENT);

        foreach ($order->getGiftCardOrders() as $giftCardOrder) {
            $giftCard = $giftCardOrder->getGiftCard();
            $giftCardRemainingAmount = (int) $giftCard->getRemainingAmount() * 100;

            $amount = $giftCardRemainingAmount > $order->getTotal() ? $order->getTotal() : $giftCardRemainingAmount;

            /** @var Adjustment $adjustment */
            $adjustment = $this->adjustmentFactory->createNew();
            $adjustment->setType(Adjustment::ORDER_GIFT_CARD_ADJUSTMENT);
            $adjustment->setAmount(-$amount);
            $adjustment->setLabel($this->translator->trans('gift_card.ui.gift_card'));

            $giftCard->addAdjustment($adjustment);

            $giftCardOrder->setAmount($amount / 100);

            $order->addAdjustment($adjustment);
        }
    }
}

The total order amount displayed in the cart and inserted in the database is correct (screenshot 1 and 2).

SCREENSHOT 1 SCREENSHOT 2

The bug occurs during payment on stripe, the amount displayed corresponds to the initial amount which does not support the reduction of the gift card (screenshot 3)

SCREENSHOT 3


Solution

  • If you are using this Sylius plugin : flux-se/sylius-payum-stripe-plugin you have to create individual coupons representing your gift cards decorating : https://github.com/FLUX-SE/SyliusPayumStripePlugin/blob/master/src/Provider/DetailsProvider.php the new array member to create is the discounts like the Stripe doc is defining it : https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-discounts

    The plugin is only taking care of default Sylius adjustments linked to an OrderItem or an OrderItemUnit. If the adjustment is linked on the Order then it won't be taken into account because Stripe is only making a sum of all line_item as total. Stripe is not allowing negative amount for a line item, that's why coupons are the only way to reduce the total amount of the payment.

    Here is the required payum extension handling the creation of coupons if you label the coupon ids with this format sprintf('GIFT_CARD_%s', $giftCard->getCode()) :

    <?php
    
    declare(strict_types=1);
    
    namespace App\GiftCard\Payum\Extension;
    
    use FluxSE\PayumStripe\Request\Api\Resource\CreateCoupon;
    use FluxSE\PayumStripe\Request\Api\Resource\RetrieveCoupon;
    use FluxSE\SyliusPayumStripePlugin\Action\ConvertPaymentAction;
    use Payum\Core\Extension\Context;
    use Payum\Core\Extension\ExtensionInterface;
    use Payum\Core\Request\Convert;
    use Stripe\Exception\ApiErrorException;
    use Sylius\Component\Core\Model\PaymentInterface;
    
    final class CheckCouponsExtension implements ExtensionInterface
    {
        public function onPreExecute(Context $context)
        {
        }
    
        public function onExecute(Context $context)
        {
        }
    
        public function onPostExecute(Context $context)
        {
            if ($context->getException()) {
                return;
            }
    
            if (false === $context->getAction() instanceof ConvertPaymentAction) {
                return;
            }
    
            /** @var mixed|Convert $request */
            $request = $context->getRequest();
            if (false === $request instanceof Convert) {
                return;
            }
    
            /** @var mixed|PaymentInterface $payment */
            $payment = $request->getSource();
            if (false === $payment instanceof PaymentInterface) {
                return;
            }
    
            $order = $payment->getOrder();
            if (null === $order) {
                return;
            }
    
            $gateway = $context->getGateway();
            foreach ($order->getGiftCardOrders() as $giftCardOrder) {
                $giftCard = $giftCardOrder->getGiftCard();
                $couponId = sprintf('GIFT_CARD_%s', $giftCard->getCode());
                $retrieveCouponRequest = new RetrieveCoupon($couponId);
                try {
                    $gateway->execute($retrieveCouponRequest);
                } catch (ApiErrorException $e) {
                    $createCouponRequest = new CreateCoupon([
                        'id' => $couponId,
                        "amount_off" => $giftCard->getAmount()/100,
                        "currency" => $order->getCurrencyCode(),
                        "metadata" => [
                            'SYLIUS_GIFTCARD_ID' => $giftCard->getId(),
                            'SYLIUS_GIFTCARD_CODE' => $giftCard->getCode(),
                        ],
                        "name" => sprintf("Gift card #%d", $giftCard->getId()),
                    ]);
                    $gateway->execute($createCouponRequest);
                }
            }
        }
    }
    

    And here is the service declaration :

    services:
      App\GiftCard\Payum\Extension\CheckCouponsExtension:
        public: true
        tags:
          - name: payum.extension
            alias: app.extension.check_coupons
            factory: stripe_checkout_session