diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index 7eb8274..0c295b8 100644 Binary files a/__pycache__/server.cpython-312.pyc and b/__pycache__/server.cpython-312.pyc differ diff --git a/server.py b/server.py index 71530b4..2aaf852 100644 --- a/server.py +++ b/server.py @@ -8395,6 +8395,29 @@ def set_setting( db.commit() +@api_router.get("/config/stripe") +async def get_stripe_public_config(db: Session = Depends(get_db)): + """ + Get Stripe publishable key for frontend (public endpoint). + + This endpoint provides the publishable key needed for Stripe.js + to initialize payment forms. No authentication required since + publishable keys are meant to be public. + """ + publishable_key = get_setting(db, 'stripe_publishable_key', decrypt=False) + + if not publishable_key: + raise HTTPException( + status_code=503, + detail="Stripe is not configured. Please contact the administrator." + ) + + return { + "publishable_key": publishable_key, + "environment": "test" if publishable_key.startswith('pk_test_') else "live" + } + + @api_router.get("/admin/settings/stripe/status") async def get_stripe_status( current_user: User = Depends(get_current_superadmin), @@ -8405,6 +8428,7 @@ async def get_stripe_status( Returns: - configured: Whether credentials exist in database + - publishable_key_prefix: First 12 chars of publishable key - secret_key_prefix: First 10 chars of secret key (for verification) - webhook_configured: Whether webhook secret exists - environment: test or live (based on key prefix) @@ -8413,10 +8437,11 @@ async def get_stripe_status( import os # Read from database + publishable_key = get_setting(db, 'stripe_publishable_key', decrypt=False) secret_key = get_setting(db, 'stripe_secret_key', decrypt=True) webhook_secret = get_setting(db, 'stripe_webhook_secret', decrypt=True) - configured = bool(secret_key) + configured = bool(secret_key) and bool(publishable_key) environment = 'unknown' if secret_key: @@ -8436,6 +8461,8 @@ async def get_stripe_status( return { "configured": configured, + "publishable_key_prefix": publishable_key[:12] if publishable_key else None, + "publishable_key_set": bool(publishable_key), "secret_key_prefix": secret_key[:10] if secret_key else None, "secret_key_set": bool(secret_key), "webhook_secret_set": bool(webhook_secret), @@ -8444,6 +8471,7 @@ async def get_stripe_status( "instructions": { "location": "Database (system_settings table)", "required_settings": [ + "stripe_publishable_key (pk_test_... or pk_live_...)", "stripe_secret_key (sk_test_... or sk_live_...)", "stripe_webhook_secret (whsec_...)" ], @@ -8501,6 +8529,7 @@ async def test_stripe_connection( class UpdateStripeSettingsRequest(BaseModel): """Request model for updating Stripe settings""" + publishable_key: str = Field(..., min_length=1, description="Stripe publishable key (pk_test_... or pk_live_...)") secret_key: str = Field(..., min_length=1, description="Stripe secret key (sk_test_... or sk_live_...)") webhook_secret: str = Field(..., min_length=1, description="Stripe webhook secret (whsec_...)") @@ -8517,6 +8546,13 @@ async def update_stripe_settings( Stores Stripe credentials encrypted in the database. Changes take effect immediately without server restart. """ + # Validate publishable key format + if not (request.publishable_key.startswith('pk_test_') or request.publishable_key.startswith('pk_live_')): + raise HTTPException( + status_code=400, + detail="Invalid Stripe publishable key format. Must start with 'pk_test_' or 'pk_live_'" + ) + # Validate secret key format if not (request.secret_key.startswith('sk_test_') or request.secret_key.startswith('sk_live_')): raise HTTPException( @@ -8531,7 +8567,27 @@ async def update_stripe_settings( detail="Invalid Stripe webhook secret format. Must start with 'whsec_'" ) + # Validate key environment consistency (publishable and secret should match) + pk_is_live = request.publishable_key.startswith('pk_live_') + sk_is_live = request.secret_key.startswith('sk_live_') + if pk_is_live != sk_is_live: + raise HTTPException( + status_code=400, + detail="Publishable key and Secret key must be from the same environment (both test or both live)" + ) + try: + # Store publishable key (NOT encrypted - it's meant to be public) + set_setting( + db=db, + key='stripe_publishable_key', + value=request.publishable_key, + user_id=str(current_user.id), + description='Stripe publishable key for frontend payment forms', + is_sensitive=False, + encrypt=False + ) + # Store secret key (encrypted) set_setting( db=db,