Tài liệu

Catalog và đơn hàng

Catalog và đơn hàng Ví dụ này bao quát một luồng cửa hàng đơn giản: sản phẩm công khai, bản ghi checkout của người dùng đã xác thực và kiểm tra tồn kho ở server. Table Tạo shop_product . Field hoặc relation Kiểu Ghi chú name string Bắt buộc slug string Duy nhất priceCents integer

Catalog và đơn hàng

Ví dụ này bao quát một luồng cửa hàng đơn giản: sản phẩm công khai, bản ghi checkout của người dùng đã xác thực và kiểm tra tồn kho ở server.

Table

Tạo shop_product.

Field hoặc relation Kiểu Ghi chú
name string Bắt buộc
slug string Duy nhất
priceCents integer Bắt buộc
stock integer Bắt buộc
status select draft, active, archived

Tạo shop_order.

Field hoặc relation Kiểu Ghi chú
customer relation many-to-one tới enfyra_user Bắt buộc
status select pending, paid, fulfilled, canceled
totalCents integer Do server tính

Tạo shop_order_item.

Field hoặc relation Kiểu Ghi chú
order relation many-to-one tới shop_order Bắt buộc
product relation many-to-one tới shop_product Bắt buộc
quantity integer Bắt buộc
unitPriceCents integer Snapshot tại thời điểm checkout

Đọc sản phẩm công khai

Cho phép công khai GET /shop_product và thêm pre-hook.

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

Tạo checkout route

Tạo custom route POST /shop/checkout. Route handler tạo bản ghi đơn hàng và các item một cách nguyên tử từ dữ liệu sản phẩm đáng tin cậy.

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;

Danh sách đơn hàng của khách

Thêm GET /shop_order pre-hook để người dùng chỉ đọc được đơn hàng của họ.

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"