Complete context package for when we're ready to implement: - Current webhook handler code (checkout.session.completed only) - Database schema (what exists vs what's needed) - Missing handlers list (payment_failed, cancelled, chargeback) - Grace period sweeper requirements - Reference to March 30 code blocks This is prep for future session, not immediate work. Signed-off-by: claude@firefrostgaming.com
5.5 KiB
Gemini: Arbiter Lifecycle Handlers - Code Review Request
Date: April 4, 2026, 11:20 PM CDT
From: Michael (The Wizard) + Claude (Chronicler #59)
To: Gemini (Architectural Partner)
Re: Code needed for subscription lifecycle (cancellation, grace period, chargebacks)
Current Arbiter State
Version: 3.5.0
Location: Command Center (63.143.34.217), /opt/arbiter-3.0/
Systemd Service: arbiter-3
What IS Implemented
Stripe Webhook Handler (current)
From /opt/arbiter-3.0/src/routes/stripe.js:
Only handles checkout.session.completed:
case 'checkout.session.completed': {
const session = event.data.object;
// Extract Discord ID from client_reference_id
const discordId = session.client_reference_id;
const customerEmail = session.customer_details.email;
const tierLevel = parseInt(session.metadata.tier_level);
const tierName = session.metadata.tier_name;
if (!discordId) {
console.error('❌ No Discord ID in checkout session:', session.id);
return res.status(400).send('Missing Discord ID');
}
console.log(`✅ Payment complete: ${session.metadata.discord_username} (${discordId}) - ${tierName}`);
// Determine subscription ID based on billing type
let stripeSubscriptionId = null;
let status = 'lifetime';
if (session.mode === 'subscription') {
stripeSubscriptionId = session.subscription;
status = 'active';
}
// Insert/update subscription with Discord ID
await pool.query(`
INSERT INTO subscriptions (
stripe_subscription_id,
stripe_customer_id,
discord_id,
tier_level,
status,
created_at
)
VALUES ($1, $2, $3, $4, $5, NOW())
ON CONFLICT (discord_id)
DO UPDATE SET
tier_level = EXCLUDED.tier_level,
status = EXCLUDED.status,
stripe_subscription_id = EXCLUDED.stripe_subscription_id,
stripe_customer_id = EXCLUDED.stripe_customer_id,
updated_at = NOW()
`, [stripeSubscriptionId, session.customer, discordId, tierLevel, status]);
console.log(`✅ Database updated: Discord ${discordId} → Tier ${tierLevel} (${status})`);
// TODO: Trigger Discord role assignment via Arbiter
break;
}
What is NOT Implemented (Needed)
Per your March 30, 2026 architectural review (Task #87):
1. Database Schema Updates
Need columns:
permanent_tier(TEXT) — Awakened or Sovereign (never expires)monthly_tier(TEXT) — Elemental/Knight/Master/Legend (can be cancelled)grace_period_start(DATETIME) — When payment failedis_banned(INTEGER) — Chargeback permanent ban flag
2. Webhook Handlers Needed
| Stripe Event | Action |
|---|---|
invoice.payment_failed |
Start 3-day grace period |
invoice.payment_succeeded |
Clear grace period if late payment succeeds (Stripe Smart Retries fix) |
customer.subscription.deleted |
Handle cancellation (downgrade to Awakened) |
charge.dispute.created |
Immediate permanent ban |
3. Grace Period Sweeper Job (4 AM Cron)
Check for subscriptions where:
status = 'grace_period'grace_period_ends_at < NOW()
Action: Downgrade to permanent Awakened tier, remove monthly Discord role, keep in community.
4. "We Don't Kick People Out" Philosophy
- Payment failure → 3-day grace period → downgrade to Awakened (never remove)
- Cancellation → downgrade to Awakened (never remove)
- Chargeback → immediate permanent ban (only exception)
Current Database Schema
CREATE TABLE subscriptions (
id SERIAL PRIMARY KEY,
stripe_subscription_id VARCHAR(255),
stripe_customer_id VARCHAR(255),
discord_id VARCHAR(255),
tier_level INTEGER,
status VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- These columns DO NOT exist yet:
-- permanent_tier TEXT DEFAULT 'awakened'
-- monthly_tier TEXT
-- grace_period_start DATETIME
-- is_banned INTEGER DEFAULT 0
Environment
- Database: PostgreSQL (
arbiter_dbon Command Center) - Stripe Webhook Secret: Configured in
.envasSTRIPE_WEBHOOK_SECRET - Discord Bot Token: Available in
.env - discordService.js: Has
assignDiscordRole()andremoveDiscordRole()functions
What We Need From You, Gemini
When the time comes to implement this, we need:
- Database migration SQL — Add the new columns
- Updated webhook handler — Handle all lifecycle events
- Grace period sweeper job — Node.js script for 4 AM cron
- Tier resolver helper — Returns highest tier between permanent and monthly
- Email templates — Day 0, Day 1, Day 2, Day 3 grace period messages
Your Previous Code Blocks (March 30, 2026)
You provided ready-to-implement code blocks for:
- Database schema updates
- Tier hierarchy resolver
- UPSERT logic (double-buy protection)
- Ban check middleware
- Grace period sweeper
These are documented in docs/tasks/arbiter-2-1-cancellation-flow/README.md.
Priority
This is HIGH PRIORITY for subscriber lifecycle but not a launch blocker.
We're live now (April 3, 2026) and the happy path works. But we need this before:
- First cancellation
- First payment failure
- First chargeback (hopefully never)
Summary
Please save this context. When we're ready to implement, we'll come back and ask you to write the complete code. For now, just confirming you have what you need to pick this up later.
Thanks Gemini! 🔥❄️
— Michael + Claude (Chronicler #59)
Fire + Frost + Foundation = Where Love Builds Legacy 💙🔥❄️