From 83064438d126aa6913efdc2932a361823bdf18c0 Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Wed, 8 Mar 2023 17:23:21 +0500 Subject: [PATCH 1/3] fix: Pick the right purchase from ios response iOS response contain multiple purchases, instead of picking the first purchase, pick the one which have given product id and latest date. LEARNER-9261 --- .../extensions/iap/processors/base_iap.py | 18 ++++++ .../iap/tests/processors/test_ios_iap.py | 57 +++++++++++++------ 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/ecommerce/extensions/iap/processors/base_iap.py b/ecommerce/extensions/iap/processors/base_iap.py index 772dbafcc5c..26f9d9d4be6 100644 --- a/ecommerce/extensions/iap/processors/base_iap.py +++ b/ecommerce/extensions/iap/processors/base_iap.py @@ -113,6 +113,9 @@ def handle_processor_response(self, response, basket=None): ) raise GatewayError(validation_response) + if self.NAME == 'ios-iap': + validation_response = self.parse_ios_response(validation_response, product_id) + transaction_id = response.get('transactionId', self._get_transaction_id_from_receipt(validation_response)) # original_transaction_id is primary identifier for a purchase on iOS original_transaction_id = response.get('originalTransactionId', self._get_attribute_from_receipt( @@ -147,6 +150,21 @@ def handle_processor_response(self, response, basket=None): card_type=None ) + def parse_ios_response(self, response, product_id): + """ + iOS response have multiple receipts data, and we need to select the purchase we just made + with the given product id. + """ + purchases = response['receipt'].get('in_app', []) + for purchase in purchases: + if purchase['product_id'] == product_id and \ + response['receipt']['receipt_creation_date_ms'] == purchase['purchase_date_ms']: + + response['receipt']['in_app'] = [purchase] + break + + return response + def record_processor_response(self, response, transaction_id=None, basket=None, original_transaction_id=None): # pylint: disable=arguments-differ """ Save the processor's response to the database for auditing. diff --git a/ecommerce/extensions/iap/tests/processors/test_ios_iap.py b/ecommerce/extensions/iap/tests/processors/test_ios_iap.py index 6c3c2c658ea..6b4886da48d 100644 --- a/ecommerce/extensions/iap/tests/processors/test_ios_iap.py +++ b/ecommerce/extensions/iap/tests/processors/test_ios_iap.py @@ -54,11 +54,41 @@ def setUp(self): u"IOSInAppPurchase's response was recorded in entry [{entry_id}]." ) self.RETURN_DATA = { - 'transactionId': 'transactionId.ios.test.purchased', - 'originalTransactionId': 'originalTransactionId.ios.test.purchased', - 'productId': 'ios.test.purchased', + 'transactionId': '2000000279135357', + 'originalTransactionId': '2000000279135357', + 'productId': 'org.edx.mobile.integrationtest', 'purchaseToken': 'inapp:org.edx.mobile:ios.test.purchased', } + self.mock_validation_response = { + 'environment': 'Sandbox', + 'receipt': { + 'bundle_id': 'org.edx.mobile', + 'in_app': [ + { + 'in_app_ownership_type': 'PURCHASED', + 'original_transaction_id': '2000000279124188', + 'product_id': 'org.edx.mobile.test_product1', + 'purchase_date_ms': '1676562309000', + 'transaction_id': '2000000279124188' + }, + { + 'in_app_ownership_type': 'PURCHASED', + 'original_transaction_id': '2000000279128532', + 'product_id': 'org.edx.mobile.test_product3', + 'purchase_date_ms': '1676562544000', + 'transaction_id': '2000000279128532' + }, + { + 'in_app_ownership_type': 'PURCHASED', + 'original_transaction_id': '2000000279135357', + 'product_id': 'org.edx.mobile.integrationtest', + 'purchase_date_ms': '1676562978000', + 'transaction_id': '2000000279135357' + } + ], + 'receipt_creation_date_ms': '1676562978000', + } + } def _get_receipt_url(self): """ @@ -124,14 +154,13 @@ def test_handle_processor_response_payment_error(self, mock_ios_validator): """ Verify that appropriate PaymentError is raised in absence of originalTransactionId parameter. """ - mock_ios_validator.return_value = { - 'resource': { - 'orderId': 'orderId.ios.test.purchased' - } - } + modified_validation_response = self.mock_validation_response + modified_validation_response['receipt']['in_app'][2].pop('original_transaction_id') + mock_ios_validator.return_value = modified_validation_response with self.assertRaises(PaymentError): modified_return_data = self.RETURN_DATA modified_return_data.pop('originalTransactionId') + self.processor.handle_processor_response(modified_return_data, basket=self.basket) @mock.patch.object(BaseIAP, '_is_payment_redundant') @@ -141,11 +170,7 @@ def test_handle_processor_response_redundant_error(self, mock_ios_validator, moc Verify that appropriate RedundantPaymentNotificationError is raised in case payment with same originalTransactionId exists with another user """ - mock_ios_validator.return_value = { - 'resource': { - 'orderId': 'orderId.ios.test.purchased' - } - } + mock_ios_validator.return_value = self.mock_validation_response mock_payment_redundant.return_value = True with self.assertRaises(RedundantPaymentNotificationError): @@ -156,11 +181,7 @@ def test_handle_processor_response(self, mock_ios_validator): # pylint: disable """ Verify that the processor creates the appropriate PaymentEvent and Source objects. """ - mock_ios_validator.return_value = { - 'resource': { - 'orderId': 'orderId.ios.test.purchased' - } - } + mock_ios_validator.return_value = self.mock_validation_response handled_response = self.processor.handle_processor_response(self.RETURN_DATA, basket=self.basket) self.assertEqual(handled_response.currency, self.basket.currency) From eaba166f4974244e33485b6e9d7865a914a3bb2f Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Thu, 9 Mar 2023 22:53:17 +0500 Subject: [PATCH 2/3] fix: fixed review objections --- .../extensions/iap/processors/base_iap.py | 2 +- .../iap/tests/processors/test_ios_iap.py | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ecommerce/extensions/iap/processors/base_iap.py b/ecommerce/extensions/iap/processors/base_iap.py index 26f9d9d4be6..940f551f06d 100644 --- a/ecommerce/extensions/iap/processors/base_iap.py +++ b/ecommerce/extensions/iap/processors/base_iap.py @@ -152,7 +152,7 @@ def handle_processor_response(self, response, basket=None): def parse_ios_response(self, response, product_id): """ - iOS response have multiple receipts data, and we need to select the purchase we just made + iOS response has multiple receipts data, and we need to select the purchase we just made with the given product id. """ purchases = response['receipt'].get('in_app', []) diff --git a/ecommerce/extensions/iap/tests/processors/test_ios_iap.py b/ecommerce/extensions/iap/tests/processors/test_ios_iap.py index 6b4886da48d..585999faccd 100644 --- a/ecommerce/extensions/iap/tests/processors/test_ios_iap.py +++ b/ecommerce/extensions/iap/tests/processors/test_ios_iap.py @@ -54,40 +54,40 @@ def setUp(self): u"IOSInAppPurchase's response was recorded in entry [{entry_id}]." ) self.RETURN_DATA = { - 'transactionId': '2000000279135357', - 'originalTransactionId': '2000000279135357', - 'productId': 'org.edx.mobile.integrationtest', - 'purchaseToken': 'inapp:org.edx.mobile:ios.test.purchased', + 'transactionId': 'test_id', + 'originalTransactionId': 'original_test_id', + 'productId': 'test_product_id', + 'purchaseToken': 'inapp:test.edx.edx:ios.test.purchased', } self.mock_validation_response = { 'environment': 'Sandbox', 'receipt': { - 'bundle_id': 'org.edx.mobile', + 'bundle_id': 'test_bundle_id', 'in_app': [ { 'in_app_ownership_type': 'PURCHASED', - 'original_transaction_id': '2000000279124188', + 'original_transaction_id': 'very_old_purchase_id', 'product_id': 'org.edx.mobile.test_product1', 'purchase_date_ms': '1676562309000', - 'transaction_id': '2000000279124188' + 'transaction_id': 'vaery_old_purchase_id' }, { 'in_app_ownership_type': 'PURCHASED', - 'original_transaction_id': '2000000279128532', + 'original_transaction_id': 'old_purchase_id', 'product_id': 'org.edx.mobile.test_product3', 'purchase_date_ms': '1676562544000', - 'transaction_id': '2000000279128532' + 'transaction_id': 'old_purchase_id' }, { 'in_app_ownership_type': 'PURCHASED', - 'original_transaction_id': '2000000279135357', - 'product_id': 'org.edx.mobile.integrationtest', + 'original_transaction_id': 'original_test_id', + 'product_id': 'test_product_id', 'purchase_date_ms': '1676562978000', - 'transaction_id': '2000000279135357' + 'transaction_id': 'test_id' } ], 'receipt_creation_date_ms': '1676562978000', - } + } } def _get_receipt_url(self): From 3f64f30de30b345e29b88f3155a8425475b7f369 Mon Sep 17 00:00:00 2001 From: jawad-khan Date: Thu, 9 Mar 2023 23:49:22 +0500 Subject: [PATCH 3/3] fix: fixed test case --- ecommerce/extensions/iap/api/v1/tests/test_views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ecommerce/extensions/iap/api/v1/tests/test_views.py b/ecommerce/extensions/iap/api/v1/tests/test_views.py index b224fd9e1e3..bf3aa2891f9 100644 --- a/ecommerce/extensions/iap/api/v1/tests/test_views.py +++ b/ecommerce/extensions/iap/api/v1/tests/test_views.py @@ -387,7 +387,8 @@ def test_iap_payment_execution_ios(self): 'receipt': { 'in_app': [{ 'original_transaction_id': '123456', - 'transaction_id': '123456' + 'transaction_id': '123456', + 'product_id': 'fake_product_id' }] } }