Files
firefrost-services/services/arbiter-3.0/migrations/stripe-integration.sql
Claude (Chronicler #57) 836163dd07 feat: Add Stripe direct integration database migration
WHAT WAS DONE:
- Created stripe-integration.sql migration for eliminating Paymenter
- Adds stripe_products table (product catalog with recurring/one-time support)
- Adds webhook_events_processed table (idempotency tracking)
- Updates subscriptions table for lifetime tier support (Sovereign)
- Adds performance indexes for webhook lookups and grace period queries
- Includes constraint to ensure exactly one of subscription_id or payment_intent_id exists

WHY:
- Part of Gemini-approved architecture to eliminate Paymenter
- Supports both recurring subscriptions (tiers 1-9) and one-time payment (Sovereign tier 10)
- Ensures grace period logic never affects lifetime users
- Production-ready with full comments and validation

FILES CHANGED:
- services/arbiter-3.0/migrations/stripe-integration.sql (new, 127 lines)

CONTEXT:
- Gemini consultation verdict: Rip out Paymenter before first customer
- Phase 1 of 5 in Stripe direct integration
- Supports $499 Sovereign one-time payment with is_lifetime flag

Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
2026-04-03 14:50:07 +00:00

125 lines
5.8 KiB
SQL

-- ============================================================================
-- STRIPE DIRECT INTEGRATION MIGRATION
-- ============================================================================
-- Date: April 3, 2026
-- Purpose: Eliminate Paymenter, integrate Stripe directly into Trinity Console
-- Gemini Reviewed: Yes (v2 with Sovereign one-time payment support)
--
-- This migration adds:
-- 1. stripe_products - Product catalog with recurring/one-time billing types
-- 2. webhook_events_processed - Idempotency tracking for webhooks
-- 3. subscriptions updates - Lifetime tier support (Sovereign $499)
-- 4. Performance indexes
-- ============================================================================
-- ============================================================================
-- 1. PRODUCT CATALOG
-- ============================================================================
CREATE TABLE IF NOT EXISTS stripe_products (
tier_level INT PRIMARY KEY,
tier_name VARCHAR(50) NOT NULL,
fire_or_frost VARCHAR(10) NOT NULL CHECK (fire_or_frost IN ('fire', 'frost', 'both')),
price_monthly DECIMAL(10,2) NOT NULL,
stripe_product_id VARCHAR(255) UNIQUE NOT NULL,
stripe_price_id VARCHAR(255) UNIQUE NOT NULL,
benefits JSONB,
billing_type VARCHAR(20) DEFAULT 'recurring' CHECK (billing_type IN ('recurring', 'one-time')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE stripe_products IS 'Stripe product catalog - maps tier levels to Stripe Price IDs';
COMMENT ON COLUMN stripe_products.billing_type IS 'recurring for monthly subscriptions (tiers 1-9), one-time for Sovereign (tier 10)';
COMMENT ON COLUMN stripe_products.price_monthly IS 'Monthly price for recurring, total price for one-time';
-- ============================================================================
-- 2. WEBHOOK IDEMPOTENCY TRACKING
-- ============================================================================
CREATE TABLE IF NOT EXISTS webhook_events_processed (
stripe_event_id VARCHAR(255) PRIMARY KEY,
event_type VARCHAR(100) NOT NULL,
processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_webhook_processed_at ON webhook_events_processed(processed_at);
COMMENT ON TABLE webhook_events_processed IS 'Tracks processed Stripe webhook events to ensure idempotency';
-- ============================================================================
-- 3. SUBSCRIPTIONS TABLE UPDATES (Lifetime Support)
-- ============================================================================
-- Add lifetime flag and payment intent ID for one-time payments
ALTER TABLE subscriptions
ADD COLUMN IF NOT EXISTS is_lifetime BOOLEAN DEFAULT FALSE;
ALTER TABLE subscriptions
ADD COLUMN IF NOT EXISTS stripe_payment_intent_id VARCHAR(255);
-- Add unique constraints for both subscription IDs and payment intent IDs
-- Note: We use separate constraints because a record will have EITHER sub_id OR payment_intent_id, never both
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'unique_stripe_sub'
) THEN
ALTER TABLE subscriptions ADD CONSTRAINT unique_stripe_sub UNIQUE (stripe_subscription_id);
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'unique_stripe_pi'
) THEN
ALTER TABLE subscriptions ADD CONSTRAINT unique_stripe_pi UNIQUE (stripe_payment_intent_id);
END IF;
END $$;
COMMENT ON COLUMN subscriptions.is_lifetime IS 'TRUE for Sovereign tier (one-time $499), FALSE for recurring subscriptions';
COMMENT ON COLUMN subscriptions.stripe_payment_intent_id IS 'Payment Intent ID for one-time payments (Sovereign), NULL for recurring';
-- ============================================================================
-- 4. PERFORMANCE INDEXES
-- ============================================================================
-- Index for webhook lookups by customer ID
CREATE INDEX IF NOT EXISTS idx_stripe_customer_id ON subscriptions(stripe_customer_id);
-- Index for grace period queries (excludes lifetime users)
CREATE INDEX IF NOT EXISTS idx_grace_period_lookup ON subscriptions(grace_period_ends_at, is_lifetime)
WHERE grace_period_ends_at IS NOT NULL;
-- ============================================================================
-- 5. VALIDATION & CONSTRAINTS
-- ============================================================================
-- Ensure either subscription_id OR payment_intent_id exists, but not both
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'check_stripe_id_exists'
) THEN
ALTER TABLE subscriptions ADD CONSTRAINT check_stripe_id_exists
CHECK (
(stripe_subscription_id IS NOT NULL AND stripe_payment_intent_id IS NULL) OR
(stripe_subscription_id IS NULL AND stripe_payment_intent_id IS NOT NULL)
);
END IF;
END $$;
COMMENT ON CONSTRAINT check_stripe_id_exists ON subscriptions IS 'Ensures exactly one of subscription_id or payment_intent_id exists';
-- ============================================================================
-- 6. MIGRATION VERIFICATION QUERIES
-- ============================================================================
-- Run these to verify migration succeeded:
-- SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename IN ('stripe_products', 'webhook_events_processed');
-- SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'subscriptions' AND column_name IN ('is_lifetime', 'stripe_payment_intent_id');
-- SELECT indexname FROM pg_indexes WHERE tablename = 'subscriptions' AND indexname IN ('idx_stripe_customer_id', 'idx_grace_period_lookup');
-- ============================================================================
-- END OF MIGRATION
-- ============================================================================