Docs

Catalog And Orders

Catalog And Orders This example covers a simple store flow: public products, authenticated checkout records, and server-side inventory checks. Tables Create shop_product . Field or relation Type Notes name string Required slug string Unique priceCents integer Required stock integ

Catalog And Orders

This example covers a simple store flow: public products, authenticated checkout records, and server-side inventory checks.

Tables

Create shop_product.

Field or relation Type Notes
name string Required
slug string Unique
priceCents integer Required
stock integer Required
status select draft, active, archived

Create shop_order.

Field or relation Type Notes
customer many-to-one relation to enfyra_user Required
status select pending, paid, fulfilled, canceled
totalCents integer Server-derived

Create shop_order_item.

Field or relation Type Notes
order many-to-one relation to shop_order Required
product many-to-one relation to shop_product Required
quantity integer Required
unitPriceCents integer Snapshot at checkout

Public Product Reads

Make GET /shop_product public and add a pre-hook.

@QUERY.filter = {
  _and: [
    @QUERY.filter || {},
    { status: { _eq: 'active' } }
  ]
};

Create Checkout Route

Create a custom POST /shop/checkout route. The route handler creates the order and item rows atomically from trusted product records.

if (!@USER?.id) {
  @THROW401();
}

const items = Array.isArray(@BODY.items) ? @BODY.items : [];
if (!items.length) {
  @THROW400('items are required');
}

const productIds = items.map((item) => item.product);
const productsResult = await #shop_product.find({
  filter: {
    id: { _in: productIds },
    status: { _eq: 'active' }
  },
  fields: 'id,name,priceCents,stock',
  limit: productIds.length
});

const products = productsResult.data || [];
if (products.length !== productIds.length) {
  @THROW400('one or more products are unavailable');
}

let totalCents = 0;
for (const item of items) {
  const product = products.find((row) => row.id === item.product);
  if (!product || item.quantity < 1 || item.quantity > product.stock) {
    @THROW400('invalid quantity');
  }
  totalCents += product.priceCents * item.quantity;
}

const orderResult = await #shop_order.create({
  data: {
    customer: { id: @USER.id },
    status: 'pending',
    totalCents
  }
});

const order = orderResult.data?.[0];
if (!order) {
  @THROW500('order was not created');
}

for (const item of items) {
  const product = products.find((row) => row.id === item.product);
  await #shop_order_item.create({
    data: {
      order: { id: order.id },
      product: { id: product.id },
      quantity: item.quantity,
      unitPriceCents: product.priceCents
    }
  });
}

return order;

Customer Order List

Add a GET /shop_order pre-hook so users only read their own orders.

if (@USER?.isRootAdmin) {
  return;
}

@QUERY.filter = {
  _and: [
    @QUERY.filter || {},
    { customer: { id: { _eq: @USER.id } } }
  ]
};
curl "$ENFYRA_API_URL/shop_order?fields=id,status,totalCents,createdAt&sort=-createdAt" \
  -H "Authorization: Bearer $ACCESS_TOKEN"