Skip to content

Step completion

Checkout component evaluation

Checkouts consist of one or more steps, for example "Shipping", "Billing" and "Review".
It is important a user is only able to proceed to the next step once the previous ones are complete.

For example, on the shipping step in the default checkout, a shipping address needs to be entered and a shipping method needs to be selected before a customer may proceed to the payment step.

The shipping address form and the shipping method selection are two distinct components.
Only when both are completed the customer can go on to the next step.

To enable this behavior, components can implement the \Hyva\Checkout\Model\Magewire\Component\EvaluationInterface interface.

<?php

namespace Hyva\Checkout\Model\Magewire\Component;

interface EvaluationInterface
{
    public function evaluateCompletion(EvaluationResultFactory $factory): EvaluationResultInterface;
}

The returned value can be an error message, an error event, a blocking or a success result as described below.

The evaluation process

Under the hood a EvaluationInterface component is evaluated each time it is instantiated, that is, during each request. Even when a component is rendered during the initial preceding request, it is evaluated.
The returned EvaluationResult data is part of the component state.

The result data is stored in the JavaScript component representation.

However, the evaluation results are only stored on the component - they are not immediately processed on the front end.
Initially, no evaluation events are dispatched and no evaluation messages are displayed.

Only when the "Proceed" button is clicked, the results of the last evaluation for each component are processed.
This is when the JavaScript evaluation result events are dispatched and messages are displayed.

Component evaluation results

Evaluation result instances are created by the \Hyva\Checkout\Model\Magewire\Component\EvaluationResultFactory, which is conveniently passed as an argument to the evaluateCompletion() method.

The EvaluationResultFactory provides a number of factory methods to indicate if a component is "complete":

  • createSuccess()
  • createErrorMessage()
  • createErrorEvent()
  • createBlocking()

Each of those returns an EvaluationResultInterface implementation matching the method name.

For example, the following component will never evaluate successfully.
Every time a visitor wants to proceed to the next step, a message is displayed and they are never allow to continue.

<?php

namespace My\Module\Magewire;

use Hyva\Checkout\Model\Magewire\Component\EvaluationInterface;
use Hyva\Checkout\Model\Magewire\Component\EvaluationResultFactory;
use Hyva\Checkout\Model\Magewire\Component\EvaluationResultInterface;
use Magewirephp\Magewire\Component;

class Example extends Component implements EvaluationInterface
{
    public function evaluateCompletion(EvaluationResultFactory $factory): EvaluationResultInterface
    {
        return $factory->createErrorMessage((string) __('YOU SHALL NOT PASS!'));
    }
}

The following event types are available for components:

Evaluation\Success

To indicate the customer is allowed to proceed to the next step, a component can return a \Hyva\Checkout\Model\Magewire\Component\Evaluation\Success instance.

It can be instantiated by calling Component\EvaluationResultFactory::createSuccess().

public function evaluateCompletion(EvaluationResultFactory $factory): EvaluationResultInterface
{
    return $factory->createSuccess();
}

Evaluation\ErrorMessage

Returning an ErrorMessage evaluation result will prevent users from proceeding to the next step.
Additionally, the specified error message will be displayed as a flash message above the checkout.
(To display a message on the component, see the description for the ErrorEvent result below)

It is instantiated as follows:

public function evaluateCompletion(EvaluationResultFactory $factory): EvaluationResultInterface
{
    return $factory->createErrorMessage((string) __('Enter the correct value'));
}

Two optional additional arguments are available to specify a different message type (for example 'warning') and the duration in milliseconds.

Evaluation\ErrorEvent

Returning an ErrorEvent evaluation result will prevent users from proceeding to the next step.
The JavaScript event evaluation:event:error will be dispatched together with the specified data as the payload.

This is useful to display error messages on a component itself, rather than displaying the message at the top of the page.

This result type is created by calling Component\EvaluationResultFactory::createErrorEvent().

public function evaluateCompletion(EvaluationResultFactory $factory): EvaluationResultInterface
{
    return $factory->createErrorEvent([
        'error' => __('Foo is too short. Please check the frub is blarg.')
    ]);
}

Since all ErrorEvents dispatch the same event evaluation:event:error, observing components need to check if the event was dispatched for themselves by checking if the component ID matches.

For example, the template code could look something like this:

<div>
    <div x-data="{
      message: '',
      isForMe(event) {
        return event.detail.component && event.detail.component.id === '<?= $escaper->escapeJs($block->getNameInLayout()) ?>';
      }
    }"
         x-on:evaluation:event:error.window="isForMe($event) && (message = $event.detail.message)"

    >
        <template x-if="message.length > 0">
            <div class="bg-red-500 rounded-lg p-4 text-white" x-text="message"></div>
        </template>
    </div>
</div>

The event needs to be captured on the window scope (not the component itself)

Instead of observing the generic evaluation:event:error event and checking if the component ID matches, it is also possible to dispatch custom events instead:

public function evaluateCompletion(EvaluationResultFactory $factory): EvaluationResultInterface
{
    return $factory->createErrorEvent(
        ['error' => 'Ping'],
        'my-custom-evaluation-failure-event'
    );
}

For custom events the frontend code can be simplified, since the component ID check can be omitted:

<div>
    <div x-data="{ message: '' }"
         @my-custom-evaluation-failure-event.window="message = $event.detail.message"
    >
        <template x-if="message.length > 0">
            <div class="bg-red-500 rounded-lg p-4 text-white" x-text="message"></div>
        </template>
    </div>
</div>

Evaluation\Blocking

As long as an EvaluationInterface component returns a Blocking evaluation result, the button to proceed to the next step is disabled.
This is used for example on the Payment step, to force a user provide the required data for the selected payment method, before they can click the "Place Order" button.

The Blocking result type is created by calling Component\EvaluationResultFactory::createBlocking().

public function evaluateCompletion(EvaluationResultFactory $factory): EvaluationResultInterface
{
    return $factory->createBlocking();
}