# Validate checkout

> Learn how to validate customer’s data.

To make sure you get all the data you need from your customers, Merx uses Kirby’s page validation. You can define the required fields and set `validate` options for each field you want to validate. See [Kirby’s documentation](https://getkirby.com/docs/reference/system/validators) for all available validators. If you need more specific validation, you can [create your own validators](https://getkirby.com/docs/reference/plugins/extensions/validators).

## Define fields

Set up the fields that are required to fulfill orders as a field group of your own. If you don’t create one, the [default personal data fields](https://merx2.wagnerwagner.de/reference/blueprints/fields/personal-data.md) are used, which only ask for an email address.

```yaml title="/site/blueprints/fields/personal-data.yml"
type: group
fields:
  email:
    label: Email
    type: email
    required: true
  given_name:
    label: Given name
    type: text
    required: true
    validate:
      minLength: 2
  family_name:
    label: Family name
    type: text
    required: true
  organization: # Optional field
    label: Organization
    type: text
# …
```

## Update checkout template

Add the new fields to the checkout template.

```php title="/site/templates/checkout.php"
…
<form id="checkout-form" action="<?= url('api/shop/checkout') ?>" method="post">
  <label>
    Email
    <input type="email" name="email" autocomplete="email" required>
  </label>
  <label>
    Given name 
    <input type="text" name="given_name" autocomplete="given-name" required>
  </label>
  <label>
    Family name
    <input type="text" name="family_name" autocomplete="family-name" required>
  </label>
  <label>
    Organization (optional)
    <input type="text" name="organization" autocomplete="organization">
  </label>
  <input type="hidden" name="payment_gateway" value="invoice">
  <button>Submit order</button>
</form>
…
```

> [!NOTE]
> To build these inputs from the blueprint instead of writing them out, see [Build the checkout form from the blueprint](https://merx2.wagnerwagner.de/cookbook/build-checkout-form-from-blueprint.md).


### Example response

When there are no validation errors, the API endpoint returns a redirect response, which redirects to the payment provider or directly to the order page.

When the validation fails, the [API returns a JSON](https://merx2.wagnerwagner.de/reference/api/routes/shop-checkout-post.md) with one or more error messages per invalid field.

```json title="POST https://shop.test/api/shop/checkout"
{
	"status": "error",
	"message": "Field validation failed.",
	"code": 400,
	"key": "error.merx.fieldsvalidation",
	"details": {
		"email": {
			"label": "Email",
			"message": {
				"required": "Please enter something"
			}
		},
		"given_name": {
			"label": "Given name",
			"message": {
				"required": "Please enter something",
				"minlength": "Please enter a longer value. (min. 2 characters)"
			}
		},
		"family_name": {
			"label": "Family name",
			"message": {
				"required": "Please enter something"
			}
		}
	}
}
```

## Use JavaScript to handle form submit

As in _[Adding products](https://merx2.wagnerwagner.de/guide/getting-started/adding-products.md#add-the-product-to-the-cart)_, we intercept the submit event of the checkout form, and use the validation details to show the error messages to the customer.

```js title="/assets/js/checkout.js"
const checkoutForm = document.getElementById('checkout-form');
checkoutForm.addEventListener('submit', async (event) => {
  event.preventDefault();
  const response = await fetch(checkoutForm.action, {
    method: 'post',
    body: new FormData(checkoutForm),
    headers: {
      'Accept': 'application/json',
    }
  });
  console.log(response);

  /** @see https://merx.wagnerwagner.de/reference/api/routes/shop-checkout-post */
  const json = await response.json();

  if (json.status === 'redirect') {
    // [1] Redirect user to payment provider or directly to order page
    window.location.href = json.redirectUrl;
  } else {
    // [2] Handle validation errors
    const errorDetails = json.details;
    console.log(errorDetails);
  }
});
```


### Complete validation handling


```js title="/assets/js/checkout.js"
const checkoutForm = document.getElementById('checkout-form');

/**
 * Creates a p element with class “error”.
 * @param {String} error e.g. “Please enter a valid email address”
 */
function createErrorElement(errorMessage) {
  return Object.assign(document.createElement('p'), {
    classList: 'error',
    innerText: errorMessage,
  });
}

/**
 * Removes all elements with class “error”.
 */
function clearErrors() {
  checkoutForm.querySelectorAll('.error').forEach((_) => _.remove());
}

checkoutForm.addEventListener('submit', async (event) => {
  event.preventDefault();

  clearErrors();

  const formData = new FormData(checkoutForm);

  // Disables all form fields to prevent multiple submissions
  [...checkoutForm.elements].forEach(_ => _.toggleAttribute('disabled', true));
  checkoutForm.setAttribute('data-state', 'loading');

  // Send form data to the API endpoint
  const response = await fetch(checkoutForm.action, {
    method: 'post',
    body: formData,
  });

  /** @see https://merx.wagnerwagner.de/reference/api/routes/shop-checkout-post */
  const json = await response.json();

  console.log(json);

  // Enables all form fields
  [...checkoutForm.elements].forEach(_ => _.toggleAttribute('disabled', false));
  checkoutForm.removeAttribute('data-state');

  if (json.status === 'redirect') {
    // [1] Redirect user to payment provider or directly to order page
    window.location.href = json.redirectUrl;
  } else {
    // [2] Handle validation errors
    try {
      if (json.key === 'error.merx.fieldsvalidation') {
        // Handle field errors
        const errorDetails = json.details;

        for (const [key, error] of Object.entries(errorDetails)) {
          const labelElement = checkoutForm.querySelector(`label:has([name="${key}"])`);

          if (labelElement) {
            for (const errorMessage of Object.values(error.message)) {
              // Display error message(s) inside of label element
              labelElement.appendChild(createErrorElement(errorMessage));
            }
          } else {
            throw json.message ?? 'Fatal error';
          }
        }
      } else {
        throw json.message ?? 'Fatal error';
      }
    } catch (errorMessage) {
      // Display general error message before submit button
      checkoutForm.insertBefore(createErrorElement(errorMessage), event.submitter)
    }
  }
});
```

> [!NOTE]
> You should add some CSS styles for `#checkout-form[data-state="loading"]`, so that the customer knows that something is happening.

## Checkout flow

We’re using *invoice* as the *payment gateway* here, which means it’s up to you as the shop operator to collect the money. 

In practice you might use a *payment provider* such as *Stripe* or *PayPal* to collect money from your customer during the checkout.

Depending on the payment provider **and** the payment gateway the checkout process might vary.

For some payment gateways the payment is handled asynchronously. This means that the money is received by the payment provider after hours or days. As soon as the money has been received, the payment provider notifies the Merx shop via [hook](https://merx2.wagnerwagner.de/reference/options/hooks/stripe-hooks.md).

1. Customer enters personal data, selects payment gateway and submits the checkout form.
2. Personal data is validated. When valid, the data is stored together with the cart in the customer’s session on the server. ([`/api/shop/checkout`](https://merx2.wagnerwagner.de/reference/api/routes/shop-checkout-post.md))
3. *Optional:* Payment details are validated and payment process is handled on page (e.g. *credit card* via *Stripe*) **or** customer is redirected to payment provider (e.g. *PayPal*) to handle the payment process.
4. Customer is redirected back to the shop. ([`/api/shop/success`](https://merx2.wagnerwagner.de/reference/api/routes/shop-success-get.md))
6. Order page is created with data from the session
7. *Optional:* Order is marked as complete via [hook](https://merx2.wagnerwagner.de/reference/options/hooks/stripe-hooks.md) (asynchronous payment).

Next, learn [how to show the order information](https://merx2.wagnerwagner.de/guide/getting-started/displaying-order.md) to the customer.
