From 22687efd056d386a850ff17aeb471856698e9d3d Mon Sep 17 00:00:00 2001 From: Syed Sajjad Hussain Shah Date: Mon, 6 Jan 2025 17:03:13 +0500 Subject: [PATCH] temp: POC on PayPal paritial refund with multiple items --- .../commercetools/catalog_info/edx_utils.py | 22 +++++++++++++++---- .../apps/commercetools/filters.py | 9 +++++++- .../apps/commercetools/pipeline.py | 16 +++++++++----- .../apps/commercetools/serializers.py | 3 +++ .../sub_messages/signals_delayed.py | 1 + .../apps/commercetools/sub_messages/tasks.py | 6 ++++- .../apps/commercetools/views.py | 6 +++-- commerce_coordinator/apps/paypal/clients.py | 13 +++++++++-- commerce_coordinator/apps/paypal/pipeline.py | 2 +- commerce_coordinator/apps/rollout/utils.py | 8 +++++-- 10 files changed, 68 insertions(+), 18 deletions(-) diff --git a/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py b/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py index 6a3144ff3..b11391b30 100644 --- a/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py +++ b/commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py @@ -76,13 +76,27 @@ 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_info(payment: CTPayment) -> decimal: - refund_amount = decimal.Decimal(0.00) +def _cents_to_dollars(in_amount): + return in_amount.cent_amount / pow( + 10, in_amount.fraction_digits + if hasattr(in_amount, 'fraction_digits') + else 2 + ) + +def get_line_item_price(order: CTOrder, return_line_item_id: str): + for line_item in get_edx_items(order): + if line_item.id == return_line_item_id: + return _cents_to_dollars(line_item.price.value) + return decimal.Decimal(0.00) + +def get_edx_refund_info(payment: CTPayment, order: CTOrder, return_line_item_id: str) -> (decimal.Decimal, str): 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)) interaction_id = transaction.interaction_id - return refund_amount, interaction_id + + refund_amount = get_line_item_price(order, return_line_item_id) + print('\n\n\n\n\n line item price = ',refund_amount, '\n\n\n\n\n') return refund_amount, interaction_id diff --git a/commerce_coordinator/apps/commercetools/filters.py b/commerce_coordinator/apps/commercetools/filters.py index 7177a169e..ab15081fc 100644 --- a/commerce_coordinator/apps/commercetools/filters.py +++ b/commerce_coordinator/apps/commercetools/filters.py @@ -10,7 +10,13 @@ class OrderRefundRequested(OpenEdxPublicFilter): filter_type = "org.edx.coordinator.commercetools.order.refund.requested.v1" @classmethod - def run_filter(cls, order_id, return_line_item_return_id, message_id): + def run_filter( + cls, + order_id, + return_line_item_return_id, + return_line_item_id, + message_id + ): """ Call the PipelineStep(s) defined for this filter. Arguments: @@ -21,4 +27,5 @@ def run_filter(cls, order_id, return_line_item_return_id, message_id): """ return super().run_pipeline(order_id=order_id, return_line_item_return_id=return_line_item_return_id, + return_line_item_id=return_line_item_id, message_id=message_id) diff --git a/commerce_coordinator/apps/commercetools/pipeline.py b/commerce_coordinator/apps/commercetools/pipeline.py index 05c6b5a95..c590dbbab 100644 --- a/commerce_coordinator/apps/commercetools/pipeline.py +++ b/commerce_coordinator/apps/commercetools/pipeline.py @@ -18,12 +18,13 @@ ) from commerce_coordinator.apps.commercetools.catalog_info.edx_utils import ( get_edx_refund_info, - get_edx_successful_payment_info + get_edx_successful_payment_info, get_edx_items ) from commerce_coordinator.apps.commercetools.clients import CommercetoolsAPIClient from commerce_coordinator.apps.commercetools.constants import COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM from commerce_coordinator.apps.commercetools.data import order_from_commercetools -from commerce_coordinator.apps.commercetools.utils import create_retired_fields, has_refund_transaction +from commerce_coordinator.apps.commercetools.utils import create_retired_fields, has_refund_transaction, \ + has_full_refund_transaction from commerce_coordinator.apps.core.constants import PipelineCommand from commerce_coordinator.apps.core.exceptions import InvalidFilterType from commerce_coordinator.apps.rollout.utils import ( @@ -132,7 +133,12 @@ def run_filter(self, active_order_management_system, order_number, **kwargs): # class FetchOrderDetailsByOrderID(PipelineStep): """ Fetch the order details and if we can, set the PaymentIntent """ - def run_filter(self, active_order_management_system, order_id, **kwargs): # pylint: disable=arguments-differ + def run_filter(self, + active_order_management_system, + order_id, + return_line_item_id, + **kwargs + ): # pylint: disable=arguments-differ """ Execute a filter with the signature specified. Arguments: @@ -169,10 +175,10 @@ def run_filter(self, active_order_management_system, order_id, **kwargs): # pyl 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) + refund_amount, ct_transaction_interaction_id = get_edx_refund_info(ct_payment, ct_order, return_line_item_id) 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['has_been_refunded'] = is_commercetools_line_item_already_refunded(ct_order, return_line_item_id) or has_full_refund_transaction(ct_payment) ret_val['payment_data'] = ct_payment else: ret_val['amount_in_cents'] = decimal.Decimal(0.00) diff --git a/commerce_coordinator/apps/commercetools/serializers.py b/commerce_coordinator/apps/commercetools/serializers.py index 5a3775817..0f9f10f58 100644 --- a/commerce_coordinator/apps/commercetools/serializers.py +++ b/commerce_coordinator/apps/commercetools/serializers.py @@ -157,3 +157,6 @@ def get_return_info(self): def get_return_line_item_return_id(self): return self.get_return_info().get('id', None) + + def get_return_line_item_id(self): + return self.get_return_info().get('lineItemId', None) diff --git a/commerce_coordinator/apps/commercetools/sub_messages/signals_delayed.py b/commerce_coordinator/apps/commercetools/sub_messages/signals_delayed.py index bb953af49..1bed4eb81 100644 --- a/commerce_coordinator/apps/commercetools/sub_messages/signals_delayed.py +++ b/commerce_coordinator/apps/commercetools/sub_messages/signals_delayed.py @@ -42,6 +42,7 @@ def fulfill_order_returned_signal(**kwargs): async_result = fulfill_order_returned_signal_task.delay( order_id=kwargs['order_id'], return_line_item_return_id=kwargs['return_line_item_return_id'], + return_line_item_id=kwargs['return_line_item_id'], message_id=kwargs['message_id'] ) return async_result.id diff --git a/commerce_coordinator/apps/commercetools/sub_messages/tasks.py b/commerce_coordinator/apps/commercetools/sub_messages/tasks.py index 41a821f6e..9f373460d 100644 --- a/commerce_coordinator/apps/commercetools/sub_messages/tasks.py +++ b/commerce_coordinator/apps/commercetools/sub_messages/tasks.py @@ -215,6 +215,7 @@ def fulfill_order_sanctioned_message_signal_task( def fulfill_order_returned_signal_task( order_id, return_line_item_return_id, + return_line_item_id, message_id ): """Celery task for an order return (and refunded) message.""" @@ -278,7 +279,10 @@ def _prepare_segment_event_properties(in_order, return_line_item_return_id): # 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 + order_id=order_id, + return_line_item_return_id=return_line_item_return_id, + return_line_item_id=return_line_item_id, + message_id=message_id ) if 'refund_response' in result and result['refund_response']: diff --git a/commerce_coordinator/apps/commercetools/views.py b/commerce_coordinator/apps/commercetools/views.py index f159248a9..29a821b87 100644 --- a/commerce_coordinator/apps/commercetools/views.py +++ b/commerce_coordinator/apps/commercetools/views.py @@ -133,18 +133,20 @@ def post(self, request): message_details.is_valid(raise_exception=True) order_id = message_details.data['order_id'] return_line_item_return_id = message_details.get_return_line_item_return_id() + return_line_item_id = message_details.get_return_line_item_id() message_id = message_details.data['message_id'] - if self._is_running(tag, order_id): # pragma no cover + if self._is_running(tag, return_line_item_return_id): # pragma no cover self.meta_should_mark_not_running = False return Response(status=status.HTTP_200_OK) else: - self.mark_running(tag, order_id) + self.mark_running(tag, return_line_item_return_id) fulfill_order_returned_signal.send_robust( sender=self, order_id=order_id, return_line_item_return_id=return_line_item_return_id, + return_line_item_id=return_line_item_id, message_id=message_id ) diff --git a/commerce_coordinator/apps/paypal/clients.py b/commerce_coordinator/apps/paypal/clients.py index 1ef2bc99a..f9204e0f0 100644 --- a/commerce_coordinator/apps/paypal/clients.py +++ b/commerce_coordinator/apps/paypal/clients.py @@ -20,7 +20,7 @@ def __init__(self): ), ) - def refund_order(self, capture_id): + def refund_order(self, capture_id, amount): """ Capture PayPal refund. @@ -34,7 +34,16 @@ def refund_order(self, capture_id): paypal_client = self.paypal_client payments_controller: PaymentsController = paypal_client.payments - collect = {"capture_id": capture_id, "prefer": "return=representation"} + collect = { + "capture_id": capture_id, + "prefer": "return=representation", + "body": { + "amount": { + "value": amount, + "currency_code": "USD" + } + } + } refund_response = payments_controller.captures_refund(collect) if refund_response.body: diff --git a/commerce_coordinator/apps/paypal/pipeline.py b/commerce_coordinator/apps/paypal/pipeline.py index 955bd4c60..c4b990d22 100644 --- a/commerce_coordinator/apps/paypal/pipeline.py +++ b/commerce_coordinator/apps/paypal/pipeline.py @@ -70,7 +70,7 @@ def run_filter( try: paypal_client = PayPalClient() - paypal_refund_response = paypal_client.refund_order(capture_id=ct_transaction_interaction_id) + paypal_refund_response = paypal_client.refund_order(capture_id=ct_transaction_interaction_id, amount=amount_in_cents) return { 'refund_response': paypal_refund_response, diff --git a/commerce_coordinator/apps/rollout/utils.py b/commerce_coordinator/apps/rollout/utils.py index 60204001a..a8b464311 100644 --- a/commerce_coordinator/apps/rollout/utils.py +++ b/commerce_coordinator/apps/rollout/utils.py @@ -5,6 +5,8 @@ from commercetools.platform.models import Order as CTOrder from commercetools.platform.models import ReturnItem as CTReturnItem +from commercetools.platform.models import ReturnPaymentState + def is_legacy_order(order_number: str) -> bool: @@ -38,8 +40,10 @@ def is_commercetools_line_item_already_refunded(order: CTOrder, order_line_item_ return_info_return_items = get_order_return_info_return_items(order) - return len(list(filter(lambda item: item.line_item_id == order_line_item_id, return_info_return_items))) >= 1 - + return len(list(filter( + lambda item: item.line_item_id == order_line_item_id and item.payment_state == ReturnPaymentState.REFUNDED, + return_info_return_items + ))) >= 1 def is_commercetools_stripe_refund(source_system: str) -> bool: """