-
Notifications
You must be signed in to change notification settings - Fork 0
feat: adding new canceled field to stripe event summary view #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR enhances the Stripe event summary endpoint to include subscription cancellation information. The endpoint now returns a canceled_date field in addition to the existing upcoming_invoice_amount_due and currency fields, enabling the admin portal to display subscription cancellation status.
Key Changes:
- Added retrieval of
customer.subscription.updatedevents to extract cancellation dates - Updated endpoint response to include
canceled_datefield - Enhanced tests to verify cancellation date behavior in both scenarios (with and without cancellation)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
enterprise_access/apps/api/v1/views/customer_billing.py |
Modified first_upcoming_invoice_amount_due method to query for subscription updated events and include cancellation date in response |
enterprise_access/apps/api/v1/tests/test_stripe_event_summary_views.py |
Added test data for subscription updated events and new test case to verify canceled_date functionality |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
pwnage101
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved, with recommendation for a possible helper passthrough.
| detail=False, | ||
| methods=['get'], | ||
| url_path='first-invoice-upcoming-amount-due', | ||
| url_path='get-stripe-subscription-plan-info', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, to mitigate deployment risk I think we could introduce a simple passthrough:
@action(
detail=False,
methods=['get'],
url_path='first-invoice-upcoming-amount-due',
)
def first_upcoming_invoice_amount_due(self, request, *args, **kwargs):
"""
Deprecated first-invoice-upcoming-amount-due endpoint.
Temporary passthrough to aid with transitioning to get-stripe-subscription-plan-info.
"""
return self.get_stripe_subscription_plan_info(request, *args, **kwargs)| 'currency': summary.currency, | ||
| 'upcoming_invoice_amount_due': created_event_summary.upcoming_invoice_amount_due, | ||
| 'currency': created_event_summary.currency, | ||
| 'canceled_date': canceled_date, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this actually work without casting it to a string first? Or does that happen automatically somewhere in the Response base code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: I didn't mention anything because you were just editing a pre-existing pattern, but at some point a couple years ago Titans adopted a new REST pattern of using input/output serializers which would avoid this ambiguity and also automatically result in an automatically correct Redoc. In this case just to move forward I'd be open to just manually serializing a string as Alex suggests might be needed, but in the future consider instead using an output serializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def test_get_canceled_date(self): | ||
| # updated Stripe event triggered after cancellation | ||
| self.stripe_event_data = StripeEventData.objects.create( | ||
| event_id='evt_test_subscription_update', | ||
| event_type='customer.subscription.updated', | ||
| checkout_intent=self.checkout_intent, | ||
| data=self.subscription_updated_event_data, | ||
| ) | ||
|
|
||
| canceled_datetime = datetime(2021, 9, 15, 0, 0, 0, tzinfo=tz.utc) | ||
|
|
||
| test_summary = StripeEventSummary.objects.filter(event_id='evt_test_subscription_update').first() | ||
| test_summary.subscription_cancel_at = canceled_datetime | ||
| test_summary.subscription_plan_uuid = self.subscription_plan_uuid | ||
| test_summary.save(update_fields=['subscription_cancel_at', 'subscription_plan_uuid']) |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test creates a subscription.updated event but doesn't verify the order_by behavior when multiple updated events exist. Consider adding a test case that creates multiple customer.subscription.updated events with different timestamps to ensure the most recent cancellation date is returned correctly.
| return Response({ | ||
| 'upcoming_invoice_amount_due': summary.upcoming_invoice_amount_due, | ||
| 'currency': summary.currency, | ||
| 'upcoming_invoice_amount_due': created_event_summary.upcoming_invoice_amount_due, | ||
| 'currency': created_event_summary.currency, | ||
| 'canceled_date': canceled_date, |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The endpoint always returns upcoming_invoice_amount_due and currency from the created_event_summary, but if the subscription has been updated (e.g., plan changed, price changed), these values might be outdated. Consider whether the updated_event_summary should be checked first for these values if it exists and has more recent data.
| import uuid | ||
| from datetime import timedelta | ||
| from datetime import datetime, timedelta | ||
| from datetime import timezone as tz |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Importing timezone from two different modules (datetime.timezone as tz on line 6 and django.utils.timezone on line 10) can be confusing. The code uses django.utils.timezone in setUp (line 204) and datetime.timezone as tz in test_get_canceled_date (line 295). Consider using a consistent approach throughout the file, preferably django.utils.timezone since it's Django-aware and already used elsewhere in the codebase.
| if not (subscription_plan_uuid and created_event_summary): | ||
| return Response({}) |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition on line 624 checks both subscription_plan_uuid and created_event_summary, returning an empty response if either is missing. However, if only the created event is missing but the updated event exists with cancellation information, the endpoint won't return the cancellation date even though that information is available. This makes the API less useful in edge cases. Consider separating these concerns and returning available information even if some fields are missing.
| @action( | ||
| detail=False, | ||
| methods=['get'], | ||
| url_path='first-invoice-upcoming-amount-due', | ||
| url_path='get-stripe-subscription-plan-info', | ||
| ) | ||
| def first_upcoming_invoice_amount_due(self, request, *args, **kwargs): | ||
| def get_stripe_subscription_plan_info(self, request, *args, **kwargs): | ||
| """ | ||
| Given a license-manager SubscriptionPlan uuid, returns an upcoming | ||
| invoice amount due, dervied from Stripe's preview invoice API. | ||
| Given a license-manager SubscriptionPlan uuid, returns information needed for the | ||
| Subscription management page on admin portal, like the upcoming subscription price | ||
| and if the subscription has been cancelled | ||
| """ |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The renamed endpoint is missing API documentation and permission checks. The list method on line 594 has both @extend_schema decorator (for API documentation) and @permission_required decorator (for authorization), but this action endpoint doesn't have either. This creates a security concern as the endpoint may be accessible without proper authorization checks, and it lacks proper API documentation for consumers.
| Subscription management page on admin portal, like the upcoming subscription price | ||
| and if the subscription has been cancelled | ||
| """ | ||
| subscription_plan_uuid = self.request.query_params.get('subscription_plan_uuid') |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing input validation for the subscription_plan_uuid parameter. If subscription_plan_uuid is None or empty, the queries will still execute but will return incorrect results. Consider adding explicit validation similar to the validation in the get_queryset method on line 574-575 to return a proper error response.
| subscription_plan_uuid = self.request.query_params.get('subscription_plan_uuid') | |
| subscription_plan_uuid = self.request.query_params.get('subscription_plan_uuid') | |
| if not subscription_plan_uuid: | |
| raise exceptions.ValidationError(detail='subscription_plan_uuid query param is required') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Description:
Changing the endpoint (previously named
first_upcoming_invoice_amount_due) toget_stripe_subscription_plan_infoand adding an additional fieldcanceled_date.Jira:
ENT-11295
Merge checklist:
./manage.py makemigrationshas been runPost merge: