Task B — founder checkout is SEAT-GATED; payment-decline grace spans retries

1+2. Checkout price was CODE-gated (founder price only with a valid founder
   code) — so "Claim a Founder Desk" would have charged the $44.99 standard
   price, not the advertised $34.99. Now it's SEAT-gated: resolveCheckoutPrice()
   attaches the founder price while founder seats remain (< FOUNDER_SEATS_TOTAL,
   read from the SAME countFounderSeats() truth as the ClaimMeter), and flips to
   standard at seat 100. createCheckoutSession uses it; the founderCode param is
   kept for back-compat but no longer drives price. The meter flips to "SOLD
   OUT" at capacity. When the count can't be verified we honor the advertised
   founder price (never overcharge).
   - Also hardened countFounderSeats to manual pagination (the for-await form
     broke on non-async-iterable list mocks).

3. Tests: resolveCheckoutPrice at seat 0 → founder, seat 100 → standard, the
   99/100 boundary, null-count → advertised founder price.

4. Grace: invoice.payment_failed now sets a 14-DAY grace (spans Stripe's Smart
   Retry window) instead of 48h — a transient decline no longer revokes access
   mid-retry. Access is revoked only when Stripe actually cancels
   (customer.subscription.deleted keeps its 48h grace). Test updated.

Stripe + founders suites green, web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-18 23:04:19 -04:00
parent 39c07a03b9
commit ccb9668f0c
3 changed files with 131 additions and 18 deletions
+7 -2
View File
@@ -37,13 +37,16 @@ export default function ClaimMeter() {
const total = data.total || 100;
const claimed = Math.max(0, Math.min(total, data.claimed));
const pct = Math.min(100, Math.round((claimed / total) * 100));
// Item B — at capacity the meter flips to sold-out (checkout has already
// flipped to standard pricing via the same countFounderSeats truth).
const soldOut = claimed >= total;
return (
<div style={{ maxWidth: 420, margin: '0 auto', width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
<SectionHead accent="var(--amber)">FOUNDER SEATS</SectionHead>
<span className="mono amber-glow" style={{ fontSize: 13, fontWeight: 700, color: 'var(--amber)' }}>
{claimed} / {total} CLAIMED
{soldOut ? 'SOLD OUT' : `${claimed} / ${total} CLAIMED`}
</span>
</div>
<div style={{ height: 6, background: 'var(--bg-2)', border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden' }}>
@@ -59,7 +62,9 @@ export default function ClaimMeter() {
/>
</div>
<div className="mono" style={{ fontSize: 11, color: 'var(--text-1)', marginTop: 8, letterSpacing: '0.02em' }}>
Founder pricing locks for life. When the seats are gone, the rate is gone.
{soldOut
? 'Founder seats are gone — standard pricing now applies.'
: 'Founder pricing locks for life. When the seats are gone, the rate is gone.'}
</div>
</div>
);