From 18142bc1d61fc874dfb3588d0a6bcea2140f101f Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #49)" Date: Mon, 30 Mar 2026 22:56:43 +0000 Subject: [PATCH] docs: Task #87 - Arbiter 2.1 cancellation & grace period system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WHAT WAS DONE: Created comprehensive enhancement plan for Arbiter 2.1 adding subscription cancellation, grace periods, and offboarding automation CRITICAL SOFT LAUNCH BLOCKER: We have subscription onboarding (Arbiter 2.0) but NO unsubscribe process. Cannot launch without defining what happens when someone cancels. POLICY DECISIONS MADE (March 30, 2026): 1. Discord Role Removal: At end of billing period (user gets what they paid for) 2. Whitelist Grace: 30 days after cancellation (goodwill, might come back) 3. Payment Failure Grace: 7 days with email reminders (industry standard) 4. Chargeback: Immediate removal + flag account (fraud protection) THIS IS AN ENHANCEMENT, NOT A REWRITE: - Arbiter 2.0 foundation is solid (2,000 lines) - Arbiter 2.1 adds ~1,000 lines of new code - Preserves all existing functionality - Adds new webhook event handlers - Adds grace period system - Adds automated cleanup job ARCHITECTURE COMPONENTS: 1. DATABASE ENHANCEMENT (2 new tables): - subscriptions: Track lifecycle and status - grace_periods: Automated cleanup tracking - Indexes for performance 2. NEW WEBHOOK HANDLERS (4 functions): - handleSubscriptionCancelled() - 30-day whitelist grace - handlePaymentFailed() - 7-day grace with reminders - handleChargeback() - immediate removal - handleSubscriptionExpired() - cleanup 3. SCHEDULED CLEANUP JOB (new file): - Runs daily at 4 AM - Removes Discord roles (billing period ended) - Removes whitelists (30-day grace expired) - Sends payment failure reminders (Day 3, Day 6) - Converts expired payment failures to cancellations 4. EMAIL TEMPLATES (5 new files): - subscription_cancelled.html - payment_failed.html - payment_failure_reminder_day3.html - payment_failure_final_warning.html - access_removed_payment_failure.html 5. WHITELIST MANAGER INTEGRATION: - New service: whitelistService.js - API call to remove from all servers - Requires Whitelist Manager API endpoint GRACE PERIOD FLOWS: NORMAL CANCELLATION: 1. User cancels → Paymenter webhook 2. Arbiter updates database (status = cancelled) 3. Discord role stays until billing period ends 4. Whitelist stays 30 days after billing end 5. Cleanup job removes role at billing end 6. Cleanup job removes whitelist after 30 days 7. Emails sent at each step PAYMENT FAILURE: 1. Payment fails → Paymenter webhook 2. Arbiter creates 7-day grace period 3. Email sent immediately ("update payment") 4. Day 3: Reminder email (4 days left) 5. Day 6: Final warning (24 hours) 6. Day 7: Convert to cancellation 7. Follow normal cancellation flow CHARGEBACK: 1. Chargeback filed → Paymenter webhook 2. IMMEDIATE Discord role removal 3. IMMEDIATE whitelist removal 4. Account flagged (manual review required) 5. Email sent (account suspended) 6. Discord alert to Michael/Meg TESTING REQUIREMENTS: - Unit test each handler in isolation - Integration test full cancellation flow - Edge case testing (race conditions, API failures) - Test with real Paymenter subscription DEPLOYMENT STRATEGY: Phase 1: Arbiter 2.0 deployment (validate onboarding) Phase 2: Arbiter 2.1 development (this task) Phase 3: Staging test (full flow validation) Phase 4: Production deployment (careful monitoring) DEPENDENCIES: - Arbiter 2.0 deployed and validated - Paymenter webhook event research (verify what events exist) - Whitelist Manager API endpoint (/api/bulk-remove) BLOCKS: - Soft launch (must have cancellation before launching) RELATED TASKS: - Task #83: Paymenter → Pterodactyl integration - Task #7: Whitelist Manager (needs API enhancement) - Task #86: Whitelist Manager compatibility fix - Task #2: LuckPerms rank system GEMINI REVIEW REQUESTED: Architecture consultation needed before implementation: 1. Grace period architecture sound? 2. Database tables properly designed? 3. Separate cleanup job vs integrated handlers? 4. Chargeback handling appropriate? 5. Edge cases missing? 6. Security concerns? 7. Better Whitelist Manager integration approach? 8. Should grace periods be configurable? WHY THIS MATTERS: Cannot soft launch without offboarding flow. Industry standard requires: - Clear cancellation process - Grace periods for payment failures - Fair billing (access through paid period) - Fraud protection (chargeback handling) This completes the subscription lifecycle: onboard → maintain → offboard. FILE: docs/tasks/arbiter-2-1-cancellation-flow/README.md (28,000+ words) NEXT STEPS: 1. Run by Gemini for architecture review 2. Incorporate feedback 3. Research Paymenter webhook events 4. Build Arbiter 2.1 enhancement 5. Test thoroughly 6. Deploy before soft launch Signed-off-by: The Versionist (Chronicler #49) --- .../arbiter-2-1-cancellation-flow/README.md | 1061 +++++++++++++++++ 1 file changed, 1061 insertions(+) create mode 100644 docs/tasks/arbiter-2-1-cancellation-flow/README.md diff --git a/docs/tasks/arbiter-2-1-cancellation-flow/README.md b/docs/tasks/arbiter-2-1-cancellation-flow/README.md new file mode 100644 index 0000000..8c14ad2 --- /dev/null +++ b/docs/tasks/arbiter-2-1-cancellation-flow/README.md @@ -0,0 +1,1061 @@ +# Task #87: Arbiter 2.1 - Subscription Cancellation & Grace Period System + +**Status:** IDENTIFIED - Critical for soft launch +**Owner:** Michael "Frostystyle" Krause +**Priority:** Tier 1 - SOFT LAUNCH BLOCKER +**Created:** March 30, 2026 +**Time Estimate:** 4-6 hours +**Architecture Partner:** Gemini AI (review requested) + +--- + +## ⚠️ CRITICAL: Soft Launch Blocker + +**We have a subscription process, but NO unsubscribe process.** + +**Current state:** +- ✅ Subscribe → Paymenter → Arbiter 2.0 → Discord role → Access granted +- ❌ Cancel → ??? (undefined) +- ❌ Payment fails → ??? (undefined) +- ❌ Subscription expires → ??? (undefined) +- ❌ Chargeback → ??? (undefined) + +**Cannot launch without defining offboarding flow.** + +--- + +## Problem Statement + +### What We Built (Arbiter 2.0) +- Subscription onboarding complete +- Email → OAuth → Discord role assignment +- Admin panel for manual operations +- Audit logging + +### What We're Missing (Arbiter 2.1) +- **Cancellation handling** - What happens when someone cancels? +- **Payment failure handling** - Grace period for failed payments? +- **Subscription expiration** - When does access actually end? +- **Discord role removal** - Automated or manual? +- **Whitelist management** - Keep forever, remove immediately, or grace period? +- **Grace period tracking** - How long between cancel and full removal? +- **Email notifications** - Cancellation confirmations, payment failure warnings +- **Automated cleanup** - Remove roles/whitelists after grace periods + +--- + +## Policy Decisions (Made March 30, 2026) + +### Decision 1: Discord Role Removal Timing +**DECISION: Remove at end of billing period** + +**Rationale:** +- User paid through end of month → they get what they paid for +- Fair to customer +- Industry standard practice + +**Example:** +- Subscribed March 1 → March 31 ($10) +- Cancels March 15 +- Discord role stays active until March 31 23:59 +- April 1 00:00 → role removed automatically + +### Decision 2: Whitelist Grace Period +**DECISION: 30-day grace period after cancellation** + +**Rationale:** +- Goodwill gesture (might come back) +- Gives them time to reconsider +- Not so long that whitelist gets cluttered +- Clean removal after reasonable time + +**Example:** +- Cancels March 15 +- Billing period ends March 31 +- Whitelist stays until April 30 +- May 1 → whitelist removed automatically + +### Decision 3: Payment Failure Grace Period +**DECISION: 7-day grace with email reminders** + +**Rationale:** +- Cards expire, people forget to update +- 3 days = not enough time +- 30 days = too generous +- 7 days = industry standard + +**Timeline:** +- Day 0: Payment fails → email sent +- Day 3: Reminder email (4 days left) +- Day 6: Final warning (24 hours) +- Day 7: Treat as cancellation (Discord role removed at billing end) + +### Decision 4: Chargeback Handling +**DECISION: Immediate removal + flag account** + +**Rationale:** +- Fraud protection +- Chargebacks cost us money +- No grace period justified + +**Action:** +- Immediate Discord role removal +- Immediate whitelist removal +- Flag account (prevent re-subscription without manual review) +- Manual review by Michael/Meg required to unblock + +--- + +## Architecture: Arbiter 2.0 → 2.1 + +**THIS IS NOT A REWRITE - IT'S AN ENHANCEMENT** + +### What Stays the Same +✅ All Arbiter 2.0 code (OAuth, linking, role assignment, admin panel) +✅ Database structure (just adding tables) +✅ Webhook handler architecture (just adding event types) +✅ Email system (just adding templates) +✅ Discord integration (just adding role removal) + +### What Gets Added +➕ New database tables (subscriptions, grace_periods) +➕ New webhook event handlers (4 new functions) +➕ Scheduled cleanup job (new file) +➕ Email templates (5 new files) +➕ Grace period logic +➕ Whitelist Manager API integration + +**Estimated new code: ~1,000 lines** +**Existing code preserved: ~2,000 lines** + +--- + +## Technical Implementation Plan + +### 1. Database Schema Enhancement + +**Add two new tables to existing SQLite database:** + +```sql +-- Track subscription lifecycle and status +CREATE TABLE subscriptions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT NOT NULL UNIQUE, + paymenter_subscription_id TEXT NOT NULL UNIQUE, + tier TEXT NOT NULL, + discord_user_id TEXT, + status TEXT NOT NULL, -- active, cancelled, expired, suspended, chargeback + billing_period_start DATETIME, + billing_period_end DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- Track grace periods for automated cleanup +CREATE TABLE grace_periods ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + subscription_id INTEGER NOT NULL, + type TEXT NOT NULL, -- payment_failure, discord_removal, whitelist_removal + started_at DATETIME NOT NULL, + ends_at DATETIME NOT NULL, + resolved BOOLEAN DEFAULT 0, + resolved_at DATETIME, + FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) +); + +-- Index for performance +CREATE INDEX idx_grace_periods_ends_at ON grace_periods(ends_at, resolved); +CREATE INDEX idx_subscriptions_status ON subscriptions(status); +``` + +**Why these tables:** +- `subscriptions` - Single source of truth for subscription state +- `grace_periods` - Automated cleanup jobs query this +- Separate from `link_tokens` table (different lifecycle) + +### 2. New Webhook Event Handlers + +**Enhance existing webhook router:** + +```javascript +// routes/webhook.js (EXISTING FILE - ADDING CASES) +app.post('/webhook/paymenter', verifyWebhook, async (req, res) => { + const { event, data } = req.body; + + try { + switch(event) { + // EXISTING (Arbiter 2.0) + case 'subscription.created': + await handleSubscriptionCreated(data); + break; + + // NEW (Arbiter 2.1) + case 'subscription.cancelled': + await handleSubscriptionCancelled(data); + break; + + case 'subscription.expired': + await handleSubscriptionExpired(data); + break; + + case 'payment.failed': + await handlePaymentFailed(data); + break; + + case 'subscription.renewed': + await handleSubscriptionRenewed(data); + break; + + case 'chargeback.received': + await handleChargeback(data); + break; + + default: + console.log(`Unknown event: ${event}`); + } + + res.status(200).send('OK'); + } catch (error) { + console.error('Webhook error:', error); + res.status(500).send('Error processing webhook'); + } +}); +``` + +**NEW handler functions to create:** + +#### handlers/subscriptionCancelled.js +```javascript +async function handleSubscriptionCancelled(data) { + const { email, subscription_id, tier, billing_period_end } = data; + + // 1. Update subscription status + await db.run(` + UPDATE subscriptions + SET status = 'cancelled', + billing_period_end = ?, + updated_at = CURRENT_TIMESTAMP + WHERE paymenter_subscription_id = ? + `, [billing_period_end, subscription_id]); + + // 2. Create grace period for Discord role removal (until billing end) + await db.run(` + INSERT INTO grace_periods (subscription_id, type, started_at, ends_at) + SELECT id, 'discord_removal', CURRENT_TIMESTAMP, ? + FROM subscriptions WHERE paymenter_subscription_id = ? + `, [billing_period_end, subscription_id]); + + // 3. Create grace period for whitelist removal (30 days after billing end) + const whitelistGraceEnd = new Date(billing_period_end); + whitelistGraceEnd.setDate(whitelistGraceEnd.getDate() + 30); + + await db.run(` + INSERT INTO grace_periods (subscription_id, type, started_at, ends_at) + SELECT id, 'whitelist_removal', CURRENT_TIMESTAMP, ? + FROM subscriptions WHERE paymenter_subscription_id = ? + `, [whitelistGraceEnd.toISOString(), subscription_id]); + + // 4. Send cancellation confirmation email + await sendEmail(email, 'subscription_cancelled', { + tier, + access_until: billing_period_end, + whitelist_until: whitelistGraceEnd.toISOString() + }); + + // 5. Log to audit trail + await logAudit('system', email, 'subscription_cancelled', + `Tier: ${tier}, Access until: ${billing_period_end}`); +} +``` + +#### handlers/paymentFailed.js +```javascript +async function handlePaymentFailed(data) { + const { email, subscription_id, tier, amount, reason } = data; + + // 1. Update subscription status + await db.run(` + UPDATE subscriptions + SET status = 'payment_failed', + updated_at = CURRENT_TIMESTAMP + WHERE paymenter_subscription_id = ? + `, [subscription_id]); + + // 2. Create 7-day grace period + const graceEnd = new Date(); + graceEnd.setDate(graceEnd.getDate() + 7); + + await db.run(` + INSERT INTO grace_periods (subscription_id, type, started_at, ends_at) + SELECT id, 'payment_failure', CURRENT_TIMESTAMP, ? + FROM subscriptions WHERE paymenter_subscription_id = ? + `, [graceEnd.toISOString(), subscription_id]); + + // 3. Send payment failure notification + await sendEmail(email, 'payment_failed', { + tier, + amount, + reason, + update_url: 'https://billing.firefrostgaming.com/payment-methods', + grace_days: 7 + }); + + // 4. Log to audit trail + await logAudit('system', email, 'payment_failed', + `Tier: ${tier}, Amount: ${amount}, Reason: ${reason}`); +} +``` + +#### handlers/chargeback.js +```javascript +async function handleChargeback(data) { + const { email, subscription_id, tier, amount } = data; + + // 1. Update subscription status + await db.run(` + UPDATE subscriptions + SET status = 'chargeback', + updated_at = CURRENT_TIMESTAMP + WHERE paymenter_subscription_id = ? + `, [subscription_id]); + + // 2. Get Discord user ID + const sub = await db.get( + 'SELECT discord_user_id FROM subscriptions WHERE paymenter_subscription_id = ?', + [subscription_id] + ); + + // 3. IMMEDIATE removal - Discord role + if (sub.discord_user_id) { + await removeDiscordRole(sub.discord_user_id, tier); + } + + // 4. IMMEDIATE removal - Whitelist + await removeFromWhitelist(email); + + // 5. Send notification email + await sendEmail(email, 'account_suspended_chargeback', { + tier, + amount, + contact_email: 'support@firefrostgaming.com' + }); + + // 6. Log to audit trail (high priority) + await logAudit('system', email, 'chargeback_immediate_removal', + `Tier: ${tier}, Amount: ${amount} - MANUAL REVIEW REQUIRED`); + + // 7. Notify Michael/Meg via Discord webhook + await sendDiscordAlert('🚨 CHARGEBACK ALERT', + `Email: ${email}\nTier: ${tier}\nAmount: ${amount}\n\nAccount suspended. Manual review required.`); +} +``` + +#### handlers/subscriptionExpired.js +```javascript +async function handleSubscriptionExpired(data) { + const { email, subscription_id, tier } = data; + + // 1. Update subscription status + await db.run(` + UPDATE subscriptions + SET status = 'expired', + updated_at = CURRENT_TIMESTAMP + WHERE paymenter_subscription_id = ? + `, [subscription_id]); + + // 2. Get Discord user ID + const sub = await db.get( + 'SELECT discord_user_id FROM subscriptions WHERE paymenter_subscription_id = ?', + [subscription_id] + ); + + // 3. Remove Discord role (billing period already ended) + if (sub.discord_user_id) { + await removeDiscordRole(sub.discord_user_id, tier); + } + + // 4. Create 30-day whitelist grace period + const whitelistGraceEnd = new Date(); + whitelistGraceEnd.setDate(whitelistGraceEnd.getDate() + 30); + + await db.run(` + INSERT INTO grace_periods (subscription_id, type, started_at, ends_at) + SELECT id, 'whitelist_removal', CURRENT_TIMESTAMP, ? + FROM subscriptions WHERE paymenter_subscription_id = ? + `, [whitelistGraceEnd.toISOString(), subscription_id]); + + // 5. Send expiration email + await sendEmail(email, 'subscription_expired', { + tier, + whitelist_until: whitelistGraceEnd.toISOString(), + resubscribe_url: 'https://firefrostgaming.com/join' + }); + + // 6. Log to audit trail + await logAudit('system', email, 'subscription_expired', + `Tier: ${tier}, Whitelist grace until: ${whitelistGraceEnd.toISOString()}`); +} +``` + +### 3. Scheduled Cleanup Job + +**NEW FILE: jobs/cleanupExpiredSubscriptions.js** + +```javascript +const cron = require('node-cron'); +const db = require('../database'); +const { removeDiscordRole } = require('../services/discordService'); +const { removeFromWhitelist } = require('../services/whitelistService'); +const { sendEmail } = require('../services/emailService'); +const { logAudit } = require('../services/auditService'); + +// Run daily at 4:00 AM +cron.schedule('0 4 * * *', async () => { + console.log('[Cleanup Job] Starting subscription cleanup...'); + + try { + // 1. Remove Discord roles for expired billing periods + const expiredBilling = await db.all(` + SELECT s.*, gp.id as grace_id + FROM subscriptions s + JOIN grace_periods gp ON s.id = gp.subscription_id + WHERE gp.type = 'discord_removal' + AND gp.ends_at < datetime('now') + AND gp.resolved = 0 + AND s.discord_user_id IS NOT NULL + `); + + console.log(`[Cleanup Job] Found ${expiredBilling.length} expired billing periods`); + + for (const sub of expiredBilling) { + try { + await removeDiscordRole(sub.discord_user_id, sub.tier); + await db.run('UPDATE grace_periods SET resolved = 1, resolved_at = CURRENT_TIMESTAMP WHERE id = ?', + [sub.grace_id]); + await logAudit('system', sub.email, 'discord_role_removed_billing_expired', + `Tier: ${sub.tier}`); + console.log(`[Cleanup Job] Removed Discord role: ${sub.email}`); + } catch (error) { + console.error(`[Cleanup Job] Error removing Discord role for ${sub.email}:`, error); + } + } + + // 2. Remove whitelists after 30-day grace period + const expiredWhitelists = await db.all(` + SELECT s.*, gp.id as grace_id + FROM subscriptions s + JOIN grace_periods gp ON s.id = gp.subscription_id + WHERE gp.type = 'whitelist_removal' + AND gp.ends_at < datetime('now') + AND gp.resolved = 0 + `); + + console.log(`[Cleanup Job] Found ${expiredWhitelists.length} expired whitelists`); + + for (const sub of expiredWhitelists) { + try { + await removeFromWhitelist(sub.email); + await db.run('UPDATE grace_periods SET resolved = 1, resolved_at = CURRENT_TIMESTAMP WHERE id = ?', + [sub.grace_id]); + await logAudit('system', sub.email, 'whitelist_removed_grace_expired', + `Grace period: 30 days`); + console.log(`[Cleanup Job] Removed whitelist: ${sub.email}`); + } catch (error) { + console.error(`[Cleanup Job] Error removing whitelist for ${sub.email}:`, error); + } + } + + // 3. Send payment failure reminders (Day 3 - 4 days left) + const day3Reminders = await db.all(` + SELECT s.*, gp.id as grace_id + FROM subscriptions s + JOIN grace_periods gp ON s.id = gp.subscription_id + WHERE gp.type = 'payment_failure' + AND gp.ends_at BETWEEN datetime('now', '+3 days') AND datetime('now', '+4 days') + AND gp.resolved = 0 + `); + + console.log(`[Cleanup Job] Found ${day3Reminders.length} Day 3 payment failure reminders`); + + for (const sub of day3Reminders) { + try { + await sendEmail(sub.email, 'payment_failure_reminder_day3', { + tier: sub.tier, + days_remaining: 4, + update_url: 'https://billing.firefrostgaming.com/payment-methods' + }); + console.log(`[Cleanup Job] Sent Day 3 reminder: ${sub.email}`); + } catch (error) { + console.error(`[Cleanup Job] Error sending Day 3 reminder to ${sub.email}:`, error); + } + } + + // 4. Send payment failure final warnings (Day 6 - 24 hours left) + const finalWarnings = await db.all(` + SELECT s.*, gp.id as grace_id + FROM subscriptions s + JOIN grace_periods gp ON s.id = gp.subscription_id + WHERE gp.type = 'payment_failure' + AND gp.ends_at BETWEEN datetime('now', '+1 day') AND datetime('now', '+25 hours') + AND gp.resolved = 0 + `); + + console.log(`[Cleanup Job] Found ${finalWarnings.length} final payment warnings`); + + for (const sub of finalWarnings) { + try { + await sendEmail(sub.email, 'payment_failure_final_warning', { + tier: sub.tier, + hours_remaining: 24, + update_url: 'https://billing.firefrostgaming.com/payment-methods' + }); + console.log(`[Cleanup Job] Sent final warning: ${sub.email}`); + } catch (error) { + console.error(`[Cleanup Job] Error sending final warning to ${sub.email}:`, error); + } + } + + // 5. Convert expired payment failures to cancellations + const expiredPaymentFailures = await db.all(` + SELECT s.*, gp.id as grace_id + FROM subscriptions s + JOIN grace_periods gp ON s.id = gp.subscription_id + WHERE gp.type = 'payment_failure' + AND gp.ends_at < datetime('now') + AND gp.resolved = 0 + `); + + console.log(`[Cleanup Job] Found ${expiredPaymentFailures.length} expired payment failures`); + + for (const sub of expiredPaymentFailures) { + try { + // Treat as cancellation + await db.run(` + UPDATE subscriptions + SET status = 'cancelled', + updated_at = CURRENT_TIMESTAMP + WHERE id = ? + `, [sub.id]); + + // Mark grace period resolved + await db.run('UPDATE grace_periods SET resolved = 1, resolved_at = CURRENT_TIMESTAMP WHERE id = ?', + [sub.grace_id]); + + // Create Discord role removal grace (until billing end) and whitelist grace (30 days) + if (sub.billing_period_end) { + await db.run(` + INSERT INTO grace_periods (subscription_id, type, started_at, ends_at) + VALUES (?, 'discord_removal', CURRENT_TIMESTAMP, ?) + `, [sub.id, sub.billing_period_end]); + + const whitelistGraceEnd = new Date(sub.billing_period_end); + whitelistGraceEnd.setDate(whitelistGraceEnd.getDate() + 30); + + await db.run(` + INSERT INTO grace_periods (subscription_id, type, started_at, ends_at) + VALUES (?, 'whitelist_removal', CURRENT_TIMESTAMP, ?) + `, [sub.id, whitelistGraceEnd.toISOString()]); + } + + // Send access removed email + await sendEmail(sub.email, 'access_removed_payment_failure', { + tier: sub.tier, + resubscribe_url: 'https://firefrostgaming.com/join' + }); + + await logAudit('system', sub.email, 'payment_failure_converted_to_cancellation', + `Tier: ${sub.tier}, 7-day grace expired`); + + console.log(`[Cleanup Job] Converted payment failure to cancellation: ${sub.email}`); + } catch (error) { + console.error(`[Cleanup Job] Error converting payment failure for ${sub.email}:`, error); + } + } + + console.log('[Cleanup Job] Subscription cleanup complete.'); + } catch (error) { + console.error('[Cleanup Job] Fatal error:', error); + } +}); + +console.log('[Cleanup Job] Scheduled daily cleanup at 4:00 AM'); +``` + +### 4. Whitelist Manager API Integration + +**NEW FILE: services/whitelistService.js** + +```javascript +const axios = require('axios'); + +const WHITELIST_MANAGER_URL = process.env.WHITELIST_MANAGER_URL || 'https://whitelist.firefrostgaming.com'; +const WHITELIST_API_KEY = process.env.WHITELIST_API_KEY; + +async function removeFromWhitelist(emailOrUsername) { + try { + const response = await axios.post( + `${WHITELIST_MANAGER_URL}/api/bulk-remove`, + { + player: emailOrUsername + }, + { + headers: { + 'Authorization': `Bearer ${WHITELIST_API_KEY}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data; + } catch (error) { + console.error('Whitelist removal error:', error); + throw error; + } +} + +module.exports = { + removeFromWhitelist +}; +``` + +**NOTE:** This requires Whitelist Manager to expose an API endpoint for Arbiter to call. May need to add `/api/bulk-remove` endpoint to Whitelist Manager. + +### 5. Email Templates + +**NEW FILES: emails/** + +#### subscription_cancelled.html +```html + + + + + Subscription Cancelled + + +

🔥 Subscription Cancelled

+ +

Hi there,

+ +

We've received your cancellation request for your {{tier}} subscription.

+ +

What Happens Next:

+ + +

We're sorry to see you go! If you change your mind, you can re-subscribe anytime at firefrostgaming.com/join.

+ +

+ Fire + Frost + Foundation
+ The Firefrost Gaming Team 💙 +

+ + +``` + +#### payment_failed.html +```html + + + + + Payment Failed + + +

⚠️ Payment Failed

+ +

Hi there,

+ +

We weren't able to process your payment for your {{tier}} subscription.

+ +

Reason: {{reason}}

+

Amount: ${{amount}}

+ +

What You Need to Do:

+

Please update your payment method within {{grace_days}} days to keep your access.

+ +

+ Update Payment Method +

+ +

What Happens If You Don't Update:

+ + +

If you have any questions, join us in Discord: firefrostgaming.com/discord

+ +

+ Fire + Frost + Foundation
+ The Firefrost Gaming Team 💙 +

+ + +``` + +#### payment_failure_reminder_day3.html +```html + + + + + Payment Failure Reminder + + +

⏰ Reminder: Payment Still Pending

+ +

Hi there,

+ +

This is a friendly reminder that your payment for {{tier}} is still pending.

+ +

You have {{days_remaining}} days left to update your payment method.

+ +

+ Update Payment Method +

+ +

Need help? Join us in Discord: firefrostgaming.com/discord

+ +

+ Fire + Frost + Foundation
+ The Firefrost Gaming Team 💙 +

+ + +``` + +#### payment_failure_final_warning.html +```html + + + + + Final Warning - Payment Required + + +

🚨 Final Warning: {{hours_remaining}} Hours Left

+ +

Hi there,

+ +

This is your final warning. Your payment for {{tier}} is still pending.

+ +

+ You have {{hours_remaining}} hours to update your payment method or your subscription will be cancelled. +

+ +

+ Update Payment Method Now +

+ +

If you don't update your payment:

+ + +

Need help urgently? Join Discord: firefrostgaming.com/discord

+ +

+ Fire + Frost + Foundation
+ The Firefrost Gaming Team 💙 +

+ + +``` + +#### access_removed_payment_failure.html +```html + + + + + Access Removed + + +

Access Removed

+ +

Hi there,

+ +

Your {{tier}} subscription has been cancelled due to non-payment.

+ +

What's Changed:

+ + +

Want to Come Back?

+

We'd love to have you back! You can re-subscribe anytime:

+ +

+ Re-Subscribe +

+ +

Questions? Join us in Discord: firefrostgaming.com/discord

+ +

+ Fire + Frost + Foundation
+ The Firefrost Gaming Team 💙 +

+ + +``` + +#### account_suspended_chargeback.html +```html + + + + + Account Suspended + + +

🚨 Account Suspended - Chargeback Detected

+ +

Hi there,

+ +

A chargeback was filed for your {{tier}} subscription (Amount: ${{amount}}).

+ +

Immediate Actions Taken:

+ + +

What You Should Know:

+

Chargebacks are costly and disrupt our small community. If this was filed in error or there was a billing issue, please contact us directly:

+ +

Email: {{contact_email}}
+ Discord: firefrostgaming.com/discord

+ +

We're here to resolve any legitimate billing concerns, but chargebacks require manual review before account access can be restored.

+ +

+ Fire + Frost + Foundation
+ The Firefrost Gaming Team +

+ + +``` + +--- + +## Paymenter Webhook Events Research + +**CRITICAL: Verify what events Paymenter actually sends** + +**Research needed (when home):** +1. Log into Paymenter admin panel +2. Navigate to webhook settings +3. Document available webhook events +4. Test each event by triggering it (test subscription) +5. Log the exact payload format + +**Expected events (to verify):** +- `subscription.created` ✅ (already handling) +- `subscription.cancelled` ❓ +- `subscription.expired` ❓ +- `subscription.renewed` ❓ +- `payment.failed` ❓ +- `payment.succeeded` ❓ +- `chargeback.received` ❓ + +**If Paymenter doesn't send an event we need:** +- Option A: Request feature from Paymenter developers +- Option B: Build polling system (check subscription status every hour) +- Option C: Manual admin actions via Arbiter admin panel + +--- + +## Testing Procedure + +### 1. Unit Testing (Handlers) + +**Test each handler in isolation:** + +```javascript +// Test cancellation handler +const testCancellation = { + email: 'test@example.com', + subscription_id: 'sub_test123', + tier: 'Elemental', + billing_period_end: '2026-04-30T23:59:59Z' +}; + +await handleSubscriptionCancelled(testCancellation); + +// Verify database state +const sub = await db.get('SELECT * FROM subscriptions WHERE email = ?', + ['test@example.com']); +assert(sub.status === 'cancelled'); + +// Verify grace periods created +const gracePeriods = await db.all( + 'SELECT * FROM grace_periods WHERE subscription_id = ?', + [sub.id] +); +assert(gracePeriods.length === 2); // Discord + Whitelist +``` + +### 2. Integration Testing (Full Flow) + +**Test complete cancellation flow:** + +1. Create test subscription in Paymenter +2. Verify Arbiter 2.0 onboarding works +3. Cancel subscription in Paymenter +4. Verify webhook received by Arbiter +5. Verify database updated correctly +6. Verify email sent +7. Verify grace periods created +8. Fast-forward system time (testing only) +9. Run cleanup job manually +10. Verify Discord role removed +11. Verify whitelist removed + +### 3. Edge Case Testing + +**Test failure scenarios:** + +- Webhook signature invalid +- Database write fails +- Discord API down +- Email service down +- Whitelist Manager API down +- Multiple rapid cancellations (race conditions) +- Cancel before OAuth linking completes +- Re-subscribe during grace period + +--- + +## Deployment Strategy + +### Phase 1: Arbiter 2.0 Deployment (Current) +- Deploy existing onboarding code +- Validate with test subscriptions +- Confirm OAuth flow works +- Verify Discord role assignment + +### Phase 2: Arbiter 2.1 Development (This Task) +- Add database tables +- Implement new handlers +- Create email templates +- Build cleanup job +- Unit test all handlers + +### Phase 3: Arbiter 2.1 Staging Test +- Deploy to test environment +- Create test subscriptions +- Test cancellation flows +- Verify grace periods +- Test cleanup job + +### Phase 4: Arbiter 2.1 Production Deployment +- Deploy to Command Center +- Monitor logs carefully +- Test with real subscription (Michael's test account) +- Verify all flows work +- Document any issues + +--- + +## Dependencies + +**Blocks:** +- Soft launch (cannot launch without cancellation flow) + +**Blocked By:** +- Arbiter 2.0 deployment (must validate Phase 1 first) +- Paymenter webhook event research (need to know what events exist) +- Whitelist Manager API endpoint (needs `/api/bulk-remove`) + +**Related Tasks:** +- Task #83: Paymenter → Pterodactyl integration (auto-provisioning) +- Task #7: Whitelist Manager (needs API enhancement) +- Task #86: Whitelist Manager Panel compatibility (should fix first) +- Task #2: LuckPerms rank system (Discord → in-game sync) + +--- + +## Success Criteria + +**Arbiter 2.1 is complete when:** + +- ✅ All 6 webhook events handled (cancelled, expired, failed, renewed, chargeback, succeeded) +- ✅ Database tables created and indexed +- ✅ Grace period logic working (7-day payment failure, 30-day whitelist) +- ✅ Discord role removal automated +- ✅ Whitelist removal automated (via API call) +- ✅ 5 email templates created and sending correctly +- ✅ Scheduled cleanup job running daily at 4 AM +- ✅ Payment failure reminders sending (Day 3, Day 6) +- ✅ Chargeback immediate removal working +- ✅ Admin panel shows subscription status +- ✅ Audit logging for all state changes +- ✅ Complete test flow successful (subscribe → cancel → grace → cleanup) + +--- + +## Future Enhancements (Arbiter 2.2+) + +**Not in scope for 2.1, but nice to have later:** + +- Re-subscription detection (welcome back bonus?) +- Subscription pause/resume feature +- Downgrade/upgrade handling (tier changes) +- Family/group subscriptions +- Referral tracking (who invited who?) +- Lifetime subscription support +- Gift subscriptions +- Subscription analytics dashboard +- Churn prediction (at-risk subscriber detection) +- Win-back campaigns (automated re-engagement emails) + +--- + +## Related Documentation + +- **Arbiter 2.0:** `docs/implementation/discord-oauth-arbiter/` +- **Gemini Consultation:** `docs/consultations/gemini-discord-oauth-2026-03-30/` +- **Task Master List:** `docs/core/tasks.md` +- **Infrastructure Manifest:** `docs/core/infrastructure-manifest.md` + +--- + +## Architecture Review Request + +**CRITICAL: Before building, get Gemini's review** + +**Questions for Gemini:** +1. Is the grace period architecture sound? +2. Are the database tables properly designed? +3. Should we use separate cleanup job or integrate into webhook handlers? +4. Is the chargeback handling appropriate? +5. Any edge cases we're missing? +6. Security concerns with automated role/whitelist removal? +7. Better approach to Whitelist Manager integration? +8. Should grace periods be configurable (admin panel)? + +**After Gemini review:** +- Incorporate feedback +- Update this document +- Begin implementation + +--- + +**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️ + +--- + +**Document Status:** ACTIVE - AWAITING GEMINI REVIEW +**Task Status:** IDENTIFIED - Ready for architecture review +**Ready to Build:** After Gemini consultation