commerce icon indicating copy to clipboard operation
commerce copied to clipboard

[5.x]: Return line item order errors in PaymentsController::actionPay()

Open timeverts opened this issue 8 months ago • 3 comments

What happened?

Description

I am developing a React application that uses JSON requests to call the Commerce action end points.

I have a custom line item rule being for a custom ticket purchasable that determines whether the maximum quantity of that purchasable against a third-party system.

        /**
         * Register validation rules for when adding Ticket purchasable to a line item.
         *
         * This event is called before a line item is created and also when an order is
         * updated, marked as completed or about to be paid.
         */
        Event::on(LineItem::class, Entry::EVENT_DEFINE_RULES, function(DefineRulesEvent $e) {
            /** @var LineItem $lineItem */
            $lineItem = $e->sender;

            if (!$lineItem->getPurchasable()) {
                return;
            }

            if ($lineItem->getPurchasable()::class !== Ticket::class) {
                return;
            }

            $maxPurchasableTicketQuantity = $lineItem->getPurchasable()->getMaxQuantity();

            $e->rules[] = ['qty', 'integer', 'min' => 1, 'max' => $maxPurchasableTicketQuantity];
        });

Say I have 5 tickets in my cart when I arrive at my payment step. I then complete the payment form and submit it. In the meantime (prior to submitting the payment form), it's possible that the maximum quantity of available tickets has now reduced to less than 5 (e.g. 1 max).

At some point in the PaymentsController::actionPay(), the line item validation is executing and detecting the qty is now invalid. This means the order now has errors. Consequently, this triggers the following response from actionPay() to be returned:

{
    "cart": {
       ...
    },
    "paymentFormErrors": [],
    "modelName": "paymentForm",
    "paymentForm": {
        "nonce": "tokencc_bj_znzy56_27bbxm_b5sr7x_37d5js_9q2",
        "storeInVault": false,
        "firstName": null,
        "lastName": null,
        "number": "",
        "month": null,
        "year": null,
        "cvv": null,
        "token": null,
        "expiry": null,
        "threeDSecure": false,
        "savePaymentSource": false
    },
    "errors": [],
    "message": "Invalid payment or order. Please review."
}

The problem is that the errors for the order are not accessible in the response, so there's no easy way to indicate to the user what the actual error is.

Debugging the PHP code in actionPay() shows that $order->getErrors() returns this:

 { ["lineItems.0.qty"]=> array(1) { [0]=> string(30) "Qty must be no greater than 1." } }

I would have expected the order errors are returned in the actionPay() response.

Craft CMS version

5.7.5

Craft Commerce version

5.3.12

PHP version

8.2

Operating system and version

No response

Database type and version

No response

Image driver and version

No response

Installed plugins and versions

timeverts avatar May 29 '25 03:05 timeverts

See also https://github.com/craftcms/commerce/issues/601.

timeverts avatar May 29 '25 03:05 timeverts

@timeverts thanks for the feedback. I agree we should be bubbling errors on the cart back during the payment response. Will look to make a fix soon.

lukeholder avatar Jun 03 '25 08:06 lukeholder

@timeverts have a potential fix in PR #4081 - will let you know here once this is included in the next release.

lukeholder avatar Jul 24 '25 03:07 lukeholder

Commerce 5.4.4 is out with that change.

brandonkelly avatar Jul 31 '25 18:07 brandonkelly