Skip to content

Step completion

Can I join the Hyvä Checkout Beta?

Would you like to join the list of Beta participants?
Existing Hyvä customers and service integrators can contact us in Slack or email checkout@hyva.io.

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\CheckoutCore\Model\Magewire\Component\EvaluationInterface interface.

<?php

namespace Hyva\CheckoutCore\Model\Magewire\Component;

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

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

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 evaluation result events are dispatched and messages are displayed.

Component evaluation results

Evaluation result instances are created by the \Hyva\CheckoutCore\Model\Magewire\Component\EvaluationResultFactory, which can be conveniently injected as a constructor argument into Magewire components.

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\CheckoutCore\Model\Magewire\Component\EvaluationInterface;
use Hyva\CheckoutCore\Model\Magewire\Component\EvaluationResultFactory;
use Hyva\CheckoutCore\Model\Magewire\Component\EvaluationResultInterface;
use Magewirephp\Magewire\Component;

class Example extends Component implements EvaluationInterface
{
    private EvaluationResultFactory $evaluationResultFactory;

    public function __construct(EvaluationResultFactory $evaluationResultFactory)
    {
        $this->evaluationResultFactory = $evaluationResultFactory;
    }

    public function evaluateCompletion(): EvaluationResultInterface
    {
        return $this->evaluationResultFactory
            ->createErrorMessage('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\CheckoutCore\Model\Magewire\Component\Evaluation\Success instance.

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

public function evaluateCompletion(): EvaluationResultInterface
{
    return $this->evaluationResultFactory->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(): EvaluationResultInterface
{
    return $this->evaluationResultFactory->createErrorMessage('Enter the correct value');
}

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

Evaluation\ErrorEvent

Returning an ErrorEvent evaluation result will prevent users from proceeding to the next step.
The JavaScript event evaluation: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(): EvaluationResultInterface
{
    return $this->evaluationResultFactory->createErrorEvent(['error' => 'Foo is too short.  Please check the frub.']);
}

Since all ErrorEvents dispatch the same event evaluation: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:error.window="isForMe($event) && (message = $event.detail.error)"

    >
        <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:error event and checking if the component ID matches, it is also possible to dispatch custom events instead:

public function evaluateCompletion(): EvaluationResultInterface
{
    return $this->evaluationResultFactory->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.error"
    >
        <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 to force a guest user on the Shipping step to enter an address and click the "Ship here" button, before they can click "Proceed to Payment" button.

As a rule of thumb, to provide a good user experience, a visitor should always only see a single enabled primary button. This gives them a clear direction what they need to do next.

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

public function evaluateCompletion(): EvaluationResultInterface
{
    return $this->evaluationResultFactory->createBlocking();
}