Skip to content

Conversation

@brobro10000
Copy link
Member

@brobro10000 brobro10000 commented Oct 1, 2025

Description:
This pull request introduces significant improvements and new tests for the customer billing API endpoints, focusing on the creation of Stripe billing portal sessions for both enterprise admins and learners. The changes include new comprehensive API tests, refactoring of the view logic for portal session creation, improved permission handling, and minor admin interface enhancements.

Key changes include:

1. New and Improved API Tests

  • Added a comprehensive test suite in test_customer_billing.py to cover all major cases for admin and checkout portal session creation, including success, authentication, permission, error handling, and edge cases.

2. Customer Billing API View Refactor

  • Split the portal session creation logic into two separate endpoints: one for enterprise admins (create_enterprise_admin_portal_session) and one for learners/checkout (create_checkout_portal_session), each with dedicated permission checks and error handling.
  • Enhanced error handling and logging for Stripe errors and missing parameters, returning appropriate HTTP status codes (400, 401, 403, 404, 422) and messages.
  • Removed unnecessary dependencies and cleaned up imports for clarity. [1] [2]

3. Permissions and Security

  • Introduced a custom CheckoutIntentPermission to ensure only the correct user can access their checkout intent for portal session creation, returning 403 when unauthorized.

4. Django Admin Improvements

  • Updated the CheckoutIntentAdmin configuration to include enterprise_uuid and stripe_customer_id fields in the admin interface for better visibility and management of checkout intents. [1] [2]

Jira:
ENT-XXXX

Merge checklist:

  • ./manage.py makemigrations has been run
    • Note: This must be run if you modified any models.
      • It may or may not make a migration depending on exactly what you modified, but it should still be run.

Post merge:

  • Ensure that your changes went out to the stage instance
  • Deploy to prod instance

@brobro10000 brobro10000 force-pushed the hu/ent-create-billing-portal-api-update branch from 322882f to 8b303e8 Compare October 1, 2025 13:17
logger.error(f"No stripe customer id associated to CheckoutIntent {checkout_intent}")
return Response(customer_portal_session, status=status.HTTP_404_NOT_FOUND)

if not checkout_intent:
Copy link
Contributor

@pwnage101 pwnage101 Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting here and until the end of the function, there a lot of code that is common between the two new views. I'd recommend making this DRY and consolidating as much common code into a single function, possibly an internal python API function stored in the customer_billing domain (e.g. enterprise_access/apps/customer_billing/api.py)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our goal was just to get into a functional state to unblock frontend development. There's a handful of things that can be cleaned up on these two views/actions.

logger.exception(
f"StripeError creating billing portal session for CheckoutIntent {checkout_intent}: {e}",
)
return Response(customer_portal_session, status=status.HTTP_422_UNPROCESSABLE_ENTITY)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

422 Unprocessable Entity usually indicates only something wrong with the request. It's not a guarantee that stripe.error.StripeError will get raised only for issues with the request, so always returning 422 would be misleading.

I'd recommend not catching the exception, and letting DRF's default error boundary convert this into a 500 server error response.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if an exception is caught, customer_portal_session will be null still, so it doesn't make much sense to include it in the response.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returning msg string instead of None. 👍🏽

Copy link
Member Author

@brobro10000 brobro10000 Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 422 was replaced from a 502 status for Stripe based exception and 500 status for general exceptions in an earlier implementation after seeking feedback.

I added a TODO to be more explicit when utilizing the Stripe error class that can be done in followup work. I think the Stripe specific exception is a good way to quickly whether the errors are happening internally or via the Stripe API and I am in leaning towards keeping it for now. Lmk if its a hard blocker and I can remove it 👍🏽

@brobro10000 brobro10000 force-pushed the hu/ent-create-billing-portal-api-update branch from 4b55824 to 72a2ed9 Compare October 1, 2025 22:30
Comment on lines +211 to +213
CUSTOMER_BILLING_CREATE_PORTAL_SESSION_PERMISSION,
fn=lambda request, **kwargs: request.GET.get('enterprise_customer_uuid') or kwargs.get(
'enterprise_customer_uuid')
Copy link
Member Author

@brobro10000 brobro10000 Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iloveagent57 , @pwnage101 - Sanity check: The fn in @permission_required now pulls enterprise_customer_uuid from request.GET (query param) instead of only kwargs, so RBAC actually evaluates the right enterprise UUID instead of None (which caused blanket 403s before).
Without this, every call failed early with 403 because the permission check never saw the UUID, blocking view logic from running.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh, right, that makes sense.

@iloveagent57
Copy link
Contributor

Nice job, I think we can improve this in the future to make it align with standard DRF patterns, namely moving the create_checkout_portal_session action into the CheckoutIntent viewset. But this will definitely work for now.

@brobro10000 brobro10000 merged commit 81b1fc9 into main Oct 2, 2025
4 checks passed
@brobro10000 brobro10000 deleted the hu/ent-create-billing-portal-api-update branch October 2, 2025 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants