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

@@ -2,9 +2,12 @@
Stripe payment integration for subscriptions and credits
"""
import os
import logging
from typing import Optional, Dict, Any
from datetime import datetime
logger = logging.getLogger(__name__)
# Try to import stripe
try:
import stripe
@@ -347,13 +350,33 @@ async def handle_subscription_deleted(subscription: Dict):
async def handle_payment_failed(invoice: Dict):
"""Handle failed payment"""
"""Handle failed payment — set subscription status to PAST_DUE"""
customer_id = invoice.get("customer")
if not customer_id:
return
# Find user by customer ID and update status
# In production, query database by stripe_customer_id
# Find user by stripe_customer_id
user = None
try:
from database.connection import get_sync_session
from database.models import User as DBUser
with get_sync_session() as session:
db_user = (
session.query(DBUser)
.filter(DBUser.stripe_customer_id == customer_id)
.first()
)
if db_user:
user_id = str(db_user.id)
db_user.subscription_status = SubscriptionStatus.PAST_DUE
session.commit()
logger.warning(
"Payment failed for customer %s (user %s) — status set to past_due",
customer_id, user_id,
)
except Exception as exc:
logger.error("handle_payment_failed DB error: %s", exc)
async def cancel_subscription(user_id: str) -> Dict[str, Any]: