Cancel a Stripe subscription for an organization immediately.
(
*,
request: Request,
org_id: str,
body: CancelSubscriptionBody,
orm: Session = Depends(get_orm_session),
)
| 1445 | |
| 1446 | |
| 1447 | async def cancel_subscription( |
| 1448 | *, |
| 1449 | request: Request, |
| 1450 | org_id: str, |
| 1451 | body: CancelSubscriptionBody, |
| 1452 | orm: Session = Depends(get_orm_session), |
| 1453 | ) -> StatusResponse: |
| 1454 | """ |
| 1455 | Cancel a Stripe subscription for an organization immediately. |
| 1456 | """ |
| 1457 | stripe.api_key = STRIPE_SECRET_KEY |
| 1458 | |
| 1459 | if not STRIPE_SECRET_KEY: |
| 1460 | raise HTTPException(status_code=500, detail="Stripe secret key not configured.") |
| 1461 | |
| 1462 | user: Optional[UserModel] = UserModel.get_by_id(orm, request.state.session.user_id) |
| 1463 | if not user: # Minimal check, primary auth is via endpoint protection |
| 1464 | raise HTTPException(status_code=401, detail="User not authenticated.") |
| 1465 | |
| 1466 | org: Optional[OrgModel] = OrgModel.get_by_id(orm, org_id) |
| 1467 | |
| 1468 | if not org: |
| 1469 | raise HTTPException(status_code=404, detail="Organization not found") |
| 1470 | |
| 1471 | if not org.is_user_admin_or_owner(request.state.session.user_id): |
| 1472 | raise HTTPException( |
| 1473 | status_code=403, |
| 1474 | detail="User does not have permission to manage this organization's subscription.", |
| 1475 | ) |
| 1476 | |
| 1477 | if not org.subscription_id: |
| 1478 | raise HTTPException(status_code=400, detail="Organization does not have an active subscription.") |
| 1479 | |
| 1480 | if org.subscription_id != body.subscription_id: |
| 1481 | raise HTTPException(status_code=400, detail="Subscription ID mismatch.") |
| 1482 | |
| 1483 | try: |
| 1484 | # Add idempotency key to prevent duplicate cancellation requests |
| 1485 | idempotency_key = f"cancel_{org.id}_{body.subscription_id}_{int(time.time())}" |
| 1486 | stripe.Subscription.modify( |
| 1487 | body.subscription_id, cancel_at_period_end=True, idempotency_key=idempotency_key |
| 1488 | ) |
| 1489 | |
| 1490 | logger.info(f"Subscription {body.subscription_id} set to cancel at period end for org {org_id}") |
| 1491 | |
| 1492 | return StatusResponse( |
| 1493 | message="Subscription will be cancelled at the end of the current billing period." |
| 1494 | ) |
| 1495 | |
| 1496 | except stripe.error.StripeError as e: |
| 1497 | orm.rollback() |
| 1498 | logger.error( |
| 1499 | "Stripe error cancelling subscription %s for org %s: %s", body.subscription_id, org_id, str(e) |
| 1500 | ) |
| 1501 | raise HTTPException( |
| 1502 | status_code=500, |
| 1503 | detail=( |
| 1504 | "Stripe error: Could not cancel subscription. " |
nothing calls this directly
no test coverage detected
searching dependent graphs…