v0.2.0:45 — Zaprite recurring auto-charge + mobile-friendly admin UI
Two routine bumps land together in this release: :44 — Admin UI mobile pass. Adds a phone breakpoint (≤640px) and hamburger-driven off-canvas drawer (≤720px) to the embedded web/index.html so triage flows (status check, license lookup, revoke) work from a phone. Tables now scroll horizontally inside their card, tap targets bump to ~40px, stats grid collapses to 1-up, toolbar inputs go full-width. Desktop layout unchanged. CSS + small JS toggle. :45 — Zaprite recurring auto-charge wired end-to-end. Closes the gap the subscriptions.rs module comment promised but never delivered: first-cycle invoices on recurring policies set allow_save_payment_profile, the on-settle hook captures the resulting Zaprite paymentProfileId into four new nullable columns on the subscriptions table (migration 0019, additive only), and the renewal worker calls POST /v1/orders/charge against the saved profile instead of waiting for manual pay. On charge failure (declined card, expired profile, network) the worker logs + audits + falls through to the existing subscription.renewal_pending event so the buyer still has a recovery path. Two new operator webhook events: subscription.auto_charge_initiated and subscription.auto_charge_failed. BTCPay subs and Zaprite subs whose buyer paid with Bitcoin/Lightning or declined the save-card prompt are untouched. NOT yet end-to-end tested against the Zaprite sandbox — control flow follows api.zaprite.com/llms.txt but exact failure-body shapes for declined cards aren't documented; sandbox validation pass recommended before relying in production. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
-- Zaprite saved-payment-profile metadata for recurring subscriptions.
|
||||
--
|
||||
-- Wires up the auto-charge path that the v0.2.0:1+ subscriptions
|
||||
-- module comment promised but never delivered: when a buyer pays the
|
||||
-- FIRST cycle of a recurring subscription via Zaprite (Stripe card),
|
||||
-- Keysat asks Zaprite to save the payment profile and persists the
|
||||
-- profile id here. The renewal worker then calls
|
||||
-- `POST /v1/orders/charge` against the saved profile instead of
|
||||
-- waiting for the buyer to manually pay each renewal.
|
||||
--
|
||||
-- All four columns are nullable + nothing in the existing read path
|
||||
-- requires them, so this migration is a pure additive drop-in:
|
||||
-- - BTCPay subscriptions stay NULL on all four (BTCPay has no
|
||||
-- equivalent concept; renewals continue to require manual pay).
|
||||
-- - Pre-feature Zaprite subscriptions stay NULL — the renewal
|
||||
-- worker falls through to the existing "buyer pays manually"
|
||||
-- branch when `zaprite_payment_profile_id IS NULL`.
|
||||
-- - Zaprite subscriptions whose buyer either paid with Bitcoin/
|
||||
-- Lightning instead of card, OR declined the save-card prompt,
|
||||
-- also stay NULL. Same fallback.
|
||||
--
|
||||
-- Decisions encoded here:
|
||||
-- - `zaprite_contact_id`: needed because Zaprite's order endpoint
|
||||
-- doesn't surface the profile id directly. After settle we fetch
|
||||
-- the contact, find the profile whose `sourceOrder.externalUniqId`
|
||||
-- matches our invoice id, and persist both.
|
||||
-- - `zaprite_payment_profile_method` / `expires_at`: informational
|
||||
-- only — the admin UI uses them to render "card ending 4242,
|
||||
-- expires 03/27" on the subscription detail. The renewal worker
|
||||
-- doesn't gate on either today; if Zaprite returns expired-card
|
||||
-- errors on the auto-charge we fall through to manual pay and
|
||||
-- log the failure, same as any other decline.
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
ALTER TABLE subscriptions
|
||||
ADD COLUMN zaprite_contact_id TEXT;
|
||||
ALTER TABLE subscriptions
|
||||
ADD COLUMN zaprite_payment_profile_id TEXT;
|
||||
ALTER TABLE subscriptions
|
||||
ADD COLUMN zaprite_payment_profile_method TEXT;
|
||||
ALTER TABLE subscriptions
|
||||
ADD COLUMN zaprite_payment_profile_expires_at TEXT;
|
||||
|
||||
-- Helps the admin-UI "subs with auto-charge configured" filter and
|
||||
-- any future "subs whose saved card is about to expire" sweep.
|
||||
CREATE INDEX IF NOT EXISTS idx_subs_zaprite_profile
|
||||
ON subscriptions(zaprite_payment_profile_id)
|
||||
WHERE zaprite_payment_profile_id IS NOT NULL;
|
||||
Reference in New Issue
Block a user