From 836163dd078d82951e106abe59ea9a1fa755cb1e Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #57)" Date: Fri, 3 Apr 2026 14:50:07 +0000 Subject: [PATCH] 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) --- .../migrations/stripe-integration.sql | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 services/arbiter-3.0/migrations/stripe-integration.sql diff --git a/services/arbiter-3.0/migrations/stripe-integration.sql b/services/arbiter-3.0/migrations/stripe-integration.sql new file mode 100644 index 0000000..e196230 --- /dev/null +++ b/services/arbiter-3.0/migrations/stripe-integration.sql @@ -0,0 +1,124 @@ +-- ============================================================================ +-- 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 +-- ============================================================================