frontpage-host 0.1.29 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -119,7 +119,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
119
119
  ## Modules & articles
120
120
 
121
121
  - Forms: `data-fp-form` markup in pages + `frontpage modules` for enable/config. No submit JS / honeypot / `action`. Do not put a CRM webhook or token in HTML.
122
- - Connect-once modules: Store Stripe connect is `frontpage store connect`. `--no-open` for CI. `request-setup` remains valid when you want the **editor amber bar** instead. Payments and the store share one Stripe account — payments disconnect fails if the store still uses it. Auto-active modules (forms/callbar/…) need markup, not request-setup. Typed settings: `frontpage modules callbar|forms|booking|signups get|set` (forms set needs `--instance`; do not target `default`). Form destinations (HubSpot / Pipedrive / Privyr): connect once, then attach each form. `frontpage modules forms destinations|connect-destination|attach-destination|detach-destination|remove-destination --yes|request-destination-setup`. Token write is **admin**; `request-destination-setup` lights the editor amber bar (**editor** key). Do not use `request-setup forms` for a CRM (no-op). Connecting does not attach every form. Privyr: paste the Incoming Webhook URL from Automations → Lead Sources → Webhooks (`https://www.privyr.com/api/v1/incoming-leads/…`), not a token and not Outgoing Webhooks. HubSpot: Development → Keys → Service keys or Development → Legacy apps → Auth → Show token. Pipedrive: profile → Company settings → Personal preferences → API. Full docs: https://frontpage.host/developers/modules#forms-destinations. `frontpage modules <key>` prints that key's CLI usage (exit 2). Index: https://frontpage.host/developers/modules. Store: https://frontpage.host/developers/store
122
+ - Connect-once modules: Store Stripe connect is `frontpage payments connect`. `--no-open` for CI. `request-setup` remains valid when you want the **editor amber bar** instead. Payments and Store share one Stripe account. Pause Payments through its availability action; generic module writes cannot bypass invoice/recurring stop rules. Auto-active modules (forms/callbar/…) need markup, not request-setup. Typed settings: `frontpage modules callbar|forms|booking|signups get|set` (forms set needs `--instance`; do not target `default`). Form destinations (HubSpot / Pipedrive / Privyr): connect once, then attach each form. `frontpage modules forms destinations|connect-destination|attach-destination|detach-destination|remove-destination --yes|request-destination-setup`. Token write is **admin**; `request-destination-setup` lights the editor amber bar (**editor** key). Do not use `request-setup forms` for a CRM (no-op). Connecting does not attach every form. Privyr: paste the Incoming Webhook URL from Automations → Lead Sources → Webhooks (`https://www.privyr.com/api/v1/incoming-leads/…`), not a token and not Outgoing Webhooks. HubSpot: Development → Keys → Service keys or Development → Legacy apps → Auth → Show token. Pipedrive: profile → Company settings → Personal preferences → API. Full docs: https://frontpage.host/developers/modules#forms-destinations. `frontpage modules <key>` prints that key's CLI usage (exit 2). Index: https://frontpage.host/developers/modules. Store: https://frontpage.host/developers/store
123
123
  - Google reviews: platform nodes only (`data-fp-google-reviews` / `data-fp-google-ask` + required `data-fp-requires="google_reviews"`). `data-layout` is `feed` (default), `badge`, or `link`. Never invent stars, quotes, a 1–10, or a Maps URL. Do not pick a Place ID via Autocomplete and do not write `placeId` through `modules config`. Owner-supplied Place ID / Maps URL: `frontpage modules google_reviews connect --place-id …` or `--maps-url …` (server resolves + seeds). Curation: `list-reviews`, `hide --id`, `show --id`, `set-stars`, `set-sort`, `set-weight`, `reset-weights`, `refresh`, `disconnect`. Usage: `frontpage modules google_reviews`. Docs: https://frontpage.host/developers/modules/google-reviews. Ask-widget scores: `frontpage inbox google_reviews`.
124
124
  - Articles: `frontpage articles …` for lifecycle; bodies via sync under `content/` or `articles content get|set`. Section landing is an owned `src/pages/<section>/index.astro`.
125
125
  - Migrate a folder into Articles: prefer `frontpage prompt "migrate … into articles"` — do not invent a mass delete+write migration.
@@ -129,7 +129,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
129
129
  - Analytics: bare `frontpage analytics` = dashboard; also `summary|query|funnel|timeseries|compare|sql` for structured reads (SQL is guard + RLS site-scoped).
130
130
  - People CRM: `frontpage people list|get|add|set|note|rm-note|follow-up|hide|unhide|tag|export`. Site-scoped person graph (email or phone). Inbox stays the raw ledger. Hide is not forget. List export is CSV (not a one-person legal dump). Full guide: https://frontpage.host/developers/people
131
131
  - Routines: `frontpage routines list|get|add|set|pause|resume|rm|run|runs`. One routine can hold several hours and several days. `8am,12pm,4pm` on `tue,thu,fri` stores hours `[8, 12, 16]` and weekdays `[2, 4, 5]`. Ask for an IANA timezone. Do not guess UTC. Full guide: https://frontpage.host/developers/routines
132
- - Store catalog: `frontpage store items|variants|categories|settings|orders|payments|connect|status|disconnect`. Integer cents only (`$49.99` is `4999`). `--price-cents` is the usual cost. Optional `--discount-cents` is the sale the buyer pays (must be lower). Create a category before assigning it. Photos are a site path `/__media/uploads/<this-slug>/…` or an HTTPS URL already saved on this site. `frontpage store connect [--no-open]` opens Frontpage then Stripe. Checkout is store-wide (`settings set-checkout`); do not use leftover `items --checkout` to pick cart vs Pay now. Service fee is `settings set-checkout-fee`. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one `src/pages/products/item.astro` (or shop) with `data-fp-store-product`. If they said no, skip that file and do not bind `href` or `url`. Description is a small markdown subset; product-page bind is `<div data-fp-store-bind="description">`. Every store control carries `data-fp-requires="store"`. Do not bake titles or prices. Full guide: https://frontpage.host/developers/store
132
+ - Store catalog: `frontpage store items|variants|categories|settings|orders|import`. Integer cents only (`$49.99` is `4999`). `--price-cents` is the usual cost. Optional `--discount-cents` is the sale the buyer pays (must be lower). Create a category before assigning it. Photos are a site path `/__media/uploads/<this-slug>/…` or an HTTPS URL already saved on this site. `frontpage payments connect [--no-open]` opens Frontpage then Stripe. Store always uses Add to cart. Use Payments for standalone Pay now and invoices. Service fee is `settings set-checkout-fee`. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one `src/pages/products/item.astro` (or shop) with `data-fp-store-product`. If they said no, skip that file and do not bind `href` or `url`. Description is a small markdown subset; product-page bind is `<div data-fp-store-bind="description">`. Every store control carries `data-fp-requires="store"`. Do not bake titles or prices. Full guide: https://frontpage.host/developers/store
133
133
 
134
134
  ## Agent prompt loop
135
135
 
@@ -261,3 +261,6 @@ Use `frontpage people overview`, `groups`, `stages`, `membership`, `rules`, `att
261
261
  ## Customer Inbox
262
262
 
263
263
  Internal, allowlisted sites only. Use `frontpage customer-inbox get|put|post RESOURCE`, stable write keys and current versions. Full guide: [CUSTOMER-INBOX.md](./CUSTOMER-INBOX.md). `export CONVERSATION_ID --out FILE` writes authorized paginated history and file metadata; `download FILE_ID --out FILE` downloads through a file grant. Outputs never overwrite existing files. `frontpage usage` reports shared site credits, mail and storage. Human owners may handle conversations; website agents use `manage_customer_inbox` for setup/settings/widget/internal knowledge only, never a human CLI key to read or join customer threads.
264
+
265
+
266
+ Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
package/CUSTOMER-INBOX.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Customer Inbox
2
2
 
3
- Customer Inbox uses the site's existing AI credits and storage allowance. It is available on internal plans and approved pilot sites. The support agent uses fixed GPT-6 Luna and bills AI usage at 10× raw provider cost. Human handling does not call a model for each message.
3
+ Customer Inbox uses the site's existing AI credits and storage allowance. It is available on internal plans and approved pilot sites. The support agent uses fixed GPT-6 Luna and bills AI usage at 10× raw provider cost. During human handling, customer messages use ordinary routing. Each authenticated owner email uses a small metered Luna Decisions call to keep private guidance, ownership controls and customer-facing replies distinct; the full support agent stays paused. UI and CLI controls need no intent call.
4
4
 
5
5
  Run `frontpage help customer-inbox`. Use the site's normal CLI key:
6
6
 
@@ -64,9 +64,19 @@ Lock a knowledge note with `locked:true`. The support agent cannot change locked
64
64
 
65
65
  Private guidance stays in the same internal thread. The agent asks by a private email and stores its question in the history. Replying by email or UI gives context and leaves the agent responsible for the customer. The agent uses that context to answer and can update unlocked internal knowledge.
66
66
 
67
- Email replies route through opaque, recipient-bound Reply-To addresses linked to the site, conversation, purpose, owner and handler version. Subjects and message text never choose a customer thread. The service checks current owner membership and sender authentication before accepting private owner mail. To take over by email, reply only `Take over this conversation`. The owner receives the customer-visible history and replies through Inbox. If history exceeds the email limit, the notification links to the full paginated history/export; it never claims a shortened transcript is complete. To return control, reply only `Agent, take it from here`. Mixed or unclear handoff text stays private for review. Customer and owner addresses are never added to a shared CC thread by Inbox.
67
+ General owner facts, such as support hours and standard policies, update related unlocked knowledge without an explicit save request. One-time customer exceptions and private staffing details stay in the conversation. The agent reads the full note and records verified evidence before changing it. Late private guidance can still update knowledge after the customer has an answer; that work cannot send a duplicate customer reply. Locks, manual handling, paused automation and work limits still apply.
68
68
 
69
- Manual customer/owner exchanges use ordinary routing code. No support inference runs for every manual message. Returning control checks only eligible unanswered input and suppresses stale automatic sends.
69
+ Search results contain previews. The support agent reads the complete matching note or approved source before deciding an answer is missing. A verified owner reply can provide lasting facts on a different topic from the customer's question; the same job processes those facts before replying or finishing. The worker verifies the message kind and owner identity. Customer text cannot claim that authority. A specific offer to one customer stays in that thread even when the same owner reply also contains a general policy.
70
+
71
+ Email replies route through opaque, recipient-bound Reply-To addresses linked to the site, conversation, purpose, owner and handler version. These are local addresses on one domain, not a new subdomain per thread. Subjects and message text never select the customer thread. The service checks the exact verified sender, current owner membership, route and receiving-server DMARC proof before interpreting owner mail.
72
+
73
+ Every owner email and its files enter private custody first. A Luna Decisions call reads the fresh text and bounded, labeled thread context to distinguish guidance, takeover, return, customer reply or unclear input. Use ordinary language to ask to handle the customer, or ask the agent to resume. Questions, quoted commands, negations and future conditions do not transfer control. UI/CLI `control` remains a typed, model-free action. No phrase whitelist controls ownership. A valid but uncertain control answer can use one bounded Luna tool-call fallback. It can choose only private guidance, takeover, return or unclear, with an exact fresh-text excerpt; it cannot authorize a customer reply. Both calls require current credits and distinct once-only receipts. Invalid responses, unknown paid calls and provider outages stay private rather than triggering another inference.
74
+
75
+ Guidance keeps the agent responsible. Takeover sends the owner a separate customer-visible transcript; reply through that relay address so Inbox preserves the history. A customer-facing email creates a separate public message only after a settled decision and current owner-session check. The original intake remains private for audit. Mixed or uncertain input, unavailable inference and exhausted credits stay private with an attention notice. Retry recoverable budget/receipt pauses using the existing job operation; an unknown paid attempt is never inferred again. A fresh reply to the original guidance address can also request return, while stale customer-relay sessions cannot publish.
76
+
77
+ Private owner help, customer relay and customer email use separate threading chains. Inbox stores Resend's actual Message-ID and sends In-Reply-To/References; it never invents provider message IDs. Missing metadata uses scoped reconciliation without another send. Mail clients still control their own grouping. Owners and customers do not share a CC help thread. If the owner already addressed the customer, Inbox records the canonical public reply but does not send a second customer email.
78
+
79
+ Return checks current settings, site eligibility, unanswered work and site credits before support can resume. It cancels stale automatic work rather than replaying an old answer.
70
80
 
71
81
  Set `enabled:false` in settings to use manual handling for new conversations. The service binds that relay to the configured verified owner. Existing agent threads switch to manual handling on the next customer message or public owner reply. Re-enable the agent before returning a thread to it. Private guidance alone always stays private and never transfers control.
72
82
 
@@ -214,4 +224,14 @@ The support agent has its own system prompt plus the versioned private business
214
224
  | `wait` | `reason` | Pause safely without sending. |
215
225
  | `finish` | None | Complete this bounded job. |
216
226
 
217
- Decisions screens malicious input only. It never chooses customer recipients, business answers, tools, owner handling or knowledge changes. Shared inference utilities provide the existing same-model credential/transport fallback; no expensive model substitution or customer model picker is added.
227
+ Decisions screens malicious customer input and interprets authenticated owner-email intent. It never chooses IDs, recipients, permissions, business answers, support tools or knowledge changes. Owner intent uses bounded labeled context, one primary Luna call, at most one bounded same-Luna control fallback, and the same exact-token, raw-cost, 10× ledger receipt as support. Shared inference utilities provide the existing same-model credential/transport fallback; no expensive model substitution or customer model picker is added.
228
+
229
+ The installed widget loads two public static scripts: `/customer-inbox/widget.js` and `/customer-inbox/view.js` on `https://app.frontpage.host`. The shared renderer contains no session, owner data or credentials. If your app sets a Content Security Policy, allow those script URLs and connections to `https://frontpage-inbox-api.fly.dev`; keep the existing widget origin and identity checks. Owner Inbox pages remain authenticated.
230
+
231
+ ## Live updates and email continuation
232
+
233
+ The workspace uses a short authenticated stream through its cookie bridge; the installed widget uses authenticated fetch with its existing visitor/app-identity headers. Streams carry empty invalidation hints, never message text, private counters, recipients or file metadata. Clients re-read their authorized history/status. One restricted Postgres listener per Inbox process wakes the existing outbox publisher and fans out hints. Notifications are best-effort; durable rows, leases, recovery scans and receipt reconciliation remain authoritative. Disconnected clients refresh and use bounded fallback polling. No token enters a stream URL, and each short stream reauthorizes on reconnect.
234
+
235
+ A new unverified chat with an email requests a generic verification email automatically. It does not send private history or assume that typing an address proves identity. The owner can still answer in chat while the visitor verifies. Confirmation rotates the session and binds future public replies to that email; if the latest customer input already has an unsent public answer, Inbox queues that one answer once. It does not mail a backlog or an answer that predates a newer customer question. Verified replies mirror to email whether the page is open or closed; no presence-tracking service is needed.
236
+
237
+ The workspace shows customer name/email first, latest public message in each row, and a copyable reference in conversation details. Conversation list and history scroll independently inside a bounded viewport. Settings use shared workspace controls and a presentation-only preview built from the same renderer as the installed widget. Preview changes send no email or model request. Internal note history/locks/revert stay on the note's own page.
package/PAYMENTS.md ADDED
@@ -0,0 +1,334 @@
1
+ # Payments reference
2
+
3
+ Frontpage keeps one Stripe Express account per site. Store owns products, cart
4
+ checkout, fulfillment, buyer service fees, receipt selection and orders. Payments
5
+ owns Stripe setup, tax, payment activity, standalone Pay now and merchant invoices.
6
+ Frontpage subscription billing stays separate.
7
+
8
+ Use `frontpage help payments --json` for discoverable command help. Normal
9
+ `--site`, authentication, `--json`, `--quiet` and exit-code rules apply. The API
10
+ requires Scale, Enterprise or Internal CLI access. This does not remove product
11
+ permissions or invoice entitlements.
12
+
13
+ ## Access and output
14
+
15
+ | Operation | Minimum role | Additional rule |
16
+ | --- | --- | --- |
17
+ | Connection status | Viewer | Site membership |
18
+ | Private activity, invoices, arrangements, tax and Pay now reads | Editor | Site membership |
19
+ | Existing tax settings and registration writes | Editor | Explicit supplied jurisdiction/address and confirmation |
20
+ | Stripe browser setup, payment availability, Pay now writes, invoice sends and billing links | Admin | Confirmation where specified |
21
+ | New invoice, new arrangement or resume | Admin | Growth or higher and Payments ready |
22
+ | Historical reads, refresh, void and future cancellation after downgrade | Existing role above | No new invoice entitlement required |
23
+
24
+ All routes authorize the API-key owner against the requested site. A different
25
+ site's record returns 404. Request fields cannot grant a role, change the trusted
26
+ site or choose a connected account. JSON success uses the existing CLI envelope
27
+ `{ "ok": true, "command": "payments", "data": { ... } }`. Failure uses the
28
+ existing error envelope with request ID and exit code. Human mode prints the
29
+ returned fields, including partial outcomes. A successful HTTP request does not
30
+ mean a payment settled or an email arrived.
31
+
32
+ ## Setup, availability and history
33
+
34
+ ```sh
35
+ frontpage payments status
36
+ frontpage payments settings --json
37
+ frontpage payments connect --no-open
38
+ frontpage payments availability preview --json
39
+ frontpage payments availability off --yes
40
+ frontpage payments availability on --yes
41
+ frontpage payments activity list --json
42
+ frontpage payments activity get <record-uuid> --source order
43
+ ```
44
+
45
+ Connect returns the existing signed-in admin browser URL. Without `--no-open`,
46
+ human mode opens it. The CLI does not accept Stripe terms, finish onboarding or
47
+ create another account to get around a restriction. Recheck `status` after the
48
+ browser flow. Account existence or a return URL alone does not prove readiness.
49
+ Currency comes from Stripe and is read-only.
50
+
51
+ Payments off first blocks new charges, then stops recurring billing and voids
52
+ unpaid issued invoices. It preserves paid history and the Express account. Review
53
+ the counts with `availability preview`. `off` reads current counts before its
54
+ confirmed write; a changed set returns 409. A bounded request may return
55
+ `pause.complete:false`, remaining counts and failed record IDs. New charging stays
56
+ blocked. Resolve errors and repeat `off`; do not turn Payments on while a pause is
57
+ incomplete. A payment already processing may settle and keeps its real result.
58
+
59
+ Turning Payments on rechecks Stripe readiness. It does not enable Store, restore
60
+ void invoices, resume arrangements or collect missed periods. Store off affects
61
+ only Store; it leaves standalone payments and billing running.
62
+
63
+ Activity combines the latest 200 Store orders and 200 Pay now payments with up to
64
+ 100 invoices and 100 recurring arrangements each. `mayHaveMore` states when older
65
+ records may exist. It preserves captured currencies, amounts and payment states.
66
+ Recurring amounts are per-period amounts before tax. Use invoice/recurring list
67
+ commands for their paginated history. Get an order/payment with its explicit
68
+ source; equal IDs in different tables do not identify the same record.
69
+
70
+ ## Tax
71
+
72
+ ```sh
73
+ frontpage payments tax get --json
74
+ frontpage payments tax settings --file tax-settings.json --yes
75
+ frontpage payments tax registrations list
76
+ frontpage payments tax registrations add --country US --state CA --yes
77
+ frontpage payments tax registrations end <tax-registration-id> --yes
78
+ ```
79
+
80
+ Tax settings JSON contains `country`, `line1`, `city`, optional `state`, `postal`
81
+ and an existing supported `taxCode`. GET returns current choices and registrations.
82
+ Use the business's actual address and registrations. Frontpage does not choose
83
+ jurisdictions or provide tax advice. End uses the exact registration ID returned
84
+ by Stripe. These operations reuse current connected-account tax helpers. They do
85
+ not expose Account Session secrets or a second tax calculator. An invoice preview
86
+ blocks if tax settings cannot be confirmed; it does not silently drop tax.
87
+
88
+ ## Invoice input and review
89
+
90
+ For an existing CRM person:
91
+
92
+ ```json
93
+ {
94
+ "recipient": { "personId": "<crm-person-uuid>" },
95
+ "description": "Website maintenance",
96
+ "amountCents": 12500,
97
+ "kind": "once",
98
+ "collection": "manual",
99
+ "dueDays": 14
100
+ }
101
+ ```
102
+
103
+ To add or match a CRM customer when sending, replace `recipient` with:
104
+
105
+ ```json
106
+ {
107
+ "name": "Customer name",
108
+ "email": "customer@example.com",
109
+ "address": {
110
+ "line1": "Customer billing street",
111
+ "city": "City",
112
+ "region": "CA",
113
+ "postal": "94080",
114
+ "country": "US"
115
+ }
116
+ }
117
+ ```
118
+
119
+ `address` also accepts `line2`. Supply a real billing address when automatic tax
120
+ needs it. A CRM person must be visible and have a valid billing email. Inline
121
+ matching stays within the site and does not grant marketing permission. Preview
122
+ creates no CRM person, Stripe Customer, invoice, invitation or charge.
123
+
124
+ Money fields use integer minor units in the account currency despite the existing
125
+ `Cents` field names. USD 12500 means $125.00; JPY 12500 means ¥12500. The current
126
+ amount range is 50 through 10,000,000 minor units. Description contains 1–500
127
+ characters. `dueDays` is 7, 14 or 30; omitted means 7. A one-time invoice uses
128
+ manual collection and cannot carry recurring date/cadence fields.
129
+
130
+ ```sh
131
+ frontpage payments invoices preview --file invoice.json --json
132
+ frontpage payments invoices send --file invoice.json \
133
+ --preview-hash <hash-from-preview> --operation-key <stable-uuid> --yes --json
134
+ ```
135
+
136
+ Preview returns the normalized recipient/terms, currency, subtotal, tax, total,
137
+ tax status and `previewHash`. When `billingContactChanged` is true, review the CRM email, name and address and pass `--confirm-billing-contact` to update the saved Stripe contact for future billing. This does not rewrite issued invoices or prior consent. Send repeats those exact terms and hash. It rechecks
138
+ readiness and totals before finalization/delivery. Changed terms or totals return
139
+ 409. Common flags can replace simple JSON fields: `--person` or `--email` with
140
+ `--name`, `--description`, `--amount-cents`, `--kind`, `--collection`, `--due-days`,
141
+ `--cadence`, `--first-date`, `--time-zone`. Nested addresses use `--file`.
142
+
143
+ The operation key contains 16–120 letters, digits, dots, colons, underscores or
144
+ hyphens and starts with a letter/digit. A UUID is suitable. Preserve the same key
145
+ and input after a timeout or uncertain result. Do not use a new key to force a
146
+ send. Repeated requests return the same local/Stripe invoice rather than charging
147
+ again. If Stripe's safe retry window has passed, reconcile the saved object first.
148
+
149
+ ## Invoice management
150
+
151
+ ```sh
152
+ frontpage payments invoices list --limit 50 --offset 0 --json
153
+ frontpage payments invoices list --arrangement <arrangement-uuid> --json
154
+ frontpage payments invoices get <invoice-uuid>
155
+ frontpage payments invoices refresh <invoice-uuid>
156
+ frontpage payments invoices resend <invoice-uuid> --operation-key <stable-uuid> --yes
157
+ frontpage payments invoices void <invoice-uuid> --yes
158
+ ```
159
+
160
+ List accepts limit 1–100 and offset 0–100000; it returns `nextOffset` or null.
161
+ Records include immutable recipient/terms, actual currency/subtotal/tax/total,
162
+ paid/remaining amounts, due/issued/paid dates, Hosted Invoice Page/PDF, delivery
163
+ and any last error. Expected platform fee and actual collected fee are separate.
164
+ An unpaid invoice does not claim a collected fee. Source account/setup secrets
165
+ and customer-access nonces are not returned.
166
+
167
+ Resend only accepts an open invoice and uses its existing saved billing customer.
168
+ It never creates a replacement invoice. Void requires confirmation and cannot
169
+ restore an invoice after Stripe confirms it is void. A payment race preserves an
170
+ actual paid result. An unsent draft's cancellation is recorded separately from
171
+ Stripe's real draft state. There is no manual mark-paid, refund or raw charge API.
172
+
173
+ Stripe sends invoice emails and hosts payment/recovery. `delivery:accepted` means
174
+ the send API accepted the request, not that the email arrived or was opened.
175
+ Stripe test mode may suppress email delivery; provider acceptance is the most
176
+ that a test-mode report may claim.
177
+
178
+ ## Recurring manual invoices and autopay
179
+
180
+ Use the same recipient, description, amount and due window. Set `kind:recurring`,
181
+ `cadence`, `firstDate` and `timeZone`. Collection is `manual` or `autopay`.
182
+
183
+ ```json
184
+ {
185
+ "recipient": { "personId": "<crm-person-uuid>" },
186
+ "description": "Monthly maintenance",
187
+ "amountCents": 12500,
188
+ "kind": "recurring",
189
+ "collection": "manual",
190
+ "cadence": "monthly",
191
+ "firstDate": "2026-12-01",
192
+ "timeZone": "America/Chicago",
193
+ "dueDays": 14
194
+ }
195
+ ```
196
+
197
+ Supported cadences are `weekly`, `fortnightly`, `monthly`, `quarterly`, `yearly`.
198
+ The first date is YYYY-MM-DD in an explicit IANA zone. Billing begins at 09:00 in
199
+ that zone. Choose a future date. Stripe owns later native calendar periods,
200
+ including short months/leap years. No Frontpage recurring scheduler, backfill or
201
+ proration is added.
202
+
203
+ New recurring arrangements use Stripe's native platform fee: 2% of the invoice
204
+ total, including tax, unless the existing server fee setting is zero. The saved
205
+ arrangement freezes that server-controlled rate before billing starts. For
206
+ example, a $100 invoice plus $9.88 tax has a $2.20 platform fee. The fee is
207
+ deducted from the business's proceeds, not added as a customer surcharge.
208
+ Stripe's own processing and Billing/Invoicing charges are separate. Store,
209
+ Pay now and one-time invoice fee calculations keep their existing basis.
210
+ Reconciliation records expected and actual fees; it does not set the fee or
211
+ collect recurring payments. Stripe handles collection even if a webhook is late.
212
+
213
+ ```sh
214
+ frontpage payments recurring preview --file recurring.json --json
215
+ frontpage payments recurring create --file recurring.json \
216
+ --preview-hash <hash> --operation-key <stable-uuid> --yes
217
+ frontpage payments recurring list --limit 50 --offset 0 --json
218
+ frontpage payments recurring get <arrangement-uuid>
219
+ frontpage payments recurring refresh <arrangement-uuid>
220
+ frontpage payments recurring cancel <arrangement-uuid> --yes
221
+ frontpage payments recurring resend-invitation <arrangement-uuid> \
222
+ --operation-key <stable-uuid> --yes
223
+ ```
224
+
225
+ Manual collection creates a native future Stripe schedule. It does not issue an
226
+ immediate first invoice. Stripe creates one invoice per period and emails the
227
+ hosted payment link. Autopay create saves a pending authorization and sends an
228
+ invitation. The customer reviews the amount, tax, cadence and first date, agrees,
229
+ and finishes Stripe-hosted setup on the same connected account/customer. Only
230
+ verified setup can start the schedule. A saved method, setup return query or owner
231
+ flag alone never supplies consent. Setup itself is not a payment.
232
+
233
+ Arrangement cancellation stops future billing without changing issued invoices
234
+ or adding a final prorated charge. Payments off pauses arrangements by stopping
235
+ their native future schedule. Only paused arrangements may resume. Preview the
236
+ same recipient/amount/cadence/collection with a new future date, then:
237
+
238
+ ```sh
239
+ frontpage payments recurring resume <paused-arrangement-uuid> \
240
+ --first-date 2026-12-01 --preview-hash <hash-from-preview> \
241
+ --operation-key <stable-uuid> --yes
242
+ ```
243
+
244
+ Resume creates a linked future arrangement with no missed-period invoices. A
245
+ second different resume request is refused. Autopay seeks fresh customer consent.
246
+ Changed terms require cancellation and a new reviewed request; there is no hidden
247
+ edit/proration action. Invitation resend applies only while awaiting authorization.
248
+ It expires/replaces an unfinished old setup and keeps its own retry key.
249
+
250
+ ## Customer billing access
251
+
252
+ ```sh
253
+ frontpage payments customers send-billing-link <customerMapId> \
254
+ --operation-key <stable-uuid> --yes
255
+ frontpage payments customers billing-link <customerMapId> \
256
+ --operation-key <stable-uuid> --json
257
+ ```
258
+
259
+ Use `customerMapId` from the site's invoice/arrangement. It is not a raw Stripe
260
+ customer ID. Send uses the mapped customer's current saved Stripe billing email;
261
+ the caller cannot choose another recipient. Links expire after 30 minutes. A raw
262
+ returned link is sensitive: do not log/share it. The website agent only sends
263
+ links; it cannot request raw customer access.
264
+
265
+ Customer access needs no owner login. It stays scoped to site/account/customer
266
+ and uses a small customer page plus the connected-account Stripe portal. The
267
+ customer can pay an invoice, change the saved method, cancel future billing or
268
+ choose manual payment for future invoices. These actions do not pay an existing
269
+ balance, authorize another arrangement or expose Frontpage subscription billing.
270
+ Authorization invitations expire after 24 hours; an expired link requires a new
271
+ invitation. A completed authorization exposes Manage billing.
272
+
273
+ ## Standalone Pay now
274
+
275
+ ```sh
276
+ frontpage payments buttons list --limit 50 --offset 0 --json
277
+ frontpage payments buttons get consultation
278
+ frontpage payments buttons create --id consultation --title Consultation --price-cents 5000
279
+ frontpage payments buttons update consultation --price-cents 7500
280
+ frontpage payments buttons update consultation --file button-details.json
281
+ frontpage payments buttons archive consultation --yes
282
+ ```
283
+
284
+ These reuse current item/variant pricing and visitor `/__pay`. Creation registers
285
+ the exact ID atomically with the item in existing Store settings; it returns
286
+ markup for an explicit `data-fp-pay` button. Existing standalone references come
287
+ from the site's authorized markup. A Store card/CTA, no-shipping flag or old
288
+ instant setting is not proof of Pay now ownership. No new button table or fleet
289
+ conversion exists. Price updates are separate from detail updates. Open amount
290
+ creation uses `--amount open --min-cents N` and a minimum variant price. Item IDs
291
+ remain existing 2–40-character kebab IDs. Lists are bounded with `nextOffset`.
292
+
293
+ ## REST paths
294
+
295
+ All paths below use `/api/v1/sites/{slug}/payments`. GET query fields match the
296
+ documented filters. POST/PATCH fields match the reviewed domain inputs above.
297
+ Financial consequences need `confirm:true`; stable retryable sends additionally
298
+ need `operationKey`. This is a resource API, not arbitrary action dispatch.
299
+
300
+ | Method | Resource | Result |
301
+ | --- | --- | --- |
302
+ | GET | `/status`, `/settings` | Safe current connection/readiness and read-only currency |
303
+ | POST | `/connect` | Signed-in admin browser handoff |
304
+ | GET / POST | `/availability` | Pause counts/hash or confirmed availability outcome |
305
+ | GET | `/activity`, `/activity/{order\|payment}/{id}` | Current source-aware activity/detail |
306
+ | GET | `/tax`, `/tax/registrations` | Existing tax state/options/registrations |
307
+ | POST | `/tax/settings`, `/tax/registrations` | Existing tax settings/registration write |
308
+ | POST | `/tax/registrations/{id}/end` | End the specified registration |
309
+ | GET / POST | `/buttons` | List / create an explicit Pay now item |
310
+ | GET / PATCH | `/buttons/{id}` | Read / update that explicit Pay now item |
311
+ | POST | `/buttons/{id}/archive` | Confirmed archive |
312
+ | POST | `/invoices/preview`, `/invoices/send` | Read-only review / send |
313
+ | GET | `/invoices`, `/invoices/{id}` | Paginated invoices / detail |
314
+ | POST | `/invoices/{id}/{refresh\|resend\|void}` | Reconcile, resend or confirmed void |
315
+ | POST | `/recurring/preview`, `/recurring` | Read-only review / create request |
316
+ | GET | `/recurring`, `/recurring/{id}` | Paginated arrangements / detail |
317
+ | POST | `/recurring/{id}/{refresh\|cancel\|resume\|resend-invitation}` | Scoped lifecycle action |
318
+ | POST | `/customers/{customerMapId}/{billing-link\|send-billing-link}` | Private customer access |
319
+
320
+ Common failures: 400 invalid input/retired write; 401 no authentication; 403 role,
321
+ CLI tier or product entitlement; 404 wrong site/record; 409 stale review, conflict,
322
+ unfinished pause or unknown provider outcome; 429 existing rate limit; 503 missing
323
+ rate-limit service, tax-read failure or provider failure. Keep the request ID when
324
+ reporting a failure. Never retry a financial send with a new key just because an
325
+ HTTP response was lost.
326
+
327
+ ## Breaking release
328
+
329
+ Version 0.2.0 removes Store `connect/status/disconnect/payments`, writable currency
330
+ and checkout mode from CLI/public API/agent contracts. Use `payments` for money
331
+ operations. Store remains cart-only and keeps existing resource paths under
332
+ `/shop`. Saved products/orders, Express accounts, current visitor hooks,
333
+ `/__pay`, Connect webhooks and old browser callbacks remain intact. Old binaries
334
+ may fail on retired endpoints; no compatibility alias or data conversion runs.
package/README.md CHANGED
@@ -60,3 +60,6 @@ See [AGENTS.md](./AGENTS.md) and [llms.txt](./llms.txt) in this package.
60
60
  ## People groups, stages, rules, and imports
61
61
 
62
62
  Use `frontpage people overview`, `groups`, `stages`, `membership`, `rules`, `attachments`, `import`, and `company`. Use `frontpage mail campaign` for one-off group/stage/list audiences in Emails. Every editor action is available through these commands or `frontpage people workspace OPERATION --file payload.json`. Full operations and examples: [PEOPLE.md](./PEOPLE.md); public guide: https://frontpage.host/developers/people. Automated emails select a saved template or series. Campaigns use their own drafts.
63
+
64
+
65
+ Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
package/dist/argv.d.ts CHANGED
@@ -10,3 +10,5 @@ export declare function looksLikeFlag(token: string): boolean;
10
10
  * caller also checks them.
11
11
  */
12
12
  export declare function assertNoUnknownFlags(rest: string[], command: string): void;
13
+ /** Required value for new command parsers; keep existing parsers unchanged. */
14
+ export declare function takeFlagValue(rest: string[], name: string): string | undefined;
package/dist/argv.js CHANGED
@@ -46,3 +46,11 @@ export function assertNoUnknownFlags(rest, command) {
46
46
  return;
47
47
  throw new CliHttpError(`Unknown flag: ${unknown}. Run: frontpage help ${command}`, EXIT_USAGE, 0, "bad_request");
48
48
  }
49
+ /** Required value for new command parsers; keep existing parsers unchanged. */
50
+ export function takeFlagValue(rest, name) {
51
+ const present = rest.some(token => token === name || token.startsWith(`${name}=`));
52
+ const value = takeFlag(rest, name);
53
+ if (present && (!value || looksLikeFlag(value)))
54
+ throw new CliHttpError(`${name} requires a value.`, EXIT_USAGE, 0, "bad_request");
55
+ return value;
56
+ }
package/dist/bin.js CHANGED
@@ -32,6 +32,7 @@ import { runRedirects } from "./commands/redirects.js";
32
32
  import { runPeople } from "./commands/people.js";
33
33
  import { runRoutines } from "./commands/routines.js";
34
34
  import { runShop } from "./commands/shop.js";
35
+ import { runPayments } from "./commands/payments.js";
35
36
  import { runMail } from "./commands/mail.js";
36
37
  import { runArticles } from "./commands/articles.js";
37
38
  import { runSettings } from "./commands/settings.js";
@@ -63,6 +64,8 @@ async function main() {
63
64
  return runHelp([cmd, ...args]);
64
65
  }
65
66
  switch (cmd) {
67
+ case "payments":
68
+ return runPayments(args);
66
69
  case "login":
67
70
  return runLogin(args);
68
71
  case "logout":
@@ -0,0 +1 @@
1
+ export declare function runPayments(args: string[]): Promise<number>;