feat: Stripe integration complete - products created, DB migration, payment_failed handler, credit buttons wired
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m5s

- Create Stripe products/prices (Starter/Pro/Business monthly+yearly)
- Fix CRITICAL bug: add subscription_ends_at + cancel_at_period_end columns to users table
- Alembic migration: f6a7b8c9d0e1_add_subscription_ends_at_cancel_at_period_end
- Fix: implement handle_payment_failed() to set subscription_status=PAST_DUE
- Fix: harmonize .env.production Stripe variable names to match pricing_config.py
- Fix: add missing FRONTEND_URL and STRIPE_PUBLISHABLE_KEY to .env.production
- Add all Stripe Price IDs (test mode) to .env.production
- Wire credit purchase buttons to /api/v1/auth/create-credits-checkout
- Dashboard sync post-checkout was already implemented (no change needed)

Stripe test keys: configured in .env.production
Webhook: must be configured on server via stripe CLI or Stripe Dashboard
Webhook URL: https://wordly.art/api/v1/auth/webhook/stripe
This commit is contained in:
2026-05-31 21:40:31 +02:00
parent 3a9de12f26
commit 374c605027
9 changed files with 550 additions and 12 deletions

View File

@@ -0,0 +1,37 @@
"""Add subscription_ends_at and cancel_at_period_end to users
Revision ID: f6a7b8c9d0e1
Revises: e5b2c9d1f4a8
Create Date: 2026-05-31
Fixes critical bug: these columns were used in update_user() but never
persisted to the database, causing subscription end dates and cancellation
state to be lost on server restart.
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers
revision = "f6a7b8c9d0e1"
down_revision = "e5b2c9d1f4a8"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column(
"users",
sa.Column("subscription_ends_at", sa.DateTime(), nullable=True),
)
op.add_column(
"users",
sa.Column("cancel_at_period_end", sa.Boolean(), nullable=False, server_default="false"),
)
def downgrade() -> None:
op.drop_column("users", "cancel_at_period_end")
op.drop_column("users", "subscription_ends_at")