Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -19,7 +19,7 @@ from elevenlabs import ElevenLabs
|
|
| 19 |
# Import and configure Google GenAI, matching the Streamlit app
|
| 20 |
from google import genai
|
| 21 |
from google.genai import types
|
| 22 |
-
|
| 23 |
# -----------------------------------------------------------------------------
|
| 24 |
# 1. CONFIGURATION & INITIALIZATION
|
| 25 |
# -----------------------------------------------------------------------------
|
|
@@ -74,7 +74,29 @@ GENERATION_MODEL = "gemini-2.0-flash-exp-image-generation"
|
|
| 74 |
#GENERATION_MODEL = "gemini-2.5-flash-image-preview"
|
| 75 |
#TTS_MODEL = "gemini-2.5-flash-preview-tts"
|
| 76 |
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
# -----------------------------------------------------------------------------
|
| 79 |
# 2. HELPER FUNCTIONS (Adapted directly from Streamlit App & Template)
|
| 80 |
# -----------------------------------------------------------------------------
|
|
@@ -233,6 +255,61 @@ import logging
|
|
| 233 |
logging.basicConfig(level=logging.INFO)
|
| 234 |
logger = logging.getLogger(__name__)
|
| 235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
# =============================================================================
|
| 237 |
# OPEN IMAGE PROXY ENDPOINT (NO AUTHENTICATION)
|
| 238 |
# =============================================================================
|
|
@@ -1293,8 +1370,109 @@ def log_call_usage(project_id):
|
|
| 1293 |
logger.error(f"[LOGGING] A database error occurred for user '{uid}': {e}")
|
| 1294 |
return jsonify({'error': 'A server error occurred while updating credits.'}), 500
|
| 1295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1296 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1297 |
|
|
|
|
|
|
|
| 1298 |
# -----------------------------------------------------------------------------
|
| 1299 |
# 7. MAIN EXECUTION
|
| 1300 |
# -----------------------------------------------------------------------------
|
|
|
|
| 19 |
# Import and configure Google GenAI, matching the Streamlit app
|
| 20 |
from google import genai
|
| 21 |
from google.genai import types
|
| 22 |
+
import stripe
|
| 23 |
# -----------------------------------------------------------------------------
|
| 24 |
# 1. CONFIGURATION & INITIALIZATION
|
| 25 |
# -----------------------------------------------------------------------------
|
|
|
|
| 74 |
#GENERATION_MODEL = "gemini-2.5-flash-image-preview"
|
| 75 |
#TTS_MODEL = "gemini-2.5-flash-preview-tts"
|
| 76 |
|
| 77 |
+
# Stripe
|
| 78 |
+
# --- Stripe Initialization ---
|
| 79 |
+
STRIPE_SECRET_KEY = os.environ.get("STRIPE_SECRET_KEY")
|
| 80 |
+
STRIPE_WEBHOOK_SECRET = os.environ.get("STRIPE_WEBHOOK_SECRET")
|
| 81 |
+
|
| 82 |
+
# Price IDs from Stripe dashboard (price_xxx...) – set these in your env
|
| 83 |
+
STRIPE_PRICE_FIXER = os.environ.get("STRIPE_PRICE_FIXER") # "The Fixer" (standard)
|
| 84 |
+
STRIPE_PRICE_PRO = os.environ.get("STRIPE_PRICE_PRO") # "The Pro" (premium)
|
| 85 |
+
|
| 86 |
+
# Frontend URLs for redirect after Checkout
|
| 87 |
+
STRIPE_SUCCESS_URL = os.environ.get(
|
| 88 |
+
"STRIPE_SUCCESS_URL",
|
| 89 |
+
"https://sozofix.tech/billing/success"
|
| 90 |
+
)
|
| 91 |
+
STRIPE_CANCEL_URL = os.environ.get(
|
| 92 |
+
"STRIPE_CANCEL_URL",
|
| 93 |
+
"https://sozofix.tech/billing/cancel"
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
if STRIPE_SECRET_KEY:
|
| 97 |
+
stripe.api_key = STRIPE_SECRET_KEY
|
| 98 |
+
else:
|
| 99 |
+
print("WARNING: STRIPE_SECRET_KEY is not set – Stripe endpoints will fail.")
|
| 100 |
# -----------------------------------------------------------------------------
|
| 101 |
# 2. HELPER FUNCTIONS (Adapted directly from Streamlit App & Template)
|
| 102 |
# -----------------------------------------------------------------------------
|
|
|
|
| 255 |
logging.basicConfig(level=logging.INFO)
|
| 256 |
logger = logging.getLogger(__name__)
|
| 257 |
|
| 258 |
+
## Stripe
|
| 259 |
+
PLAN_CONFIG = {
|
| 260 |
+
# The Fixer – standard
|
| 261 |
+
"standard": {"price_id": STRIPE_PRICE_FIXER, "credits": 200},
|
| 262 |
+
"fixer": {"price_id": STRIPE_PRICE_FIXER, "credits": 200},
|
| 263 |
+
|
| 264 |
+
# The Pro – premium
|
| 265 |
+
"premium": {"price_id": STRIPE_PRICE_PRO, "credits": 500},
|
| 266 |
+
"pro": {"price_id": STRIPE_PRICE_PRO, "credits": 500},
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
def get_or_create_stripe_customer(uid: str) -> str:
|
| 270 |
+
"""
|
| 271 |
+
Ensure the Firebase user has a Stripe customer.
|
| 272 |
+
Stores the customer id on /users/{uid}/stripeCustomerId.
|
| 273 |
+
"""
|
| 274 |
+
user_ref = db_ref.child(f'users/{uid}')
|
| 275 |
+
user_data = user_ref.get() or {}
|
| 276 |
+
|
| 277 |
+
existing_id = user_data.get("stripeCustomerId")
|
| 278 |
+
if existing_id:
|
| 279 |
+
return existing_id
|
| 280 |
+
|
| 281 |
+
email = user_data.get("email")
|
| 282 |
+
customer = stripe.Customer.create(
|
| 283 |
+
email=email,
|
| 284 |
+
metadata={"firebase_uid": uid}
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
user_ref.update({"stripeCustomerId": customer.id})
|
| 288 |
+
return customer.id
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def apply_plan_credits(uid: str, plan_key: str):
|
| 292 |
+
"""
|
| 293 |
+
Adds the correct number of credits for a plan
|
| 294 |
+
(200 for standard, 500 for premium).
|
| 295 |
+
"""
|
| 296 |
+
plan_cfg = PLAN_CONFIG.get(plan_key)
|
| 297 |
+
if not plan_cfg:
|
| 298 |
+
logger.error(f"[STRIPE] Unknown plan '{plan_key}' for user {uid}")
|
| 299 |
+
return
|
| 300 |
+
|
| 301 |
+
credits_to_add = plan_cfg["credits"]
|
| 302 |
+
user_ref = db_ref.child(f'users/{uid}')
|
| 303 |
+
user_data = user_ref.get() or {}
|
| 304 |
+
current = user_data.get("credits", 0)
|
| 305 |
+
|
| 306 |
+
new_total = current + credits_to_add
|
| 307 |
+
user_ref.update({"credits": new_total})
|
| 308 |
+
logger.info(
|
| 309 |
+
f"[STRIPE] Applied {credits_to_add} credits to user {uid} "
|
| 310 |
+
f"for plan '{plan_key}'. New total: {new_total}"
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
# =============================================================================
|
| 314 |
# OPEN IMAGE PROXY ENDPOINT (NO AUTHENTICATION)
|
| 315 |
# =============================================================================
|
|
|
|
| 1370 |
logger.error(f"[LOGGING] A database error occurred for user '{uid}': {e}")
|
| 1371 |
return jsonify({'error': 'A server error occurred while updating credits.'}), 500
|
| 1372 |
|
| 1373 |
+
#Stripe Payments
|
| 1374 |
+
@app.route("/api/billing/create-checkout-session", methods=["POST"])
|
| 1375 |
+
def create_checkout_session():
|
| 1376 |
+
"""
|
| 1377 |
+
Creates a Stripe Checkout Session for a recurring subscription.
|
| 1378 |
+
Plans:
|
| 1379 |
+
- 'standard' / 'fixer' → The Fixer (200 credits / month)
|
| 1380 |
+
- 'premium' / 'pro' → The Pro (500 credits / month)
|
| 1381 |
+
"""
|
| 1382 |
+
uid = verify_token(request.headers.get("Authorization"))
|
| 1383 |
+
if not uid:
|
| 1384 |
+
return jsonify({"error": "Unauthorized"}), 401
|
| 1385 |
|
| 1386 |
+
if not STRIPE_SECRET_KEY or not STRIPE_PRICE_FIXER or not STRIPE_PRICE_PRO:
|
| 1387 |
+
logger.error("[STRIPE] Missing Stripe configuration.")
|
| 1388 |
+
return jsonify({"error": "Stripe is not configured on the server."}), 500
|
| 1389 |
+
|
| 1390 |
+
data = request.get_json() or {}
|
| 1391 |
+
plan = (data.get("plan") or "").lower().strip()
|
| 1392 |
+
|
| 1393 |
+
plan_cfg = PLAN_CONFIG.get(plan)
|
| 1394 |
+
if not plan_cfg or not plan_cfg["price_id"]:
|
| 1395 |
+
return jsonify({"error": "Invalid plan selected."}), 400
|
| 1396 |
+
|
| 1397 |
+
try:
|
| 1398 |
+
customer_id = get_or_create_stripe_customer(uid)
|
| 1399 |
+
|
| 1400 |
+
session = stripe.checkout.Session.create(
|
| 1401 |
+
mode="subscription",
|
| 1402 |
+
customer=customer_id,
|
| 1403 |
+
payment_method_types=["card"],
|
| 1404 |
+
line_items=[{
|
| 1405 |
+
"price": plan_cfg["price_id"],
|
| 1406 |
+
"quantity": 1,
|
| 1407 |
+
}],
|
| 1408 |
+
success_url=STRIPE_SUCCESS_URL + "?session_id={CHECKOUT_SESSION_ID}",
|
| 1409 |
+
cancel_url=STRIPE_CANCEL_URL,
|
| 1410 |
+
# So we can find the user/plan again from webhooks
|
| 1411 |
+
metadata={
|
| 1412 |
+
"firebase_uid": uid,
|
| 1413 |
+
"plan": plan,
|
| 1414 |
+
},
|
| 1415 |
+
subscription_data={
|
| 1416 |
+
"metadata": {
|
| 1417 |
+
"firebase_uid": uid,
|
| 1418 |
+
"plan": plan,
|
| 1419 |
+
}
|
| 1420 |
+
},
|
| 1421 |
+
)
|
| 1422 |
+
|
| 1423 |
+
logger.info(f"[STRIPE] Created checkout session {session.id} for user {uid}, plan {plan}")
|
| 1424 |
+
return jsonify({
|
| 1425 |
+
"id": session.id,
|
| 1426 |
+
"url": session.url,
|
| 1427 |
+
})
|
| 1428 |
+
|
| 1429 |
+
except Exception as e:
|
| 1430 |
+
logger.error(f"[STRIPE] Error creating checkout session: {e}")
|
| 1431 |
+
return jsonify({"error": "Failed to create checkout session."}), 500
|
| 1432 |
+
|
| 1433 |
+
@app.route("/api/billing/webhook", methods=["POST"])
|
| 1434 |
+
def stripe_webhook():
|
| 1435 |
+
if not STRIPE_WEBHOOK_SECRET:
|
| 1436 |
+
logger.error("[STRIPE] STRIPE_WEBHOOK_SECRET not set.")
|
| 1437 |
+
return jsonify({"error": "Webhook secret not configured."}), 500
|
| 1438 |
+
|
| 1439 |
+
payload = request.data
|
| 1440 |
+
sig_header = request.headers.get("Stripe-Signature")
|
| 1441 |
+
|
| 1442 |
+
try:
|
| 1443 |
+
event = stripe.Webhook.construct_event(
|
| 1444 |
+
payload, sig_header, STRIPE_WEBHOOK_SECRET
|
| 1445 |
+
)
|
| 1446 |
+
except stripe.error.SignatureVerificationError as e:
|
| 1447 |
+
logger.error(f"[STRIPE] Webhook signature verification failed: {e}")
|
| 1448 |
+
return "Invalid signature", 400
|
| 1449 |
+
except Exception as e:
|
| 1450 |
+
logger.error(f"[STRIPE] Webhook parsing error: {e}")
|
| 1451 |
+
return "Bad request", 400
|
| 1452 |
+
|
| 1453 |
+
event_type = event.get("type")
|
| 1454 |
+
obj = event.get("data", {}).get("object", {})
|
| 1455 |
+
|
| 1456 |
+
# We only care about successful subscription payments
|
| 1457 |
+
if event_type == "invoice.payment_succeeded":
|
| 1458 |
+
try:
|
| 1459 |
+
subscription_id = obj.get("subscription")
|
| 1460 |
+
if not subscription_id:
|
| 1461 |
+
return "", 200
|
| 1462 |
+
|
| 1463 |
+
sub = stripe.Subscription.retrieve(subscription_id)
|
| 1464 |
+
metadata = sub.get("metadata", {}) or {}
|
| 1465 |
+
uid = metadata.get("firebase_uid")
|
| 1466 |
+
plan = metadata.get("plan")
|
| 1467 |
+
|
| 1468 |
+
if uid and plan:
|
| 1469 |
+
logger.info(f"[STRIPE] invoice.payment_succeeded for user {uid}, plan {plan}")
|
| 1470 |
+
apply_plan_credits(uid, plan)
|
| 1471 |
+
except Exception as e:
|
| 1472 |
+
logger.error(f"[STRIPE] Error handling invoice.payment_succeeded: {e}")
|
| 1473 |
|
| 1474 |
+
# You can log other event types here if needed
|
| 1475 |
+
return "", 200
|
| 1476 |
# -----------------------------------------------------------------------------
|
| 1477 |
# 7. MAIN EXECUTION
|
| 1478 |
# -----------------------------------------------------------------------------
|