diff --git a/common/djangoapps/util/date_utils.py b/common/djangoapps/util/date_utils.py index fde0bfe..e9c4f90 100644 --- a/common/djangoapps/util/date_utils.py +++ b/common/djangoapps/util/date_utils.py @@ -89,7 +89,7 @@ def from_timestamp(timestamp): If the timestamp cannot be converted, returns None instead. """ try: - return datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC) + return datetime.utcfromtimestamp(int(timestamp)).replace(tzinfo=UTC) except (ValueError, TypeError): return None diff --git a/openedx/core/djangoapps/credit/tests/test_views.py b/openedx/core/djangoapps/credit/tests/test_views.py index d454009..0ca8bc7 100644 --- a/openedx/core/djangoapps/credit/tests/test_views.py +++ b/openedx/core/djangoapps/credit/tests/test_views.py @@ -190,6 +190,15 @@ class CreditProviderViewTests(UrlResetMixin, TestCase): response = self._credit_provider_callback(request_uuid, "approved", timestamp=timestamp) self.assertEqual(response.status_code, 403) + def test_credit_provider_callback_handles_string_timestamp(self): + request_uuid = self._create_credit_request_and_get_uuid(self.USERNAME, self.COURSE_KEY) + + # Simulate a callback from the credit provider with a timestamp + # encoded as a string instead of an integer. + timestamp = str(to_timestamp(datetime.datetime.now(pytz.UTC))) + response = self._credit_provider_callback(request_uuid, "approved", timestamp=timestamp) + self.assertEqual(response.status_code, 200) + def test_credit_provider_callback_is_idempotent(self): request_uuid = self._create_credit_request_and_get_uuid(self.USERNAME, self.COURSE_KEY) diff --git a/openedx/core/djangoapps/credit/views.py b/openedx/core/djangoapps/credit/views.py index 8956580..9e4506f 100644 --- a/openedx/core/djangoapps/credit/views.py +++ b/openedx/core/djangoapps/credit/views.py @@ -152,8 +152,9 @@ def credit_provider_callback(request, provider_id): * status (string): Either "approved" or "rejected". - * timestamp (int): The datetime at which the POST request was made, represented + * timestamp (int or string): The datetime at which the POST request was made, represented as the number of seconds since January 1, 1970 00:00:00 UTC. + If the timestamp is a string, it will be converted to an integer. * signature (string): A digital signature of the request parameters, created using a secret key shared with the credit provider. @@ -259,7 +260,8 @@ def _validate_timestamp(timestamp_value, provider_id): Check that the timestamp of the request is recent. Arguments: - timestamp (int): Number of seconds since Jan. 1, 1970 UTC. + timestamp (int or string): Number of seconds since Jan. 1, 1970 UTC. + If specified as a string, it will be converted to an integer. provider_id (unicode): Identifier for the credit provider. Returns: