Skip to content

A beer store on Laravel, delivered by Uber: age gate, deposits and a courier at the door ​

A beer store that delivers has three questions a shoe shop never asks. Is the person ordering old enough, and is the person opening the door the same one? When may a van or a courier bring beer at all — the licence says ten to ten, not when the customer would like it? And what does a six-pack cost once the deposit on the cans is on the bill, taxed or not, as the province says? The shop itself — products, a cart, a checkout, orders — is ninety percent of the work and the same work as everywhere; the beer is the last ten, and it is the ten that gets a store fined.

kitloom answers it with two modules. kitloom/store is the shop: a Laravel-first store with tables of its own, built for a new site rather than one that came from WooCommerce. kitloom/beer-store is the vertical on top of it: what a beer is beyond a product, the age at every door, the deposits, the hours, and Uber Direct as the courier — with an ID check at the door written into the delivery. The admin is Filament or Nova; the theme that comes with it, Taproom, is a Vue storefront with the age gate over every page.

Ask the agent ​

With kitloom/ai connected, an agent runs the store with the rights of the user whose key it holds:

  • "Add Harbour Wit, a witbier from Harbour & Hop: five percent, 473 millilitre cans, sold as a single and a six-pack at 4.25 and 22." (store_products_save, with data for the ABV and the container and terms for the style and the brewery)
  • "Which orders are waiting to be packed?" (store_orders_list)
  • "Order 1042 is packed — mark it ready." (store_orders_update_status; the courier is called by it)
  • "Tell the customer of 1042 the keg comes at six, not four." (store_orders_add_note, sent to the customer)
  • "The driver could not find 1038 — put the delivery back on the board for tomorrow." (store_deliveries_update)

Every tool calls the same service the admin's button does, as the agent's user, so the order's history names who did what. Nothing is deleted: a product is made unsellable, an order is cancelled, a delivery is called off. describe_site tells the agent the store's statuses, the couriers it has, and the beer's fields and taxonomies, so it writes a beer as the owner would.

The age, at every door ​

Selling alcohol online is three checks, and the module makes all three one setting: Minimum age, eighteen in Manitoba, Quebec and Alberta, nineteen elsewhere in Canada, twenty-one in the US.

  • On the site, an age gate: every page asks "Are you 18 or older?" before it shows anything. The theme draws it in the browser and keeps the answer in a cookie, because the page cache keeps one page for everyone; No sends the visitor to the exit address, Google by default.
  • At the checkout, the date of birth, asked when the cart holds anything with a minimum age and refused under it. The order keeps it.
  • At the door, the delivery carries what the courier must do before handing over — an ID for the minimum age, a signature, a photo — and a courier called by API gets it as its verification. A beer that nobody of age signs for comes back, and the owner is told: ID refused at the door for order 1042, in Telegram, Slack or by email with kitloom/notifications.

A beer starts at the minimum age the moment it has a container or a strength; a glass or a tote on the same shelf is for anyone.

What a beer is ​

A beer is a product of the store with a box of its own on the form, Details: the alcohol by volume, the bitterness in IBU, the volume of one container, and what the container is — a can, a bottle, a keg, a growler. Its style and brewery are taxonomies of the product type with pages of their own, /store/style/ipa/ and /store/brewery/north-fork/, drawn by the catalogue like a category. A beer sold by the pack is a variable product with the option Pack and a variant per size, each with its own price and stock. With kitloom/inventory the product page shows the best-before date of the lot on the shelf.

Deposits are a table the owner edits: one row per kind of container, the amount per container, the tax class the province taxes it by — zero in Manitoba. A six-pack of cans carries six deposits beside its price, as a line of its own on the order, the receipt and the emails, never discounted by a coupon, so the refund with the empties is one line to find.

Hours are the licence's: per weekday, ten to twenty-two, a day left empty closed. A delivery window the customer picks outside them is refused at the checkout with the hours in the message, an order placed while closed without a window too, and Uber is not asked for a quote outside them.

Uber Direct at the door ​

Uber Direct is the courier: a method of the delivery zone, like a flat rate or pickup, and a courier the store calls by API. At the checkout, once the customer's city or postcode is known, Uber quotes the fee and the time — plus the markup the owner adds — and the customer picks it beside the store's own ways. When the order is ready (or paid, as the store's Call the courier setting says) the delivery is made at Uber: meet at the door, bring it back when nobody of age is there, the ID and the signature as the delivery requires. Uber's webhooks move it on the board — scheduled, picked up, on the way, delivered, which completes the order — and a status missed by a webhook is asked for every fifteen minutes.

Setting it up is the Customer ID, Client ID and Secret from the Uber Direct dashboard and the webhook's signing key, on the Beer Store settings page; the secret and the key are kept encrypted and never shown again, and Check Uber access asks Uber with what is saved. Test mode runs the deliveries with Uber's robot courier, so the whole flow is tried before a real van moves.

The store underneath ​

kitloom/store is a Laravel package that happens to be a shop, not a WooCommerce in Laravel. It is for a new site; a site that comes from WordPress keeps kitloom/shop, which reads WooCommerce's data as it is.

  • Products are pages of the site — SEO, search, revisions, translations and custom fields work as on any page — with their sale half in two tables: the product's row (kind, tax class, unit, flags, the minimum age, the sale window, the options) and a variant per thing sold (SKU, price, stock, measures, a picture).
  • The cart and the checkout are pages of the store's own, /cart/ and /checkout/, with no WordPress page behind them; a theme draws them from their parts. The cart is a table — a customer's by their account, a guest's by a token, joined on sign-in.
  • Taxes are rates by name for a country, a region and a postcode pattern, on tax classes, applied in order, compound or not, at the delivery address — a pickup at the store. Delivery zones are places with their ways: a flat rate, free over an amount, pickup, the store's own driver, and the couriers the modules add. Coupons take a percent, an amount or the delivery off.
  • Orders go pending, paid, preparing, ready, completed; cancelled, refunded or failed on the side. The stock is held from the moment an order is placed, given back when it is cancelled, taken for good when it completes. Payment is kitloom/payments: Stripe, PayPal, Mollie, Square or an offline method; a declined payment fails the order, an abandoned one cancels it. The customer hears of every step by email, and by text with kitloom/text-messages; a paid order gets its receipt as a PDF.
  • Deliveries are one row per order: the way, the window, the address or the pickup place, the courier's id and tracking link, what must be checked at the door. The board shows the day's work by tab — open, today, on the way, pickups — and moves a delivery by hand where no courier does.
  • The customer's account gets Orders and Addresses; a visitor's own AI assistant gets store_search, store_product and store_cart_link through kitloom/public-agents, so Claude can fill a cart for them.

For a Laravel developer ​

Everything is contracts and tags, so the beer store names nothing in the core and the core names no beer:

  • Kitloom\Store\Contracts\ProductExtension — a vertical's fields on a product (kept in the product's data, drawn under Details by both admins, exported by the sheet, on the storeProduct part), its taxonomies, the defaults of its own products, the rows on the page.
  • Kitloom\Store\Contracts\LineExtras — an amount beside a line's price: the deposit is one.
  • Kitloom\Store\Contracts\CheckoutStep — fields the checkout asks, their rules, what they write on the order and on the delivery: the date of birth and the hours.
  • Kitloom\Store\Contracts\ShippingMethod and Courier — a way of delivering that quotes a fee, and the courier that carries it: Uber Direct is a class of each, on Laravel's HTTP client with client credentials, nothing more.
php
// A beer, written as the admin's form or an agent writes it.
app(\Kitloom\Store\Products\ProductWriter::class)->create([
    'title' => 'Harbour Wit', 'status' => 'publish', 'kind' => 'variable',
    'data' => ['abv' => 5.0, 'volume' => 473, 'container' => 'can'],
    'attributes' => [['name' => 'Pack', 'values' => ['1', '6']]],
    'variants' => [
        ['sku' => 'HH-WIT-1', 'values' => ['Pack' => '1'], 'price' => '4.25', 'stock' => 40],
        ['sku' => 'HH-WIT-6', 'values' => ['Pack' => '6'], 'price' => '22.00', 'stock' => 15],
    ],
], $user, ['beer_style' => [$wheat->term_id], 'beer_brewery' => [$harbour->term_id]]);

Prices are decimals in the calls and minor units in the tables; every write takes the acting user; the notices, webhooks (store.order.placed, store.delivery.status), the sheets, the privacy exporter and the agent tools are all tagged, so each is optional. The next vertical — a cannabis store with its own limits per order — is the same four contracts again.

Watch it ​

Products: prices, stock and variants
Orders: from pending to ready
Deliveries: the board from ready to delivered
Delivery zones, tax rates and coupons
Store settings: currency, checkout, orders
Beers and deposits
Age, hours and the door: the beer store settings

Install it ​

Both modules come with kitloom, for either admin. On a new Filament site:

bash
composer require kitloom/store kitloom/store-filament kitloom/beer-store kitloom/beer-store-filament kitloom/taproom-theme
php artisan migrate
php artisan beer-store:demo
php artisan site:theme Taproom

On a Nova site the admin packages end in -nova. The demo fills the shelf — five styles, four breweries, a dozen beers by the pack, the deposits, the hours, Uber Direct on the zone, orders in every status — so the store looks lived in before the first real beer is typed. Every detail is in the guides: Store, Beer Store.

Last updated:

kitloom — modules and themes for Laravel.
Contact · Pricing · Services · License · Refunds · Privacy · Community · YouTube · [email protected]