From 5533198cab5ec07f659e2e189f438c8d6e002828 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Sun, 11 Jan 2026 07:50:34 +0100 Subject: [PATCH] Imporve UI and token --- ARCHITECTURE_PROBLEMS_CODE_ANALYSIS.md | 552 ------------ STRIPE_TESTING_GUIDE.md | 828 ++++++++++++++++++ frontend/package-lock.json | 22 +- frontend/package.json | 4 +- frontend/src/api/client/apiClient.ts | 17 +- frontend/src/api/types/subscription.ts | 4 + .../components/domain/auth/PaymentForm.tsx | 84 +- .../components/domain/auth/RegisterForm.tsx | 16 +- .../subscription/SubscriptionPage.tsx | 47 +- frontend/src/stores/auth.store.ts | 17 +- services/tenant/app/api/webhooks.py | 322 ++++++- .../tenant/app/services/payment_service.py | 51 ++ services/tenant/requirements.txt | 2 +- shared/clients/stripe_client.py | 74 +- 14 files changed, 1370 insertions(+), 670 deletions(-) delete mode 100644 ARCHITECTURE_PROBLEMS_CODE_ANALYSIS.md create mode 100644 STRIPE_TESTING_GUIDE.md diff --git a/ARCHITECTURE_PROBLEMS_CODE_ANALYSIS.md b/ARCHITECTURE_PROBLEMS_CODE_ANALYSIS.md deleted file mode 100644 index 1c165da5..00000000 --- a/ARCHITECTURE_PROBLEMS_CODE_ANALYSIS.md +++ /dev/null @@ -1,552 +0,0 @@ -# Code-Level Architecture Analysis: Notification & Subscription Endpoints -**Date:** 2026-01-10 -**Analysis Method:** SigNoz Distributed Tracing + Deep Code Review -**Status:** ARCHITECTURAL FLAWS IDENTIFIED - ---- - -## 🎯 Executive Summary - -After deep code analysis, I've identified **SEVERE architectural problems** causing the 2.5s notification latency and 5.5s subscription latency. The issues are NOT simple missing indexes - they're **fundamental design flaws** in the auth/authorization chain. - -### Critical Problems Found: - -1. **Gateway makes 5 SYNCHRONOUS external HTTP calls** for EVERY request -2. **No caching layer** - same auth checks repeated millions of times -3. **Decorators stacked incorrectly** - causing redundant checks -4. **Header extraction overhead** - parsing on every request -5. **Subscription data fetched from database** instead of being cached in JWT - ---- - -## πŸ” Problem 1: Notification Endpoint Architecture (2.5s latency) - -### Current Implementation - -**File:** `services/notification/app/api/notification_operations.py:46-56` - -```python -@router.post( - route_builder.build_base_route("send"), - response_model=NotificationResponse, - status_code=201 -) -@track_endpoint_metrics("notification_send") # Decorator 1 -async def send_notification( - notification_data: Dict[str, Any], - tenant_id: UUID = Path(..., description="Tenant ID"), - current_user: Dict[str, Any] = Depends(get_current_user_dep), # Decorator 2 (hidden) - notification_service: EnhancedNotificationService = Depends(get_enhanced_notification_service) -): -``` - -### The Authorization Chain - -When a request hits this endpoint, here's what happens: - -#### Step 1: `get_current_user_dep` (line 55) - -**File:** `shared/auth/decorators.py:448-510` - -```python -async def get_current_user_dep(request: Request) -> Dict[str, Any]: - # Logs EVERY request (expensive string operations) - logger.debug( - "Authentication attempt", # Line 452 - path=request.url.path, - method=request.method, - has_auth_header=bool(request.headers.get("authorization")), - # ... 8 more header checks - ) - - # Try header extraction first - try: - user = get_current_user(request) # Line 468 - CALL 1 - except HTTPException: - # Fallback to JWT extraction - auth_header = request.headers.get("authorization", "") - if auth_header.startswith("Bearer "): - user = extract_user_from_jwt(auth_header) # Line 473 - CALL 2 -``` - -#### Step 2: `get_current_user()` extracts headers - -**File:** `shared/auth/decorators.py:320-333` - -```python -def get_current_user(request: Request) -> Dict[str, Any]: - if hasattr(request.state, 'user') and request.state.user: - return request.state.user - - # Fallback to headers (for dev/testing) - user_info = extract_user_from_headers(request) # CALL 3 - if not user_info: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="User not authenticated" - ) - return user_info -``` - -#### Step 3: `extract_user_from_headers()` - THE BOTTLENECK - -**File:** `shared/auth/decorators.py:343-374` - -```python -def extract_user_from_headers(request: Request) -> Optional[Dict[str, Any]]: - """Extract user information from forwarded headers""" - user_id = request.headers.get("x-user-id") # HTTP call to gateway? - if not user_id: - return None - - # Build user context from 15+ headers - user_context = { - "user_id": user_id, - "email": request.headers.get("x-user-email", ""), # Another header - "role": request.headers.get("x-user-role", "user"), # Another - "tenant_id": request.headers.get("x-tenant-id"), # Another - "permissions": request.headers.get("X-User-Permissions", "").split(","), - "full_name": request.headers.get("x-user-full-name", ""), - "subscription_tier": request.headers.get("x-subscription-tier", ""), # Gateway lookup! - "is_demo": request.headers.get("x-is-demo", "").lower() == "true", - "demo_session_id": request.headers.get("x-demo-session-id", ""), - "demo_account_type": request.headers.get("x-demo-account-type", "") - } - return user_context -``` - -### πŸ”΄ **ROOT CAUSE: Gateway Performs 5 Sequential Database/Service Calls** - -The trace shows that **BEFORE** the notification service is even called, the gateway makes these calls: - -``` -Gateway Middleware Chain: -1. GET /tenants/{tenant_id}/access/{user_id} 294ms ← Verify user access -2. GET /subscriptions/{tenant_id}/tier 110ms ← Get subscription tier -3. GET /tenants/{tenant_id}/access/{user_id} 12ms ← DUPLICATE! Why? -4. GET (unknown - maybe features?) 2ms ← Unknown call -5. GET /subscriptions/{tenant_id}/status 102ms ← Get subscription status -───────────────────────────────────────────────────────── -TOTAL OVERHEAD: 520ms (43% of total request time!) -``` - -### Where This Happens (Hypothesis - needs gateway code) - -Based on the headers being injected, the gateway likely does: - -```python -# Gateway middleware (not in repo, but this is what's happening) -async def inject_user_context_middleware(request, call_next): - # Extract tenant_id and user_id from JWT - token = extract_token(request) - user_id = token.get("user_id") - tenant_id = extract_tenant_from_path(request.url.path) - - # PROBLEM: Make external HTTP calls to get auth data - # Call 1: Check if user has access to tenant (294ms) - access = await tenant_service.check_access(tenant_id, user_id) - - # Call 2: Get subscription tier (110ms) - subscription = await tenant_service.get_subscription_tier(tenant_id) - - # Call 3: DUPLICATE access check? (12ms) - access2 = await tenant_service.check_access(tenant_id, user_id) # WHY? - - # Call 4: Unknown (2ms) - something = await tenant_service.get_something(tenant_id) - - # Call 5: Get subscription status (102ms) - status = await tenant_service.get_subscription_status(tenant_id) - - # Inject into headers - request.headers["x-user-role"] = access.role - request.headers["x-subscription-tier"] = subscription.tier - request.headers["x-subscription-status"] = status.status - - # Forward request - return await call_next(request) -``` - -### Why This is BAD Architecture: - -1. ❌ **Service-to-Service HTTP calls** instead of shared cache -2. ❌ **Sequential execution** (each waits for previous) -3. ❌ **No caching** - every request makes ALL calls -4. ❌ **Redundant checks** - access checked twice -5. ❌ **Wrong layer** - auth data should be in JWT, not fetched per request - ---- - -## πŸ” Problem 2: Subscription Tier Query (772ms!) - -### Current Query (Hypothesis) - -**File:** `services/tenant/app/repositories/subscription_repository.py` (lines not shown, but likely exists) - -```python -async def get_subscription_by_tenant(tenant_id: str) -> Subscription: - query = select(Subscription).where( - Subscription.tenant_id == tenant_id, - Subscription.status == 'active' - ) - result = await self.session.execute(query) - return result.scalar_one_or_none() -``` - -### Why It's Slow: - -**Missing Index!** - -```sql --- Current situation: Full table scan -EXPLAIN ANALYZE -SELECT * FROM subscriptions -WHERE tenant_id = 'uuid' AND status = 'active'; - --- Result: Seq Scan on subscriptions (cost=0.00..1234.56 rows=1) --- Planning Time: 0.5 ms --- Execution Time: 772.3 ms ← SLOW! -``` - -**Database Metrics Confirm:** -``` -Average Block Reads: 396 blocks/query -Max Block Reads: 369,161 blocks (!!) -Average Index Scans: 0.48 per query ← Almost no indexes used! -``` - -### The Missing Indexes: - -```sql --- Check existing indexes -SELECT - tablename, - indexname, - indexdef -FROM pg_indexes -WHERE tablename = 'subscriptions'; - --- Result: Probably only has PRIMARY KEY on `id` --- Missing: --- - Index on tenant_id --- - Composite index on (tenant_id, status) --- - Covering index including tier, status, valid_until -``` - ---- - -## πŸ”§ Architectural Solutions - -### Solution 1: Move Auth Data Into JWT (BEST FIX) - -**Current (BAD):** -``` -User Request β†’ Gateway β†’ 5 HTTP calls to tenant-service β†’ Inject headers β†’ Forward -``` - -**Better:** -``` -User Login β†’ Generate JWT with ALL auth data β†’ Gateway validates JWT β†’ Forward -``` - -**Implementation:** - -#### Step 1: Update JWT Payload - -**File:** Create `shared/auth/jwt_builder.py` - -```python -from datetime import datetime, timedelta -import jwt - -def create_access_token(user_data: dict, subscription_data: dict) -> str: - """ - Create JWT with ALL required auth data embedded - No need for runtime lookups! - """ - now = datetime.utcnow() - - payload = { - # Standard JWT claims - "sub": user_data["user_id"], - "iat": now, - "exp": now + timedelta(hours=24), - "type": "access", - - # User data (already available at login) - "user_id": user_data["user_id"], - "email": user_data["email"], - "role": user_data["role"], - "full_name": user_data.get("full_name", ""), - "tenant_id": user_data["tenant_id"], - - # Subscription data (fetch ONCE at login, cache in JWT) - "subscription": { - "tier": subscription_data["tier"], # professional, enterprise - "status": subscription_data["status"], # active, cancelled - "valid_until": subscription_data["valid_until"].isoformat(), - "features": subscription_data["features"], # list of enabled features - "limits": { - "max_users": subscription_data.get("max_users", -1), - "max_products": subscription_data.get("max_products", -1), - "max_locations": subscription_data.get("max_locations", -1) - } - }, - - # Permissions (computed once at login) - "permissions": compute_user_permissions(user_data, subscription_data) - } - - return jwt.encode(payload, SECRET_KEY, algorithm="HS256") -``` - -**Impact:** -- Gateway calls: 5 β†’ **0** (everything in JWT) -- Latency: 520ms β†’ **<1ms** (JWT decode) -- Database load: **99% reduction** - ---- - -#### Step 2: Simplify Gateway Middleware - -**File:** Gateway middleware (Kong/nginx/custom) - -```python -# BEFORE: 520ms of HTTP calls -async def auth_middleware(request): - # 5 HTTP calls... - pass - -# AFTER: <1ms JWT decode -async def auth_middleware(request): - # Extract JWT - token = request.headers.get("Authorization", "").replace("Bearer ", "") - - # Decode (no verification needed if from trusted source) - payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) - - # Inject ALL data into headers at once - request.headers["x-user-id"] = payload["user_id"] - request.headers["x-user-email"] = payload["email"] - request.headers["x-user-role"] = payload["role"] - request.headers["x-tenant-id"] = payload["tenant_id"] - request.headers["x-subscription-tier"] = payload["subscription"]["tier"] - request.headers["x-subscription-status"] = payload["subscription"]["status"] - request.headers["x-permissions"] = ",".join(payload.get("permissions", [])) - - return await call_next(request) -``` - ---- - -### Solution 2: Add Database Indexes (Complementary) - -Even with JWT optimization, some endpoints still query subscriptions directly: - -```sql --- Critical indexes for tenant service -CREATE INDEX CONCURRENTLY idx_subscriptions_tenant_status - ON subscriptions (tenant_id, status) - WHERE status IN ('active', 'trial'); - --- Covering index (avoids table lookup) -CREATE INDEX CONCURRENTLY idx_subscriptions_tenant_covering - ON subscriptions (tenant_id) - INCLUDE (tier, status, valid_until, features, max_users, max_products); - --- Index for status checks -CREATE INDEX CONCURRENTLY idx_subscriptions_status_valid - ON subscriptions (status, valid_until DESC) - WHERE status = 'active'; -``` - -**Expected Impact:** -- Query time: 772ms β†’ **5-10ms** (99% improvement) -- Block reads: 369K β†’ **<100 blocks** - ---- - -### Solution 3: Add Redis Cache Layer (Defense in Depth) - -Even with JWT, cache critical data: - -```python -# shared/caching/subscription_cache.py -import redis -import json - -class SubscriptionCache: - def __init__(self, redis_client): - self.redis = redis_client - self.TTL = 300 # 5 minutes - - async def get_subscription(self, tenant_id: str): - """Get subscription from cache or database""" - cache_key = f"subscription:{tenant_id}" - - # Try cache - cached = await self.redis.get(cache_key) - if cached: - return json.loads(cached) - - # Fetch from database - subscription = await self._fetch_from_db(tenant_id) - - # Cache it - await self.redis.setex( - cache_key, - self.TTL, - json.dumps(subscription) - ) - - return subscription - - async def invalidate(self, tenant_id: str): - """Invalidate cache when subscription changes""" - cache_key = f"subscription:{tenant_id}" - await self.redis.delete(cache_key) -``` - -**Usage:** - -```python -# services/tenant/app/api/subscription.py -@router.get("/api/v1/subscriptions/{tenant_id}/tier") -async def get_subscription_tier(tenant_id: str): - # Try cache first - subscription = await subscription_cache.get_subscription(tenant_id) - return {"tier": subscription["tier"]} -``` - ---- - -## πŸ“ˆ Expected Performance Improvements - -| Component | Before | After (JWT) | After (JWT + Index + Cache) | Improvement | -|-----------|--------|-------------|----------------------------|-------------| -| **Gateway Auth Calls** | 520ms (5 calls) | <1ms (JWT decode) | <1ms | **99.8%** | -| **Subscription Query** | 772ms | 772ms | 2ms (cache hit) | **99.7%** | -| **Notification POST** | 2,500ms | 1,980ms (20% faster) | **50ms** | **98%** | -| **Subscription GET** | 5,500ms | 4,780ms | **20ms** | **99.6%** | - -### Overall Impact: - -**Notification endpoint:** 2.5s β†’ **50ms** (98% improvement) -**Subscription endpoint:** 5.5s β†’ **20ms** (99.6% improvement) - ---- - -## 🎯 Implementation Priority - -### CRITICAL (Day 1-2): JWT Auth Data - -**Why:** Eliminates 520ms overhead on EVERY request across ALL services - -**Steps:** -1. Update JWT payload to include subscription data -2. Modify login endpoint to fetch subscription once -3. Update gateway to use JWT data instead of HTTP calls -4. Test with 1-2 endpoints first - -**Risk:** Low - JWT is already used, just adding more data -**Impact:** **98% latency reduction** on auth-heavy endpoints - ---- - -### HIGH (Day 3-4): Database Indexes - -**Why:** Fixes 772ms subscription queries - -**Steps:** -1. Add indexes to subscriptions table -2. Analyze `pg_stat_statements` for other slow queries -3. Add covering indexes where needed -4. Monitor query performance - -**Risk:** Low - indexes don't change logic -**Impact:** **99% query time reduction** - ---- - -### MEDIUM (Day 5-7): Redis Cache Layer - -**Why:** Defense in depth, handles JWT expiry edge cases - -**Steps:** -1. Implement subscription cache service -2. Add cache to subscription repository -3. Add cache invalidation on updates -4. Monitor cache hit rates - -**Risk:** Medium - cache invalidation can be tricky -**Impact:** **Additional 50% improvement** for cache hits - ---- - -## 🚨 Critical Architectural Lesson - -### The Real Problem: - -**"Microservices without proper caching become a distributed monolith with network overhead"** - -Every request was: -1. JWT decode (cheap) -2. β†’ 5 HTTP calls to tenant-service (expensive!) -3. β†’ 5 database queries in tenant-service (very expensive!) -4. β†’ Forward to actual service -5. β†’ Actual work finally happens - -**Solution:** -- **Move static/slow-changing data into JWT** (subscription tier, role, permissions) -- **Cache everything else** in Redis (user preferences, feature flags) -- **Only query database** for truly dynamic data (current notifications, real-time stats) - -This is a **classic distributed systems anti-pattern** that's killing your performance! - ---- - -## πŸ“Š Monitoring After Fix - -```sql --- Monitor gateway performance -SELECT - name, - quantile(0.95)(durationNano) / 1000000 as p95_ms -FROM signoz_traces.signoz_index_v3 -WHERE serviceName = 'gateway' - AND timestamp >= now() - INTERVAL 1 DAY -GROUP BY name -ORDER BY p95_ms DESC; - --- Target: All gateway calls < 10ms --- Current: 520ms average - --- Monitor subscription queries -SELECT - query, - calls, - mean_exec_time, - max_exec_time -FROM pg_stat_statements -WHERE query LIKE '%subscriptions%' -ORDER BY mean_exec_time DESC; - --- Target: < 5ms average --- Current: 772ms max -``` - ---- - -## πŸš€ Conclusion - -The performance issues are caused by **architectural choices**, not missing indexes: - -1. **Auth data fetched via HTTP** instead of embedded in JWT -2. **5 sequential database/HTTP calls** on every request -3. **No caching layer** - same data fetched millions of times -4. **Wrong separation of concerns** - gateway doing too much - -**The fix is NOT to add caching to the current architecture.** -**The fix is to CHANGE the architecture to not need those calls.** - -Embedding auth data in JWT is the **industry standard** for exactly this reason - it eliminates the need for runtime authorization lookups! diff --git a/STRIPE_TESTING_GUIDE.md b/STRIPE_TESTING_GUIDE.md new file mode 100644 index 00000000..8caaf18d --- /dev/null +++ b/STRIPE_TESTING_GUIDE.md @@ -0,0 +1,828 @@ +# Stripe Integration Testing Guide + +## Table of Contents +1. [Prerequisites](#prerequisites) +2. [Environment Setup](#environment-setup) +3. [Stripe Dashboard Configuration](#stripe-dashboard-configuration) +4. [Test Card Numbers](#test-card-numbers) +5. [Testing Scenarios](#testing-scenarios) +6. [Webhook Testing](#webhook-testing) +7. [Common Issues & Solutions](#common-issues--solutions) +8. [Production Checklist](#production-checklist) + +--- + +## Prerequisites + +Before you begin testing, ensure you have: + +- βœ… Stripe account created (sign up at [stripe.com](https://stripe.com)) +- βœ… Node.js and Python environments set up +- βœ… Frontend application running (React + Vite) +- βœ… Backend API running (FastAPI) +- βœ… Database configured and accessible +- βœ… Redis instance running (for caching) + +--- + +## Environment Setup + +### Step 1: Access Stripe Test Mode + +1. Log in to your Stripe Dashboard: [https://dashboard.stripe.com](https://dashboard.stripe.com) +2. Click on your profile icon in the top right corner +3. Ensure **Test Mode** is enabled (you'll see "TEST DATA" banner at the top) +4. If not enabled, toggle to "Switch to test data" + +### Step 2: Retrieve API Keys + +1. Navigate to **Developers** β†’ **API keys** +2. You'll see two types of keys: + - **Publishable key** (starts with `pk_test_...`) - Used in frontend + - **Secret key** (starts with `sk_test_...`) - Used in backend + +3. Click "Reveal test key" for the Secret key and copy both keys + +### Step 3: Configure Environment Variables + +#### Frontend `.env` file: +```bash +# Create or update: /frontend/.env +VITE_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here +``` + +#### Backend `.env` file: +```bash +# Create or update: /services/tenant/.env +STRIPE_SECRET_KEY=sk_test_your_secret_key_here +STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here +``` + +**Note:** The webhook secret will be obtained in Step 4 when setting up webhooks. + +### Step 4: Install/Update Dependencies + +#### Backend: +```bash +cd services/tenant +pip install -r requirements.txt +# This will install stripe==14.1.0 +``` + +#### Frontend: +```bash +cd frontend +npm install +# Verifies @stripe/react-stripe-js and @stripe/stripe-js are installed +``` + +--- + +## Stripe Dashboard Configuration + +### Step 1: Create Products and Prices + +1. In Stripe Dashboard (Test Mode), go to **Products** β†’ **Add product** + +2. **Create Starter Plan:** + - Product name: `Starter Plan` + - Description: `Basic subscription for small businesses` + - Pricing: + - Price: `$29.00` + - Billing period: `Monthly` + - Currency: `USD` (or your preferred currency) + - Click **Save product** + - Copy the **Price ID** (starts with `price_...`) - you'll need this + +3. **Create Professional Plan:** + - Product name: `Professional Plan` + - Description: `Advanced subscription for growing businesses` + - Pricing: + - Price: `$99.00` + - Billing period: `Monthly` + - Currency: `USD` + - Click **Save product** + - Copy the **Price ID** + +4. **Update your application configuration:** + - Store these Price IDs in your application settings + - You'll use these when creating subscriptions + +### Step 2: Configure Webhooks + +1. Navigate to **Developers** β†’ **Webhooks** +2. Click **+ Add endpoint** + +3. **For Local Development:** + - Endpoint URL: `https://your-ngrok-url.ngrok.io/webhooks/stripe` + - (We'll set up ngrok later for local testing) + +4. **Select events to listen to:** + - `checkout.session.completed` + - `customer.subscription.created` + - `customer.subscription.updated` + - `customer.subscription.deleted` + - `invoice.payment_succeeded` + - `invoice.payment_failed` + - `customer.subscription.trial_will_end` + +5. Click **Add endpoint** + +6. **Copy the Webhook Signing Secret:** + - Click on the newly created endpoint + - Click **Reveal** next to "Signing secret" + - Copy the secret (starts with `whsec_...`) + - Add it to your backend `.env` file as `STRIPE_WEBHOOK_SECRET` + +--- + +## Test Card Numbers + +Stripe provides test card numbers to simulate different scenarios. **Never use real card details in test mode.** + +### Basic Test Cards + +| Scenario | Card Number | CVC | Expiry Date | +|----------|-------------|-----|-------------| +| **Successful payment** | `4242 4242 4242 4242` | Any 3 digits | Any future date | +| **Visa (debit)** | `4000 0566 5566 5556` | Any 3 digits | Any future date | +| **Mastercard** | `5555 5555 5555 4444` | Any 3 digits | Any future date | +| **American Express** | `3782 822463 10005` | Any 4 digits | Any future date | + +### Authentication & Security + +| Scenario | Card Number | Notes | +|----------|-------------|-------| +| **3D Secure authentication required** | `4000 0025 0000 3155` | Triggers authentication modal | +| **3D Secure 2 authentication** | `4000 0027 6000 3184` | Requires SCA authentication | + +### Declined Cards + +| Scenario | Card Number | Error Message | +|----------|-------------|---------------| +| **Generic decline** | `4000 0000 0000 0002` | Card declined | +| **Insufficient funds** | `4000 0000 0000 9995` | Insufficient funds | +| **Lost card** | `4000 0000 0000 9987` | Lost card | +| **Stolen card** | `4000 0000 0000 9979` | Stolen card | +| **Expired card** | `4000 0000 0000 0069` | Expired card | +| **Incorrect CVC** | `4000 0000 0000 0127` | Incorrect CVC | +| **Processing error** | `4000 0000 0000 0119` | Processing error | +| **Card declined (rate limit)** | `4000 0000 0000 9954` | Exceeds velocity limit | + +### Additional Scenarios + +| Scenario | Card Number | Notes | +|----------|-------------|-------| +| **Charge succeeds, then fails** | `4000 0000 0000 0341` | Attaches successfully but charge fails | +| **Dispute (fraudulent)** | `4000 0000 0000 0259` | Creates a fraudulent dispute | +| **Dispute (warning)** | `4000 0000 0000 2685` | Creates early fraud warning | + +**Important Notes:** +- For **expiry date**: Use any future date (e.g., 12/30) +- For **CVC**: Use any 3-digit number (e.g., 123) or 4-digit for Amex (e.g., 1234) +- For **postal code**: Use any valid format (e.g., 12345) + +--- + +## Testing Scenarios + +### Scenario 1: Successful Registration with Payment + +**Objective:** Test the complete registration flow with valid payment method. + +**Steps:** + +1. **Start your applications:** + ```bash + # Terminal 1 - Backend + cd services/tenant + uvicorn app.main:app --reload --port 8000 + + # Terminal 2 - Frontend + cd frontend + npm run dev + ``` + +2. **Navigate to registration page:** + - Open browser: `http://localhost:5173/register` (or your frontend URL) + +3. **Fill in user details:** + - Full Name: `John Doe` + - Email: `john.doe+test@example.com` + - Company: `Test Company` + - Password: Create a test password + +4. **Fill in payment details:** + - Card Number: `4242 4242 4242 4242` + - Expiry: `12/30` + - CVC: `123` + - Cardholder Name: `John Doe` + - Email: `john.doe+test@example.com` + - Address: `123 Test Street` + - City: `Test City` + - State: `CA` + - Postal Code: `12345` + - Country: `US` + +5. **Select a plan:** + - Choose `Starter Plan` or `Professional Plan` + +6. **Submit the form** + +**Expected Results:** +- βœ… Payment method created successfully +- βœ… User account created +- βœ… Subscription created in Stripe +- βœ… Database records created +- βœ… User redirected to dashboard +- βœ… No console errors + +**Verification:** + +1. **In Stripe Dashboard:** + - Go to **Customers** β†’ Find "John Doe" + - Go to **Subscriptions** β†’ See active subscription + - Status should be `active` + +2. **In your database:** + ```sql + SELECT * FROM subscriptions WHERE tenant_id = 'your-tenant-id'; + ``` + - Verify subscription record exists + - Status should be `active` + - Check `stripe_customer_id` is populated + +3. **Check application logs:** + - Look for successful subscription creation messages + - Verify no error logs + +--- + +### Scenario 2: Payment with 3D Secure Authentication + +**Objective:** Test Strong Customer Authentication (SCA) flow. + +**Steps:** + +1. Follow steps 1-3 from Scenario 1 + +2. **Fill in payment details with 3DS card:** + - Card Number: `4000 0025 0000 3155` + - Expiry: `12/30` + - CVC: `123` + - Fill remaining details as before + +3. **Submit the form** + +4. **Complete authentication:** + - Stripe will display an authentication modal + - Click **"Complete"** (in test mode, no real auth needed) + +**Expected Results:** +- βœ… Authentication modal appears +- βœ… After clicking "Complete", payment succeeds +- βœ… Subscription created successfully +- βœ… User redirected to dashboard + +**Note:** This simulates European and other markets requiring SCA. + +--- + +### Scenario 3: Declined Payment + +**Objective:** Test error handling for declined cards. + +**Steps:** + +1. Follow steps 1-3 from Scenario 1 + +2. **Use a declined test card:** + - Card Number: `4000 0000 0000 0002` + - Fill remaining details as before + +3. **Submit the form** + +**Expected Results:** +- ❌ Payment fails with error message +- βœ… Error displayed to user: "Your card was declined" +- βœ… No customer created in Stripe +- βœ… No subscription created +- βœ… No database records created +- βœ… User remains on payment form +- βœ… Can retry with different card + +**Verification:** +- Check Stripe Dashboard β†’ Customers (should not see new customer) +- Check application logs for error handling +- Verify user-friendly error message displayed + +--- + +### Scenario 4: Insufficient Funds + +**Objective:** Test specific decline reason handling. + +**Steps:** + +1. Use card number: `4000 0000 0000 9995` +2. Follow same process as Scenario 3 + +**Expected Results:** +- ❌ Payment fails +- βœ… Error message: "Your card has insufficient funds" +- βœ… Proper error handling and logging + +--- + +### Scenario 5: Subscription Cancellation + +**Objective:** Test subscription cancellation flow. + +**Steps:** + +1. **Create an active subscription** (use Scenario 1) + +2. **Cancel the subscription:** + - Method 1: Through your application UI (if implemented) + - Method 2: API call: + ```bash + curl -X POST http://localhost:8000/api/v1/subscriptions/cancel \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_AUTH_TOKEN" \ + -d '{ + "tenant_id": "your-tenant-id", + "reason": "Testing cancellation" + }' + ``` + +**Expected Results:** +- βœ… Subscription status changes to `pending_cancellation` +- βœ… `cancellation_effective_date` is set +- βœ… User retains access until end of billing period +- βœ… Response includes days remaining +- βœ… Subscription cache invalidated + +**Verification:** +1. Check database: + ```sql + SELECT status, cancellation_effective_date, cancelled_at + FROM subscriptions + WHERE tenant_id = 'your-tenant-id'; + ``` + +2. Verify API response: + ```json + { + "success": true, + "message": "Subscription cancelled successfully...", + "status": "pending_cancellation", + "cancellation_effective_date": "2026-02-10T...", + "days_remaining": 30 + } + ``` + +--- + +### Scenario 6: Subscription Reactivation + +**Objective:** Test reactivating a cancelled subscription. + +**Steps:** + +1. **Cancel a subscription** (use Scenario 5) + +2. **Reactivate the subscription:** + ```bash + curl -X POST http://localhost:8000/api/v1/subscriptions/reactivate \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_AUTH_TOKEN" \ + -d '{ + "tenant_id": "your-tenant-id", + "plan": "starter" + }' + ``` + +**Expected Results:** +- βœ… Subscription status changes back to `active` +- βœ… `cancelled_at` and `cancellation_effective_date` cleared +- βœ… Next billing date set +- βœ… Subscription cache invalidated + +--- + +### Scenario 7: Retrieve Invoices + +**Objective:** Test invoice retrieval from Stripe. + +**Steps:** + +1. **Create subscription with successful payment** (Scenario 1) + +2. **Retrieve invoices:** + ```bash + curl -X GET http://localhost:8000/api/v1/subscriptions/{tenant_id}/invoices \ + -H "Authorization: Bearer YOUR_AUTH_TOKEN" + ``` + +**Expected Results:** +- βœ… List of invoices returned +- βœ… Each invoice contains: + - `id` + - `date` + - `amount` + - `currency` + - `status` + - `invoice_pdf` URL + - `hosted_invoice_url` URL + +**Verification:** +- Click on `hosted_invoice_url` to view invoice in browser +- Download PDF from `invoice_pdf` URL + +--- + +## Webhook Testing + +Webhooks are critical for handling asynchronous events from Stripe. Test them thoroughly. + +### Option 1: Using Stripe CLI (Recommended for Local Development) + +#### Step 1: Install Stripe CLI + +**macOS:** +```bash +brew install stripe/stripe-cli/stripe +``` + +**Windows:** +Download from: https://github.com/stripe/stripe-cli/releases + +**Linux:** +```bash +wget https://github.com/stripe/stripe-cli/releases/latest/download/stripe_linux_x86_64.tar.gz +tar -xvf stripe_linux_x86_64.tar.gz +sudo mv stripe /usr/local/bin/ +``` + +#### Step 2: Login to Stripe + +```bash +stripe login +``` + +This opens a browser to authorize the CLI. + +#### Step 3: Forward Webhooks to Local Server + +```bash +stripe listen --forward-to localhost:8000/webhooks/stripe +``` + +**Expected Output:** +``` +> Ready! Your webhook signing secret is whsec_abc123... (^C to quit) +``` + +**Important:** Copy this webhook signing secret and add it to your backend `.env`: +```bash +STRIPE_WEBHOOK_SECRET=whsec_abc123... +``` + +#### Step 4: Trigger Test Events + +Open a new terminal and run: + +```bash +# Test subscription created +stripe trigger customer.subscription.created + +# Test payment succeeded +stripe trigger invoice.payment_succeeded + +# Test payment failed +stripe trigger invoice.payment_failed + +# Test subscription updated +stripe trigger customer.subscription.updated + +# Test subscription deleted +stripe trigger customer.subscription.deleted + +# Test trial ending +stripe trigger customer.subscription.trial_will_end +``` + +#### Step 5: Verify Webhook Processing + +**Check your application logs for:** +- βœ… "Processing Stripe webhook event" +- βœ… Event type logged +- βœ… Database updates (check subscription status) +- βœ… No signature verification errors + +**Example log output:** +``` +INFO Processing Stripe webhook event event_type=customer.subscription.updated +INFO Subscription updated in database subscription_id=sub_123 tenant_id=tenant-id +``` + +### Option 2: Using ngrok (For Public URL Testing) + +#### Step 1: Install ngrok + +Download from: https://ngrok.com/download + +#### Step 2: Start ngrok + +```bash +ngrok http 8000 +``` + +**Output:** +``` +Forwarding https://abc123.ngrok.io -> http://localhost:8000 +``` + +#### Step 3: Update Stripe Webhook Endpoint + +1. Go to Stripe Dashboard β†’ Developers β†’ Webhooks +2. Click on your endpoint +3. Update URL to: `https://abc123.ngrok.io/webhooks/stripe` +4. Save changes + +#### Step 4: Test by Creating Real Events + +Create a test subscription through your app, and webhooks will be sent to your ngrok URL. + +### Option 3: Testing Webhook Handlers Directly + +You can also test webhook handlers by sending test payloads: + +```bash +curl -X POST http://localhost:8000/webhooks/stripe \ + -H "Content-Type: application/json" \ + -H "stripe-signature: test-signature" \ + -d @webhook-test-payload.json +``` + +**Note:** This will fail signature verification unless you disable it temporarily for testing. + +--- + +## Common Issues & Solutions + +### Issue 1: "Stripe.js has not loaded correctly" + +**Symptoms:** +- Error when submitting payment form +- Console error about Stripe not being loaded + +**Solutions:** +1. Check internet connection (Stripe.js loads from CDN) +2. Verify `VITE_STRIPE_PUBLISHABLE_KEY` is set correctly +3. Check browser console for loading errors +4. Ensure no ad blockers blocking Stripe.js + +### Issue 2: "Invalid signature" on Webhook + +**Symptoms:** +- Webhook endpoint returns 400 error +- Log shows "Invalid webhook signature" + +**Solutions:** +1. Verify `STRIPE_WEBHOOK_SECRET` matches Stripe Dashboard +2. For Stripe CLI, use the secret from `stripe listen` output +3. Ensure you're using the test mode secret, not live mode +4. Check that you're not modifying the request body before verification + +### Issue 3: Payment Method Not Attaching + +**Symptoms:** +- PaymentMethod created but subscription fails +- Error about payment method not found + +**Solutions:** +1. Verify you're passing `paymentMethod.id` to backend +2. Check that payment method is being attached to customer +3. Ensure customer_id exists before creating subscription +4. Review backend logs for detailed error messages + +### Issue 4: Test Mode vs Live Mode Confusion + +**Symptoms:** +- Keys not working +- Data not appearing in dashboard + +**Solutions:** +1. **Always check the mode indicator** in Stripe Dashboard +2. Test keys start with `pk_test_` and `sk_test_` +3. Live keys start with `pk_live_` and `sk_live_` +4. Never mix test and live keys +5. Use separate databases for test and live environments + +### Issue 5: CORS Errors + +**Symptoms:** +- Browser console shows CORS errors +- Requests to backend failing + +**Solutions:** +1. Ensure FastAPI CORS middleware is configured: + ```python + from fastapi.middleware.cors import CORSMiddleware + + app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + ``` + +### Issue 6: Webhook Events Not Processing + +**Symptoms:** +- Webhooks received but database not updating +- Events logged but handlers not executing + +**Solutions:** +1. Check event type matches handler (case-sensitive) +2. Verify database session is committed +3. Check for exceptions in handler functions +4. Review logs for specific error messages +5. Ensure subscription exists in database before update + +### Issue 7: Card Element vs Payment Element Confusion + +**Symptoms:** +- TypeError when calling `stripe.createPaymentMethod()` +- Elements not rendering correctly + +**Solutions:** +- Use `PaymentElement` (modern, recommended) +- Call `elements.submit()` before creating payment method +- Pass `elements` object to `createPaymentMethod({ elements })` +- **Our implementation now uses the correct PaymentElement API** + +--- + +## Production Checklist + +Before going live with Stripe payments: + +### Security + +- [ ] All API keys stored in environment variables (never in code) +- [ ] Webhook signature verification enabled and working +- [ ] HTTPS enabled on all endpoints +- [ ] Rate limiting implemented on payment endpoints +- [ ] Input validation on all payment-related forms +- [ ] SQL injection prevention (using parameterized queries) +- [ ] XSS protection enabled +- [ ] CSRF tokens implemented where needed + +### Stripe Configuration + +- [ ] Live mode API keys obtained from Stripe Dashboard +- [ ] Live mode webhook endpoints configured +- [ ] Webhook signing secret updated for live mode +- [ ] Products and prices created in live mode +- [ ] Business information completed in Stripe Dashboard +- [ ] Bank account added for payouts +- [ ] Tax settings configured (if applicable) +- [ ] Stripe account activated and verified + +### Application Configuration + +- [ ] Environment variables updated for production +- [ ] Database migrations run on production database +- [ ] Redis cache configured and accessible +- [ ] Error monitoring/logging configured (e.g., Sentry) +- [ ] Payment failure notifications set up +- [ ] Trial ending notifications configured +- [ ] Invoice email delivery tested + +### Testing + +- [ ] All test scenarios passed (see above) +- [ ] Webhook handling verified for all event types +- [ ] 3D Secure authentication tested +- [ ] Subscription lifecycle tested (create, update, cancel, reactivate) +- [ ] Error handling tested for all failure scenarios +- [ ] Invoice retrieval tested +- [ ] Load testing completed +- [ ] Security audit performed + +### Monitoring + +- [ ] Stripe Dashboard monitoring set up +- [ ] Application logs reviewed regularly +- [ ] Webhook delivery monitoring configured +- [ ] Payment success/failure metrics tracked +- [ ] Alert thresholds configured +- [ ] Failed payment retry logic implemented + +### Compliance + +- [ ] Terms of Service updated to mention subscriptions +- [ ] Privacy Policy updated for payment data handling +- [ ] GDPR compliance verified (if applicable) +- [ ] PCI compliance requirements reviewed +- [ ] Customer data retention policy defined +- [ ] Refund policy documented + +### Documentation + +- [ ] API documentation updated +- [ ] Internal team trained on Stripe integration +- [ ] Customer support documentation created +- [ ] Troubleshooting guide prepared +- [ ] Subscription management procedures documented + +--- + +## Quick Reference Commands + +### Stripe CLI Commands + +```bash +# Login to Stripe +stripe login + +# Listen for webhooks (local development) +stripe listen --forward-to localhost:8000/webhooks/stripe + +# Trigger test events +stripe trigger customer.subscription.created +stripe trigger invoice.payment_succeeded +stripe trigger invoice.payment_failed + +# View recent events +stripe events list + +# Get specific event +stripe events retrieve evt_abc123 + +# Test webhook endpoint +stripe webhooks test --endpoint-secret whsec_abc123 +``` + +### Testing Shortcuts + +```bash +# Start backend (from project root) +cd services/tenant && uvicorn app.main:app --reload --port 8000 + +# Start frontend (from project root) +cd frontend && npm run dev + +# Update backend dependencies +cd services/tenant && pip install -r requirements.txt + +# Run database migrations (if using Alembic) +cd services/tenant && alembic upgrade head +``` + +--- + +## Additional Resources + +- **Stripe Documentation:** https://stripe.com/docs +- **Stripe API Reference:** https://stripe.com/docs/api +- **Stripe Testing Guide:** https://stripe.com/docs/testing +- **Stripe Webhooks Guide:** https://stripe.com/docs/webhooks +- **Stripe CLI Documentation:** https://stripe.com/docs/stripe-cli +- **React Stripe.js Docs:** https://stripe.com/docs/stripe-js/react +- **Stripe Support:** https://support.stripe.com + +--- + +## Support + +If you encounter issues not covered in this guide: + +1. **Check Stripe Dashboard Logs:** + - Developers β†’ Logs + - View detailed request/response information + +2. **Review Application Logs:** + - Check backend logs for detailed error messages + - Look for structured log output from `structlog` + +3. **Test in Isolation:** + - Test frontend separately + - Test backend API with cURL + - Verify webhook handling with Stripe CLI + +4. **Contact Stripe Support:** + - Live chat available in Stripe Dashboard + - Email support: support@stripe.com + - Community forum: https://stripe.com/community + +--- + +**Last Updated:** January 2026 +**Stripe Library Versions:** +- Frontend: `@stripe/stripe-js@4.0.0`, `@stripe/react-stripe-js@3.0.0` +- Backend: `stripe@14.1.0` diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3dc8df18..6a4a92dc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,8 +18,8 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", - "@stripe/react-stripe-js": "^2.7.3", - "@stripe/stripe-js": "^3.0.10", + "@stripe/react-stripe-js": "^3.0.0", + "@stripe/stripe-js": "^4.0.0", "@tanstack/react-query": "^5.12.0", "axios": "^1.6.2", "chart.js": "^4.5.0", @@ -6037,23 +6037,23 @@ } }, "node_modules/@stripe/react-stripe-js": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.9.0.tgz", - "integrity": "sha512-+/j2g6qKAKuWSurhgRMfdlIdKM+nVVJCy/wl0US2Ccodlqx0WqfIIBhUkeONkCG+V/b+bZzcj4QVa3E/rXtT4Q==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.10.0.tgz", + "integrity": "sha512-UPqHZwMwDzGSax0ZI7XlxR3tZSpgIiZdk3CiwjbTK978phwR/fFXeAXQcN/h8wTAjR4ZIAzdlI9DbOqJhuJdeg==", "license": "MIT", "dependencies": { "prop-types": "^15.7.2" }, "peerDependencies": { - "@stripe/stripe-js": "^1.44.1 || ^2.0.0 || ^3.0.0 || ^4.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@stripe/stripe-js": ">=1.44.1 <8.0.0", + "react": ">=16.8.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" } }, "node_modules/@stripe/stripe-js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-3.5.0.tgz", - "integrity": "sha512-pKS3wZnJoL1iTyGBXAvCwduNNeghJHY6QSRSNNvpYnrrQrLZ6Owsazjyynu0e0ObRgks0i7Rv+pe2M7/MBTZpQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-4.10.0.tgz", + "integrity": "sha512-KrMOL+sH69htCIXCaZ4JluJ35bchuCCznyPyrbN8JXSGQfwBI1SuIEMZNwvy8L8ykj29t6sa5BAAiL7fNoLZ8A==", "license": "MIT", "peer": true, "engines": { diff --git a/frontend/package.json b/frontend/package.json index 9fb4df08..a82e3776 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,8 +39,8 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", - "@stripe/react-stripe-js": "^2.7.3", - "@stripe/stripe-js": "^3.0.10", + "@stripe/react-stripe-js": "^3.0.0", + "@stripe/stripe-js": "^4.0.0", "@tanstack/react-query": "^5.12.0", "axios": "^1.6.2", "chart.js": "^4.5.0", diff --git a/frontend/src/api/client/apiClient.ts b/frontend/src/api/client/apiClient.ts index 9529ac15..822287a5 100644 --- a/frontend/src/api/client/apiClient.ts +++ b/frontend/src/api/client/apiClient.ts @@ -279,14 +279,29 @@ class ApiClient { try { // Dynamically import to avoid circular dependency const { useAuthStore } = await import('../../stores/auth.store'); + const { getSubscriptionFromJWT, getTenantAccessFromJWT, getPrimaryTenantIdFromJWT } = await import('../../utils/jwt'); const setState = useAuthStore.setState; - // Update the store with new tokens + // CRITICAL: Extract fresh subscription data from new JWT + const jwtSubscription = getSubscriptionFromJWT(accessToken); + const jwtTenantAccess = getTenantAccessFromJWT(accessToken); + const primaryTenantId = getPrimaryTenantIdFromJWT(accessToken); + + // Update the store with new tokens AND subscription data setState(state => ({ ...state, token: accessToken, refreshToken: refreshToken || state.refreshToken, + // IMPORTANT: Update subscription from fresh JWT + jwtSubscription, + jwtTenantAccess, + primaryTenantId, })); + + console.log('βœ… Auth store updated with new token and subscription:', jwtSubscription?.tier); + + // Broadcast change to all Zustand subscribers + console.log('πŸ“’ Zustand state updated - all useJWTSubscription() hooks will re-render'); } catch (error) { console.warn('Failed to update auth store:', error); } diff --git a/frontend/src/api/types/subscription.ts b/frontend/src/api/types/subscription.ts index c7a82bb6..a7cf9da1 100644 --- a/frontend/src/api/types/subscription.ts +++ b/frontend/src/api/types/subscription.ts @@ -262,6 +262,10 @@ export interface PlanUpgradeResult { message: string; new_plan: SubscriptionTier; effective_date: string; + old_plan?: string; + new_monthly_price?: number; + validation?: any; + requires_token_refresh?: boolean; // Backend signals that token should be refreshed } export interface SubscriptionInvoice { diff --git a/frontend/src/components/domain/auth/PaymentForm.tsx b/frontend/src/components/domain/auth/PaymentForm.tsx index eba86e5d..7c73a19e 100644 --- a/frontend/src/components/domain/auth/PaymentForm.tsx +++ b/frontend/src/components/domain/auth/PaymentForm.tsx @@ -1,11 +1,11 @@ import React, { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Card, Input, Button } from '../../ui'; -import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; +import { PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js'; import { AlertCircle, CheckCircle, CreditCard, Lock } from 'lucide-react'; interface PaymentFormProps { - onPaymentSuccess: () => void; + onPaymentSuccess: (paymentMethodId?: string) => void; onPaymentError: (error: string) => void; className?: string; bypassPayment?: boolean; @@ -50,7 +50,7 @@ const PaymentForm: React.FC = ({ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + if (!stripe || !elements) { // Stripe.js has not loaded yet onPaymentError('Stripe.js no ha cargado correctamente'); @@ -67,29 +67,41 @@ const PaymentForm: React.FC = ({ setError(null); try { - // Create payment method - const { error, paymentMethod } = await stripe.createPaymentMethod({ - type: 'card', - card: elements.getElement('card')!, - billing_details: { - name: billingDetails.name, - email: billingDetails.email, - address: billingDetails.address, - }, - }); + // Submit the payment element to validate all inputs + const { error: submitError } = await elements.submit(); - if (error) { - setError(error.message || 'Error al procesar el pago'); - onPaymentError(error.message || 'Error al procesar el pago'); + if (submitError) { + setError(submitError.message || 'Error al validar el formulario'); + onPaymentError(submitError.message || 'Error al validar el formulario'); setLoading(false); return; } - // In a real application, you would send the paymentMethod.id to your server - // to create a subscription. For now, we'll simulate success. + // Create payment method using the PaymentElement + // This is the correct way to create a payment method with PaymentElement + const { error: paymentError, paymentMethod } = await stripe.createPaymentMethod({ + elements, + params: { + billing_details: { + name: billingDetails.name, + email: billingDetails.email, + address: billingDetails.address, + }, + }, + }); + + if (paymentError) { + setError(paymentError.message || 'Error al procesar el pago'); + onPaymentError(paymentError.message || 'Error al procesar el pago'); + setLoading(false); + return; + } + + // Send the paymentMethod.id to your server to create a subscription console.log('Payment method created:', paymentMethod); - - onPaymentSuccess(); + + // Pass the payment method ID to the parent component for server-side processing + onPaymentSuccess(paymentMethod?.id); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Error desconocido al procesar el pago'; setError(errorMessage); @@ -99,7 +111,7 @@ const PaymentForm: React.FC = ({ } }; - const handleCardChange = (event: any) => { + const handlePaymentElementChange = (event: any) => { setError(event.error?.message || null); setCardComplete(event.complete); }; @@ -208,33 +220,29 @@ const PaymentForm: React.FC = ({ - {/* Card Element */} + {/* Payment Element */}
-

- {t('auth:payment.card_info_secure', 'Tu informaciΓ³n de tarjeta estΓ‘ segura')} + {t('auth:payment.payment_info_secure', 'Tu informaciΓ³n de pago estΓ‘ segura')}

@@ -275,7 +283,7 @@ const PaymentForm: React.FC = ({