-- Standardize BCT platform fees across all organizations -- Organizations can only customize HOW fees are applied, not the fee amounts -- Remove fee amount columns from organizations table since fees are now standard -- Keep only the fee application model (how fees are presented to customers) ALTER TABLE organizations DROP COLUMN IF EXISTS platform_fee_type, DROP COLUMN IF EXISTS platform_fee_percentage, DROP COLUMN IF EXISTS platform_fee_fixed; -- Rename fee model column for clarity ALTER TABLE organizations RENAME COLUMN platform_fee_model TO fee_display_model; -- Add comment for clarity COMMENT ON COLUMN organizations.fee_display_model IS 'How fees are displayed to customers: customer_pays (separate line) or absorbed_in_price (included)'; COMMENT ON COLUMN organizations.absorb_fee_in_price IS 'Whether to include BCT fee in displayed ticket price (true) or show separately (false)'; -- Create platform_settings table for system-wide configuration CREATE TABLE platform_settings ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), setting_key TEXT UNIQUE NOT NULL, setting_value JSONB NOT NULL, description TEXT, is_public BOOLEAN DEFAULT false, -- Whether this setting can be viewed by organizers updated_by UUID REFERENCES users(id), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Insert standard BCT fee structure INSERT INTO platform_settings (setting_key, setting_value, description, is_public) VALUES ('bct_platform_fee_percentage', '0.025', 'BCT platform fee percentage (2.5%)', true), ('bct_platform_fee_fixed', '150', 'BCT platform fee fixed amount in cents ($1.50)', true), ('stripe_fee_percentage', '0.0299', 'Stripe processing fee percentage (2.99%)', true), ('stripe_fee_fixed', '30', 'Stripe processing fee fixed amount in cents ($0.30)', true), ('platform_name', '"Black Canyon Tickets"', 'Platform display name', true), ('platform_email', '"support@blackcanyontickets.com"', 'Platform support email', true), ('max_events_per_organization', '100', 'Maximum events per organization', false); -- Update all existing organizations to use standard fee display model UPDATE organizations SET fee_display_model = COALESCE(fee_display_model, 'customer_pays'), absorb_fee_in_price = COALESCE(absorb_fee_in_price, false); -- Set default fee display model for new organizations ALTER TABLE organizations ALTER COLUMN fee_display_model SET DEFAULT 'customer_pays', ALTER COLUMN absorb_fee_in_price SET DEFAULT false; -- Create function to get current BCT platform fees CREATE OR REPLACE FUNCTION get_bct_platform_fees() RETURNS TABLE( fee_percentage DECIMAL, fee_fixed INTEGER ) AS $$ BEGIN RETURN QUERY SELECT (SELECT (setting_value#>>'{}')::DECIMAL FROM platform_settings WHERE setting_key = 'bct_platform_fee_percentage'), (SELECT (setting_value#>>'{}')::INTEGER FROM platform_settings WHERE setting_key = 'bct_platform_fee_fixed'); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Create function to get current Stripe fees CREATE OR REPLACE FUNCTION get_stripe_fees() RETURNS TABLE( fee_percentage DECIMAL, fee_fixed INTEGER ) AS $$ BEGIN RETURN QUERY SELECT (SELECT (setting_value#>>'{}')::DECIMAL FROM platform_settings WHERE setting_key = 'stripe_fee_percentage'), (SELECT (setting_value#>>'{}')::INTEGER FROM platform_settings WHERE setting_key = 'stripe_fee_fixed'); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Update the display price calculation function to use standard fees CREATE OR REPLACE FUNCTION calculate_display_price_standard( base_price DECIMAL, fee_display_model VARCHAR DEFAULT 'customer_pays' ) RETURNS DECIMAL AS $$ DECLARE bct_fee_percentage DECIMAL; bct_fee_fixed INTEGER; platform_fee DECIMAL; display_price DECIMAL; BEGIN -- Get current BCT platform fees SELECT fee_percentage, fee_fixed INTO bct_fee_percentage, bct_fee_fixed FROM get_bct_platform_fees(); -- Calculate BCT platform fee platform_fee := (base_price * bct_fee_percentage) + (bct_fee_fixed / 100.0); -- Calculate display price based on fee model CASE fee_display_model WHEN 'customer_pays' THEN -- Customer pays base price + platform fee separately display_price := base_price; WHEN 'absorbed_in_price' THEN -- Platform fee is absorbed into the display price display_price := base_price + platform_fee; ELSE display_price := base_price; END CASE; RETURN ROUND(display_price, 2); END; $$ LANGUAGE plpgsql; -- Update the customer total calculation function to use standard fees CREATE OR REPLACE FUNCTION calculate_customer_total_standard( base_price DECIMAL, fee_display_model VARCHAR DEFAULT 'customer_pays' ) RETURNS DECIMAL AS $$ DECLARE bct_fee_percentage DECIMAL; bct_fee_fixed INTEGER; platform_fee DECIMAL; customer_total DECIMAL; BEGIN -- Get current BCT platform fees SELECT fee_percentage, fee_fixed INTO bct_fee_percentage, bct_fee_fixed FROM get_bct_platform_fees(); -- Calculate BCT platform fee platform_fee := (base_price * bct_fee_percentage) + (bct_fee_fixed / 100.0); -- Calculate total amount customer pays CASE fee_display_model WHEN 'customer_pays' THEN -- Customer pays base price + platform fee separately customer_total := base_price + platform_fee; WHEN 'absorbed_in_price' THEN -- Customer pays only the display price (fee is included) customer_total := base_price; ELSE customer_total := base_price + platform_fee; END CASE; RETURN ROUND(customer_total, 2); END; $$ LANGUAGE plpgsql; -- Update the organizer net calculation function to use standard fees CREATE OR REPLACE FUNCTION calculate_organizer_net_standard( base_price DECIMAL ) RETURNS DECIMAL AS $$ DECLARE bct_fee_percentage DECIMAL; bct_fee_fixed INTEGER; platform_fee DECIMAL; organizer_net DECIMAL; BEGIN -- Get current BCT platform fees SELECT fee_percentage, fee_fixed INTO bct_fee_percentage, bct_fee_fixed FROM get_bct_platform_fees(); -- Calculate BCT platform fee platform_fee := (base_price * bct_fee_percentage) + (bct_fee_fixed / 100.0); -- Calculate organizer net (what they receive before Stripe fees) organizer_net := base_price - platform_fee; -- Ensure organizer net is never negative IF organizer_net < 0 THEN organizer_net := 0; END IF; RETURN ROUND(organizer_net, 2); END; $$ LANGUAGE plpgsql; -- Enable RLS on platform_settings ALTER TABLE platform_settings ENABLE ROW LEVEL SECURITY; -- Public settings can be viewed by authenticated users CREATE POLICY "Authenticated users can view public platform settings" ON platform_settings FOR SELECT USING (is_public = true AND auth.role() = 'authenticated'); -- Only admins can manage platform settings CREATE POLICY "Admins can manage platform settings" ON platform_settings FOR ALL USING (auth.uid() IN (SELECT id FROM users WHERE role = 'admin')); -- Add indexes for performance CREATE INDEX idx_platform_settings_key ON platform_settings(setting_key); CREATE INDEX idx_platform_settings_public ON platform_settings(is_public); CREATE INDEX idx_organizations_fee_display_model ON organizations(fee_display_model); -- Add check constraint to ensure valid fee display models ALTER TABLE organizations DROP CONSTRAINT IF EXISTS check_platform_fee_model, ADD CONSTRAINT check_fee_display_model CHECK (fee_display_model IN ('customer_pays', 'absorbed_in_price')); -- Update fee_structures table to remove custom fee amounts (since fees are now standard) -- Keep only for reference and templates ALTER TABLE fee_structures ADD COLUMN is_deprecated BOOLEAN DEFAULT false; -- Mark old custom fee structures as deprecated UPDATE fee_structures SET is_deprecated = true WHERE is_template = false; -- Clean up old fee structure templates and add new standard ones DELETE FROM fee_structures WHERE is_template = true; INSERT INTO fee_structures (name, description, fee_type, fee_percentage, fee_fixed, fee_model, absorb_fee_in_price, is_template) VALUES ('BCT Standard - Customer Pays', 'Customer pays BCT fee (2.5% + $1.50) as separate line item', 'percentage_plus_fixed', 0.025, 150, 'customer_pays', false, true), ('BCT Standard - All Inclusive', 'BCT fee (2.5% + $1.50) included in ticket price', 'percentage_plus_fixed', 0.025, 150, 'absorbed_in_price', true, true), ('Stripe Processing Fee', 'Stripe credit card processing: 2.99% + $0.30', 'percentage_plus_fixed', 0.0299, 30, 'customer_pays', false, true); -- Add audit logging for platform settings changes CREATE OR REPLACE FUNCTION log_platform_settings_change() RETURNS TRIGGER AS $$ BEGIN IF TG_OP = 'UPDATE' THEN INSERT INTO audit_logs (user_id, action, resource_type, resource_id, old_values, new_values) VALUES ( auth.uid(), 'update', 'platform_settings', NEW.id, row_to_json(OLD), row_to_json(NEW) ); ELSIF TG_OP = 'INSERT' THEN INSERT INTO audit_logs (user_id, action, resource_type, resource_id, new_values) VALUES ( auth.uid(), 'create', 'platform_settings', NEW.id, row_to_json(NEW) ); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER platform_settings_audit_trigger AFTER INSERT OR UPDATE ON platform_settings FOR EACH ROW EXECUTE FUNCTION log_platform_settings_change();