diff --git a/enterprise_access/apps/subsidy_access_policy/models.py b/enterprise_access/apps/subsidy_access_policy/models.py index d0a3b1de..3946494b 100644 --- a/enterprise_access/apps/subsidy_access_policy/models.py +++ b/enterprise_access/apps/subsidy_access_policy/models.py @@ -664,7 +664,7 @@ def _redemptions_for_idempotency_key(self, all_transactions): ) ] - def redeem(self, lms_user_id, content_key, all_transactions, metadata=None): + def redeem(self, lms_user_id, content_key, all_transactions, metadata=None, **kwargs): """ Redeem a subsidy for the given learner and content. @@ -684,14 +684,18 @@ def redeem(self, lms_user_id, content_key, all_transactions, metadata=None): historical_redemptions_uuids=self._redemptions_for_idempotency_key(all_transactions), ) try: - return self.subsidy_client.create_subsidy_transaction( - subsidy_uuid=str(self.subsidy_uuid), - lms_user_id=lms_user_id, - content_key=content_key, - subsidy_access_policy_uuid=str(self.uuid), - metadata=metadata, - idempotency_key=idempotency_key, - ) + creation_payload = { + 'subsidy_uuid': str(self.subsidy_uuid), + 'lms_user_id': lms_user_id, + 'content_key': content_key, + 'subsidy_access_policy_uuid': str(self.uuid), + 'metadata': metadata, + 'idempotency_key': idempotency_key, + } + requested_price_cents = kwargs.get('requested_price_cents') + if requested_price_cents is not None: + creation_payload['requested_price_cents'] = requested_price_cents + return self.subsidy_client.create_subsidy_transaction(**creation_payload) except requests.exceptions.HTTPError as exc: raise SubsidyAPIHTTPError('HTTPError occurred in Subsidy API request.') from exc else: @@ -1081,7 +1085,7 @@ def can_redeem(self, lms_user_id, content_key, skip_customer_user_check=False): # Learner can redeem the subsidy access policy return (True, None, existing_redemptions) - def redeem(self, lms_user_id, content_key, all_transactions, metadata=None): + def redeem(self, lms_user_id, content_key, all_transactions, metadata=None, **kwargs): """ Redeem content, but only if there's a matching assignment. On successful redemption, the assignment state will be set to 'accepted', otherwise 'errored'. @@ -1110,7 +1114,14 @@ def redeem(self, lms_user_id, content_key, all_transactions, metadata=None): f"and content_key=<{content_key}>." ) try: - ledger_transaction = super().redeem(lms_user_id, content_key, all_transactions, metadata) + requested_price_cents = -1 * found_assignment.content_quantity + ledger_transaction = super().redeem( + lms_user_id, + content_key, + all_transactions, + metadata=metadata, + requested_price_cents=requested_price_cents, + ) except SubsidyAPIHTTPError: # Migrate assignment to errored if the subsidy API call errored. found_assignment.state = LearnerContentAssignmentStateChoices.ERRORED @@ -1164,9 +1175,6 @@ def can_allocate(self, number_of_learners, content_key, content_price_cents): if not self.is_subsidy_active: return (False, REASON_SUBSIDY_EXPIRED) - # TODO ENT-7793: validate that the provided price - # matches what we have in our content metadata source-of-truth - # Determine total cost, in cents, of content to potentially allocated positive_total_price_cents = number_of_learners * content_price_cents diff --git a/enterprise_access/apps/subsidy_access_policy/tests/test_models.py b/enterprise_access/apps/subsidy_access_policy/tests/test_models.py index c822feaf..d75e6844 100644 --- a/enterprise_access/apps/subsidy_access_policy/tests/test_models.py +++ b/enterprise_access/apps/subsidy_access_policy/tests/test_models.py @@ -2,7 +2,7 @@ Tests for subsidy_access_policy models. """ from datetime import datetime, timedelta -from unittest.mock import PropertyMock, patch +from unittest.mock import ANY, PropertyMock, patch from uuid import uuid4 import ddt @@ -952,12 +952,31 @@ def test_redeem( state=assignment_starting_state, ) + # Do the redemption if redeem_raises: with self.assertRaises(redeem_raises): self.active_policy.redeem(self.lms_user_id, self.course_run_key, []) else: self.active_policy.redeem(self.lms_user_id, self.course_run_key, []) + # Assert that we call the subsidy client's `create_subsidy_transaction` method + # with the expected payload, but only for test conditions where redeem() doesn't + # fail before getting to that point. + if fail_subsidy_create_transaction or not redeem_raises: + expected_redeem_payload = { + 'subsidy_uuid': str(self.active_policy.subsidy_uuid), + 'lms_user_id': self.lms_user_id, + 'content_key': self.course_run_key, + 'subsidy_access_policy_uuid': str(self.active_policy.uuid), + 'metadata': None, + 'idempotency_key': ANY, + } + if assignment: + expected_redeem_payload['requested_price_cents'] = -1 * assignment.content_quantity + self.mock_subsidy_client.create_subsidy_transaction.assert_called_once_with( + **expected_redeem_payload, + ) + # Finally, assert that the assignment object was correctly updated to reflect the success/failure. if assignment: assignment.refresh_from_db() diff --git a/requirements/base.txt b/requirements/base.txt index 6ec78682..695100b9 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -160,7 +160,7 @@ edx-drf-extensions==8.13.0 # via # -r requirements/base.in # edx-rbac -edx-enterprise-subsidy-client==0.3.7 +edx-enterprise-subsidy-client==0.4.0 # via -r requirements/base.in edx-opaque-keys[django]==2.5.1 # via diff --git a/requirements/dev.txt b/requirements/dev.txt index 5117dcf6..0c9e61b7 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -245,7 +245,7 @@ edx-drf-extensions==8.13.0 # via # -r requirements/validation.txt # edx-rbac -edx-enterprise-subsidy-client==0.3.7 +edx-enterprise-subsidy-client==0.4.0 # via -r requirements/validation.txt edx-i18n-tools==1.3.0 # via -r requirements/dev.in diff --git a/requirements/doc.txt b/requirements/doc.txt index fe326d4a..86b74f08 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -247,7 +247,7 @@ edx-drf-extensions==8.13.0 # via # -r requirements/test.txt # edx-rbac -edx-enterprise-subsidy-client==0.3.7 +edx-enterprise-subsidy-client==0.4.0 # via -r requirements/test.txt edx-lint==5.3.6 # via -r requirements/test.txt diff --git a/requirements/production.txt b/requirements/production.txt index b04f175c..bbeede60 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -193,7 +193,7 @@ edx-drf-extensions==8.13.0 # via # -r requirements/base.txt # edx-rbac -edx-enterprise-subsidy-client==0.3.7 +edx-enterprise-subsidy-client==0.4.0 # via -r requirements/base.txt edx-opaque-keys[django]==2.5.1 # via diff --git a/requirements/quality.txt b/requirements/quality.txt index 9b36da92..cf025b75 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -231,7 +231,7 @@ edx-drf-extensions==8.13.0 # via # -r requirements/test.txt # edx-rbac -edx-enterprise-subsidy-client==0.3.7 +edx-enterprise-subsidy-client==0.4.0 # via -r requirements/test.txt edx-lint==5.3.6 # via diff --git a/requirements/test.txt b/requirements/test.txt index 8184c3d8..8406b901 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -219,7 +219,7 @@ edx-drf-extensions==8.13.0 # via # -r requirements/base.txt # edx-rbac -edx-enterprise-subsidy-client==0.3.7 +edx-enterprise-subsidy-client==0.4.0 # via -r requirements/base.txt edx-lint==5.3.6 # via -r requirements/test.in diff --git a/requirements/validation.txt b/requirements/validation.txt index bfa16d02..994d873b 100644 --- a/requirements/validation.txt +++ b/requirements/validation.txt @@ -301,7 +301,7 @@ edx-drf-extensions==8.13.0 # -r requirements/quality.txt # -r requirements/test.txt # edx-rbac -edx-enterprise-subsidy-client==0.3.7 +edx-enterprise-subsidy-client==0.4.0 # via # -r requirements/quality.txt # -r requirements/test.txt