Skip to content

Commit 08e9526

Browse files
committed
feat: write SelfServiceSubscriptionRenewal during provisioning
ENT-11011
1 parent 11b2271 commit 08e9526

File tree

4 files changed

+370
-8
lines changed

4 files changed

+370
-8
lines changed

enterprise_access/apps/api/v1/tests/test_provisioning_views.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
)
2323
from enterprise_access.apps.core.tests.factories import UserFactory
2424
from enterprise_access.apps.customer_billing.constants import CheckoutIntentState
25-
from enterprise_access.apps.customer_billing.models import CheckoutIntent
25+
from enterprise_access.apps.customer_billing.models import CheckoutIntent, SelfServiceSubscriptionRenewal
26+
from enterprise_access.apps.customer_billing.tests.factories import StripeEventSummaryFactory
2627
from enterprise_access.apps.provisioning.models import (
2728
GetCreateCustomerStep,
2829
GetCreateEnterpriseAdminUsersStep,
@@ -159,7 +160,7 @@ class TestProvisioningAuth(APITest):
159160
"""
160161
def setUp(self):
161162
super().setUp()
162-
self._create_checkout_intent()
163+
self.checkout_intent = self._create_checkout_intent()
163164

164165
def tearDown(self):
165166
super().tearDown()
@@ -252,6 +253,8 @@ def test_provisioning_create_allowed_for_provisioning_admins(
252253
mock_create_agreement.return_value = DEFAULT_AGREEMENT_RECORD
253254
mock_create_renewal.return_value = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE
254255

256+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
257+
255258
request_payload = {**DEFAULT_REQUEST_PAYLOAD}
256259
request_payload['pending_admins'] = [
257260
{
@@ -287,7 +290,7 @@ def setUp(self):
287290
'context': ALL_ACCESS_CONTEXT,
288291
},
289292
])
290-
self._create_checkout_intent()
293+
self.checkout_intent = self._create_checkout_intent()
291294

292295
def _create_checkout_intent(self):
293296
"""Helper to create a checkout intent for testing."""
@@ -349,6 +352,7 @@ def test_get_or_create_customer_and_admins_created(
349352
mock_client.get_enterprise_catalogs.return_value = [DEFAULT_CATALOG_RECORD]
350353
mock_create_agreement.return_value = DEFAULT_AGREEMENT_RECORD
351354
mock_create_renewal.return_value = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE
355+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
352356

353357
request_payload = {**DEFAULT_REQUEST_PAYLOAD}
354358
request_payload['pending_admins'] = [
@@ -412,6 +416,16 @@ def test_get_or_create_customer_and_admins_created(
412416
[{'user_email': '[email protected]'}, {'user_email': '[email protected]'}],
413417
)
414418

419+
# Verify that a SelfServiceSubscriptionRenewal record was created during provisioning
420+
renewal_records = SelfServiceSubscriptionRenewal.objects.filter(
421+
checkout_intent=self.checkout_intent
422+
)
423+
self.assertEqual(renewal_records.count(), 1)
424+
renewal_record = renewal_records.first()
425+
expected_renewal_id = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE["id"]
426+
self.assertEqual(renewal_record.subscription_plan_renewal_id, expected_renewal_id)
427+
self.assertIsNone(renewal_record.processed_at)
428+
415429
@ddt.data(
416430
# No admin users exist, two admins created.
417431
{
@@ -469,6 +483,7 @@ def test_customer_fetched_admins_fetched_or_created(
469483
mock_license_client = mock_license_manager_client.return_value
470484
mock_license_client.get_customer_agreement.return_value = DEFAULT_AGREEMENT_RECORD
471485
mock_license_client.create_subscription_plan_renewal.return_value = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE
486+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
472487

473488
request_payload = {**DEFAULT_REQUEST_PAYLOAD}
474489
request_payload['pending_admins'] = [
@@ -567,6 +582,8 @@ def test_catalog_fetched_or_created(
567582
mock_create_agreement.return_value = DEFAULT_AGREEMENT_RECORD
568583
mock_create_renewal.return_value = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE
569584

585+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
586+
570587
request_payload = {**DEFAULT_REQUEST_PAYLOAD}
571588
response = self.client.post(PROVISIONING_CREATE_ENDPOINT, data=request_payload)
572589

@@ -623,6 +640,8 @@ def test_catalog_created_with_generated_title_and_inferred_query_id(
623640
mock_create_agreement.return_value = DEFAULT_AGREEMENT_RECORD
624641
mock_create_renewal.return_value = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE
625642

643+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
644+
626645
# Create request payload WITHOUT enterprise_catalog section
627646
request_payload = {**DEFAULT_REQUEST_PAYLOAD}
628647
request_payload.pop('enterprise_catalog')
@@ -690,6 +709,8 @@ def test_customer_agreement_fetched_or_created(
690709

691710
mock_license_client.create_subscription_plan_renewal.return_value = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE
692711

712+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
713+
693714
request_payload = {**DEFAULT_REQUEST_PAYLOAD}
694715
if test_data['created_agreement']:
695716
request_payload['customer_agreement'] = {
@@ -748,6 +769,8 @@ def test_new_subscription_plan_created(
748769
mock_license_client.create_subscription_plan.side_effect = [trial_plan_record, first_paid_plan_record]
749770
mock_license_client.create_subscription_plan_renewal.return_value = EXPECTED_SUBSCRIPTION_PLAN_RENEWAL_RESPONSE
750771

772+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
773+
751774
# Make the provisioning request
752775
response = self.client.post(PROVISIONING_CREATE_ENDPOINT, data=DEFAULT_REQUEST_PAYLOAD)
753776
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
@@ -855,6 +878,8 @@ def test_legacy_single_plan_request_transformation(
855878
legacy_request_payload.pop('first_paid_subscription_plan')
856879
legacy_request_payload['subscription_plan'] = legacy_request_payload.pop('trial_subscription_plan')
857880

881+
StripeEventSummaryFactory.create(checkout_intent=self.checkout_intent)
882+
858883
# Make the provisioning request.
859884
response = self.client.post(PROVISIONING_CREATE_ENDPOINT, data=legacy_request_payload)
860885

@@ -953,6 +978,7 @@ def test_checkout_intent_synchronized_on_success(
953978
Test that a fulfillable checkout intent is linked to workflow and marked as FULFILLED on success.
954979
"""
955980
checkout_intent = self._create_checkout_intent(state=intent_state)
981+
StripeEventSummaryFactory.create(checkout_intent=checkout_intent)
956982
self.assertEqual(checkout_intent.state, intent_state)
957983
self.assertIsNone(checkout_intent.workflow)
958984

@@ -1097,6 +1123,7 @@ def test_checkout_intent_different_slug_ignored(self, mock_lms_api_client, mock_
10971123
"""
10981124
# The checkout intent we expect to be updated.
10991125
main_checkout_intent = self._create_checkout_intent(state=CheckoutIntentState.PAID)
1126+
StripeEventSummaryFactory.create(checkout_intent=main_checkout_intent)
11001127

11011128
# Create a checkout intent with different enterprise slug. Later, test that this is NOT modified.
11021129
different_slug = 'different-enterprise-slug'
@@ -1105,6 +1132,7 @@ def test_checkout_intent_different_slug_ignored(self, mock_lms_api_client, mock_
11051132
state=CheckoutIntentState.PAID,
11061133
enterprise_slug=different_slug,
11071134
)
1135+
StripeEventSummaryFactory.create(checkout_intent=different_checkout_intent)
11081136

11091137
# Setup mocks for successful provisioning
11101138
mock_lms_client = mock_lms_api_client.return_value

enterprise_access/apps/provisioning/models.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
from django_countries import countries
1313

1414
from enterprise_access.apps.customer_billing.constants import CheckoutIntentState
15-
from enterprise_access.apps.customer_billing.models import CheckoutIntent
15+
from enterprise_access.apps.customer_billing.models import (
16+
CheckoutIntent,
17+
SelfServiceSubscriptionRenewal,
18+
StripeEventSummary
19+
)
1620
from enterprise_access.apps.customer_billing.tasks import send_enterprise_provision_signup_confirmation_email
1721
from enterprise_access.apps.workflow.exceptions import UnitOfWorkException
1822
from enterprise_access.apps.workflow.models import AbstractWorkflow, AbstractWorkflowStep
@@ -751,6 +755,35 @@ def process_input(self, accumulated_output=None, **kwargs):
751755
f'to paid plan {first_paid_plan_uuid}'
752756
) from exc
753757

758+
# Create SelfServiceSubscriptionRenewal record to track this renewal
759+
try:
760+
checkout_intent = self.get_linked_checkout_intent()
761+
latest_summary = StripeEventSummary.get_latest_for_checkout_intent(
762+
checkout_intent,
763+
stripe_subscription_id__isnull=False,
764+
)
765+
if not latest_summary:
766+
raise self.exception_class(f'No summary for {checkout_intent}')
767+
768+
renewal_tracking_record, created = SelfServiceSubscriptionRenewal.objects.get_or_create(
769+
checkout_intent=checkout_intent,
770+
subscription_plan_renewal_id=result_dict['id'],
771+
defaults={
772+
'stripe_subscription_id': latest_summary.stripe_subscription_id,
773+
'stripe_event_data': latest_summary.stripe_event_data,
774+
}
775+
)
776+
except Exception as exc:
777+
logger.exception(
778+
'Failed to create SelfServiceSubscriptionRenewal tracking record for renewal %s: %s',
779+
result_dict.get('id'), exc
780+
)
781+
raise
782+
783+
logger.info(
784+
'SelfServiceSubscriptionRenewal record %s for renewal %s, was created=%s',
785+
renewal_tracking_record.id, result_dict['id'], created,
786+
)
754787
return self.output_class.from_dict(result_dict)
755788

756789
def get_workflow_record(self):

enterprise_access/apps/provisioning/tests/factories.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,21 @@ def input_data(self):
3636
},
3737
'create_catalog_input': {},
3838
'create_customer_agreement_input': {},
39-
'create_subscription_plan_input': {
40-
'title': 'Test Subscription Plan',
39+
'create_trial_subscription_plan_input': {
40+
'title': 'Test Trial Subscription Plan',
4141
'salesforce_opportunity_line_item': str(uuid.uuid4()),
4242
'start_date': timezone.now().date().isoformat(),
4343
'expiration_date': (timezone.now() + timezone.timedelta(days=365)).date().isoformat(),
4444
'desired_num_licenses': 10,
4545
'product_id': 123,
4646
},
47+
'create_first_paid_subscription_plan_input': {
48+
'title': 'Test First Paid Subscription Plan',
49+
'product_id': 123,
50+
'start_date': timezone.now().date().isoformat(),
51+
'expiration_date': (timezone.now() + timezone.timedelta(days=730)).date().isoformat(),
52+
'salesforce_opportunity_line_item': str(uuid.uuid4()),
53+
},
4754
}
4855

4956
output_data = factory.Dict({})
@@ -77,15 +84,28 @@ def create_complete_workflow(cls, **kwargs):
7784
'uuid': str(uuid.uuid4()),
7885
'enterprise_customer_uuid': str(uuid.uuid4()),
7986
},
80-
'create_subscription_plan_output': {
87+
'create_trial_subscription_plan_output': {
8188
'uuid': str(uuid.uuid4()),
82-
'title': 'Test Subscription Plan',
89+
'title': 'Test Trial Subscription Plan',
8390
'salesforce_opportunity_line_item': str(uuid.uuid4()),
8491
'created': timezone.now().isoformat(),
8592
'start_date': timezone.now().date().isoformat(),
8693
'expiration_date': (timezone.now() + timezone.timedelta(days=365)).date().isoformat(),
8794
'is_active': True,
8895
'is_current': True,
96+
'plan_type': 'trial',
97+
'enterprise_catalog_uuid': str(uuid.uuid4()),
98+
'product': 1,
99+
},
100+
'create_first_paid_subscription_plan_output': {
101+
'uuid': str(uuid.uuid4()),
102+
'title': 'Test First Paid Subscription Plan',
103+
'salesforce_opportunity_line_item': str(uuid.uuid4()),
104+
'created': timezone.now().isoformat(),
105+
'start_date': timezone.now().date().isoformat(),
106+
'expiration_date': (timezone.now() + timezone.timedelta(days=730)).date().isoformat(),
107+
'is_active': True,
108+
'is_current': True,
89109
'plan_type': 'subscription',
90110
'enterprise_catalog_uuid': str(uuid.uuid4()),
91111
'product': 1,

0 commit comments

Comments
 (0)