Skip to main content

Thames Valley Gifts — Founder Operations & Maker Payouts

DRAFT — Stage 1, with defaults applied 2026-09-28. Every "FOUNDER DECISION:" line still marked OPEN below must be answered in the Stage-2 interview before WP4 code starts; lines rewritten as "DECIDED 2026-09-28 (default adopted by orchestrator)" had a recommended default accepted on the founder's behalf, and the founder can still override any of them. See the Decision log at the end of this file. Items marked [UNVERIFIED] were not checked against code, the database, or a primary source in this pass.

0. Sources and fixed facts​

Inputs, in the order read: research_notes/Thames Valley Gifts build/architecture_decision.md (Option D, approved design: §2.1 tenancy, §2.2 data model, §2.5 HST, §2.7 routes, §2.8 AI desk, §2.9 payout query, §2.10 deferred, §3 R7/R11, §4 WP4/WP6); research_notes/Thames Valley Gifts traffic and vendors/vendor_recruitment.md (Q2, Q3, Q7) and platform_operations.md (Q3 HST, Q4 Ontario consignment, Q5 returns, Q8 CASL); ai_front_desk_conversion.md (Q7 KPI list); jb_creations_reference.md; knowledge/architecture/ai-bridge-principle.md; ai-company-os/brands/engraving/BRAND.md.

Fixed facts (given by the founder's brief; not re-litigated here):

#Fact
F1The hub (ChurchWiseAI LTD, trading as Thames Valley Gifts, BIN 1001760694) is the merchant of record and an HST registrant (RT0001).
F2Consignment commission is 20% for founding makers, locked for 12 months — DECIDED 2026-09-28 (FOUNDER DECISION FINAL); implemented as per-maker commission_bps, default 2000 (0–5000 range stays available in the CHECK constraint for a future non-founding maker). All worked examples below use 20% as the brief requires.
F3Makers hold their own stock and ship their own orders. The hub never warehouses inventory.
F4Payouts by Interac e-transfer on the 1st and 16th of each month, each with a statement.
F5No exclusivity. Makers may sell anywhere else.
F630-day termination notice, either side.
F7The maker warrants originality of every design and product.
F8The maker fixes maker errors (production defects, wrong item) at the maker's cost; the hub fixes proof errors (a wrong proof the hub produced or approved internally) at the hub's cost.
F9Footer and every statement/invoice line: "Thames Valley Gifts is a division of ChurchWiseAI LTD." (Business Names Act).

Conflict resolved in this spec — DECIDED 2026-09-28 (default adopted by orchestrator): the memo §2.9 describes a monthly statement; fixed fact F4 says payouts on the 1st and 16th. This spec adopts two statement periods per month — Period A (1st–15th, paid on the 16th) and Period B (16th to month-end, paid on the 1st of the next month) — plus a monthly roll-up view that must equal the sum of the two periods line for line. The memo's query is reused per period.

1. Scope and non-goals​

In scope: every founder-facing screen under churchwiseai.com/founder/[token]/tvg/* and the /api/tvg/admin/* routes behind them.

Out of scope (other specs): the public storefront and PDP (tvg-storefront.md, not yet written), the customer proof → pay pages (tvg-proof-to-pay.md, not yet written), and the chat and voice front desk (tvg-ai-front-desk.md).

Non-goals (v1, per memo §2.10): no maker portal or maker logins; no Stripe Connect automated payouts; no inventory counts; no cart; no cron-sent customer emails (the operator gets an action item instead); no Stripe Invoicing or Stripe Tax.

2. Auth, host and indexing (applies to every page and API in this spec)​

  • Every page under /founder/[token]/tvg/* is a server component that compares params.token with process.env.FOUNDER_TOKEN. A mismatch, an empty token, or an unset env var → Next.js notFound() (HTTP 404), never 401/403, never a redirect. Same pattern as acceptance/founder-hq.md §3.
  • Every /api/tvg/admin/* route re-validates the token independently (header Authorization: Bearer <token> or JSON body { token }). A mismatch → 404 with an empty body. A page render passing auth does not make the API trust the request.
  • export const dynamic = 'force-dynamic'; no ISR, no caching.
  • robots: noindex, nofollow (inherited from founder/[token]/layout.tsx) and an X-Robots-Tag: noindex, nofollow response header on every page and API response.
  • The founder token never appears in client-side JavaScript except inside the current URL; no link on these pages carries the token to a non-founder host.
  • These pages live only on churchwiseai.com. On thamesvalleygifts.ca, any path beginning /founder returns 404 (the brand host must not expose the ops surface).
  • All dates and times on these pages display in America/Toronto. All money displays as $1,234.56 CAD, computed from integer cents; no floating-point money anywhere.
TestExpected
GET /founder/WRONG/tvg404, body is the site's standard 404 page
GET /founder/<valid>/tvg200, X-Robots-Tag contains noindex
POST /api/tvg/admin/makers with no token404
GET https://thamesvalleygifts.ca/founder/<valid>/tvg404

3. Page map​

PagePurpose
/founder/[token]/tvgAction list (home) — §4
/founder/[token]/tvg/requests/[id]Request detail and every request action — §7
/founder/[token]/tvg/makers, /makers/[id]Maker list, create, edit, agreement gate — §5
/founder/[token]/tvg/catalog, /catalog/[id]Product list, create, edit, status, schema, images — §6
/founder/[token]/tvg/payouts?period=YYYY-MM-A | YYYY-MM-B | ?month=YYYY-MMPayout statements and monthly roll-up — §8
/founder/[token]/tvg/tax?from=YYYY-MM-DD&to=YYYY-MM-DDHST accounting export — §9
/founder/[token]/tvg/kpis?week=YYYY-WwwWeekly KPI scorecard — §12
/founder/[token]/tvg/settingsTax-rate table, flat shipping, pickup instructions, AI knowledge sync (sync spec lives in tvg-ai-front-desk.md §9)

Every page shows the same top tab bar in this order: Action list · Requests · Makers · Catalogue · Payouts · Tax · KPIs · Settings, and a link back to /founder/[token] (the existing founder dashboard). Founder HQ (/founder/[token]/hq) gets one new link to /founder/[token]/tvg in its Admin tab.

4. Action list — /founder/[token]/tvg​

The home page answers one question: "What do I need to do for the shop today?" It shows six queues, always in this order, each with a count badge. A queue with zero items still renders, with the text "Nothing here." (never hidden, so the founder can trust an empty queue).

#QueueRows included (server-side rule)SortRow showsRow action
Q1Unanswered requeststvg_order_requests.status IN ('new','needs_info','changes_requested')oldest firstorder number, customer name, product title(s), source badge (Web / Chat / Voice / Email / Phone / In person), age in business hours, "needed by" date if set, a red Rush chip when needed_by is earlier than today + product lead_time_min_bd + 1 business day for the proofopen request
Q2Proofs awaiting approvalstatus = 'proof_sent'oldest sent_at firstorder number, customer, proof version, days since sentopen request; "Copy reminder text" (copies a pre-written reminder to the clipboard — no email is sent by the page or any cron)
Q3Approved, not paidstatus = 'approved_awaiting_payment'oldest approved_at firstorder number, total, days since approval; rows older than 7 days (DECIDED 2026-09-28 (default adopted by orchestrator): 7) show an Expire buttonopen request; Expire (sets status='expired', writes an event, asks for confirmation first)
Q4In productionstatus IN ('paid','in_production')earliest production_due_date firstorder number, maker, due date, fulfilment (Pickup / Ship to XX)open request
Q5Due todayproduction_due_date <= today (Toronto) AND status IN ('paid','in_production')overdue firstsame as Q4; overdue rows are red with "N business days late"open request
Q6Voice and chat leads to promotelocal_business_leads rows where business_id = the TVG local_businesses row, status NOT IN ('archived','lost'), not already linked to a request (metadata.tvg_order_request_id is null), and not dismissednewest firstsource (Voice / Chat), name, phone or "caller ID only — not confirmed", one-line summary, metadata.handoff_reason if present (e.g. Memorial, Complaint), received timeCreate order request (§4.1); Dismiss (requires a reason)

Below the six queues, two single-line notices appear only when true:

  • "Payout due: Period YYYY-MM-A is finalized but not marked paid" (from the 16th / 1st onward).
  • "Stripe dispute opened on TVG-000123" (from charge.dispute.created; evidence bundle per memo §2.4).

4.1 One-click promotion of a lead (Q6)​

  • Clicking Create order request creates exactly one tvg_order_requests row: source = 'voice' or 'chat' (from the lead's source voice / chatbot), source_lead_id = the lead id, kind='retail', status='needs_info', customer_name ← contact_name, customer_email ← contact_email, customer_phone ← contact_phone; if contact_phone is null and metadata.caller_id exists, the phone is pre-filled and the request page shows "Phone is the caller ID, not a confirmed callback number" until the operator ticks "Confirmed with customer". internal_notes ← the lead's summary + message. An event row (actor='operator', event_type='promoted_from_lead') is written.
  • The lead gets metadata.tvg_order_request_id set (our own internal row — a permitted write).
  • Idempotent: a second click on the same lead opens the existing request; it never creates a second one. A double-click race produces one row (enforced by a unique index or equivalent on source_lead_id).
  • The promoted request has zero items. "Send proof" stays disabled until at least one item with a product and schema-valid personalization is added (§7.2).
  • The operator lands on the new request page within one click.

Should see / Should NOT see — Action list​

Should seeShould NOT see
All six queues, in order, with counts, even when emptyA "Delivered" / "Opened" email metric or any other vanity number
Ages in business hours/days (Mon–Fri, Ontario statutory holidays excluded)Customer email addresses or full phone numbers in the list (shown only on the request page)
The Rush chip computed server-sideAny button that emails or texts a customer directly from the list
Voice leads with "caller ID only" labelled honestlyLeads belonging to any other local_businesses row
A clear empty state "Nothing here."Leads already promoted or dismissed

5. Maker management — /founder/[token]/tvg/makers​

5.1 List​

Columns: display name, town, status badge (Onboarding / Active / Paused / Ended), commission (e.g. "20%"), agreement ("Signed 2026-10-02 · v1" or a red "Not signed"), HST ("Registrant" / "Not registered" / red "Not set"), products (available / total), unpaid balance.

5.2 Create / edit form fields and inline validation​

FieldRule (checked in the browser and again on the server)
Display namerequired, 2–80 chars
Legal namerequired, 2–120 chars; shown only on statements and the agreement, never publicly
Slugrequired, lowercase a-z0-9-, unique; changing it after products are published shows a warning that public URLs change
Town, regionrequired; region defaults to ON
Story, photo, specialties, years makingoptional; photo must have alt text
Commissioninteger basis points; the form offers only the values allowed by FOUNDER DECISION F2 (2000 and/or 2500); the DB CHECK allows 0–5000
Shipping passthroughyes/no, default yes (maker ships and pays postage, so the shipping charge is paid to the maker — memo §2.2)
Payout methode-transfer (default) / cheque / other
Payout e-transfer emailrequired when method is e-transfer; valid email; displayed masked (j•••@gmail.com) everywhere except the edit form
Contact phone / emailat least one required
Pickup instructionsrequired if the maker offers pickup through the hub
HST registrantrequired yes/no — cannot be left blank once status is Active
HST numberrequired when registrant = yes; format 9 digits + RT + 4 digits (e.g. 123456789RT0001); inline error otherwise
Agreement signed date + agreement versionboth set together or both empty; date cannot be in the future
StatusOnboarding → Active → Paused ↔ Active → Ended

5.3 The agreement gate (memo R11 — server-side, not just UI)​

  • A product cannot be set to available unless its maker has agreement_signed_at set, agreement_version set, status='active', and hst_registrant not null. The API returns 409 with the message "This maker hasn't signed the consignment agreement yet — products can't go on sale." (or the matching message for status / HST flag). The UI disables the "Available" option with the same sentence as a tooltip.
  • A maker cannot be set to Active without a signed agreement date and HST flag.
  • Setting a maker to Paused immediately makes all their available products un-orderable (the public PDP shows "Temporarily unavailable"; the order-request API and the chat tool both refuse with not_orderable). Open requests and paid orders are unaffected.
  • Setting a maker to Ended asks for the termination-notice date; on notice date + 30 days (F6) the maker's products move to retired. Until then they stay as they were unless the operator pauses them. Paid, unfinished orders stay on the action list until completed; the maker still receives payouts for them.
  • A maker's commission_bps change applies to future payments only: every item stores commission_bps_snapshot at payment time (memo §2.2), and statements always use the snapshot.

6. Product management — /founder/[token]/tvg/catalog​

From → ToServer ruleError if broken
(new) → draftalways—
draft → exampleproduct has is_demo_seed=true or its maker's agreement is not yet signed; an example product must carry the visible label "Example listing — not yet for sale" on the storefront409
draft / example / coming_soon / paused → availablemaker gate (§5.3) passes and is_demo_seed=false and base_price_cents > 0 and at least one photo with kind hero and non-empty alt text and the personalization schema validates and lead_time_min_bd ≤ lead_time_max_bd, both ≥ 1 and at least one fulfilment option409 listing every failed rule
draft / example → coming_soonstatus_note set (e.g. "Available mid-November 2026")409
available → pausedalways; open requests unaffected—
any → retiredalways; retired is terminal—
retired → anythingnever409
example with is_demo_seed=true → availablenever — a demo seed must be replaced by a real maker product with the maker's own photos409 "Demo seed listings can never be sold."
  • The order-request API and the chat tool accept orders only for available products, and kind='waitlist' only for coming_soon. example, draft, paused, retired → refused server-side regardless of what the client sends.
  • Every status change writes an audit event (who, from, to, when).

6.2 Personalization schema builder​

Two views of the same personalization_schema (memo §2.3): a form builder and a raw JSON editor, kept in sync. Both run the shared validator (src/lib/tvg/personalization.ts, same module the storefront and chat use).

Rule (inline error shown next to the offending field)Example error text
Field key is required, snake_case, unique within the schema"Key 'line1' is used twice."
type is one of text, textarea, verse, monogram, image_upload, choice, date"Unknown field type 'font_picker'."
A text/textarea field must have max_length ≥ 1, and its label must state the limit ("up to 24 characters")"The label must tell customers the 24-character limit."
min_length ≤ max_length—
verse.translations ⊆ {KJV,WEB}; default_translation must be in the list"Only KJV and WEB are available."
monogram.letters = 3 and arrangement is one of the allowed options—
image_upload.dpi.good > dpi.ok > 0; max_mb ≤ 15—
choice.options[].value unique; price_delta_cents is an integer (may be 0)"Price change must be whole cents."
quantity.tiers sorted by ascending min_qty; each unit_price_cents > 0; max ≥ min—
Preview boxes x/y/w/h are fractions between 0 and 1—
Raw JSON that does not parse"Line 12, column 5: unexpected '}'." and Save is disabled
  • Saving a changed schema increments version. Existing order items keep the personalization_schema_version they were captured with; the request page renders old items against their own version, never the new one.
  • A "Try it" panel renders the customer form from the current (unsaved) schema so the operator can type sample text and see the same limits and preview the customer sees.

6.3 Images​

  • Upload to the public tvg-catalog bucket (memo §2.6), reusing the existing image validator as a pure import. JPEG/PNG/WebP only; each image requires alt text and a kind (hero, detail, scale, swatch, example_before_after); drag to reorder; exactly one hero needed for available.
  • Example listings: the upload form requires ticking "This photo was taken by Thames Valley Gifts or supplied by the maker for this listing." Per jb_creations_reference.md, no photo, price, description or review may be copied from a maker's own public pages into an example listing.

Should see / Should NOT see — Catalogue​

Should seeShould NOT see
Status badge on every row; the Example label text on example rowsAn "Available" option on a product whose maker has no agreement
Every failed rule listed when a status change is refusedA generic "Something went wrong" on a 409
Schema errors next to the field that caused themA schema save that silently drops an invalid field
Prices as "$38.00 + HST"Any HST-inclusive shelf price

7. Request handling — /founder/[token]/tvg/requests/[id]​

7.1 What the page shows​

  • Header: order number (TVG-000123), status badge, kind (Retail / Bulk quote / Waitlist), source badge; for chat requests a link to the chat transcript; for voice requests a link to the source lead.
  • Customer: name, email, phone, organization (B2B), fulfilment (Pickup, or Ship with the full address), province_of_supply, needed-by date with Rush chip, gift note.
  • Items: product, maker, quantity, unit price, option deltas, tier applied, line subtotal, and the personalization values rendered letter by letter in a monospace box (so "Jon" vs "John" and doubled spaces are visible), plus whether spelling was confirmed and when (spelling_confirmed_at), and the verse text snapshot with its translation.
  • Uploads: thumbnail via a short-lived signed URL, effective DPI, quality rating (Good / OK / Low), and whether the customer acknowledged low resolution.
  • Proofs: every version with status, sent time, customer response, and the change-request text.
  • Payments: every payment row with method, status, amount, reference.
  • Event log: every tvg_order_events row, newest first, with actor.

7.2 Actions and their server rules​

ActionAllowed from statusServer ruleResult
Edit items / personalizationnew, needs_info, changes_requestedvalues re-validated against the product schema; price recomputed on the serverevent items_edited
Send proofnew, needs_info, changes_requested≥1 item, all items schema-valid, ≥1 proof image, quote computed server-side: subtotal + shipping + tax at the §2.5 rate for province_of_supply from tvg_tax_rates; operator may edit the shipping amount onlynew tvg_proofs row vN with status='sent', image sha256 stored; request → proof_sent; customer proof email (transactional template — FOUNDER DECISION: bless the template); customer page shows "Awaiting your approval"
Record manual payment (e-transfer / cash / cheque)approved_awaiting_paymentamount must equal the approved proof's total_cents exactly (partial payments are not supported at launch; a mismatch is refused with the expected amount shown); reference required for e-transfer and cheque; payment date required, not in the futuretvg_payments row status='paid', recorded_by='founder'; request → paid, paid_at = payment date; commission_bps_snapshot copied from the maker; production_due_date = payment date + lead_time_max_bd business days (Ontario holidays excluded); maker job sheet (§10) sent
Mark in productionpaid—in_production
Mark ready for pickuppaid, in_productionfulfilment = pickup; pickup instructions existready_for_pickup; customer "ready" email (transactional)
Mark shippedpaid, in_productionfulfilment = ship; carrier and tracking number both requiredshipped, shipped_at; tracking stored in the event payload; customer shipping email
Mark completedready_for_pickup, shipped—completed, completed_at (starts the 90-day upload-deletion clock)
Cancelany status before paidreason required: pick-list (Customer asked · No response · Can't be made · Duplicate · QA test) + free textcancelled, cancelled_reason; no money moves
Refundpaid or laterfounder click with a confirm dialog showing the amount; amount ≤ paid − already refunded; fault attribution required: Maker error / Hub proof error / Goodwill (no fault); reason text required. Stripe payments are refunded through Stripe; manual payments record the return e-transfer referencepayment refunded or partially_refunded, refunded_at set (memo WP1 note: add a real column, not updated_at); request → refunded if fully refunded; clawback rules in §8.3
Expireapproved_awaiting_payment, proof_sentconfirm dialog; for proof_sent only after the proof's expires_atexpired
Declinenew, needs_info, proof_in_progressreason required (free text)declined, declined_reason; E12 variant sent — see tvg-proof-to-pay.md §3.2
  • Every action writes one tvg_order_events row with actor='operator'.
  • Illegal transitions return 409 and name the current status.
  • Nothing on this page ever charges a card. Stripe refunds are the only Stripe write, and only on a founder click.

8. Maker payout statements — /founder/[token]/tvg/payouts​

8.1 Periods and dates (all America/Toronto)​

PeriodSales included (by paid_at)Payout date
YYYY-MM-A1st 00:00 to 15th 23:59:5916th of the same month
YYYY-MM-B16th 00:00 to last day 23:59:591st of the next month
  • A sale belongs to the period of its paid_at in Toronto time, not UTC.
  • DECIDED 2026-09-28 (default adopted by orchestrator): pay on the calendar date even on weekends/holidays (e-transfer works every day), not the next business day.
  • FOUNDER DECISION FINAL (2026-09-28): payout basis is completed (a maker is paid for an order only once it is picked up or shipped, like the 14-day hold Shop Makers uses), not paid-date. This remains implemented as a config flag (tvg_settings.payout_basis, CHECK paid/completed) so the founder can flip it without a code change, but the config default and the shipped behaviour are now both completed. This spec's worked fixture (§8.5) is recomputed below on the completed basis, as the actual default WP4 ships.

8.2 Statement lines and arithmetic​

For each maker and period, one row per item paid in the period (status in paid, in_production, ready_for_pickup, shipped, completed, and maker_payout_status='unpaid'), plus one row per clawback recorded in the period:

ColumnRule
Grossline_subtotal_cents — merchandise only, before HST, excluding shipping
Commissionround_half_up(gross × commission_bps_snapshot ÷ 10000), per line
Shipping passthroughthe order's shipping charge when the maker has shipping_passthrough=true (split pro rata by merchandise if an order ever has lines from two makers — memo §2.9); 0 for pickup
Refunds / clawbacksnegative lines per §8.3
Net to makergross − commission + shipping passthrough + clawbacks
  • HST never appears in a maker's gross or net. The hub collects and remits it (F1).
  • Stripe processing fees are absorbed by the hub out of its commission (memo §2.9 recommendation). DECIDED 2026-09-28 (default adopted by orchestrator): confirmed; the agreement states this.
  • Statement totals are sums of the lines; no total is ever recomputed from rounded percentages.
  • Negative net (clawbacks larger than new sales): the statement shows the negative balance as "Carried forward to next period"; no e-transfer is sent; the next period starts with that balance as its first line.

8.3 Refunds and clawbacks (implements F8)​

Refund faultEffect on the maker
Maker error (defect, wrong item made) and the maker has already been paid for the itemclawback line = −(merchandise portion refunded × (10000 − commission_bps_snapshot) ÷ 10000), rounded half-up. HST refunded and shipping refunded are not clawed back unless shipping was refunded because of a maker error, in which case the passthrough shipping refunded is clawed back at 100%.
Maker error, item not yet paid outthe item's line is reduced before finalizing (no separate clawback line)
Hub proof errorno clawback — the hub absorbs it. A zero-value info line "Refund absorbed by hub (proof error) — TVG-000xxx" appears so the maker sees it. If the maker remakes at the hub's request, DECIDED 2026-09-28 (default adopted by orchestrator): the hub pays the maker for the remake at the original net.
Goodwill (no fault)no clawback; hub absorbs; info line as above. DECIDED 2026-09-28 (default adopted by orchestrator): confirmed.
Remake instead of refundno money line; recorded as an event; counts in the KPI "remakes" line

8.4 Generate → Finalize → Mark paid​

  • Generate draft: computes the lines live. Re-running it before finalize gives identical output for identical data.
  • Finalize: freezes the lines into tvg_payout_statements.line_items (a frozen JSON copy) with totals; flips the items to maker_payout_status='on_statement'; the statement becomes read-only. Later edits to orders, commissions or products do not change a finalized statement. A finalized item never appears on a later statement again.
  • Mark paid: requires the e-transfer reference (or cheque number) and the paid date; stores etransfer_reference, paid_at; flips items to paid. Cannot mark paid a statement that is not finalized. Cannot mark paid twice.
  • Print / PDF: a printable statement with: "Thames Valley Gifts is a division of ChurchWiseAI LTD", the hub's GST/HST number (DECIDED 2026-09-28 (default adopted by orchestrator): supplied at runtime via an environment variable, never committed to git), the maker's legal name, the period and payout date, every line (order number, date paid, product, qty, gross, commission rate and amount, shipping, clawback, net), totals, carried-forward balance, and the payout reference once paid. No customer names, emails, phones or addresses on the statement. Emailing the PDF to the maker is a founder click (memo Phase 3), never automatic.
  • Monthly roll-up (?month=YYYY-MM): shows Period A, Period B and the month total side by side; the month total must equal A + B in every column.

8.5 Hand-computed fixture — October 2026, commission 20%, COMPLETED basis (the QA test)​

Recomputed 2026-09-28 on the founder's final payout-basis decision (§8.1: completed, not paid-date). A maker is now paid for an order only once it is picked up or shipped — so period membership below is keyed on completed at (pickup/ship date), not paid_at. Every order's paid_at is unchanged from the original paid-date illustration (kept for reference); each order now also carries a completed_at. The bulk 12-unit Nova Scotia order (TVG-000103) takes longer to produce and ships after the 15th, so — unlike the paid-date illustration — it lands in Period B, not A, under the completed basis. This is the concrete effect the founder's decision has on a real statement. The timezone-boundary property (TVG-000107) is preserved, now on completed_at.

Maker: TVG QA Maker (see §13 on QA isolation), commission_bps=2000, shipping_passthrough=true, hst_registrant=false. All times America/Toronto.

Orders in the fixture:

OrderPaid atCompleted at (picked up / shipped)FulfilmentItemsMerch (¢)Shipping (¢)Tax rateTax (¢)Other facts
TVG-0001012026-10-03 11:002026-10-07 10:00 (picked up)Pickup (ON)1 × mug @ 19991999013%260refunded in full 2026-10-25, hub proof error
TVG-0001022026-10-09 14:202026-10-14 11:00 (shipped)Ship ON2 × tumbler @ 28005600140013%910one tumbler refunded 2026-10-22 (2800 merch + 364 HST), maker error, after the item was already paid out
TVG-0001032026-10-14 09:052026-10-19 10:00 (shipped)Ship NS12 × tumbler @ tier 220026400150014% [UNVERIFIED NS rate]3906ships after the 15th → Period B under completed basis (was Period A under paid-date)
TVG-0001072026-10-15 22:00 (= 2026-10-16 02:00 UTC)2026-10-15 23:30 (= 2026-10-16 03:30 UTC) (picked up)Pickup (ON)1 × ornament @ 24002400013%312timezone boundary on completed_at: Toronto date is still 10-15 → Period A
TVG-0001082026-10-20 16:452026-10-23 10:00 (shipped)Ship ON1 × slate @ 45004500140013%767—
TVG-000104———1 × tumbler————approved_awaiting_payment → must not appear
TVG-0001052026-10-05——1 × item from a different maker————must not appear
TVG-000106————————cancelled before payment → must not appear

Expected statement 2026-10-A (payout date 2026-10-16):

OrderCompletedProductQtyGrossCommission 20%ShippingClawbackNet
TVG-00010110-07Mug1$19.99$4.00 (399.8 → 400)$0.00—$15.99
TVG-00010210-14Tumbler2$56.00$11.20$14.00—$58.80
TVG-00010710-15Ornament1$24.00$4.80$0.00—$19.20
Total$99.99$20.00$14.00$0.00$93.99

Check: 99.99 − 20.00 + 14.00 = 93.99. (TVG-000103 is NOT in Period A under the completed basis — it was paid 10-14 but not shipped until 10-19.)

Expected statement 2026-10-B (payout date 2026-11-01): (assumes 2026-10-A was finalized and marked paid on 2026-10-16 before the refunds; TVG-000103 was never on Statement A, so it appears here as a normal new sale, not a carry-over)

OrderDateProduct / lineQtyGrossCommission 20%ShippingClawbackNet
TVG-00010310-19Tumbler (tier)12$264.00$52.80$15.00—$226.20
TVG-00010810-23Slate1$45.00$9.00$14.00—$50.00
TVG-00010210-22Refund clawback — maker error (1 tumbler)————−$22.40−$22.40
TVG-00010110-25Refund absorbed by hub (proof error)————$0.00$0.00
Total$309.00$61.80$29.00−$22.40$253.80

Check: 309.00 − 61.80 + 29.00 − 22.40 = 253.80.

Clawback check: 2800 × (10000 − 2000) ÷ 10000 = 2240 → −$22.40. The $3.64 HST refunded on TVG-000102 and the $2.60 HST on TVG-000101 do not touch the maker. Both refunds are dated by refunded_at, which is unaffected by the payout-basis choice — only the SALE lines' period membership moved.

Expected monthly roll-up October 2026: Gross $408.99 · Commission $81.80 · Shipping $43.00 · Clawbacks −$22.40 · Net $347.79 (= $93.99 + $253.80).

Note — why the monthly total is unchanged from the paid-date illustration: every order still completes within October either way; the completed basis only moves TVG-000103 from Period A to Period B, so the monthly aggregate ($408.99 / $81.80 / $43.00 / −$22.40 / $347.79) is identical to the earlier paid-date worked example — only the two statements' individual totals differ ($93.99 + $253.80 here, versus $320.19 + $27.60 on a paid-date basis). This is expected and is itself a useful sanity check when QA-ing a real statement: the half-month split can move, the month cannot.

Commission-change control: after all fixture payments exist, change the maker's commission to 2500 and regenerate the drafts (before finalizing): every line above is unchanged, because every line uses commission_bps_snapshot.

Unit tests: churchwiseai-web/src/lib/tvg/__tests__/payout-statement.test.ts runs this fixture on the completed basis as the primary/default case (matching tvg_settings.payout_basis's shipped default), plus a paid-basis case using the original paid-date numbers to prove the alternative config still works, plus a positive control (test 25 below).

9. HST accounting export — /founder/[token]/tvg/tax​

The export gives the founder (and the accountant) one table per date range to file the ChurchWiseAI LTD GST/HST return. [ACCOUNTANT] FOUNDER/ACCOUNTANT DECISION (OPEN): the filing period (monthly, quarterly or annual) — the page takes any date range and offers quarter shortcuts.

9.1 Summary by jurisdiction​

One row per jurisdiction in tvg_tax_rates with activity in the range:

ColumnRule
JurisdictionON-HST, NS-HST, NB-HST, NL-HST, PE-HST, GST-only
Ratefrom the order's stored quote_tax_rate_bps (never today's rate)
Taxable salesΣ (merchandise + shipping) on orders paid in range
Tax collectedΣ order-level quote_tax_cents (sum of the per-order rounded amounts, never recomputed from the taxable total)
Refunded taxable / taxΣ refunds recorded in range, split into taxable and tax
Net taxtax collected − tax refunded

Rate table (memo §2.5; [ACCOUNTANT] ACCOUNTANT DECISION (OPEN): confirm before any product goes available): pickup in Ontario and ship to ON 13%; NB, NL, PE 15%; NS 14% [UNVERIFIED — reported reduced from 15% on 2025-04-01]; AB, BC, MB, SK, QC, YT, NT, NU 5% GST only. No PST/QST is collected ([ACCOUNTANT] ACCOUNTANT DECISION (OPEN): BC/SK/MB/QC registration thresholds [UNVERIFIED]).

Fixture check (October 2026 orders from §8.5, all to a non-registrant maker):

JurisdictionTaxableTax collectedRefunded taxableRefunded taxNet tax
ON-HST$172.99 (19.99 + 70.00 + 24.00 + 59.00)$22.49 (2.60 + 9.10 + 3.12 + 7.67)$47.99 (28.00 + 19.99)$6.24 (3.64 + 2.60)$16.25
NS-HST$279.00$39.06$0.00$0.00$39.06

In this fixture the per-order sum ($22.49) happens to equal 13% of the ON taxable total (17299 × 0.13 = 2248.87 → 2249), so it cannot tell the two methods apart. The test must add one order pair where they differ — e.g. two separate $0.05 pickup orders: 1 ¢ + 1 ¢ = 2 ¢ by the per-order rule, versus round(10 × 0.13) = 1 ¢ by recomputation. The export must show 2 ¢.

9.2 Agent-versus-principal handling per maker (CRA GI-009, ETA s.177)​

The export has a second table, one row per maker, because the correct treatment depends on whether the maker is a GST/HST registrant. The page shows which branch applied and never picks a branch the accountant has not approved.

Maker statusTreatment the export shows (per platform_operations.md Q3)Decision status
Not registered (small supplier)ETA s.177(1): the hub's sale is deemed the hub's own taxable supply. The hub reports HST on the full sale in its return; no HST on the commission; the maker is paid their share with no HST. Row shows: merchandise, tax collected, commission (no tax), net paid.Matches the research; [ACCOUNTANT] ACCOUNTANT DECISION (OPEN) to confirm.
Registered, option (a) purchase-and-resale framingMaker invoices the hub for their share + HST; the hub claims that HST as an input tax credit. Row shows: maker share, HST on maker share (ITC candidate), the maker's GST/HST number.[ACCOUNTANT] FOUNDER/ACCOUNTANT DECISION (OPEN) — note: invoicing the hub may weaken the "true consignment" position under Ontario case law (Lette criteria: consignor should not invoice the consignee).
Registered, option (b) s.177(1.1) joint election (form GST506)The hub accounts for the tax on the whole sale; the hub charges HST on its commission to the maker. Row shows: tax collected on the sale, commission, HST on commission (13%), and whether a GST506 is on file.[ACCOUNTANT] FOUNDER/ACCOUNTANT DECISION (OPEN)
  • Until the accountant picks (a) or (b), a registrant maker's products cannot be available: the §5.3 gate adds "registrant treatment not chosen" as a 409 reason.
  • The export also lists, per maker, total consignment sales in the trailing 12 months, with a note when a non-registered maker passes $25,000 ("approaching the $30,000 small-supplier threshold — their consignment sales count toward it").
  • [ACCOUNTANT] ACCOUNTANT DECISION (OPEN): whether the 2021 distribution-platform-operator rules apply instead of, or as well as, s.177 (the research says either path gives the same answer for unregistered makers) [UNVERIFIED].
  • Download formats: CSV (one file per table) and a printable page. Integer cents in the CSV plus a formatted dollars column.

10. Maker job sheet​

Sent to the maker for each paid order (on Stripe payment by the webhook handler, or on "Record manual payment"), by email from the hub's transactional address, and viewable on the request page. FOUNDER DECISION: bless the template before first send.

Must contain​

  • Order number, date paid, production due date ("Ship or have ready by Fri Oct 23").
  • Product title and maker SKU/slug, quantity, size/colour/font choices.
  • Every engraved/printed value exactly as approved, from the approved proof's rendered_text_snapshot, shown both normally and letter-spaced, plus the verse reference, translation and full verse text.
  • The approved proof image (or a link to a short-lived signed URL, valid ≥ 7 days) and its version number.
  • Customer photo(s) for photo products: the EXIF-stripped copy only, via signed URL.
  • Fulfilment: Pickup → where and when to drop off / hand over, and the hub's instructions; or Ship → recipient name, shipping address, and the recipient phone only if the carrier label requires it; the carrier/label instructions; a link to enter the tracking number.
  • Gift note, only if the customer asked for it to be included in the parcel.
  • "Notes for maker" from the request (operator-reviewed).
  • The rule reminders: "Make exactly what is on the proof. If you see a problem, stop and reply before making it. Maker errors are remade at your cost; proof errors are ours."
  • "Thames Valley Gifts is a division of ChurchWiseAI LTD."

Must NEVER contain​

  • The customer's email address, or phone number (except where the carrier label needs it) — customer data belongs to the hub (agreement clause; CASL).
  • Any payment detail: amount paid, card brand/last 4, Stripe ids, e-transfer references.
  • HST amounts, the hub's commission, or other makers' items or prices.
  • Internal notes, the chat or call transcript, AI summaries, or the source lead.
  • The customer's original photo with EXIF/GPS, or any customer upload not used for this item.
  • Customer-facing links: the request, proof or pay token links.
  • Marketing-consent status or anything inviting the maker to contact the customer for other sales.

11. Consignment agreement — contents checklist​

The agreement template must contain every item below. The ops page stores agreement_version and agreement_signed_at; the template itself is a legal document outside the code. [LAWYER] FOUNDER DECISION (OPEN): one-time Ontario lawyer review of the final text (research gap: no Ontario online drop-ship consignment template exists).

  • Parties: ChurchWiseAI LTD operating as Thames Valley Gifts (BIN 1001760694), and the maker's legal name and business name.
  • True consignment: title stays with the maker until the customer's purchase; the hub acts as the maker's agent for the sale and for collecting payment (Lette criteria a–f).
  • The maker holds and stores their own stock and ships their own orders (F3); risk of loss stays with the maker until handover to the carrier or to the customer at pickup.
  • Goods held by the hub for pickup (if ever): who bears risk and insures. FOUNDER DECISION.
  • Commission: 20% / 25% of the pre-tax merchandise price (F2 — FOUNDER DECISION); shipping charges passed through to the maker; Stripe fees absorbed by the hub.
  • Pricing authority: who sets the retail price, and whether the hub may discount without consent. DECIDED 2026-09-28 (default adopted by orchestrator): the maker controls price (artist-form default).
  • Payouts by e-transfer on the 1st and 16th, each with a statement (F4); paid-date vs completed basis (§8.1 FOUNDER DECISION); carry-forward of negative balances.
  • Proceeds held separately from operating cash until paid out (Lette criterion e). [ACCOUNTANT] FOUNDER/ACCOUNTANT DECISION (OPEN): a separate bank account, or a ledger-only separation.
  • No rent, no listing fees, no minimums, no obligation on the hub to buy unsold goods.
  • No exclusivity (F5); the maker must pause or mark sold-out any listing they can no longer make, within 1 business day of knowing.
  • 30-day termination by either side (F6); open paid orders are completed and paid out.
  • Maker warrants originality (F7): original designs, no IP infringement, rights to any artwork used; the customer-supplied photo/artwork warranty sits with the customer.
  • Product safety and labelling compliance; product liability insurance for higher-risk categories (candles, bath, children's items, food). FOUNDER DECISION: which categories require proof of insurance.
  • Indemnity by the maker for breach of the warranties.
  • Errors (F8): the maker remakes or refunds maker errors at the maker's cost; the hub bears proof errors; the approved proof is the specification.
  • Production times: the maker commits to each product's lead time from payment, and to replying on proof questions within 1 business day. FOUNDER DECISION: exact numbers.
  • HST status representation (registrant yes/no, number) and a duty to tell the hub within 30 days of any change; the registrant treatment chosen (§9.2).
  • Customer data belongs to the hub; the maker may not use order data to market to or contact the customer except to fulfil the order (CASL, PIPEDA).
  • Photo and description licence: the maker lets the hub use product photos and text on the hub's site, chat/voice front desk and marketing, during the agreement and for 30 days after.
  • Monthly/semi-monthly statements as the regular accounting of sales (Lette criterion d); the maker's right to question a statement within 30 days.
  • Governing law: Ontario. Agreement version number and signature date.
  • Footer line: "Thames Valley Gifts is a division of ChurchWiseAI LTD."

12. Weekly KPI scorecard — /founder/[token]/tvg/kpis​

One page per ISO week (Mon–Sun, Toronto). Honest metrics only: every number links to the rows it counts; a metric with no data source shows "Not measured", never 0 and never an estimate. No "Delivered" or "Opened" email metrics (OUTBOUND_PROCESS_MAP honesty rule).

#KPIDefinitionSource
K1Inquiries by channelcount of chat sessions, voice calls, web-form requests, email/phone/in-person requests created in the weekchat log, local_business_leads (source voice), tvg_order_requests.source
K2After-hours shareshare of K1 created outside Mon–Fri 9:00–18:00 Torontotimestamps
K3Capture rateshare of chat sessions and voice calls ending with a name + contactleads + chat requests ÷ K1 (chat/voice)
K4Spec-complete rateshare of chat requests created with every required personalization field valid and spelling_confirmed_at settvg_order_items
K5Proof latencymedian and slowest hours from request created → first proof sent; target: 100% within 1 business dayevents
K6Proof approval rateproofs approved ÷ proofs sent (first version and any version)tvg_proofs
K7Proof-to-paidpaid ÷ approvedtvg_order_requests
K8Hand-offs by reasoncount of hand-offs from the AI desk by reason (Memorial, Quote, Artwork, Rush, Complaint/Remake, Other language, Not in fact sheet)lead metadata.handoff_reason, chat events
K9Remakes and refundscount and $ by fault (maker / hub proof / goodwill); "caught at proof" (change requests) vs "reached production"refunds, remake events, changes_requested
K10Per-maker activityper maker: product views [Not measured until analytics exist], AI-desk questions naming their products, requests, paid orders, net earnedevents, statements
K11Payouts on timestatements paid on or before the payout date ÷ statements duetvg_payout_statements
K12Action-list healthitems in Q1 older than 1 business day; Q5 overdue count, at the moment the week closesaction-list queries
  • Excluded from every KPI: requests cancelled with reason "QA test" and anything on the QA maker.
  • The page shows the previous 6 weeks as a small table for trend, no projections.

13. QA checklist (run on the deployed URL with the founder token; evidence attached)​

QA isolation — DECIDED 2026-09-28 (default adopted by orchestrator): the checks below need orders on the production database (there is no staging). The adopted approach: one internal maker "TVG QA Maker" (slug qa-maker, agreement version internal-qa) whose products carry a qa_only flag (already part of the Wave A schema) that hides them from the storefront, the catalogue API, the AI knowledge sync, KPIs, the HST export and real payouts, and QA customers use john+tvgqa@churchwiseai.com. This isolation rule is approved; WP4 QA may write orders under it.

#CheckExpectedEvidence
1Wrong token on every page and one admin API404 each; X-Robots-Tag: noindex on the valid pagecurl output
2/founder/<valid>/tvg on thamesvalleygifts.ca404curl
3Action list with empty datasix queues, each "Nothing here."screenshot
4Create a maker without agreement date; try to set a product available409 with the agreement sentence; UI option disabledscreenshot + API response
5Add agreement date + HST flag, retryproduct becomes availableDB row
6Try available on an is_demo_seed example409 "Demo seed listings can never be sold."response
7Schema builder: text field label without the limit; raw JSON with a missing braceinline errors as in §6.2; Save disabled for invalid JSONscreenshot
8Pause the maker; attempt an order via the public API and via chatboth refused not_orderableresponses
9Promote a voice lead twice (double-click)exactly one request; second click opens it; lead has metadata.tvg_order_request_idDB count
10Send proof on the promoted request with zero itemsbutton disabled / 409screenshot
11Send proof on a valid requestproof v1 sent; the customer proof page shows "Awaiting your approval" (sample the customer page before and after — status text changes)two screenshots
12Record a manual e-transfer with the wrong amount, then the right amountfirst refused showing the expected total; second → paid, due date set, job sheet sentDB rows + job-sheet email
13Inspect the job sheetcontains every §10 "must contain" item; contains none of the "must never contain" items (grep the email body for the customer email, "$", "HST", "Stripe", token links)email source
14Mark shipped without trackingrefused; with carrier + tracking → shippedresponses
15Cancel without a reasonrefusedresponse
16Build the §8.5 fixture; generate 2026-10-Alines and totals match the fixture line by line (5 values × 4 lines + totals); TVG-000104/105/106 absent; TVG-000107 in Period Astatement vs fixture diff = empty
17Finalize A; edit TVG-000103's quantity; regeneratefinalized A unchanged; items on_statementfrozen JSON
18Mark A paid without a reference; then with one; then againrefused; paid; third refusedresponses
19Record the two refunds; generate 2026-10-Bmatches the fixture; clawback −$22.40; hub-proof-error line $0.00diff = empty
20Monthly roll-up October 2026net $347.79 and every column = A + Bscreenshot
21Change commission to 2500; regenerate drafts (before finalize)all lines unchangeddiff
22HST export for October 2026matches §9.1 fixture table; plus the rounding-divergence order proves sum-of-ordersCSV
23HST export per-maker tablenon-registrant row shows "no HST on commission"; a registrant maker with no chosen treatment blocks availableCSV + 409
24KPI page for the fixture weekQA-maker rows excluded; any metric without a source reads "Not measured"screenshot
25Positive control: deliberately break one fixture value (e.g. set commission snapshot 2100 on one line)test 16 fails and names the linefailing run

Critical-path note: WP4 does not touch middleware.ts or the Stripe webhook, but the refund action calls Stripe — refunds are tested only in Stripe test mode until the founder approves a live refund.

14. Open items for the Stage-2 interview​

  1. DECIDED 2026-09-28 (FOUNDER DECISION FINAL): commission is 20% for founding makers, locked for 12 months; implemented as per-maker commission_bps, default 2000 (a maker-level parameter, never a constant — a future maker could still be onboarded at a different rate outside the founding cohort).
  2. DECIDED 2026-09-28 (default adopted by orchestrator): two statement periods per month (Period A / Period B), each independently generated and finalized — see §0.
  3. DECIDED 2026-09-28 (FOUNDER DECISION FINAL): payout basis is completed (a maker is paid for an order only once it is picked up or shipped), not paid-date; implemented as config tvg_settings.payout_basis, default completed. The §8.5 worked fixture is recomputed on this basis. DECIDED 2026-09-28 (default adopted by orchestrator): payout dates are the calendar date even on weekends/holidays.
  4. DECIDED 2026-09-28 (default adopted by orchestrator): Stripe fees absorbed by the hub; goodwill refunds absorbed by the hub; hub pays the maker for hub-caused remakes at the original net.
  5. [ACCOUNTANT] OPEN. FOUNDER/ACCOUNTANT DECISION: tax rate table incl. NS 14% [UNVERIFIED]; PST/QST duties; registrant treatment (a) vs (b); DPO rules; separate proceeds account; filing period.
  6. DECIDED 2026-09-28 (default adopted by orchestrator): approved-unpaid expiry at 7 days; Q1 "unanswered" threshold at 1 business day.
  7. OPEN — bless transactional templates before first send: proof ready, ready for pickup, shipped, maker job sheet.
  8. DECIDED 2026-09-28 (default adopted by orchestrator): QA isolation approach per §13 — hidden QA maker + qa_only schema column (already in the Wave A schema).
  9. DECIDED 2026-09-28 (default adopted by orchestrator): pricing authority — the maker controls price (artist-form default). Still OPEN, no default: which categories require proof of insurance; [LAWYER] one-time Ontario lawyer review of the agreement.
  10. Rule #14: FEATURE_REGISTRY.md needs a Thames Valley Gifts row before code (memo §0.5).
  11. [UNVERIFIED] The TVG local_businesses row does not exist yet; every reader of local_businesses must be checked so it is not treated as a prospect or customer (memo R7).

15. Decision log (2026-09-28 editorial pass)​

Defaulted (DECIDED 2026-09-28, default adopted by orchestrator; the founder can still override): the two-period-per-month payout model (§0); the 7-day Q3/expiry threshold; calendar-date payouts on weekends/holidays; Stripe fees, goodwill refunds and hub-caused remakes absorbed by the hub; pricing authority (maker controls price); QA isolation approach (hidden QA maker + qa_only, already in the Wave A schema); the GST/HST-number-via-env-var default on payout statements.

Decided FINAL 2026-09-28 (no longer open):

  • F2 / commission — 20% for founding makers, locked 12 months; implemented as per-maker commission_bps, default 2000.
  • Payout basis — completed (pay only once picked up/shipped), not paid-date; implemented as config tvg_settings.payout_basis, default completed. The §8.5 fixture is recomputed on the completed basis to match.

Left open (per explicit instruction, or no default exists):

  • [ACCOUNTANT] items: filing period, the provincial tax rate table (incl. NS 14%), PST/QST duties and registration thresholds, registrant treatment (a) vs (b), the 2021 distribution-platform-operator question, and the separate-proceeds-account question.
  • [LAWYER] one-time Ontario lawyer review of the consignment agreement.
  • Blessing transactional templates (proof ready, ready for pickup, shipped, maker job sheet): left open until drafted.
  • No default existed for: insurance categories requiring proof, and the maker's exact lead-time / reply-time commitments (open numbers, founder's to set); the e-transfer receiving address (see tvg-proof-to-pay.md P8); who bears risk/insurance for goods held at the hub for pickup.

Inconsistency found and fixed: the request-handling action table (§7.2) had no "Decline" action and no "Expire from proof_sent" path, even though tvg-proof-to-pay.md §3.2 defines both transitions (founder "Decline" with required reason → declined; founder "Expire" on an unapproved, expired proof → expired). Both rows have been added to §7.2 so the two specs agree.