Skip to content

Commit 40438b4

Browse files
committed
Refactor Paddle customer handling to improve error extraction and logging for existing customers
1 parent 8367ac2 commit 40438b4

2 files changed

Lines changed: 82 additions & 42 deletions

File tree

src/backend/base/langflow/services/auth/clerk_utils.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -357,52 +357,51 @@ async def clerk_token_middleware(request: Request, call_next):
357357
return await call_next(request)
358358

359359
ctx_token: Token | None = None
360-
response = None
361360

362361
try:
363362
# 1️⃣ Clerk JWT
364363
auth_header = request.headers.get("Authorization")
365364
if auth_header and auth_header.startswith("Bearer "):
366365
token = auth_header[len("Bearer ") :]
366+
367367
try:
368368
payload = await verify_clerk_token(token)
369-
ctx_token = auth_header_ctx.set(payload)
370-
response = await call_next(request)
371369
except Exception as exc: # noqa: BLE001
372-
373370
logger.warning(f"[ClerkMiddleware] Failed to verify Clerk token: {exc}")
374-
response = JSONResponse(
371+
return JSONResponse(
375372
status_code=HTTP_401_UNAUTHORIZED,
376373
content={"detail": "Invalid Clerk token"},
377374
)
378375

379-
# 2️⃣ API Key
380-
elif (api_key_header := request.headers.get("x-api-key")):
376+
ctx_token = auth_header_ctx.set(payload)
377+
return await call_next(request)
381378

379+
# 2️⃣ API Key
380+
api_key_header = request.headers.get("x-api-key")
381+
if api_key_header:
382382
from langflow.services.auth.api_key_codec import decode_api_key
383383

384384
decoded = decode_api_key(api_key_header)
385385
if not decoded.is_encoded or not decoded.organization_id:
386-
logger.warning(f"[ClerkMiddleware] Invalid or unscoped API key: {api_key_header}")
387-
response = JSONResponse(
386+
logger.warning("[ClerkMiddleware] Invalid or unscoped API key")
387+
return JSONResponse(
388388
status_code=HTTP_401_UNAUTHORIZED,
389389
content={"detail": "Invalid or unscoped API key"},
390390
)
391-
else:
392-
context_payload = {
391+
392+
ctx_token = auth_header_ctx.set(
393+
{
393394
ORG_ID_KEY: decoded.organization_id,
394395
CLERK_JWT_UUID_KEY: decoded.user_id,
395396
}
396-
ctx_token = auth_header_ctx.set(context_payload)
397-
response = await call_next(request)
397+
)
398+
return await call_next(request)
398399

399-
# 3️⃣ No auth header → pass through
400-
else:
401-
response = await call_next(request)
400+
# 3️⃣ No auth → pass through
401+
return await call_next(request)
402402

403403
finally:
404404
if ctx_token is not None:
405405
auth_header_ctx.reset(ctx_token)
406406
else:
407407
auth_header_ctx.set(None)
408-
return response

src/backend/base/langflow/services/paddle/subscriptions.py

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import asyncio
66
from dataclasses import dataclass
77
from datetime import datetime
8+
import re
89
from typing import Any, Literal
910

1011
from lfx.log.logger import logger
@@ -28,6 +29,8 @@
2829
)
2930
from langflow.services.paddle.client import get_paddle_client
3031

32+
_PADDLE_CUSTOMER_ID_RE = re.compile(r"(ctm_[a-z0-9]+)", re.IGNORECASE)
33+
3134

3235
async def ensure_paddle_customer_for_user(
3336
*,
@@ -56,6 +59,11 @@ async def _get_paddle_customer_id() -> str | None:
5659
return await get_paddle_customer_id_from_clerk_payload()
5760

5861

62+
def _extract_customer_id_from_error(exc: Exception) -> str | None:
63+
match = _PADDLE_CUSTOMER_ID_RE.search(str(exc))
64+
return match.group(1) if match else None
65+
66+
5967
async def _create_paddle_customer_and_update_clerk_metadata(
6068
client: Client,
6169
clerk_user_id: str,
@@ -76,35 +84,55 @@ async def _create_paddle_customer_and_update_clerk_metadata(
7684
PADDLE_CUSTOM_DATA_USER_ID_KEY: str(clerk_user_id),
7785
}
7886
),
87+
),
7988
)
80-
)
8189
logger.info(f"Paddle customer creation response: {customer}")
8290
except Exception as exc: # noqa: BLE001
83-
if not _is_customer_already_exists_error(exc):
84-
raise
85-
logger.info(
86-
"Paddle customer already exists for email %s; resolving existing customer.",
87-
email,
88-
)
89-
existing_customer = await _find_existing_paddle_customer(
90-
client=client,
91-
email=email,
92-
clerk_user_id=clerk_user_id,
93-
)
94-
if existing_customer is None:
95-
logger.exception("Unable to resolve existing Paddle customer for email %s.", email)
96-
raise
91+
customer_id = _extract_customer_id_from_error(exc)
92+
93+
if not customer_id:
94+
if not _is_customer_already_exists_error(exc):
95+
raise
96+
97+
logger.info(
98+
"Paddle customer already exists for email %s; resolving existing customer.",
99+
email,
100+
)
101+
customer = await _find_existing_paddle_customer(
102+
client=client,
103+
email=email,
104+
clerk_user_id=clerk_user_id,
105+
)
106+
if customer is None:
107+
logger.exception("Unable to resolve existing Paddle customer for email %s.", email)
108+
raise
109+
else:
110+
logger.info(
111+
"Paddle reports existing customer %s for email %s",
112+
customer_id,
113+
email,
114+
)
115+
try:
116+
customer = await asyncio.to_thread(client.customers.get, customer_id)
117+
except Exception as exc: # noqa: BLE001
118+
logger.exception(
119+
"Failed to retrieve existing Paddle customer with ID %s for clerk user %s.",
120+
customer_id,
121+
clerk_user_id,
122+
)
123+
raise
124+
97125
await _sync_paddle_customer_metadata(
98126
client=client,
99-
customer=existing_customer,
127+
customer=customer,
100128
clerk_user_id=clerk_user_id,
101129
)
102-
paddle_customer_id = existing_customer.id
130+
103131
await update_clerk_public_metadata(
104132
clerk_user_id=clerk_user_id,
105-
public_metadata={PADDLE_CUSTOMER_ID_KEY: paddle_customer_id},
133+
public_metadata={PADDLE_CUSTOMER_ID_KEY: customer.id},
106134
)
107-
return paddle_customer_id
135+
return customer.id
108136

109137
paddle_customer_id = customer.id
110138

@@ -119,7 +147,12 @@ async def _create_paddle_customer_and_update_clerk_metadata(
119147

120148
def _is_customer_already_exists_error(exc: Exception) -> bool:
121149
message = str(exc).lower()
122-
return "customer already exists" in message or ("already exists" in message and "customer" in message)
150+
return (
151+
"customer already exists" in message
152+
or ("already exists" in message and "customer" in message)
153+
or ("customer email conflicts" in message)
154+
or ("email conflicts" in message and "customer" in message)
155+
)
123156

124157

125158
async def _find_existing_paddle_customer(
@@ -159,8 +192,16 @@ async def _sync_paddle_customer_metadata(
159192

160193
updated_custom_data = dict(existing_custom_data)
161194
updated_custom_data[PADDLE_CUSTOM_DATA_USER_ID_KEY] = str(clerk_user_id)
162-
await asyncio.to_thread(
163-
client.customers.update,
164-
customer.id,
165-
UpdateCustomer(custom_data=CustomData(updated_custom_data)),
166-
)
195+
try:
196+
await asyncio.to_thread(
197+
client.customers.update,
198+
customer.id,
199+
UpdateCustomer(custom_data=CustomData(updated_custom_data)),
200+
)
201+
except Exception as exc: # noqa: BLE001
202+
logger.exception(
203+
"Failed to sync Paddle customer metadata for customer %s and clerk user %s.",
204+
customer.id,
205+
clerk_user_id,
206+
)
207+
raise

0 commit comments

Comments
 (0)