ADMINISTRATION & USER GUIDE
Platform administration (/admin)
- Promote an account:
UPDATE "User" SET "platformRole"='ADMIN' WHERE email='you@company.com';(SUPPORT = read metadata + request access only). - Tenants: list with industry, plan, subscription state, user counts. ADMIN can suspend/reactivate a tenant (reason required, audited). Suspended tenants lose access immediately.
- Plans: edit name, price (stored in minor units), interval, trial days, module entitlements and usage limits (blank = unlimited). Changes affect entitlement checks immediately; existing data is never deleted when a module/limit is removed (writes may be refused; reads continue).
- Entitlement overrides (per tenant) are applied through
grantEntitlements(audited). - Support access: SUPPORT/ADMIN request time-boxed access with a reason; the business owner approves in Settings → Platform support access; the grant yields a read-only context; each use is audited; owners can revoke.
- Feature flags (
/admin/flags): code defaults with DB overrides and tenant-targeted rollout;signups,online_checkoutandproduct_importare enforced. - System (
/admin/system): health snapshot, recent job runs, failed/DEAD jobs with admin retry (exponential backoff, then DEAD). - Plans: monthly and annual price; annual renewals extend the period by a year. Tenants pay from Billing and get a receipt under Billing → Invoices; modules can be toggled in Settings when the pack offers them and the plan entitles them.
- Scheduled jobs run via
/api/cron/jobs(Vercel Cron) ornpm run worker. - Not yet built: per-business usage dashboards.
Platform console (/admin)
- Overview: business counts, paying/trial/lapsed, estimated monthly recurring revenue, signups per week, plan and industry mix, and a "Needs attention" list (verification requests, trials ending in 3 days, overdue payments, locked-out owners, owners who never verified their email).
- Businesses (
/admin/tenants): search by business name or owner email; filter by status, plan, industry and verified. Manage opens one business:- Change plan (ADMIN): move to any plan, choose monthly or annual, and optionally mark it active for N days (for a bank-transfer payment) or start an N-day trial. A reason is required and is audited. Nothing is deleted on a downgrade. CRM, HR and Insights switch on automatically if the new plan includes them and the business never had a switch for them.
- Entitlement overrides (ADMIN): extra modules and higher limits for that one business. Saving replaces the previous overrides; clearing everything removes them.
- Owner tools: resend the verification email, unlock a locked-out account (SUPPORT or ADMIN), reset two-factor sign-in (ADMIN only; confirm identity out-of-band first).
- Internal notes (never shown to the business), suspend/reactivate, support-access request, and the audit trail of platform actions on that business.
- Verification (
/admin/verifications): owners apply under Settings → Business verification with their registered name and registration number. An ADMIN approves (the business gets the badge and a notification) or declines with a reason. You can also verify or remove the badge directly from a business's Manage page. The platform does not check registries automatically: you check the registration number yourself (for example on the CAC search) before approving. - Verified badge shows in the business's sidebar, on its receipts and invoices (web and PDF) and in the admin directory.
- Roles: SUPPORT can look, add notes, unlock accounts and resend emails. Plan changes, overrides, suspension, verification decisions and 2FA resets are ADMIN-only.
Tenant administration (business owner)
Settings: tax rates (fractions with effective dates), receipt text, payment methods, inventory policy (negative stock off by default; expiry warning window), branding, support-access approvals. Team: invite by email, assign roles (you can only grant permissions you hold), remove members. Billing: usage vs limits, plan switch.
Onboarding flow
Sign up → (email verification link; banner until verified) → wizard: business profile + industry → industry questionnaire (drives modules/workflows) → operations (branch name, payment methods) → plan → workspace with a setup checklist computed from real data (products, tax, supplier, stock, team, first sale). Products: manual form, or CSV import (Products → Import CSV, download the industry template, validate, fix errors using the error report, then import — all-or-nothing).
Changing configuration later does not alter past transactions: tax rates are effective-dated, pack versions are stored, ledger entries are immutable.
Loyalty, deliveries and sales reps
- Loyalty (Settings → Loyalty programme, shown when the programme is enabled in the setup questionnaire): points earned per 1 spent (0.01 = 1 point per 100) and value of 1 point when redeemed. Customers earn on money paid (not on points tendered or unpaid credit); at the POS choose the customer, enter points to redeem; returns and voids reverse earned points and restore redeemed ones in proportion. Staff with
loyalty.adjustcan correct balances (reason required, audited; balances cannot go negative). Redemptions post to ledger account 5200 Loyalty redemptions. Points liability is not accrued — agree the accounting treatment with your accountant. - Deliveries (wholesale): on a sale choose Create delivery; the Deliveries board moves it pick → pack → dispatch (driver + vehicle required) → delivered (recipient name required = proof; add a note such as a signed-slip number). A failed delivery needs a reason and can be re-queued. Voided/refunded sales cannot be delivered. Role
warehouse_staffcan run this without seeing finances. - Sales reps & commission: enable “sales representatives” in setup; give a member the Sales representative role and set their commission % under Team → Manage. Sales by that user (or sales where staff pick the rep at the POS) are credited; the Reports page shows net revenue (ex-tax, after returns) × rate. How/when commission is paid is outside the system.
Two-factor authentication (MFA)
- Users enable it at Account security (
/account/security): scan the QR code (or type the key) in any authenticator app, confirm a code, save the 10 recovery codes. - Required for roles that can manage staff, billing, settings, finance posting or safety overrides — 7-day grace after joining, then blocked until enrolled. Owners can extend the requirement to all members (Settings). Platform admins/support must enrol before using
/admin(no grace). - Lost device: (1) user signs in with a recovery code and re-enrols; (2) otherwise a business owner resets a member's MFA from Team → Manage (not for owners); (3) an owner who has lost both device and recovery codes: platform ADMIN calls
adminResetMfaafter verifying identity out-of-band (record the reason; it is audited). Never reset on the strength of an email alone. - Manual reset in an emergency (database owner):
UPDATE "User" SET "mfaSecretEnc"=NULL,"mfaPendingSecretEnc"=NULL,"mfaEnabledAt"=NULL,"mfaLastStep"=NULL WHERE email='…'; DELETE FROM "MfaRecoveryCode" WHERE "userId"=…; UPDATE "Session" SET "revokedAt"=now() WHERE "userId"=…;and record an audit entry. APP_SECRETmust be ≥32 chars and stable. Changing it makes stored authenticator seeds undecryptable; all enrolled users would have to be reset and re-enrol.
Subscription lifecycle
Trial (plan trialDays) → ACTIVE via verified payment → on failure PAST_DUE with 7-day grace → READ_ONLY (reads/exports allowed, writes rejected with a clear message, banner shown). Cancel keeps access until period end.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| "Insufficient sellable stock" for a product that shows stock | Stock is in expired/quarantined/recalled batches, reserved, or in another location |
| "requires a batch number / expiry date" | Product is batch-tracked (pharmacy default); enter both when receiving |
| "No conversion defined from X" | Add the pack size on the product (e.g. 1 sack = 50 kg) |
| "Your subscription has lapsed" | Trial/period ended; renew in Billing. Data is intact |
| Verification/reset emails not arriving | No production email adapter configured yet (see INTEGRATIONS.md); in dev the link is printed in the server console |
| Tests: "Refusing to run" | TEST_DATABASE_URL database name must contain test |
Release procedure
See PRODUCTION_DEPLOYMENT.md §5. Tag the commit, keep the previous image, run migrations as a separate step, run the smoke test, record the result.
Products: edit, deactivate, delete
- Edit: Products → Edit on a row (name, barcode, price, reorder level, category, tax, industry details). SKU and base unit are fixed once created. Stock quantity is never edited on the product — use Purchasing → Receive or Inventory → Adjust so the stock ledger stays correct.
- Deactivate / Reactivate: hides a product from the till and selling screens but keeps it in reports. Show inactive on the Products page lists them.
- Delete: on the product's edit page. Allowed only if the product never had stock movements, purchases, sales, counts, serials, dispensing or recipe use; otherwise it must be deactivated so the books stay intact. Deletions are audited.
Onboarding with an Excel sheet (Products → Import from Excel)
A business can bring its whole catalogue in one upload: Products → Import from Excel (also linked from the dashboard checklist).
- Download the template (.xlsx, or CSV). It has three sheets: Start here (instructions), Products (one row per product; Unit and Pack unit have drop-downs) and Units (valid unit codes). Industry-specific attributes (for example a pharmacy's Generic name) get their own columns.
- Upload the file (.xlsx or .csv, max 4 MB / 5,000 rows). We map headers loosely (
Cost Price,Qty,Vendor… are understood) and show a preview: counts, opening-stock value, suppliers/categories that will be created, and any row problems with a downloadable problem list. Nothing is saved at this stage. - Import. It is all-or-nothing: if any row has an error, nothing is imported. Otherwise, in one transaction, it creates the suppliers and categories, the products (buying price, selling price, preferred supplier, pack sizes, reorder level) and the opening stock.
Rules worth knowing:
- Products whose SKU already exists are skipped, never changed, so the same file can be uploaded again safely. A blank SKU gets an automatic
P-0001-style code. - Opening stock accounting: each stocked product gets an
ADJUSTMENTmovement (sourceOpeningStock) at its buying price, which also seeds its average cost. The journal entry is Dr Inventory (1200) / Cr Opening balance equity (3000). It is stock the business already owns, so no supplier bill, payable or income is created. - Batch/expiry businesses (pharmacy) must give a Batch number and Expiry date for any row with stock. Serial/IMEI-tracked items (electronics) are created, but their stock must be received under Purchasing, and the preview says so.
- Needs the
import.runpermission, the imports feature flag, a writable subscription and room under the plan's product limit. - Everything imported is audited (
import.onboarding) and can afterwards be edited like any other product.
Sharing receipts (Sales → open a sale)
Every sale page has Send or download this receipt:
- Download PDF (always available to staff with
sales.read). - Create shareable link: a private, unguessable link (
/r/<token>) the customer opens without an account. It shows a clean receipt with a Download-PDF button. One live link per receipt; Turn link off kills it permanently (a new link gets a new token). - Share on WhatsApp: opens WhatsApp with a ready message and the link, addressed to the customer's saved number (Nigerian
0803…numbers are converted to234803…); with no saved number you choose the contact in WhatsApp. - Email it: sends the link from the platform's verified sender, with the business name as the display name and the business email as reply-to. Rate-limited to 100 emails/hour per business. The recipient address is never written to the audit log.
The public page shows only customer-facing facts (items, prices, payments, balance, business contact), never cost, margin, cashier or internal ids.
APP_URLmust be set so links point at the right domain. PDFs use standard fonts, so the ₦ sign prints as the currency code (NGN) and accents are simplified (ọ→o).
Invoices and getting paid (Invoices menu)
- Set up where money goes (Settings → Collect payments): connect Paystack / Flutterwave / Korapay with your own keys and/or add bank-transfer details. Press Test connection, and paste the shown webhook link into the provider dashboard.
- Create an invoice (Invoices → New invoice): choose the customer, items, due date. Issuing it takes the stock out and records the amount owed to you (it is an unpaid sale; no cashier shift is needed and till-credit limits don't apply). Payments are recorded against it afterwards. Invoices use the sale number series (S-MAIN-000123). Items that need serial numbers, batches or a prescription can't be invoiced from this form yet; use the till for those.
- Send it: on the invoice page choose Create shareable link, then WhatsApp, Email, or copy the link. Download PDF is also there.
- The customer opens the link (no account) and can: pay online (Paystack/Flutterwave/Korapay; full or part), or pay by bank transfer and press "I've made the transfer".
- Online: the invoice is marked paid only after the provider confirms the exact amount and currency to our server (webhook and/or the return page; both are idempotent). A wrong amount, or money arriving after the invoice was voided, is NOT applied: it appears under Payments to check and as a notification.
- Transfer: the business gets a notification and a Payments to check card on the invoice. After seeing the money in the bank, press Money received: record it (the amount can be edited); or Not received. Nothing is recorded until you confirm.
- Status in the Invoices list: Unpaid, Part paid, Paid, Overdue (past due date), Void. Accounting: issuing posts the receivable and revenue; each payment posts cash/bank against the receivable, exactly like a till credit sale. Online payments appear as payment method = the provider name.
Accounting (Accounting menu; permission finance.read, posting needs finance.post)
Sales, purchases, payments, expenses and stock movements already post balanced entries automatically. Accounting is where you see and adjust them:
- Overview: cash, receivables, stock, payables, equity, profit this year, and a check that the books balance. Close the books sets a lock date; manual journals can't be dated on or before it.
- Chart of accounts: built-in accounts (cannot be renamed or hidden: reports depend on them) plus your own. Codes are 4–6 digits and the first digit decides the type (1 assets, 2 liabilities, 3 equity, 4 revenue, 5–6 expenses). An account can be hidden only when its balance is zero.
- Journal: all entries; New journal entry posts a balanced manual entry (debits = credits, dated today or earlier, not on/before the lock date). Entries are immutable; a manual entry can be reversed once (a mirror entry dated today). Sales, purchases and payments are corrected from their own screens (void, return, refund), not by reversal.
- Ledger (running balance per account, CSV), Trial balance (as at a date, CSV), Balance sheet (assets = liabilities + equity; unclosed profit shown as "current earnings"), Income statement (by account for a period).
- Changes in this release: the Reports profit & loss now sums accounts by type, so custom expense accounts and loyalty redemptions are included (loyalty redemptions were previously left out of that report).
- Not included: bank reconciliation, multi-currency, fixed-asset depreciation schedules, budgets, formal period-close entries (profit stays in "current earnings"). Manual journals carry no attachments yet.
CRM (CRM menu; module crm, on Growth and Scale; uses the customers.read / customers.write permissions)
- Customer page → timeline (customer 360): lifetime value, orders, average order, last purchase, customer since; a merged newest-first timeline of sales/invoices, payments and logged interactions; tags; follow-ups; Log a call, visit, WhatsApp, email, meeting or note.
- Follow-ups: something to do for a customer or deal by a date, assigned to a team member. Each assignee gets an in-app notification when one is due or overdue (once per follow-up per day, by the daily job). The Follow-ups tab filters Mine / Everyone's / Done.
- Deals: pipeline board New → Contacted → Quoted → Negotiating → Won / Lost. A deal belongs to an existing customer or to a prospect; winning a prospect creates the customer (with the prospect's name/phone/email). Losing needs a reason. Closed deals can be reopened. Every stage change is logged on the deal. The overview shows open value, a weighted forecast (open value × a fixed chance per stage: New 10%, Contacted 25%, Quoted 50%, Negotiating 75%; an indicator, not a prediction), won this month and a 90-day win rate.
- Segments (needs
sales.read; CSV withreports.export): Best customers (bought in the last 30 days, 5+ orders, top-quarter spend), Regulars (last 60 days), New (first purchase within 30 days), Slipping away (61–120 days), Dormant (over 120 days), Never bought. Thresholds are fixed and shown on the page. - Existing businesses on Growth/Scale get CRM, HR and BI switched on by the migration; new ones get them on sign-up when their plan includes them. Starter doesn't include them.
- Not included: email/SMS campaigns, import of prospects, deal products/quotes, custom pipeline stages, per-user deal visibility rules.
HR and payroll
The HR module (Growth and Scale plans) covers your team, attendance, leave and monthly payroll. It appears as HR in the sidebar.
Who can do what
| Permission | What it allows |
|---|---|
hr.read |
See the staff directory, attendance and leave (no pay or bank details). |
hr.approve |
Approve or decline leave, clock others in and out, correct attendance. Branch managers have this. |
hr.manage |
Add and edit employees, see pay and bank details, set work hours and leave types. |
payroll.manage |
Build, approve and pay payroll runs; see payslips. Accountants have this. |
Anyone linked to an employee record can clock in, request leave and download their own payslips from My HR, with no HR permission needed. Link a login on the employee's page.
Employees. Add people under HR → Employees. Pay can be a monthly salary, a day rate or an hourly rate. Pay and bank fields are hidden from anyone without hr.manage or payroll.manage.
Attendance. Staff clock in and out from My HR. Clocking in later than the work start plus grace minutes (HR → Attendance → Work hours) is marked late. Approvers can correct any day.
Leave. Annual, Sick and Unpaid leave are created for you; add more under HR → Leave. Weekends are not counted. You cannot approve your own request unless you have hr.manage.
Payroll.
- Payroll → create a run for the month. Every current employee gets a draft payslip.
- Monthly salaries are prorated for days before the start date or after the end date, and for unpaid leave. Day and hourly pay use recorded attendance.
- Define allowances and deductions (fixed or % of basic or gross pay) on the Payroll tab and assign them on each employee's page.
- Add one-off bonuses or deductions on a payslip while the run is a draft.
- Approve books the salaries to Accounting (Dr Salaries, Cr Salaries payable and any deduction accounts). Mark as paid records the payout (Dr Salaries payable, Cr cash/bank/transfer). Both are one-way and idempotent.
- Payslips download as PDF; staff see theirs only after approval.
Limits you should know about
- No employer contributions, and no built-in tax or pension tables. Enter the rates your accountant gives you as deduction components.
- Public holidays are not tracked.
- Days an employee was absent are not deducted automatically from monthly pay. Add a deduction adjustment.
- Payslip PDFs print ₦ as NGN (standard PDF fonts).
Existing databases: run npx prisma migrate deploy and npm run db:roles. The migration adds the HR tables, the new accounts are created on first use, and existing roles receive the new permissions.
Insights (business intelligence; module bi, Growth and Scale; needs reports.read)
Insights is read-only. It never changes your books. Figures are net sales (tax excluded) before returns; the Reports and Accounting pages remain the record for profit and loss.
- Overview: net sales, gross profit and margin, orders and average order, each compared with the period of the same length just before. A daily chart with a 7-day average, a 14-day forecast (average of the same weekday over the last four weeks, shown once you have two weeks of history; it is an estimate), top products and categories, and what customers owe by age.
- Sales analysis: by product, category, customer, branch or staff member, plus your busiest days and hours (in your time zone). Staff with branch restrictions see only their branches.
- Products & stock: ABC analysis (A = first 80% of sales, B = next 15%, C = the tail), margin watch (under 15% margin, or sold below cost; based on the cost at the time of sale) and dead stock (in stock but not sold for N days, with the cash it ties up; needs
inventory.read). - CSV export on each view needs
reports.exportand is audited. - Monday digest: each Monday the owner gets a notification summarising last week against the week before.
Product search and online requests (public /find)
Customers (no account): go to /find, type a product, optionally a city or "verified only", and see the businesses that have it in stock with price (if shown) and "In stock" / "Low stock". On a business page they enter quantities and ask for an invoice or place an order with their name and phone or email. They get a private link to follow the request, cancel it while it is new, and, once the business raises the invoice, open and pay it.
Businesses: opt in under Settings → Product search listing (a city is required). You choose whether to show prices and whether to accept requests. Visitors never see exact quantities, costs, customers or sales; only active goods with stock appear, and a business disappears from search while it is suspended or its subscription is read-only. Requests arrive under Online requests (with a count in the sidebar and an in-app notification). Open one to see what was asked against today's price and stock, contact the customer by call, WhatsApp or email, then Raise invoice (matches or creates the customer by phone or email, books stock and the receivable, gives the customer the pay link; safe to click twice), Mark as contacted, or Decline with a reason.
Online store. A listed business's page (/find/b/<name>) is a small storefront: product cards with photos, search and category filters, and a cart. Share it from Settings → Product search listing (copy link, WhatsApp, Facebook, X, Telegram, LinkedIn, email, or the phone's share sheet). The same buttons are on the public page.
- Photos: upload one per product on the product's edit page. The browser shrinks it to about 900 px and the server stores it in the database (up to 450 KB; JPG, PNG or WebP). A photo is public only while the business is listed. Products without a photo show a coloured tile.
- Pay at checkout: if the business has connected Paystack, Flutterwave or Korapay (Settings → Payment accounts), shows prices, accepts requests and leaves Let customers pay at checkout on, the cart offers Pay now. Checkout saves the request, raises the invoice straight away (stock and receivable are booked, acting as the business owner) and sends the customer to the provider's payment page. The invoice is settled by the same webhook/callback as any online invoice payment. If the invoice cannot be raised (for example not enough stock) the customer lands on their request page and the business follows up as for any request. Unpaid checkout invoices stay on the invoice list and can be voided.
- Not verified against live providers: the provider hand-off reuses the invoice payment code, which is tested against stubbed provider responses only.
Things to know
- A request does not reserve stock or take payment. Stock is booked when you raise the invoice.
- Abuse limits: 6 requests per visitor per hour, 60 per business per hour, 5 open requests per phone number per business, 40 searches per visitor per minute, and a hidden spam-trap field.
- Notifications are in-app only for now (email delivery is not verified live).
- The Verified badge shows next to a business in search; apply under Settings → Business verification.
Existing databases: run npx prisma migrate deploy and npm run db:roles. The migrations add the verification tables, the cross-business search function and the requests table (with row-level security).
Notifications and emails
What exists. Every alert appears in the app (bell icon). Most also go by email. Settings → Notifications lists them all, grouped, with an In the app and a By email switch for each. Everyone can switch off their own non-essential email on the same page; security, billing and account emails are always sent.
| Who | Emails they can get |
|---|---|
| Anyone | Confirm your email, reset your password, password changed, too many failed sign-ins, two-factor turned on/off/reset, invitation to a business, welcome after creating a business |
| Owners and billing managers (always on) | Trial ending (3 days) and ended, renewal due, payment overdue, subscription payment receipt, plan or access changed by Proventory support, business suspended or reactivated, verification decision, support access requested, payment that needs attention, batch recalled |
| Staff by role | Online payment received and bank transfer reported (people who record payments), new online request (people who sell), team member joined (people who manage staff), leave to approve (HR approvers), till variance or large stock adjustment (owners) |
| Employees | Leave approved or declined, payslip ready (no amounts in the email) |
| Daily summary (owners) | One morning email: low stock, expiring or expired stock, overdue customer payments, online requests and leave waiting. It says nothing on a quiet day. People with follow-ups due get their own list |
| Weekly summary (owners) | Last week's sales and margin against the week before (Insights) |
| Customers and visitors | The invoice or receipt you send (with a pay link), "we received your request", "your invoice is ready" and "request declined" (only if they gave an email), payment confirmation after paying online. Optional: weekly overdue-invoice reminders (off until you switch them on) |
| Platform administrators | A business applied for verification |
How delivery works. Emails are saved with the event that caused them, then sent straight after the request and retried on every scheduled run (up to 5 tries, then marked failed). Nothing is lost if the email service is briefly down. Delivered rows are cleared after 60 days.
To make it send for real (not verified live; test it first):
- Create a Resend account, verify your sending domain (SPF and DKIM records).
- Set
EMAIL_PROVIDER=resend,RESEND_API_KEY,EMAIL_FROM(for exampleProventory <no-reply@yourdomain.com>) andAPP_URLin Vercel, then redeploy. - Optional:
PLATFORM_ALERT_EMAILwith comma-separated extra addresses for platform alerts. - Check it: Settings → Notifications → Send me a test email, and Admin → System → Send myself a test email. A failure shows the real reason.
- The scheduled run on Vercel's free plan is once a day (03:00 UTC), which is also when the daily summary is sent. Instant emails go out right after the action; only retries wait for the next run. For faster retries call
/api/cron/jobshourly from an external scheduler.
Existing databases: run npx prisma migrate deploy (adds the email columns and the payer email on payment records).
Units, editing stock and the invoice layout
- Units of measure (Products → Units): add your own (paint, basket, carton…) with a short code and whether quantities may have decimals. Names can be changed later; decimals can only be increased.
- Edit stock (a product's page → Stock and unit of measure): Set the exact quantity records the difference from what the system holds as a CORRECTION in the stock ledger and books. Batch- or serial-tracked products still use Inventory → Adjust stock.
- Change the unit of measure (same card): allowed only while the product has never been sold, returned, transferred or consumed, and only when its stock sits in one location. You enter how many of the new unit you hold; the stock keeps its total value (the cost per unit is worked out), two ledger movements record the change, pack sizes are cleared, and you can set the new selling and buying price. Once a product has history, add a new product in the right unit instead.
- Invoices and receipts (web page and PDF) have a branded header, billed-to details with address, issue and due dates, a status badge, numbered lines with units, a highlighted balance due, a PAID/VOID stamp, bank-transfer details with the invoice number as the reference (PDF; the web page shows them in the payment box), notes, and page numbers on every PDF page.
- Business logo / profile photo (Settings → Branding): JPG or PNG, shrunk in the browser to 512 px and stored in the database (up to 450 KB). It shows on the online store header, on invoices and receipts (web and PDF; the public document pages load it through the document's own link, which never reveals the business id). Product photos are now centre-cropped to a square at upload and shown in a square frame, two to a row on phones.
- Store cover (Settings → Branding): an optional wide picture behind the business name on its online store, centre-cropped to a 3:1 banner in the browser. A dark gradient at the bottom keeps the logo and name readable on any picture. Shop pages show only the logo bar (customers do not see the platform's sign-in); the search pages offer business owners "List your business" and "Business sign in". Product cards have fixed rows (two-line name, price, unit, button) with the stock badge on the photo, so long names never change the layout.
Sales pages (special sales and bundles)
- Sell → Sales pages: create a page for a promotion, add offers (a product at a special price, or a bundle of up to 6 products with a discount % you choose: the bundle price is the products' normal selling prices times their amounts, less the discount, shown live as you build it and kept in step if you later change a product's price; each offer has an optional limit on how many one customer can buy), add a banner, optional start and end times (entered in the business time zone), then Publish. The public link is
/s/<business>/<sale>; share it from the page with copy link / WhatsApp / Facebook / X / Telegram / LinkedIn / email. A page works whether or not the business is listed in product search. - Visitors see offer and normal prices, "−x%", bundle contents, a countdown when an end time is set, and "Sold out" when stock runs out (a bundle is sold out when any of its products cannot cover one bundle). They add offers to a cart and either send an order, ask for an invoice, or (when the business has a connected payment provider and Let customers pay at checkout is on) pay immediately. Before the start the page says it has not started; after the end, that it has ended; unpublished pages and pages of suspended or read-only businesses are not found.
- Orders arrive in Online requests marked "Sales page:
". Stock is checked when the order is placed (nothing is reserved) and booked when the invoice is raised. The invoice uses the offer prices even if the person raising it cannot normally discount. A bundle's price is spread across its products in proportion to their normal prices so the invoice total equals the bundle price (this is the same as applying the bundle's discount % to each product). - Dates and the countdown are not verified against real customers' time zones beyond the business setting; the "starts/ends" messages on the public page show UTC.
- Look of the page: each sales page has a theme: Clean, Christmas (red, green and gold, serif headings, twinkling string lights, gentle falling snow, a "Season's Greetings" ribbon and a Christmas countdown) or Black Friday (black and yellow with hazard stripes). Themed pages show no platform header or sign-in, only a small "powered by" line. Animations switch off for visitors who prefer reduced motion.
- Photos: each offer can have its own photo (for a bundle, one picture of everything in it, shown whole on a blurred background rather than cropped); without one the product's photo is used. A banner can sit behind the title.
- Bundles are not limited to 6 products: add as many as you need (up to 60) with the "Add another product" button.
- Full width and "coming soon": sales pages use the whole screen: the header banner runs edge to edge and the offers grid widens to 4 columns on large screens. Before the start time, visitors can already browse the offers (prices, bundles, discounts) but cannot add to cart or order; the page says when ordering opens, in the business's time zone. After the end time the page says the sale has ended.
- Items you do not stock (sales page only): for sales where the business collects the money first and buys the goods for the customers afterwards, use Add an item you don't have in stock or tick Not in my stock on a bundle row. Such items are sold at exactly the price entered (never discounted), can always be ordered (subject to the per-order limit), and are held as hidden non-stock products (SKU prefix
SPO-) that never show in the product list, till, inventory or the online store. A bundle's discount % applies to the in-stock products only; off-stock items stay at full price. Orders and invoices carry them normally, with a "To buy for customer" badge in Online requests; they book revenue but no stock or cost of goods, so record the purchase yourself when you buy the goods. Removing the offer stops the item being sold; the hidden product is kept for invoices already raised. - Customers are never told an item is "not in stock": off-stock items look like any other offer. The owner can add an optional short note per item (for example "Delivered within 3 days") that is shown on the card; the "To buy" markers are visible only to the business.
Sales page design (upgrade)
The public sales page now has a sticky top bar (logo, name, verified tick, "Shop the offers"), a hero with a large title, tagline, call-to-action buttons and a ticking Days/Hrs/Min/Sec countdown (to the start before the sale opens, to the end while it runs), and a "Save up to X%" panel. Under the hero is a trust strip (verified, payment options, seller confirms orders, limited time), the sale description, then the offers: bundles first as larger cards with "Includes" chips, then single offers. Cards show a discount badge and "You save ₦X"; the cart shows the total saved. A "How it works" block and share buttons close the page. Each theme (Clean, Christmas, Black Friday) fixes its own colours, so a visitor's dark-mode setting cannot make a light sale unreadable; Black Friday is fully dark.