Skip to content

Commit 1b9b151

Browse files
committed
fix:concurrency issue
1 parent a3db2ca commit 1b9b151

1 file changed

Lines changed: 35 additions & 32 deletions

File tree

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

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@
1515
from paddle_billing.Resources.Prices.Operations import CreatePrice
1616
from paddle_billing.Resources.Products.Operations import CreateProduct
1717

18-
# -------------------------------------------------------------------
19-
# Plan definitions
20-
# -------------------------------------------------------------------
2118

2219
@dataclass(frozen=True)
2320
class PlanDefinition:
2421
key: str
2522
name: str
26-
monthly_price_usd: str # MUST be string for Paddle Money.amount
23+
monthly_price_usd: str
2724
trial_days: int | None = None
2825

2926

@@ -43,22 +40,16 @@ class PlanDefinition:
4340
)
4441

4542

46-
# -------------------------------------------------------------------
47-
# Public entrypoint
48-
# -------------------------------------------------------------------
49-
5043
def provision_paddle_plans() -> None:
51-
"""Idempotently provision Paddle products (plans) and monthly subscription prices.
52-
53-
Rules:
54-
- One Product per plan (Starter, Pro)
55-
- One monthly subscription Price per Product
56-
- Starter has a trial; Pro does not
57-
- SDK-only (no raw HTTP)
58-
"""
44+
"""Idempotently provision Paddle products (plans) and monthly subscription prices."""
5945
from langflow.services.paddle.client import get_paddle_client
46+
6047
client = get_paddle_client()
6148

49+
if not _acquire_provisioning_lock(client):
50+
logger.info("Paddle provisioning already in progress or completed; skipping.")
51+
return
52+
6253
products = list(client.products.list())
6354
prices = list(client.prices.list())
6455

@@ -74,10 +65,6 @@ def provision_paddle_plans() -> None:
7465
logger.info("Paddle plan %s provisioned.", plan.key)
7566

7667

77-
# -------------------------------------------------------------------
78-
# Find helpers
79-
# -------------------------------------------------------------------
80-
8168
def _find_existing_product(plan: PlanDefinition, products: list[Any]) -> Any | None:
8269
for product in products:
8370
if _custom_data_value(product.custom_data, "plan_key") == plan.key:
@@ -87,14 +74,17 @@ def _find_existing_product(plan: PlanDefinition, products: list[Any]) -> Any | N
8774
return None
8875

8976

90-
def _find_existing_price(plan: PlanDefinition, prices: list[Any], product: Any | None) -> Any | None:
77+
def _find_existing_price(
78+
plan: PlanDefinition, prices: list[Any], product: Any | None
79+
) -> Any | None:
9180
for price in prices:
9281
if _custom_data_value(price.custom_data, "plan_key") == plan.key:
9382
return price
9483
if product and _price_matches_plan(plan, price, product.id):
9584
return price
9685
return None
9786

87+
9888
def _price_matches_plan(plan: PlanDefinition, price: Any, product_id: str) -> bool:
9989
if getattr(price, "product_id", None) != product_id:
10090
return False
@@ -121,22 +111,35 @@ def _duration_matches(duration: Any, interval: Interval, frequency: int) -> bool
121111
and getattr(duration, "frequency", None) == frequency
122112
)
123113

124-
def _custom_data_value(custom_data: Any, key: str) -> Any | None:
125-
"""Paddle CustomData is NOT a dict.
126114

127-
Safe accessor that works across SDK versions.
128-
"""
115+
def _custom_data_value(custom_data: Any, key: str) -> Any | None:
129116
if not custom_data:
130117
return None
131118
try:
132119
return custom_data[key]
133-
except Exception: #noqa: BLE001
120+
except Exception: # noqa: BLE001
134121
return None
135122

136123

137-
# -------------------------------------------------------------------
138-
# Create helpers
139-
# -------------------------------------------------------------------
124+
def _acquire_provisioning_lock(client: Any) -> bool:
125+
LOCK_KEY = "paddle_provisioning_lock"
126+
127+
for product in client.products.list():
128+
if _custom_data_value(product.custom_data, "lock") == LOCK_KEY:
129+
return False
130+
131+
try:
132+
client.products.create(
133+
CreateProduct(
134+
name="Paddle Provisioning Lock",
135+
tax_category=TaxCategory.Standard,
136+
custom_data={"lock": LOCK_KEY},
137+
)
138+
)
139+
return True
140+
except Exception:
141+
return False
142+
140143

141144
def _create_product(plan: PlanDefinition, client: Any) -> str:
142145
logger.info("Creating Paddle product for plan %s.", plan.key)
@@ -166,11 +169,11 @@ def _create_monthly_price(plan: PlanDefinition, product_id: str, client: Any) ->
166169
CreatePrice(
167170
product_id=product_id,
168171
description=f"{plan.name} Monthly",
169-
billing_cycle=billing_cycle, # subscription
170-
trial_period=trial_period, # Starter only
172+
billing_cycle=billing_cycle,
173+
trial_period=trial_period,
171174
tax_mode=TaxMode.External,
172175
unit_price=Money(
173-
amount=plan.monthly_price_usd, # MUST be string
176+
amount=plan.monthly_price_usd,
174177
currency_code=CurrencyCode.USD,
175178
),
176179
custom_data={

0 commit comments

Comments
 (0)