feat: add subscriptions feature capsule - M1 database schema and Stripe client (refs #55)
- Create 4 new tables: subscriptions, subscription_events, donations, tier_vehicle_selections - Add StripeClient wrapper with createCustomer, createSubscription, cancelSubscription, updatePaymentMethod, createPaymentIntent, constructWebhookEvent methods - Implement SubscriptionsRepository with full CRUD and mapRow case conversion - Add domain types for all subscription entities - Install stripe npm package v20.2.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
-- Migration: Subscriptions tables for Stripe integration
|
||||
-- Creates: subscriptions, subscription_events, donations, tier_vehicle_selections
|
||||
|
||||
-- Enable uuid-ossp extension if not already enabled
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Create subscription status enum
|
||||
CREATE TYPE subscription_status AS ENUM ('active', 'past_due', 'canceled', 'unpaid');
|
||||
|
||||
-- Create billing cycle enum
|
||||
CREATE TYPE billing_cycle AS ENUM ('monthly', 'yearly');
|
||||
|
||||
-- Create donation status enum
|
||||
CREATE TYPE donation_status AS ENUM ('pending', 'succeeded', 'failed', 'canceled');
|
||||
|
||||
-- Create updated_at trigger function if not exists
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Main subscriptions table
|
||||
CREATE TABLE IF NOT EXISTS subscriptions (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
stripe_customer_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
stripe_subscription_id VARCHAR(255) UNIQUE,
|
||||
tier subscription_tier NOT NULL DEFAULT 'free',
|
||||
billing_cycle billing_cycle,
|
||||
status subscription_status NOT NULL DEFAULT 'active',
|
||||
current_period_start TIMESTAMP WITH TIME ZONE,
|
||||
current_period_end TIMESTAMP WITH TIME ZONE,
|
||||
grace_period_end TIMESTAMP WITH TIME ZONE,
|
||||
cancel_at_period_end BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_subscriptions_user_id FOREIGN KEY (user_id) REFERENCES user_profiles(auth0_sub) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Subscription events table (webhook event logging)
|
||||
CREATE TABLE IF NOT EXISTS subscription_events (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
subscription_id UUID NOT NULL,
|
||||
stripe_event_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
event_type VARCHAR(100) NOT NULL,
|
||||
payload JSONB NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_subscription_events_subscription_id FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Donations table (one-time payments)
|
||||
CREATE TABLE IF NOT EXISTS donations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
stripe_payment_intent_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
amount_cents INTEGER NOT NULL,
|
||||
currency VARCHAR(3) NOT NULL DEFAULT 'usd',
|
||||
status donation_status NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_donations_user_id FOREIGN KEY (user_id) REFERENCES user_profiles(auth0_sub) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Tier vehicle selections table (tracks which vehicles user selected to keep during downgrade)
|
||||
CREATE TABLE IF NOT EXISTS tier_vehicle_selections (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
vehicle_id UUID NOT NULL,
|
||||
selected_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_tier_vehicle_selections_user_id FOREIGN KEY (user_id) REFERENCES user_profiles(auth0_sub) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_tier_vehicle_selections_vehicle_id FOREIGN KEY (vehicle_id) REFERENCES vehicles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create indexes for performance
|
||||
CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id);
|
||||
CREATE INDEX idx_subscriptions_stripe_customer_id ON subscriptions(stripe_customer_id);
|
||||
CREATE INDEX idx_subscriptions_stripe_subscription_id ON subscriptions(stripe_subscription_id);
|
||||
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
|
||||
CREATE INDEX idx_subscriptions_tier ON subscriptions(tier);
|
||||
|
||||
CREATE INDEX idx_subscription_events_subscription_id ON subscription_events(subscription_id);
|
||||
CREATE INDEX idx_subscription_events_stripe_event_id ON subscription_events(stripe_event_id);
|
||||
CREATE INDEX idx_subscription_events_event_type ON subscription_events(event_type);
|
||||
CREATE INDEX idx_subscription_events_created_at ON subscription_events(created_at);
|
||||
|
||||
CREATE INDEX idx_donations_user_id ON donations(user_id);
|
||||
CREATE INDEX idx_donations_stripe_payment_intent_id ON donations(stripe_payment_intent_id);
|
||||
CREATE INDEX idx_donations_status ON donations(status);
|
||||
CREATE INDEX idx_donations_created_at ON donations(created_at);
|
||||
|
||||
CREATE INDEX idx_tier_vehicle_selections_user_id ON tier_vehicle_selections(user_id);
|
||||
CREATE INDEX idx_tier_vehicle_selections_vehicle_id ON tier_vehicle_selections(vehicle_id);
|
||||
|
||||
-- Add updated_at triggers
|
||||
CREATE TRIGGER update_subscriptions_updated_at
|
||||
BEFORE UPDATE ON subscriptions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_donations_updated_at
|
||||
BEFORE UPDATE ON donations
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
Reference in New Issue
Block a user