Using API with PHP
Every cart and checkout operation is available in PHP, for shops that render the cart server-side.
The API exists so your pages can stay cacheable. When that does not matter — a small catalog, no page cache, or a shop that has to work without JavaScript — you can do the same things in a template, a controller or a route.
Both work on the same cart. $site->cart() and the API read from and write to the same session, so we can mix them freely.
| API route | PHP |
|---|---|
GET shop/cart |
$site->cart() |
POST shop/cart |
$cart->add() |
PATCH shop/cart |
$cart->updateItem() |
DELETE shop/cart |
$cart->remove() |
POST shop/checkout |
$merx->initializeOrder() |
GET shop/success |
$merx->createOrder() |
Showing the cart
$site->cart() returns the visitor’s Cart. It is a collection of ListItem objects, so you can loop over it like any Kirby collection.
<?php $cart = $site->cart() ?>
<?php if ($cart->count() === 0): ?>
<p>Your cart is empty.</p>
<?php else: ?>
<ul>
<?php foreach ($cart as $item): ?>
<?php /** @var \Wagnerwagner\Merx\ListItem $item */ ?>
<li>
<?= $item->quantity() ?> × <?= html($item->title()) ?>
<?= $item->total() ?>
</li>
<?php endforeach ?>
</ul>
<p>Total: <?= $cart->total() ?></p>
<?php endif ?>
Because this snippet makes the page different for every visitor, the page that includes it can no longer be cached.
Changing the cart
Merx has no route that adds an item and sends the customer back to where they came from — the API answers with JSON, which is what a JavaScript client wants, but not what a browser should be shown. Let’s add a route of our own for that.
<?php
return [
'routes' => [
[
'pattern' => 'cart/add',
'method' => 'POST',
'action' => function () {
$request = kirby()->request();
if (csrf((string)$request->get('csrf')) !== true) {
go('/', 303);
}
try {
cart()->add([
'page' => (string)$request->get('page'),
'quantity' => (float)$request->get('quantity', 1),
]);
} catch (\Kirby\Exception\Exception $exception) {
// Show the message on the next page, e.g. via the session
kirby()->session()->set('cartError', $exception->getMessage());
}
go($request->get('redirect') ?? '/', 303);
},
],
],
];
The form posts to that route instead of the API endpoint and tells it where to go afterwards.
<?php /** @var \Wagnerwagner\Merx\ProductPage $page */ ?>
<span class="price"><?= $page->price() ?></span>
<form action="<?= url('cart/add') ?>" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>">
<input type="hidden" name="page" value="<?= $page->uuid() ?>">
<input type="hidden" name="redirect" value="<?= $page->url() ?>">
<input type="number" name="quantity" min="1" value="1">
<button>Add to cart</button>
</form>
$cart->remove() and $cart->updateItem() work the same way. Both take the item’s key, which is the page id unless you set one yourself.
cart()->remove($request->get('key'));
cart()->updateItem($request->get('key'), [
'quantity' => (float)$request->get('quantity'),
]);
Never trust what the form sends. The API filters the request body before it reaches the cart, and your own route has to do the same. Cast quantity to a number, and do not pass the whole request body through — a price in it would replace the product’s own.
Checkout
The checkout needs no route of your own. /api/shop/checkout answers a browser with a redirect and a JavaScript client with JSON, so the plain form from Create checkout already works without JavaScript.
The same is true for the way back: api/shop/success completes the payment, empties the cart and redirects to the order page on its own.