summaryrefslogtreecommitdiff
path: root/Biz/PodcastItLater
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-12 16:41:03 -0500
committerBen Sima <ben@bsima.me>2025-11-12 16:41:03 -0500
commit5efd438acf7afe9fbe2d621542bcb74688a4d647 (patch)
tree896ee8e271957ffea7d02eb7a05f77d3580955f3 /Biz/PodcastItLater
parent54aff58650d9ade97cabf4d154dea114a6a8a313 (diff)
Fix Stripe webhook KeyError by using safe dict access
- Replace direct dict key access with .get() for all subscription fields - Add validation and early return for missing required fields - Add logging warnings when subscription data is incomplete - Handles both dict and Stripe object access patterns safely - Fixes KeyError: 'current_period_start' when processing webhooks
Diffstat (limited to 'Biz/PodcastItLater')
-rw-r--r--Biz/PodcastItLater/Billing.py47
1 files changed, 34 insertions, 13 deletions
diff --git a/Biz/PodcastItLater/Billing.py b/Biz/PodcastItLater/Billing.py
index 7daa3dc..9c0e1db 100644
--- a/Biz/PodcastItLater/Billing.py
+++ b/Biz/PodcastItLater/Billing.py
@@ -336,23 +336,44 @@ def _handle_payment_failed(invoice: dict[str, typing.Any]) -> None:
def _update_subscription_state(subscription: dict[str, typing.Any]) -> None:
"""Update user subscription state from Stripe subscription object."""
- customer_id = subscription["customer"]
- subscription_id = subscription["id"]
- status = subscription["status"]
+ customer_id = subscription.get("customer")
+ subscription_id = subscription.get("id")
+ status = subscription.get("status")
cancel_at_period_end = subscription.get("cancel_at_period_end", False)
- # Get billing period
- period_start = datetime.fromtimestamp(
- subscription["current_period_start"],
- tz=timezone.utc,
- )
- period_end = datetime.fromtimestamp(
- subscription["current_period_end"],
- tz=timezone.utc,
- )
+ if not customer_id or not subscription_id or not status:
+ logger.warning(
+ "Missing required fields in subscription: %s",
+ subscription_id,
+ )
+ return
+
+ # Get billing period - handle both dict and object access patterns
+ period_start_ts = subscription.get("current_period_start")
+ period_end_ts = subscription.get("current_period_end")
+
+ if not period_start_ts or not period_end_ts:
+ logger.warning(
+ "Missing period dates in subscription: %s",
+ subscription_id,
+ )
+ return
+
+ period_start = datetime.fromtimestamp(period_start_ts, tz=timezone.utc)
+ period_end = datetime.fromtimestamp(period_end_ts, tz=timezone.utc)
# Determine tier from price ID
- price_id = subscription["items"]["data"][0]["price"]["id"]
+ items = subscription.get("items", {})
+ data = items.get("data", [])
+ if not data:
+ logger.warning("No items in subscription: %s", subscription_id)
+ return
+
+ price_id = data[0].get("price", {}).get("id")
+ if not price_id:
+ logger.warning("No price ID in subscription: %s", subscription_id)
+ return
+
tier = PRICE_TO_TIER.get(price_id, "free")
# Find user by customer ID