Appearance
Store
Products with variants and prices in tables of their own, a cart, a checkout with taxes and delivery zones, orders with receipts — a store for a new site.
kitloom/ · admins: kitloom/, kitloom/
Video lessons
Recorded in the Filament admin; the Nova admin works the same way.
- Deliveries: the board from ready to delivered
- Orders: from pending to ready
- Products: prices, stock and variants
- Store settings: currency, checkout, orders
- Delivery zones, tax rates and coupons
A store for a new Laravel site: products with variants and prices in tables of their own, categories and tags, the catalogue and product pages, a cart, a checkout with taxes and delivery zones, orders paid through kitloom/payments, receipts, the customer's account. Not the WooCommerce shop (kitloom/shop) — that one carries a site over from WordPress; this one starts clean. The admin is kitloom/store-filament or kitloom/store-nova.
What a product is
A product is a post of the store_ type — its title, description, address, status, author, categories, tags and pictures are the post's, so SEO, search, revisions, translations and custom fields work as on any page — with its sale half in two tables of the store:
| Table | Row |
|---|---|
store_ | one per product: the kind (simple, variable), whether it is sellable, the tax class, the unit, the flags (featured, sold individually), the minimum quantity and step, the minimum age, the sale window, the options a variable product is picked by |
store_ | one per thing sold: the SKU, the values of the options it was made for, the price and sale price, the cost, the stock, the measures, a picture, whether it is active |
A simple product has one variant without values; a variable one a variant per combination it sells. Prices are kept in minor units (cents) with the store's currency; the forms, the sheets and the agent tools speak in decimals.
Screens
- Products — the list with the SKU, the price and the stock; the form with the post's fields and the "Store data" box: the kind, a simple product's price, SKU and stock, a variable product's options and variants, the tax class, the unit, the flags, the sale window.
- Categories — a tree, each with a picture and an order on the storefront.
- Tags — flat labels.
- Orders — the list by status with a search for the number, the customer or a product; an order's page with its items, totals, addresses, payment and delivery, the notes and the history. From it: Change status (with a reason; a settled order needs Force), Add note (the staff's own, or sent to the customer), Edit details (the email, the phone, the addresses, the note).
- Deliveries — the board: every order on its way, by tab (open, today's windows, on the way, pickups, all), with the window, the courier's status and tracking link, and the hands that move it: Move (a status with a reason), Call the courier (again, after a refusal), Call off.
- Customers — who ordered, how often, for how much, when last.
- Coupons — a code, percent off, an amount off or free delivery, from a subtotal, between dates, so many uses in all and per customer.
- Delivery zones — where the store delivers (countries, regions as
CA:, postcode patterns asMB R3*) and how: each method a type — a flat rate, free over an amount, pickup, local delivery — with its own settings. The first zone that fits the address serves it; a zone with nothing named fits everywhere, so it goes last. - Tax rates — a percentage by name (GST, PST, VAT) for a country, a region and a postcode pattern, on a tax class, by priority; a compound rate is taken on the price plus the rates before it. Prices are entered without tax unless Prices include tax is on.
- Settings — the currency (set it before the first product: prices are not converted), products per page, the default sorting, extra tax classes; the store's own address (the checkout starts from its country, a pickup is taxed there); the cart and checkout addresses; guest checkout; the order number prefix; where a new order is announced; the receipt's header; how long a guest's cart is kept.
How an order goes
Pending until the money is in — or, with an offline method such as a bank transfer, until the store starts on it — then paid, preparing, ready (for the counter or the courier), completed; cancelled, refunded or failed on the side. The stock is held from the moment the order is placed, given back when it is cancelled or fails, taken for good when it is completed. A declined payment fails the order, an abandoned one cancels it; an order still owed can be paid again or cancelled by the customer from its page. The customer hears of each step by email (and by text, with kitloom/text-messages): received, paid (with the receipt as a PDF), ready, completed, cancelled, refunded, and a note written for them; the store hears of an order to work on at the address in the settings.
How a delivery goes
Every order with a delivery method gets one delivery: the way chosen, the fee, the address or the pickup place, the window the customer picked at the checkout (delivery_, delivery_, optional). It is pending until the order is ready — the Ready status; or paid, as the setting Call the courier when the order is says — then a method carried by a courier (a Courier named by the shipping method) is handed over from the queue and becomes scheduled with the courier's id and tracking link, while a pickup becomes ready to collect. From there the courier's webhooks, store: (every fifteen minutes, for a courier without webhooks) or the board move it: picked up, out for delivery, delivered — which completes the order — or failed (the owner is told, the order waits for another try), returned, cancelled. A cancelled order calls its delivery off with the courier. The customer hears of it: out for delivery (with the tracking link), delivered, could not be delivered.
On the site
/is the catalogue,store/ /a product,store/ {slug}/ /a category with its subcategories' products,store/ category/ {path}/ /a tag.store/ tag/ {slug}/ - The catalogue is narrowed and sorted from the query string:
?q=a phrase (title, short description or SKU),?min_andprice= ?max_in the store's currency,price= ?in_,stock=1 ?orderby=one ofnewest,price,price-,desc name,featured, and?attr_for a product's options.{option}=value,value - A theme reads two page parts:
store(one page of product cards, the filters, the price bounds, the category tree, the options with their counts) andCatalog store(the card, the short description, the gallery, the options and the active variants, the spec rows, related products).Product php artisan kitloom:writes their shape.schema - A product the store no longer sells (Sellable off) keeps its page and stays out of the catalogue and the site's search.
/andcart/ /are pages of the store's own (the slugs are settings), with no WordPress page behind them;checkout/ /is where an order lands, by the key its customer holds. A theme draws them from thecheckout/ received/ {number}/ ?key=… store-,cart store-andcheckout store-templates and theorder- received store,Cart storeandCheckout storeparts;Order Received storeis on every page,Mini Cart storesays what the last action did.Notices - The forms post to
/:wp- json/ store/ v1/ cart/(add),lines cart/(a quantity),lines/ {id} cart/,lines/ {id}/ remove cart/,coupon cart/,coupon/ remove cart/(the method and address for the fees),delivery checkout,orders/,{number}/ pay orders/,{number}/ cancel orders/,{number}/ receipt account/. A form gets a redirect with a notice, a script (addresses Accept:) the priced cart or the order. Theapplication/ json urlsof each part name them. - The customer's account page gets Orders (
?section=store-) and Addresses (orders ?section=store-).addresses - Shopping carts live in a table: a customer's by their account, a guest's by a token in the session, joined on sign-in. The page cache leaves out the store's pages and every page of a visitor with a cart.
Console
bash
php artisan store:demo # three categories and a dozen products with pictures, GST and PST, a zone for Canada, a coupon, orders in every status
php artisan store:expire-carts # guest carts past their day, scheduled daily
php artisan store:sync-deliveries # ask the couriers how their active deliveries go, every fifteen minutesFor developers
Kitloom\writes a product the one way for every caller:Store\ Products\ Product Writer create($attributes, $actor, $terms, $featured,Image) update($product, $attributes, $actor, $terms, $featured,Image) update. The attributes are the post's (Store($product, $fields, $actor) title,content,excerpt,slug,status) and the store's (kind,sellable,tax_,class unit,sold_,individually min_,quantity step,min_,age featured,sale_,from sale_,until attributes,variants); a simple product may giveprice,sale_,price sku,stock,manage_at the top. Prices are decimals. The writer refuses a taken SKU, a sale price above the price, a value outside the options.stock Kitloom\after every write, with the actor.Store\ Events\ Product Saved - A vertical's fields on a product (a beer's strength, a plant's potency): a
Kitloom\taggedStore\ Contracts\ Product Extension Product—Extension:: TAG fields()by key (text,number,select,boolean,textarea, with a label, a unit, options),taxonomies()of the product type the module registered,defaults($attributes)for a new product of its own (['min_),age' => 18] specs($product)for the rows on the page. The values live in the product'sdata, the writer checks them, both admins draw them under Details, the sheet exports them, thestorepart carries the rows and theProduct taxonomies, and a taxonomy's page is a catalogue page (see kitloom/beer-store). - An amount beside a line's price (a container deposit, a fee): a
Kitloom\taggedStore\ Contracts\ Line Extras Line—Extras:: TAG extras($line)answeringCart\s (a key, a label, the amount per unit, a tax class). Summed by key on the cart (Extra extras,extras), taxed by their own class, never discounted, a line of their own on the order (Total data.), the receipt and the emails;extra Order::sums them back.extras() Kitloom\lists products byStore\ Catalog\ Product Catalog Catalog;Filters Productdraws a card.Cards - The cart:
Cart\(the request's —Shopping Cart add,set,Quantity remove,apply,Coupon set,Delivery priced()),Cart\(a cart priced for an address: today's prices, the coupon, the delivery quotes, the taxes, the total — aCart Pricing Priced).Cart - The checkout:
Checkout\writes the order (Checkout:: place($data, $priced Cart, $user) Orders\), holds the stock, counts the coupon and starts the payment;Order Writer Checkout\has the rules.Checkout Form Orders\moves an order;Order Status Changer:: change($order, $status, $actor, $force, $reason) Orders\writes notes;Order Notes Orders\changes one under its row lock. Events:Orders:: locked() Order,Placed Order(from, to, the actor, the reason),Status Changed Order.Note Added - Deliveries:
Deliveries\—Deliveries of($order),change($delivery, $status, $actor, $reason),apply($delivery, Courierfor what a courier's webhook learnt,Update) dispatch,cancel,sync,require($delivery, [...for what a vertical needs at the door; event]) Delivery.Status Changed - Contracts:
Contracts\taggedShipping Method Shipping— a delivery type of your own (Method:: TAG key,label,settings,Fields courier,quote);Contracts\taggedCourier Courier::— a courier called by API (TAG create,cancel,status, each answering aCourier); kitloom/inventory binds itsUpdate Ledgerover the store'sFor Store Stockwhen installed (the variants become its items on first contact, theirLedger stockis what the warehouse has available);Contracts\— the shelf (Stock Ledger available,reserve,release,fulfil), the store'sStock\bySimple Stock bind, a warehouse moduleIf binds its own;Contracts\taggedCheckout Step Checkout— fields a vertical adds to the checkout and what they write on the order'sStep:: TAG data;Contracts\— the receipt's engine, dompdf by default.Pdf Renderer - Offered to other modules:
NotificationsTopic store_,order. new store_andorder. cancelled store_; webhook topicsdelivery. failed store.,order. placed store.,order. paid store.,order. status store.(with kitloom/webhooks);delivery. status PersonalandData Exporter Personalover the orders and the saved addresses (with kitloom/privacy); the sheetsData Eraser store:andproduct store:; the agent toolsorder store_,products_ * store_,orders_ list store_,orders_ get store_,orders_ update_ status store_,orders_ add_ note store_; the public agent toolsdeliveries_ update store_,search store_,product store_; the trackers'cart_ link add_,to_ cart begin_andcheckout purchaseevents. - Tests:
Kitloom\boots the skeleton with the store's tables (Store\ Testing\ Store Test Case product(),category(),zone(),rate(),add,To Cart() placewith the fake gateway);Order() Testing\writes an order without the checkout;Makes Orders Testing\andFake Courier Testing\stand for a courier called by API.Courier Method - Money:
Kitloom\(minor units ↔ decimals),Store\ Support\ Money Price(the store's currency in the request's locale).Format - With kitloom/ai:
store_,products_ list store_,products_ get store_; with kitloom/spreadsheets: theproducts_ save store:sheet, a row per variant.product - Tests:
php vendor/from the application.bin/ phpunit -- testsuite Store