Merge to LOAF-PROD for Demo #27

Merged
andika merged 10 commits from dev into loaf-prod 2026-02-02 11:11:36 +00:00
2 changed files with 57 additions and 1 deletions
Showing only changes of commit e7f6e9c20a - Show all commits

Binary file not shown.

View File

@@ -8395,6 +8395,29 @@ def set_setting(
db.commit() 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") @api_router.get("/admin/settings/stripe/status")
async def get_stripe_status( async def get_stripe_status(
current_user: User = Depends(get_current_superadmin), current_user: User = Depends(get_current_superadmin),
@@ -8405,6 +8428,7 @@ async def get_stripe_status(
Returns: Returns:
- configured: Whether credentials exist in database - 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) - secret_key_prefix: First 10 chars of secret key (for verification)
- webhook_configured: Whether webhook secret exists - webhook_configured: Whether webhook secret exists
- environment: test or live (based on key prefix) - environment: test or live (based on key prefix)
@@ -8413,10 +8437,11 @@ async def get_stripe_status(
import os import os
# Read from database # Read from database
publishable_key = get_setting(db, 'stripe_publishable_key', decrypt=False)
secret_key = get_setting(db, 'stripe_secret_key', decrypt=True) secret_key = get_setting(db, 'stripe_secret_key', decrypt=True)
webhook_secret = get_setting(db, 'stripe_webhook_secret', 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' environment = 'unknown'
if secret_key: if secret_key:
@@ -8436,6 +8461,8 @@ async def get_stripe_status(
return { return {
"configured": configured, "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_prefix": secret_key[:10] if secret_key else None,
"secret_key_set": bool(secret_key), "secret_key_set": bool(secret_key),
"webhook_secret_set": bool(webhook_secret), "webhook_secret_set": bool(webhook_secret),
@@ -8444,6 +8471,7 @@ async def get_stripe_status(
"instructions": { "instructions": {
"location": "Database (system_settings table)", "location": "Database (system_settings table)",
"required_settings": [ "required_settings": [
"stripe_publishable_key (pk_test_... or pk_live_...)",
"stripe_secret_key (sk_test_... or sk_live_...)", "stripe_secret_key (sk_test_... or sk_live_...)",
"stripe_webhook_secret (whsec_...)" "stripe_webhook_secret (whsec_...)"
], ],
@@ -8501,6 +8529,7 @@ async def test_stripe_connection(
class UpdateStripeSettingsRequest(BaseModel): class UpdateStripeSettingsRequest(BaseModel):
"""Request model for updating Stripe settings""" """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_...)") 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_...)") 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. Stores Stripe credentials encrypted in the database.
Changes take effect immediately without server restart. 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 # Validate secret key format
if not (request.secret_key.startswith('sk_test_') or request.secret_key.startswith('sk_live_')): if not (request.secret_key.startswith('sk_test_') or request.secret_key.startswith('sk_live_')):
raise HTTPException( raise HTTPException(
@@ -8531,7 +8567,27 @@ async def update_stripe_settings(
detail="Invalid Stripe webhook secret format. Must start with 'whsec_'" 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: 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) # Store secret key (encrypted)
set_setting( set_setting(
db=db, db=db,