docs: Prepare Arbiter lifecycle code request for Gemini
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
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
# 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`:**
|
||||
|
||||
```javascript
|
||||
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 failed
|
||||
- `is_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
|
||||
|
||||
```sql
|
||||
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_db` on Command Center)
|
||||
- **Stripe Webhook Secret:** Configured in `.env` as `STRIPE_WEBHOOK_SECRET`
|
||||
- **Discord Bot Token:** Available in `.env`
|
||||
- **discordService.js:** Has `assignDiscordRole()` and `removeDiscordRole()` functions
|
||||
|
||||
---
|
||||
|
||||
## What We Need From You, Gemini
|
||||
|
||||
When the time comes to implement this, we need:
|
||||
|
||||
1. **Database migration SQL** — Add the new columns
|
||||
2. **Updated webhook handler** — Handle all lifecycle events
|
||||
3. **Grace period sweeper job** — Node.js script for 4 AM cron
|
||||
4. **Tier resolver helper** — Returns highest tier between permanent and monthly
|
||||
5. **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** 💙🔥❄️
|
||||
Reference in New Issue
Block a user