"""add_payment_methods Revision ID: a1b2c3d4e5f6 Revises: 956ea1628264 Create Date: 2026-01-30 10:00:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision: str = 'a1b2c3d4e5f6' down_revision: Union[str, None] = '956ea1628264' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # Create PaymentMethodType enum paymentmethodtype = postgresql.ENUM( 'card', 'cash', 'bank_transfer', 'check', name='paymentmethodtype', create_type=False ) paymentmethodtype.create(op.get_bind(), checkfirst=True) # Add stripe_customer_id to users table op.add_column('users', sa.Column( 'stripe_customer_id', sa.String(), nullable=True, comment='Stripe Customer ID for payment method management' )) op.create_index('ix_users_stripe_customer_id', 'users', ['stripe_customer_id']) # Create payment_methods table op.create_table( 'payment_methods', sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False), sa.Column('stripe_payment_method_id', sa.String(), nullable=True, unique=True, comment='Stripe pm_xxx reference'), sa.Column('card_brand', sa.String(20), nullable=True, comment='Card brand: visa, mastercard, amex, etc.'), sa.Column('card_last4', sa.String(4), nullable=True, comment='Last 4 digits of card'), sa.Column('card_exp_month', sa.Integer(), nullable=True, comment='Card expiration month'), sa.Column('card_exp_year', sa.Integer(), nullable=True, comment='Card expiration year'), sa.Column('card_funding', sa.String(20), nullable=True, comment='Card funding type: credit, debit, prepaid'), sa.Column('payment_type', paymentmethodtype, nullable=False, server_default='card'), sa.Column('is_default', sa.Boolean(), nullable=False, server_default='false', comment='Whether this is the default payment method for auto-renewals'), sa.Column('is_active', sa.Boolean(), nullable=False, server_default='true', comment='Soft delete flag - False means removed'), sa.Column('is_manual', sa.Boolean(), nullable=False, server_default='false', comment='True for manually recorded methods (cash/check)'), sa.Column('manual_notes', sa.Text(), nullable=True, comment='Admin notes for manual payment methods'), sa.Column('created_by', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, comment='Admin who added this on behalf of user'), sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()), ) # Create indexes op.create_index('ix_payment_methods_user_id', 'payment_methods', ['user_id']) op.create_index('ix_payment_methods_stripe_pm_id', 'payment_methods', ['stripe_payment_method_id']) op.create_index('idx_payment_method_user_default', 'payment_methods', ['user_id', 'is_default']) op.create_index('idx_payment_method_active', 'payment_methods', ['user_id', 'is_active']) def downgrade() -> None: # Drop indexes op.drop_index('idx_payment_method_active', table_name='payment_methods') op.drop_index('idx_payment_method_user_default', table_name='payment_methods') op.drop_index('ix_payment_methods_stripe_pm_id', table_name='payment_methods') op.drop_index('ix_payment_methods_user_id', table_name='payment_methods') # Drop payment_methods table op.drop_table('payment_methods') # Drop stripe_customer_id from users op.drop_index('ix_users_stripe_customer_id', table_name='users') op.drop_column('users', 'stripe_customer_id') # Drop PaymentMethodType enum paymentmethodtype = postgresql.ENUM( 'card', 'cash', 'bank_transfer', 'check', name='paymentmethodtype' ) paymentmethodtype.drop(op.get_bind(), checkfirst=True)