Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle paypal payment receipt #308

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class Languages:
"directDiscounts[*]"
)

STRIPE_PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED = "succeeded"
PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED = "succeeded"

EDX_STRIPE_PAYMENT_INTERFACE_NAME = "stripe_edx"

EDX_PAYPAL_PAYMENT_INTERFACE_NAME = "paypal_edx"
14 changes: 12 additions & 2 deletions commerce_coordinator/apps/commercetools/catalog_info/edx_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from commerce_coordinator.apps.commercetools.catalog_info.constants import (
EDX_STRIPE_PAYMENT_INTERFACE_NAME,
STRIPE_PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED,
PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED,
EdXFieldNames,
TwoUKeys
)
Expand Down Expand Up @@ -51,7 +51,7 @@ def get_edx_lms_user_name(customer: CTCustomer):
def get_edx_successful_stripe_payment(order: CTOrder) -> Union[CTPayment, None]:
for pr in order.payment_info.payments:
pmt = pr.obj
if pmt.payment_status.interface_code == STRIPE_PAYMENT_STATUS_INTERFACE_CODE_SUCCEEDED \
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
Expand All @@ -65,6 +65,16 @@ def get_edx_payment_intent_id(order: CTOrder) -> Union[str, None]:
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
Expand Down
21 changes: 6 additions & 15 deletions commerce_coordinator/apps/commercetools/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

from commerce_coordinator.apps.commercetools.catalog_info.edx_utils import (
get_edx_payment_intent_id,
get_edx_refund_amount
get_edx_refund_amount,
get_edx_successful_payment_info
)
from commerce_coordinator.apps.commercetools.clients import CommercetoolsAPIClient
from commerce_coordinator.apps.commercetools.constants import COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM
Expand Down Expand Up @@ -108,24 +109,14 @@ 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)

ret_val = {
"order_data": ct_order,
"psp": psp,
shafqatfarhan marked this conversation as resolved.
Show resolved Hide resolved
"payment_intent_id": intent_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)
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['has_been_refunded'] = False
ret_val['payment_data'] = None

shafqatfarhan marked this conversation as resolved.
Show resolved Hide resolved
return ret_val
except CommercetoolsError as err: # pragma no cover
log.exception(f"[{type(self).__name__}] Commercetools Error: {err}, {err.errors}")
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions commerce_coordinator/apps/paypal/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
App configuration for the Commerce Coordinator paypal app.
"""
from django.apps import AppConfig


class PayPalConfig(AppConfig):
name = 'commerce_coordinator.apps.paypal'
Empty file.
32 changes: 32 additions & 0 deletions commerce_coordinator/apps/paypal/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Pipelines for paypal app
"""

import logging
from urllib.parse import urlencode

from django.conf import settings
from openedx_filters import PipelineStep

from commerce_coordinator.apps.commercetools.catalog_info.constants import EDX_PAYPAL_PAYMENT_INTERFACE_NAME
from commerce_coordinator.apps.core.constants import PipelineCommand

logger = logging.getLogger(__name__)


class GetPayPalPaymentReceipt(PipelineStep):
""" Purpare PayPal payment recipt """

def run_filter(self, psp=None, payment_intent_id=None, **params):

if payment_intent_id is None or psp != EDX_PAYPAL_PAYMENT_INTERFACE_NAME:
return PipelineCommand.CONTINUE.value

activity_page_url = settings.PAYMENT_PROCESSOR_CONFIG['edx']['paypal']['user_activity_page_url']
query_params = {'free_text_search': params.get('order_number')}

redirect_url = activity_page_url + '?' + urlencode(query_params)

return {
'redirect_url': redirect_url,
}
1 change: 1 addition & 0 deletions commerce_coordinator/apps/paypal/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Constants for PayPal app tests."""
30 changes: 30 additions & 0 deletions commerce_coordinator/apps/paypal/tests/test_pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
""" PayPal Pipeline Tests"""
from unittest import TestCase

from django.conf import settings
from django.test import override_settings

from commerce_coordinator.apps.paypal.pipeline import GetPayPalPaymentReceipt

TEST_PAYMENT_PROCESSOR_CONFIG = settings.PAYMENT_PROCESSOR_CONFIG
ACTIVITY_URL = "https://test.paypal.com/myaccount/activities/"
TEST_PAYMENT_PROCESSOR_CONFIG['edx']['paypal']['user_activity_page_url'] = ACTIVITY_URL


class TestGetPayPalPaymentReceipt(TestCase):
"""A pytest Test case for the GetPayPalPaymentReceipt Pipeline Step"""

@override_settings(PAYMENT_PROCESSOR_CONFIG=TEST_PAYMENT_PROCESSOR_CONFIG)
def test_pipeline_step(self):
order_number = '123'
paypal_payment_pipe = GetPayPalPaymentReceipt("test_pipe", None)

result: dict = paypal_payment_pipe.run_filter(
edx_lms_user_id=1,
psp='paypal_edx',
payment_intent_id="00001",
order_number=order_number
)
url = settings.PAYMENT_PROCESSOR_CONFIG['edx']['paypal']['user_activity_page_url']
redirect_url = f"{url}?free_text_search={order_number}"
self.assertEqual(redirect_url, result['redirect_url'])
27 changes: 14 additions & 13 deletions commerce_coordinator/apps/stripe/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from openedx_filters import PipelineStep
from stripe.error import StripeError

from commerce_coordinator.apps.commercetools.catalog_info.constants import EDX_STRIPE_PAYMENT_INTERFACE_NAME
from commerce_coordinator.apps.core.constants import PaymentMethod, PipelineCommand
from commerce_coordinator.apps.stripe.clients import StripeAPIClient
from commerce_coordinator.apps.stripe.constants import Currency
Expand Down Expand Up @@ -214,26 +215,26 @@
""" Pull the receipt if the payment_intent is set """

# pylint: disable=unused-argument
def run_filter(self, payment_intent_id=None, **params):
def run_filter(self, psp=None, payment_intent_id=None, **params):
tag = type(self).__name__

if payment_intent_id is None:
logger.debug(f'[{tag}] payment_intent_id not set, skipping.')
return PipelineCommand.CONTINUE.value

stripe_api_client = StripeAPIClient()

payment_intent = stripe_api_client.retrieve_payment_intent(
payment_intent_id,
["latest_charge"]
)

receipt_url = payment_intent.latest_charge.receipt_url
if psp == EDX_STRIPE_PAYMENT_INTERFACE_NAME:
stripe_api_client = StripeAPIClient()
payment_intent = stripe_api_client.retrieve_payment_intent(
payment_intent_id,
["latest_charge"]
)
receipt_url = payment_intent.latest_charge.receipt_url

return {
'payment_intent': payment_intent,
'redirect_url': receipt_url
}
return {
'payment_intent': payment_intent,
'redirect_url': receipt_url
}
return None

Check failure on line 237 in commerce_coordinator/apps/stripe/pipeline.py

View workflow job for this annotation

GitHub Actions / tests (ubuntu-20.04, 3.12, django42)

Missing coverage

Missing coverage on line 237
mubbsharanwar marked this conversation as resolved.
Show resolved Hide resolved


class RefundPaymentIntent(PipelineStep):
Expand Down
7 changes: 6 additions & 1 deletion commerce_coordinator/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def root(*path_fragments):
'commerce_coordinator.apps.frontend_app_payment.apps.FrontendAppPaymentConfig',
'commerce_coordinator.apps.lms.apps.LmsConfig',
'commerce_coordinator.apps.stripe.apps.StripeConfig',
'commerce_coordinator.apps.paypal.apps.PayPalConfig',
'commerce_coordinator.apps.titan.apps.TitanConfig',
'commerce_coordinator.apps.commercetools',
)
Expand Down Expand Up @@ -374,7 +375,8 @@ def root(*path_fragments):
'commerce_coordinator.apps.ecommerce.pipeline.GetLegacyEcommerceReceiptRedirectUrl',
'commerce_coordinator.apps.rollout.pipeline.HaltIfRedirectUrlProvided',
'commerce_coordinator.apps.commercetools.pipeline.FetchOrderDetailsByOrderNumber',
'commerce_coordinator.apps.stripe.pipeline.GetPaymentIntentReceipt'
'commerce_coordinator.apps.stripe.pipeline.GetPaymentIntentReceipt',
'commerce_coordinator.apps.paypal.pipeline.GetPayPalPaymentReceipt'
]
},
"org.edx.coordinator.lms.order.refund.requested.v1": {
Expand Down Expand Up @@ -429,6 +431,9 @@ def root(*path_fragments):
'source_system_identifier': 'edx/commerce_coordinator?v=1',
'webhook_endpoint_secret': STRIPE_WEBHOOK_ENDPOINT_SECRET,
},
'paypal': {
'user_activity_page_url': '',
},
},
}
# END PAYMENT PROCESSING
Expand Down
3 changes: 3 additions & 0 deletions commerce_coordinator/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@
'source_system_identifier': 'edx/commerce_coordinator?v=1',
'webhook_endpoint_secret': 'SET-ME-PLEASE',
},
'paypal': {
'user_activity_page_url': 'https://sandbox.paypal.com/myaccount/activities/',
},
},
}

Expand Down
3 changes: 3 additions & 0 deletions commerce_coordinator/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
'source_system_identifier': 'edx/commerce_coordinator?v=1',
'webhook_endpoint_secret': 'SET-ME-PLEASE',
},
'paypal': {
'user_activity_page_url': 'https://test.paypal.com/myaccount/activities/',
},
},
}
# END PAYMENT PROCESSING
Expand Down
10 changes: 10 additions & 0 deletions docs/commerce_coordinator.apps.paypal.migrations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
commerce\_coordinator.apps.paypal.migrations package
====================================================

Module contents
---------------

.. automodule:: commerce_coordinator.apps.paypal.migrations
:members:
:undoc-members:
:show-inheritance:
38 changes: 38 additions & 0 deletions docs/commerce_coordinator.apps.paypal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
commerce\_coordinator.apps.paypal package
=========================================

Subpackages
-----------

.. toctree::
:maxdepth: 4

commerce_coordinator.apps.paypal.migrations
commerce_coordinator.apps.paypal.tests

Submodules
----------

commerce\_coordinator.apps.paypal.apps module
---------------------------------------------

.. automodule:: commerce_coordinator.apps.paypal.apps
:members:
:undoc-members:
:show-inheritance:

commerce\_coordinator.apps.paypal.pipeline module
-------------------------------------------------

.. automodule:: commerce_coordinator.apps.paypal.pipeline
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

.. automodule:: commerce_coordinator.apps.paypal
:members:
:undoc-members:
:show-inheritance:
21 changes: 21 additions & 0 deletions docs/commerce_coordinator.apps.paypal.tests.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
commerce\_coordinator.apps.paypal.tests package
===============================================

Submodules
----------

commerce\_coordinator.apps.paypal.tests.test\_pipeline module
-------------------------------------------------------------

.. automodule:: commerce_coordinator.apps.paypal.tests.test_pipeline
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

.. automodule:: commerce_coordinator.apps.paypal.tests
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/commerce_coordinator.apps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Subpackages
commerce_coordinator.apps.lms
commerce_coordinator.apps.rollout
commerce_coordinator.apps.stripe
commerce_coordinator.apps.paypal
commerce_coordinator.apps.titan

Module contents
Expand Down
Loading