React
Hooks with TanStack Query, and a cart.
With TanStack Query the catalogue is cached, refetched in the background and shared between components.
npm install @tanstack/react-queryimport { useMutation, useQuery } from '@tanstack/react-query';
const BASE = 'https://api.cashiar.app/v1/storefront';
const KEY = import.meta.env.VITE_CASHIAR_KEY; // pk_live_…
async function call<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
...init,
headers: { Authorization: `Bearer ${KEY}`, ...init?.headers },
});
const data = await res.json();
if (!res.ok) throw new Error(data.message);
return data;
}
export const useShop = () =>
useQuery({ queryKey: ['shop'], queryFn: () => call<Shop>('/shop'), staleTime: 5 * 60_000 });
export const useProducts = (collection?: string) =>
useQuery({
queryKey: ['products', collection],
queryFn: () =>
call<{ items: Product[]; nextCursor: string | null }>(
`/products${collection ? `?collection=${collection}` : ''}`,
),
staleTime: 60_000,
});
export const usePlaceOrder = () =>
useMutation({
mutationFn: ({ order, idempotencyKey }: { order: Order; idempotencyKey: string }) =>
call<PlacedOrder>('/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey },
body: JSON.stringify(order),
}),
});import { useState } from 'react';
import { usePlaceOrder } from './cashiar';
export function Checkout({ lines }: { lines: CartLine[] }) {
// One key per checkout: a retry after a dropped connection never orders twice.
const [idempotencyKey] = useState(() => crypto.randomUUID());
const place = usePlaceOrder();
const submit = (form: FormData) =>
place.mutate(
{
idempotencyKey,
order: {
customerName: String(form.get('name')),
customerPhone: String(form.get('phone')),
fulfilment: 'pickup',
payNow: true,
lines,
},
},
{ onSuccess: (placed) => placed.payUrl && window.location.assign(placed.payUrl) },
);
return (
<form action={submit}>
<input name="name" placeholder="Your name" required />
<input name="phone" placeholder="024 000 0000" required />
<button disabled={place.isPending}>Pay now</button>
{place.error && <p role="alert">{place.error.message}</p>}
</form>
);
}Types for Shop, Product, Order and PlacedOrder can be generated from the OpenAPI document with a tool such as openapi-typescript:
npx openapi-typescript https://docs.cashiar.app/openapi/storefront.json -o src/cashiar-api.d.ts