v0.2.0:41 — Patron implies Pro; BTCPay Connect back to one-click authorize
Patron entitlement now expands to the full Pro surface (unlimited_products / _policies / _codes, recurring_billing, zaprite_payments) in tier::current(). Existing Patron customers get the implied entitlements without re-issuing. BTCPay Connect: replace the four-field paste form (Base URL + API key + Store id + Webhook secret) with the original one-click button that fetches an authorize URL from /v1/admin/btcpay/connect, opens it in a new tab, and polls /v1/admin/btcpay/status until the BTCPay callback finishes. Zaprite path unchanged.
This commit is contained in:
@@ -58,6 +58,14 @@ const RELEASE_NOTES = [
|
||||
// in RELEASE_NOTES above (the milestone). Subsequent revisions
|
||||
// append here.
|
||||
const ROUTINE_NOTES = [
|
||||
'0.2.0:41 — **Two fixes: Patron tier now implies the full Pro feature surface, and BTCPay Connect is back to one-click authorize.** Both came from operator-side bugs that the admin-UI redesign exposed.',
|
||||
'',
|
||||
'**Patron implies Pro at the resolution layer.** Previously, every `tier.has(<pro-entitlement>)` check required the Patron POLICY on the master Keysat to redundantly list every Pro entitlement (`unlimited_products`, `unlimited_policies`, `unlimited_codes`, `recurring_billing`, `zaprite_payments`) — if the operator forgot even one slug on the Patron policy, every Patron customer was silently locked out of that feature. The Zaprite gate caught this in the wild: a Patron license without `zaprite_payments` got an "Upgrade to Pro" CTA on the payment-providers page. Fixed at the right layer: `tier::current()` now expands `patron` into the full Pro entitlement set on read, so a Patron policy can list just `patron` and have everything Pro grants flow through automatically. Existing Patron customers get the implied entitlements without re-issuing a license. Recommended cleanup: also list the entitlements explicitly on the Patron policy itself so the buy-page tier card stays informative — but the gate behavior no longer depends on it.',
|
||||
'',
|
||||
'**BTCPay Connect: one-click authorize flow restored.** When BTCPay setup moved from a StartOS action (where it was a one-click `Connect BTCPay` that returned a URL the operator opened in their browser to authorize, with the API key / store id / webhook secret auto-detected by Keysat) to the admin UI’s Payment Providers card, it regressed to a four-field paste form asking for Base URL, API key, Store id, and Webhook secret. The daemon-side `/v1/admin/btcpay/connect` endpoint never changed — it still returns an `authorize_url` for BTCPay’s consent page — the form just stopped using it. Rewrote the BTCPay path in the modal: a "Connect BTCPay Server" dialog now has one primary button, "Open BTCPay to authorize". On click, it requests the authorize URL, opens it in a new tab, displays it as copyable text (for operators on a different device than their browser), and polls `/v1/admin/btcpay/status` every 2.5 seconds. The moment BTCPay’s callback lands and the store/webhook are persisted, the modal closes itself and the Payment Providers card re-renders showing BTCPay connected. Zaprite’s connect path is unchanged (Zaprite has no OAuth-style consent endpoint; an API key paste is still required).',
|
||||
'',
|
||||
'**Upgrade path.** Drop-in. No schema, no SDK change. Operators currently stuck on the four-field BTCPay form get the new one-click button automatically; the manual-fields path is removed for BTCPay since it never actually wrote those fields server-side anyway.',
|
||||
'',
|
||||
'0.2.0:40 — **Discount-code slot reaper plugs the abandoned-cart leak.** When a buyer clicked Pay with Bitcoin with a discount code applied, the daemon reserved a slot on that code (incrementing `used_count`) BEFORE creating the BTCPay invoice. This is the right pessimistic-lock behavior — prevents two buyers from racing for the last slot of a limited code — but it meant abandoned checkouts only freed the slot when BTCPay later fired `InvoiceExpired`. If that webhook never landed (network blip, daemon offline at the firing moment, misconfigured webhook URL), the slot leaked forever. New 5-minute background reaper closes both holes: scans `discount_redemptions` where status=\'pending\' and the linked invoice is either in a terminal failure state (\'expired\' / \'invalid\') OR has been sitting in \'pending\' for more than 30 minutes, and cancels each one — flipping the redemption to \'cancelled\' and decrementing the code\'s `used_count` so the slot is available again. 30-min threshold covers BTCPay\'s default 15-min invoice expiry plus webhook-delivery buffer. Lives alongside the existing hourly session reaper in `main.rs`. Internal-only; no API or schema change. Operator-visible only in the sense that limited-discount slots no longer drift over time.',
|
||||
'',
|
||||
'0.2.0:39 — **Buy page now renders a tier card for single-public-policy products.** Previously the tier picker only rendered when a product had two or more public policies; single-public-policy products fell back to a bare price card + form, swallowing all the operator-configured entitlements, marketing bullets, and tier descriptions. Fixed: render a single centered tier card (new `.tiers-1` grid class, ~480px max-width) whenever there\'s at least one public policy. Operators who keep most tiers private and only expose one (e.g. "Pro" public, "Core" and "Max" admin-only) now see the same rich tier-card render that multi-tier products get. The price card below still renders unchanged as the buy-confirmation summary.',
|
||||
@@ -509,7 +517,7 @@ const ROUTINE_NOTES = [
|
||||
].join('\n\n')
|
||||
|
||||
export const v0_2_0 = VersionInfo.of({
|
||||
version: '0.2.0:40',
|
||||
version: '0.2.0:41',
|
||||
releaseNotes: { en_US: ROUTINE_NOTES },
|
||||
// No on-disk transformation needed — v0.2.0:0 is a label change.
|
||||
// SQLite-level migrations live separately under
|
||||
|
||||
Reference in New Issue
Block a user