diff --git a/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py b/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py index ed7a5edc6..6a3144ff3 100644 --- a/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py +++ b/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py @@ -10,7 +10,6 @@ from commercetools.platform.models import TransactionType from commerce_coordinator.apps.commercetools.catalog_info.constants import ( - EDX_STRIPE_PAYMENT_INTERFACE_NAME, PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED, EdXFieldNames, TwoUKeys @@ -48,33 +47,24 @@ def get_edx_lms_user_name(customer: CTCustomer): return customer.custom.fields[EdXFieldNames.LMS_USER_NAME] -def get_edx_successful_stripe_payment(order: CTOrder) -> Union[CTPayment, None]: +def get_edx_successful_payment_info(order: CTOrder): for pr in order.payment_info.payments: pmt = pr.obj - if pmt.payment_status.interface_code == PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED \ - and pmt.payment_method_info.payment_interface == EDX_STRIPE_PAYMENT_INTERFACE_NAME and \ - pmt.interface_id: - return pmt - return None + if pmt.payment_status.interface_code == PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED and pmt.interface_id: + ct_payment_provider_id = pmt.payment_method_info.payment_interface + return pmt, ct_payment_provider_id + return None, None +# TODO update function and its return value name +# the return value could be either stripe payment intent id or PayPal order ID def get_edx_payment_intent_id(order: CTOrder) -> Union[str, None]: - pmt = get_edx_successful_stripe_payment(order) + pmt, _ = get_edx_successful_payment_info(order) if pmt: return pmt.interface_id return None -# TODO update get_edx_successful_stripe_payment to accommodate this util logic -# and replace it with that. -def get_edx_successful_payment_info(order: CTOrder): - for pr in order.payment_info.payments: - pmt = pr.obj - if pmt.payment_status.interface_code == PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED and pmt.interface_id: - return pmt.interface_id, pmt.payment_method_info.payment_interface - return None, None - - def get_edx_order_workflow_state_key(order: CTOrder) -> Optional[str]: order_workflow_state = None if order.state and order.state.obj: # it should never be that we have one and not the other. # pragma no cover @@ -86,10 +76,13 @@ def get_edx_is_sanctioned(order: CTOrder) -> bool: return get_edx_order_workflow_state_key(order) == TwoUKeys.SDN_SANCTIONED_ORDER_STATE -def get_edx_refund_amount(order: CTOrder) -> decimal: +def get_edx_refund_info(payment: CTPayment) -> decimal: refund_amount = decimal.Decimal(0.00) - pmt = get_edx_successful_stripe_payment(order) - for transaction in pmt.transactions: + interaction_id = None + for transaction in payment.transactions: if transaction.type == TransactionType.CHARGE: # pragma no cover refund_amount += decimal.Decimal(typed_money_to_string(transaction.amount, money_as_decimal_string=True)) - return refund_amount + interaction_id = transaction.interaction_id + return refund_amount, interaction_id + + return refund_amount, interaction_id diff --git a/commerce_coordinator/apps/commercetools/pipeline.py b/commerce_coordinator/apps/commercetools/pipeline.py index ec4bb5558..05c6b5a95 100644 --- a/commerce_coordinator/apps/commercetools/pipeline.py +++ b/commerce_coordinator/apps/commercetools/pipeline.py @@ -12,9 +12,12 @@ from openedx_filters.exceptions import OpenEdxFilterException from requests import HTTPError +from commerce_coordinator.apps.commercetools.catalog_info.constants import ( + EDX_PAYPAL_PAYMENT_INTERFACE_NAME, + EDX_STRIPE_PAYMENT_INTERFACE_NAME +) from commerce_coordinator.apps.commercetools.catalog_info.edx_utils import ( - get_edx_payment_intent_id, - get_edx_refund_amount, + get_edx_refund_info, get_edx_successful_payment_info ) from commerce_coordinator.apps.commercetools.clients import CommercetoolsAPIClient @@ -109,12 +112,12 @@ def run_filter(self, active_order_management_system, order_number, **kwargs): # duration = (datetime.now() - start_time).total_seconds() log.info(f"[Performance Check] get_order_by_number call took {duration} seconds") - intent_id, psp = get_edx_successful_payment_info(ct_order) + payment, psp = get_edx_successful_payment_info(ct_order) ret_val = { "order_data": ct_order, "psp": psp, - "payment_intent_id": intent_id + "payment_intent_id": payment.interface_id if payment else None } return ret_val @@ -155,22 +158,25 @@ def run_filter(self, active_order_management_system, order_id, **kwargs): # pyl duration = (datetime.now() - start_time).total_seconds() log.info(f"[Performance Check] get_order_by_id call took {duration} seconds") + payment, psp = get_edx_successful_payment_info(ct_order) + ret_val = { "order_data": ct_order, - "order_id": ct_order.id + "order_id": ct_order.id, + "psp": psp, + "payment_intent_id": payment.interface_id } - intent_id = get_edx_payment_intent_id(ct_order) - - if intent_id: - ct_payment = ct_api_client.get_payment_by_key(intent_id) - ret_val['payment_intent_id'] = intent_id - ret_val['amount_in_cents'] = get_edx_refund_amount(ct_order) + if payment: + ct_payment = ct_api_client.get_payment_by_key(payment.interface_id) + refund_amount, ct_transaction_interaction_id = get_edx_refund_info(ct_payment) + ret_val['amount_in_cents'] = refund_amount + ret_val['ct_transaction_interaction_id'] = ct_transaction_interaction_id ret_val['has_been_refunded'] = has_refund_transaction(ct_payment) ret_val['payment_data'] = ct_payment else: - ret_val['payment_intent_id'] = None ret_val['amount_in_cents'] = decimal.Decimal(0.00) + ret_val['ct_transaction_interaction_id'] = None ret_val['has_been_refunded'] = False ret_val['payment_data'] = None @@ -287,21 +293,23 @@ def run_filter( class CreateReturnPaymentTransaction(PipelineStep): """ Creates a Transaction for a return payment of a Commercetools order - based on Stripes refund object on a refunded charge. + based on PSP refund object on a refunded charge. """ def run_filter( self, - refund_response, active_order_management_system, payment_data, has_been_refunded, + payment_intent_id, + psp, + refund_response=None, **kwargs ): # pylint: disable=arguments-differ """ Execute a filter with the signature specified. Arguments: - refund_response: Stripe refund object or str value "charge_already_refunded" + refund_response: PSP refund object or str value "charge_already_refunded" active_order_management_system: The Active Order System payment_data: CT payment object attached to the refunded order has_been_refunded (bool): Has this payment been refunded @@ -322,30 +330,46 @@ def run_filter( ct_api_client = CommercetoolsAPIClient() try: + payment_on_order = None if payment_data is not None: payment_on_order = payment_data - else: + elif psp == EDX_STRIPE_PAYMENT_INTERFACE_NAME: payment_key = refund_response['payment_intent'] payment_on_order = ct_api_client.get_payment_by_key(payment_key) + elif psp == EDX_PAYPAL_PAYMENT_INTERFACE_NAME: + payment_on_order = ct_api_client.get_payment_by_key(payment_intent_id) updated_payment = ct_api_client.create_return_payment_transaction( payment_id=payment_on_order.id, payment_version=payment_on_order.version, - refund=refund_response + refund=refund_response, + psp=psp, ) return { 'returned_payment': updated_payment } except CommercetoolsError as err: # pragma no cover + error_message = "unknown" + if psp == EDX_STRIPE_PAYMENT_INTERFACE_NAME: + error_message = f"[stripe_payment_intent_id: {refund_response['payment_intent']}, " + elif psp == EDX_PAYPAL_PAYMENT_INTERFACE_NAME: + error_message = f"[paypal_capture_id: {refund_response['paypal_capture_id']}, " log.info(f"[{tag}] Unsuccessful attempt to create refund payment transaction with details: " - f"[stripe_payment_intent_id: {refund_response['payment_intent']}, " + f"psp: {psp}, " + f"{error_message}" f"payment_id: {payment_on_order.id}], message_id: {kwargs['message_id']}") log.exception(f"[{tag}] Commercetools Error: {err}, {err.errors}") return PipelineCommand.CONTINUE.value except HTTPError as err: # pragma no cover + error_message = "unknown" + if psp == EDX_STRIPE_PAYMENT_INTERFACE_NAME: + error_message = f"[stripe_payment_intent_id: {refund_response['payment_intent']}, " + elif psp == EDX_PAYPAL_PAYMENT_INTERFACE_NAME: + error_message = f"[paypal_capture_id: {refund_response['paypal_capture_id']}, " log.info(f"[{tag}] Unsuccessful attempt to create refund payment transaction with details: " - f"[stripe_payment_intent_id: {refund_response['payment_intent']}, " + f"psp: {psp}, " + f"{error_message}" f"payment_id: {payment_on_order.id}], message_id: {kwargs['message_id']}") log.exception(f"[{tag}] HTTP Error: {err}") return PipelineCommand.CONTINUE.value diff --git a/commerce_coordinator/apps/commercetools/sub_messages/tasks.py b/commerce_coordinator/apps/commercetools/sub_messages/tasks.py index b6505d6ff..a1d5590a9 100644 --- a/commerce_coordinator/apps/commercetools/sub_messages/tasks.py +++ b/commerce_coordinator/apps/commercetools/sub_messages/tasks.py @@ -270,9 +270,9 @@ def _prepare_segment_event_properties(in_order, return_line_item_return_id): lms_user_name = get_edx_lms_user_name(customer) lms_user_id = get_edx_lms_user_id(customer) - logger.info(f'[CT-{tag}] calling stripe to refund payment intent {payment_intent_id}, message id: {message_id}') + logger.info(f'[CT-{tag}] calling PSP to refund payment "{payment_intent_id}", message id: {message_id}') - # Return payment if payment intent id is set + # Return payment if payment id is set if payment_intent_id is not None: result = OrderRefundRequested.run_filter( order_id=order_id, return_line_item_return_id=return_line_item_return_id, message_id=message_id diff --git a/commerce_coordinator/apps/commercetools/tests/sub_messages/test_tasks.py b/commerce_coordinator/apps/commercetools/tests/sub_messages/test_tasks.py index b7d882608..d8e3163cc 100644 --- a/commerce_coordinator/apps/commercetools/tests/sub_messages/test_tasks.py +++ b/commerce_coordinator/apps/commercetools/tests/sub_messages/test_tasks.py @@ -3,9 +3,11 @@ from unittest import TestCase from unittest.mock import MagicMock, call, patch +from commercetools.platform.models import MoneyType as CTMoneyType from commercetools.platform.models import Order as CTOrder from commercetools.platform.models import ReturnInfo as CTReturnInfo from commercetools.platform.models import ReturnPaymentState as CTReturnPaymentState +from commercetools.platform.models import TypedMoney as CTTypedMoney from edx_django_utils.cache import TieredCache from commerce_coordinator.apps.commercetools.clients import CommercetoolsAPIClient @@ -252,6 +254,18 @@ def setUp(self): self.mock.update_return_payment_state_after_successful_refund } ) + # TODO: Properly mock the Payment object. + payment = self.mock.get_payment_by_key.return_value + amount = CTTypedMoney( + currency_code='USD', + cent_amount=1000, + type=CTMoneyType.CENT_PRECISION, + fraction_digits=2, + ) + for transaction in payment.transactions: + transaction.amount = amount + + self.payment_mock = payment def tearDown(self): MonkeyPatch.unmonkey(CommercetoolsAPIClient) @@ -270,7 +284,6 @@ def unpack_for_uut(values): def get_uut(): return fulfill_order_returned_uut - # todo this flow is broken @patch('commerce_coordinator.apps.commercetools.sub_messages.tasks.is_edx_lms_order') @patch('commerce_coordinator.apps.stripe.pipeline.StripeAPIClient') def test_correct_arguments_passed_already_refunded_doest_break(self, _stripe_api_mock, _lms_signal): @@ -279,7 +292,6 @@ def test_correct_arguments_passed_already_refunded_doest_break(self, _stripe_api expected_data. """ mock_values = self.mock - ret_val = self.get_uut()(*self.unpack_for_uut(self.mock.example_payload)) self.assertTrue(ret_val) diff --git a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py index 1f02f1dec..b5a74bff1 100644 --- a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py +++ b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py @@ -8,6 +8,7 @@ from django.test import RequestFactory from rest_framework.test import APITestCase +from commerce_coordinator.apps.commercetools.catalog_info.constants import EDX_STRIPE_PAYMENT_INTERFACE_NAME from commerce_coordinator.apps.commercetools.constants import COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM from commerce_coordinator.apps.commercetools.pipeline import ( AnonymizeRetiredUser, @@ -146,7 +147,9 @@ def test_commercetools_transaction_create(self, mock_returned_payment, mock_paym payment_data=self.mock_response_payment, refund_response={"payment_intent": "mock_payment_intent"}, active_order_management_system=COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM, - has_been_refunded=False + has_been_refunded=False, + payment_intent_id="pi_4MtwBwLkdIwGlenn28a3tqPa", + psp=EDX_STRIPE_PAYMENT_INTERFACE_NAME ) mock_payment_result = ret['returned_payment'] @@ -167,7 +170,9 @@ def test_commercetools_transaction_create_no_payment_data(self, mock_returned_pa payment_data=None, refund_response={"payment_intent": "mock_payment_intent"}, active_order_management_system=COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM, - has_been_refunded=False + has_been_refunded=False, + payment_intent_id="pi_4MtwBwLkdIwGlenn28a3tqPa", + psp=EDX_STRIPE_PAYMENT_INTERFACE_NAME ) mock_payment_result = ret['returned_payment'] @@ -184,7 +189,9 @@ def test_commercetools_transaction_create_has_refund(self, mock_logger, mock_has payment_data=self.mock_response_payment, refund_response="charge_already_refunded", active_order_management_system=COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM, - has_been_refunded=True + has_been_refunded=True, + payment_intent_id="pi_4MtwBwLkdIwGlenn28a3tqPa", + psp=EDX_STRIPE_PAYMENT_INTERFACE_NAME ) mock_logger.assert_called_once_with('[CreateReturnPaymentTransaction] refund has already been processed, ' 'skipping refund payment transaction creation') diff --git a/commerce_coordinator/apps/paypal/clients.py b/commerce_coordinator/apps/paypal/clients.py new file mode 100644 index 000000000..1ef2bc99a --- /dev/null +++ b/commerce_coordinator/apps/paypal/clients.py @@ -0,0 +1,50 @@ +"""PayPal client""" +import json + +from django.conf import settings +from paypalserversdk.api_helper import ApiHelper +from paypalserversdk.controllers.payments_controller import PaymentsController +from paypalserversdk.http.auth.o_auth_2 import ClientCredentialsAuthCredentials +from paypalserversdk.paypalserversdk_client import PaypalserversdkClient + + +class PayPalClient: + """ + PayPal SDK client to call PayPal APIs. + """ + def __init__(self): + self.paypal_client: PaypalserversdkClient = PaypalserversdkClient( + client_credentials_auth_credentials=ClientCredentialsAuthCredentials( + o_auth_client_id=settings.PAYMENT_PROCESSOR_CONFIG['edx']['paypal']['client_id'], + o_auth_client_secret=settings.PAYMENT_PROCESSOR_CONFIG['edx']['paypal']['client_secret'], + ), + ) + + def refund_order(self, capture_id): + """ + Capture PayPal refund. + + Args: + capture_id (str): The identifier of the PayPal order to capture refund. + + Returns: + The response from PayPal. + """ + + paypal_client = self.paypal_client + payments_controller: PaymentsController = paypal_client.payments + + collect = {"capture_id": capture_id, "prefer": "return=representation"} + refund_response = payments_controller.captures_refund(collect) + + if refund_response.body: + response = json.loads(ApiHelper.json_serialize(refund_response.body)) + return { + "id": response.get("id"), + "created": response.get("create_time"), + "status": response.get("status"), + "amount": response.get("amount").get("value"), + "currency": response.get("amount").get("currency_code"), + } + + return None diff --git a/commerce_coordinator/apps/paypal/pipeline.py b/commerce_coordinator/apps/paypal/pipeline.py index 9d4841d9c..955bd4c60 100644 --- a/commerce_coordinator/apps/paypal/pipeline.py +++ b/commerce_coordinator/apps/paypal/pipeline.py @@ -7,9 +7,11 @@ from django.conf import settings from openedx_filters import PipelineStep +from requests import RequestException from commerce_coordinator.apps.commercetools.catalog_info.constants import EDX_PAYPAL_PAYMENT_INTERFACE_NAME from commerce_coordinator.apps.core.constants import PipelineCommand +from commerce_coordinator.apps.paypal.clients import PayPalClient logger = logging.getLogger(__name__) @@ -30,3 +32,53 @@ def run_filter(self, psp=None, payment_intent_id=None, **params): return { 'redirect_url': redirect_url, } + + +class RefundPayPalPayment(PipelineStep): + """ + Refunds a PayPal payment + """ + + def run_filter( + self, + order_id, + amount_in_cents, + has_been_refunded, + ct_transaction_interaction_id, + psp, + **kwargs + ): # pylint: disable=arguments-differ + """ + Execute a filter with the signature specified. + Arguments: + order_id (str): The identifier of the order. + amount_in_cents (decimal): Total amount to refund + has_been_refunded (bool): Has this payment been refunded + kwargs: arguments passed through from the filter. + """ + + tag = type(self).__name__ + + if psp != EDX_PAYPAL_PAYMENT_INTERFACE_NAME or not amount_in_cents or not ct_transaction_interaction_id: + return PipelineCommand.CONTINUE.value + + if has_been_refunded: + logger.info(f'[{tag}] payment already refunded from psp: {psp}, skipping.') + return { + 'refund_response': "charge_already_refunded" + } + + try: + paypal_client = PayPalClient() + paypal_refund_response = paypal_client.refund_order(capture_id=ct_transaction_interaction_id) + + return { + 'refund_response': paypal_refund_response, + } + except Exception as ex: + logger.error(f'[CT-{tag}] Unsuccessful PayPal refund with details: ' + f'[order_id: {order_id} ' + f'message_id: {kwargs["message_id"]} ' + f'exception: {ex}') + + raise RequestException(str(ex)) from ex diff --git a/commerce_coordinator/apps/paypal/tests/__init__.py b/commerce_coordinator/apps/paypal/tests/__init__.py index 2e06d0049..e69de29bb 100644 --- a/commerce_coordinator/apps/paypal/tests/__init__.py +++ b/commerce_coordinator/apps/paypal/tests/__init__.py @@ -1 +0,0 @@ -"""Constants for PayPal app tests.""" diff --git a/commerce_coordinator/apps/stripe/pipeline.py b/commerce_coordinator/apps/stripe/pipeline.py index 1840223de..25b9433ff 100644 --- a/commerce_coordinator/apps/stripe/pipeline.py +++ b/commerce_coordinator/apps/stripe/pipeline.py @@ -218,7 +218,7 @@ class GetPaymentIntentReceipt(PipelineStep): def run_filter(self, psp=None, payment_intent_id=None, **params): tag = type(self).__name__ - if payment_intent_id is None: + if psp == EDX_STRIPE_PAYMENT_INTERFACE_NAME and payment_intent_id is None: logger.debug(f'[{tag}] payment_intent_id not set, skipping.') return PipelineCommand.CONTINUE.value @@ -249,6 +249,7 @@ def run_filter( payment_intent_id, amount_in_cents, has_been_refunded, + psp, **kwargs ): # pylint: disable=arguments-differ """ @@ -263,7 +264,7 @@ def run_filter( tag = type(self).__name__ - if not payment_intent_id or not amount_in_cents: # pragma: no cover + if psp != EDX_STRIPE_PAYMENT_INTERFACE_NAME or not payment_intent_id or not amount_in_cents: # pragma: no cover logger.info(f'[{tag}] payment_intent_id or amount_in_cents not set, skipping.') return PipelineCommand.CONTINUE.value diff --git a/commerce_coordinator/settings/base.py b/commerce_coordinator/settings/base.py index 8bcec13aa..3d183c000 100644 --- a/commerce_coordinator/settings/base.py +++ b/commerce_coordinator/settings/base.py @@ -395,6 +395,7 @@ def root(*path_fragments): 'commerce_coordinator.apps.rollout.pipeline.DetermineActiveOrderManagementSystemByOrderID', 'commerce_coordinator.apps.commercetools.pipeline.FetchOrderDetailsByOrderID', 'commerce_coordinator.apps.stripe.pipeline.RefundPaymentIntent', + 'commerce_coordinator.apps.paypal.pipeline.RefundPayPalPayment', 'commerce_coordinator.apps.commercetools.pipeline.CreateReturnPaymentTransaction', 'commerce_coordinator.apps.commercetools.pipeline.UpdateCommercetoolsOrderReturnPaymentStatus', ] @@ -438,6 +439,8 @@ def root(*path_fragments): 'paypal': { 'user_activity_page_url': '', 'paypal_webhook_id': PAYPAL_WEBHOOK_ID, + 'client_id': '', + 'client_secret': '', }, }, } diff --git a/commerce_coordinator/settings/local.py b/commerce_coordinator/settings/local.py index a41afccd8..ff2d11745 100644 --- a/commerce_coordinator/settings/local.py +++ b/commerce_coordinator/settings/local.py @@ -147,6 +147,8 @@ 'paypal': { 'user_activity_page_url': 'https://sandbox.paypal.com/myaccount/activities/', 'paypal_webhook_id': 'SET-ME-PLEASE', + 'client_id': '', + 'client_secret': '', }, }, } diff --git a/commerce_coordinator/settings/test.py b/commerce_coordinator/settings/test.py index 11e804173..b44ce0274 100644 --- a/commerce_coordinator/settings/test.py +++ b/commerce_coordinator/settings/test.py @@ -18,6 +18,8 @@ 'paypal': { 'user_activity_page_url': 'https://test.paypal.com/myaccount/activities/', 'paypal_webhook_id': PAYPAL_WEBHOOK_ID, + 'client_id': '', + 'client_secret': '', }, }, } diff --git a/requirements/base.in b/requirements/base.in index c9985da90..bb309d7fd 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -25,6 +25,7 @@ edx-rest-api-client mysqlclient openedx-filters Pillow +paypal-server-sdk pytz segment-analytics-python stripe diff --git a/requirements/base.txt b/requirements/base.txt index f1484a6b3..2ccda0686 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -6,6 +6,15 @@ # amqp==5.2.0 # via kombu +apimatic-core==0.2.17 + # via paypal-server-sdk +apimatic-core-interfaces==0.1.5 + # via + # apimatic-core + # apimatic-requests-client-adapter + # paypal-server-sdk +apimatic-requests-client-adapter==0.1.6 + # via paypal-server-sdk asgiref==3.7.2 # via # -r requirements/base.in @@ -17,6 +26,8 @@ backoff==2.2.1 # via segment-analytics-python billiard==4.2.1 # via celery +cachecontrol==0.12.14 + # via apimatic-requests-client-adapter celery[redis]==5.4.0 # via -r requirements/base.in certifi==2024.8.30 @@ -121,6 +132,10 @@ itypes==1.2.0 # via coreapi jinja2==3.1.4 # via coreschema +jsonpickle==3.3.0 + # via apimatic-core +jsonpointer==2.4 + # via apimatic-core kombu==5.4.2 # via celery markupsafe==3.0.2 @@ -133,6 +148,8 @@ marshmallow==3.23.0 # marshmallow-enum marshmallow-enum==1.5.1 # via commercetools +msgpack==1.1.0 + # via cachecontrol mysqlclient==2.2.5 # via -r requirements/base.in newrelic==10.2.0 @@ -147,6 +164,8 @@ openedx-filters==1.11.0 # via -r requirements/base.in packaging==24.1 # via marshmallow +paypal-server-sdk==0.5.1 + # via -r requirements/base.in pbr==6.1.0 # via stevedore pillow==11.0.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index 5cb7e1a73..1c29a9e1c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,6 +8,20 @@ amqp==5.2.0 # via # -r requirements/validation.txt # kombu +apimatic-core==0.2.17 + # via + # -r requirements/validation.txt + # paypal-server-sdk +apimatic-core-interfaces==0.1.5 + # via + # -r requirements/validation.txt + # apimatic-core + # apimatic-requests-client-adapter + # paypal-server-sdk +apimatic-requests-client-adapter==0.1.6 + # via + # -r requirements/validation.txt + # paypal-server-sdk asgiref==3.7.2 # via # -r requirements/validation.txt @@ -32,6 +46,10 @@ build==1.2.2.post1 # via # -r requirements/pip-tools.txt # pip-tools +cachecontrol==0.12.14 + # via + # -r requirements/validation.txt + # apimatic-requests-client-adapter cachetools==5.5.0 # via # -r requirements/validation.txt @@ -254,6 +272,14 @@ jinja2==3.1.4 # code-annotations # coreschema # diff-cover +jsonpickle==3.3.0 + # via + # -r requirements/validation.txt + # apimatic-core +jsonpointer==2.4 + # via + # -r requirements/validation.txt + # apimatic-core keyring==25.5.0 # via # -r requirements/validation.txt @@ -301,6 +327,10 @@ more-itertools==10.5.0 # -r requirements/validation.txt # jaraco-classes # jaraco-functools +msgpack==1.1.0 + # via + # -r requirements/validation.txt + # cachecontrol mysqlclient==2.2.5 # via -r requirements/validation.txt newrelic==10.2.0 @@ -333,6 +363,8 @@ packaging==24.1 # tox path==16.16.0 # via edx-i18n-tools +paypal-server-sdk==0.5.1 + # via -r requirements/validation.txt pbr==6.1.0 # via # -r requirements/validation.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index b37232a49..515f902d1 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -12,6 +12,20 @@ amqp==5.2.0 # via # -r requirements/test.txt # kombu +apimatic-core==0.2.17 + # via + # -r requirements/test.txt + # paypal-server-sdk +apimatic-core-interfaces==0.1.5 + # via + # -r requirements/test.txt + # apimatic-core + # apimatic-requests-client-adapter + # paypal-server-sdk +apimatic-requests-client-adapter==0.1.6 + # via + # -r requirements/test.txt + # paypal-server-sdk asgiref==3.7.2 # via # -r requirements/test.txt @@ -41,6 +55,10 @@ build==1.2.2.post1 # kombu beautifulsoup4==4.12.3 # via pydata-sphinx-theme +cachecontrol==0.12.14 + # via + # -r requirements/test.txt + # apimatic-requests-client-adapter cachetools==5.5.0 # via # -r requirements/test.txt @@ -256,6 +274,14 @@ jinja2==3.1.4 # code-annotations # coreschema # sphinx +jsonpickle==3.3.0 + # via + # -r requirements/test.txt + # apimatic-core +jsonpointer==2.4 + # via + # -r requirements/test.txt + # apimatic-core keyring==25.5.0 # via twine kombu==5.4.2 @@ -290,6 +316,10 @@ more-itertools==10.5.0 # via # jaraco-classes # jaraco-functools +msgpack==1.1.0 + # via + # -r requirements/test.txt + # cachecontrol mysqlclient==2.2.5 # via -r requirements/test.txt newrelic==10.2.0 @@ -319,6 +349,8 @@ packaging==24.1 # pytest # sphinx # tox +paypal-server-sdk==0.5.1 + # via -r requirements/test.txt pbr==6.1.0 # via # -r requirements/test.txt diff --git a/requirements/production.txt b/requirements/production.txt index e55689f74..2c972f65b 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -8,6 +8,20 @@ amqp==5.2.0 # via # -r requirements/base.txt # kombu +apimatic-core==0.2.17 + # via + # -r requirements/base.txt + # paypal-server-sdk +apimatic-core-interfaces==0.1.5 + # via + # -r requirements/base.txt + # apimatic-core + # apimatic-requests-client-adapter + # paypal-server-sdk +apimatic-requests-client-adapter==0.1.6 + # via + # -r requirements/base.txt + # paypal-server-sdk asgiref==3.7.2 # via # -r requirements/base.txt @@ -23,6 +37,10 @@ billiard==4.2.1 # via # -r requirements/base.txt # celery +cachecontrol==0.12.14 + # via + # -r requirements/base.txt + # apimatic-requests-client-adapter celery[redis]==5.4.0 # via -r requirements/base.txt certifi==2024.8.30 @@ -162,6 +180,14 @@ jinja2==3.1.4 # via # -r requirements/base.txt # coreschema +jsonpickle==3.3.0 + # via + # -r requirements/base.txt + # apimatic-core +jsonpointer==2.4 + # via + # -r requirements/base.txt + # apimatic-core kombu==5.4.2 # via # -r requirements/base.txt @@ -180,6 +206,10 @@ marshmallow-enum==1.5.1 # via # -r requirements/base.txt # commercetools +msgpack==1.1.0 + # via + # -r requirements/test.txt + # cachecontrol mysqlclient==2.2.5 # via # -r requirements/base.txt @@ -204,6 +234,8 @@ packaging==24.1 # -r requirements/base.txt # gunicorn # marshmallow +paypal-server-sdk==0.5.1 + # via -r requirements/base.txt pbr==6.1.0 # via # -r requirements/base.txt diff --git a/requirements/quality.txt b/requirements/quality.txt index 252fe2127..6dca4f474 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -8,6 +8,20 @@ amqp==5.2.0 # via # -r requirements/test.txt # kombu +apimatic-core==0.2.17 + # via + # -r requirements/test.txt + # paypal-server-sdk +apimatic-core-interfaces==0.1.5 + # via + # -r requirements/test.txt + # apimatic-core + # apimatic-requests-client-adapter + # paypal-server-sdk +apimatic-requests-client-adapter==0.1.6 + # via + # -r requirements/test.txt + # paypal-server-sdk asgiref==3.7.2 # via # -r requirements/test.txt @@ -28,6 +42,10 @@ billiard==4.2.1 # via # -r requirements/test.txt # celery +cachecontrol==0.12.14 + # via + # -r requirements/test.txt + # apimatic-requests-client-adapter cachetools==5.5.0 # via # -r requirements/test.txt @@ -234,6 +252,14 @@ jinja2==3.1.4 # -r requirements/test.txt # code-annotations # coreschema +jsonpickle==3.3.0 + # via + # -r requirements/test.txt + # apimatic-core +jsonpointer==2.4 + # via + # -r requirements/test.txt + # apimatic-core keyring==25.5.0 # via twine kombu==5.4.2 @@ -268,6 +294,10 @@ more-itertools==10.5.0 # via # jaraco-classes # jaraco-functools +msgpack==1.1.0 + # via + # -r requirements/test.txt + # cachecontrol mysqlclient==2.2.5 # via -r requirements/test.txt newrelic==10.2.0 @@ -294,6 +324,8 @@ packaging==24.1 # pyproject-api # pytest # tox +paypal-server-sdk==0.5.1 + # via -r requirements/test.txt pbr==6.1.0 # via # -r requirements/test.txt diff --git a/requirements/test.txt b/requirements/test.txt index 1ad7269bb..6cbdd8ea1 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -8,6 +8,20 @@ amqp==5.2.0 # via # -r requirements/base.txt # kombu +apimatic-core==0.2.17 + # via + # -r requirements/base.txt + # paypal-server-sdk +apimatic-core-interfaces==0.1.5 + # via + # -r requirements/base.txt + # apimatic-core + # apimatic-requests-client-adapter + # paypal-server-sdk +apimatic-requests-client-adapter==0.1.6 + # via + # -r requirements/base.txt + # paypal-server-sdk asgiref==3.7.2 # via # -r requirements/base.txt @@ -27,6 +41,10 @@ billiard==4.2.1 # via # -r requirements/base.txt # celery +cachecontrol==0.12.14 + # via + # -r requirements/base.txt + # apimatic-requests-client-adapter cachetools==5.5.0 # via tox celery[redis]==5.4.0 @@ -199,6 +217,14 @@ jinja2==3.1.4 # -r requirements/base.txt # code-annotations # coreschema +jsonpickle==3.3.0 + # via + # -r requirements/base.txt + # apimatic-core +jsonpointer==2.4 + # via + # -r requirements/base.txt + # apimatic-core kombu==5.4.2 # via # -r requirements/base.txt @@ -221,6 +247,10 @@ mccabe==0.7.0 # via pylint mock==5.1.0 # via -r requirements/test.in +msgpack==1.1.0 + # via + # -r requirements/base.txt + # cachecontrol mysqlclient==2.2.5 # via -r requirements/base.txt newrelic==10.2.0 @@ -245,6 +275,8 @@ packaging==24.1 # pyproject-api # pytest # tox +paypal-server-sdk==0.5.1 + # via -r requirements/base.txt pbr==6.1.0 # via # -r requirements/base.txt diff --git a/requirements/validation.txt b/requirements/validation.txt index 5c181e201..0a4dd42da 100644 --- a/requirements/validation.txt +++ b/requirements/validation.txt @@ -9,6 +9,23 @@ amqp==5.2.0 # -r requirements/quality.txt # -r requirements/test.txt # kombu +apimatic-core==0.2.17 + # via + # -r requirements/quality.txt + # -r requirements/test.txt + # paypal-server-sdk +apimatic-core-interfaces==0.1.5 + # via + # -r requirements/quality.txt + # -r requirements/test.txt + # apimatic-core + # apimatic-requests-client-adapter + # paypal-server-sdk +apimatic-requests-client-adapter==0.1.6 + # via + # -r requirements/quality.txt + # -r requirements/test.txt + # paypal-server-sdk asgiref==3.7.2 # via # -r requirements/quality.txt @@ -35,6 +52,11 @@ billiard==4.2.1 # -r requirements/quality.txt # -r requirements/test.txt # celery +cachecontrol==0.12.14 + # via + # -r requirements/quality.txt + # -r requirements/test.txt + # apimatic-requests-client-adapter cachetools==5.5.0 # via # -r requirements/quality.txt @@ -307,6 +329,16 @@ jinja2==3.1.4 # -r requirements/test.txt # code-annotations # coreschema +jsonpickle==3.3.0 + # via + # -r requirements/quality.txt + # -r requirements/test.txt + # apimatic-core +jsonpointer==2.4 + # via + # -r requirements/quality.txt + # -r requirements/test.txt + # apimatic-core keyring==25.5.0 # via # -r requirements/quality.txt @@ -355,6 +387,11 @@ more-itertools==10.5.0 # -r requirements/quality.txt # jaraco-classes # jaraco-functools +msgpack==1.1.0 + # via + # -r requirements/quality.txt + # -r requirements/test.txt + # cachecontrol mysqlclient==2.2.5 # via # -r requirements/quality.txt @@ -391,6 +428,10 @@ packaging==24.1 # pyproject-api # pytest # tox +paypal-server-sdk==0.5.1 + # via + # -r requirements/quality.txt + # -r requirements/test.txt pbr==6.1.0 # via # -r requirements/quality.txt