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

Ignore duplicates #16

Merged
merged 3 commits into from
Jul 14, 2015
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
18 changes: 14 additions & 4 deletions adyen/facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def _record_audit_trail(self, request, status, txn_details):

pass

def handle_payment_feedback(self, request, record_audit_trail):
def handle_payment_feedback(self, request):
"""
Validate, process, optionally record audit trail and provide feedback
about the current payment response.
Expand Down Expand Up @@ -167,9 +167,8 @@ def handle_payment_feedback(self, request, record_audit_trail):
success, status, details = response.process()
txn_details = self._unpack_details(details)

# ... and record the audit trail if instructed to...
if record_audit_trail:
self._record_audit_trail(request, status, txn_details)
# ... and record the audit trail.
self._record_audit_trail(request, status, txn_details)

# ... prepare the feedback data...
output_data = {
Expand Down Expand Up @@ -220,6 +219,17 @@ def assess_notification_relevance(self, request):
if request.POST.get(Constants.EVENT_CODE) != Constants.EVENT_CODE_AUTHORISATION:
return False, True

# Adyen duplicates many notifications. This bit makes sure we ignore them.
# "Duplicate notifications have the same corresponding values for their eventCode and
# pspReference fields." https://docs.adyen.com/display/TD/Accept+notifications
reference = request.POST[Constants.PSP_REFERENCE]
# The event code gets checked above, so we only need to check for the reference now.
if AdyenTransaction.objects.filter(reference=reference).exists():
# We already stored a transaction with this reference, so we can ignore the
# notification. As above, we still acknowledge it to Adyen, in case it missed
# our previous acknowledgment.
return False, True

# Seems legit, just do it :)
return True, True

Expand Down
7 changes: 1 addition & 6 deletions adyen/scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ def _normalize_feedback(self, feedback):
return success, common_status, details

def handle_payment_feedback(self, request):
return self._normalize_feedback(
Facade().handle_payment_feedback(request, record_audit_trail=True))

def check_payment_outcome(self, request):
return self._normalize_feedback(
Facade().handle_payment_feedback(request, record_audit_trail=False))
return self._normalize_feedback(Facade().handle_payment_feedback(request))

def assess_notification_relevance(self, request):
return Facade().assess_notification_relevance(request)
Expand Down
39 changes: 19 additions & 20 deletions tests/test_adyen.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,26 +261,6 @@ def test_get_origin_ip_address(self):

def test_handle_authorised_payment(self):

# Before the test, there are no recorded transactions in the database.
num_recorded_transactions = AdyenTransaction.objects.all().count()
self.assertEqual(num_recorded_transactions, 0)

self.request.GET = deepcopy(AUTHORISED_PAYMENT_PARAMS_GET)
success, status, details = self.scaffold.check_payment_outcome(self.request)

self.assertTrue(success)
self.assertEqual(status, Scaffold.PAYMENT_STATUS_ACCEPTED)
self.assertEqual(details.get('amount'), 13894)
self.assertEqual(details.get('ip_address'), '127.0.0.1')
self.assertEqual(details.get('method'), 'adyen')
self.assertEqual(details.get('psp_reference'), '8814136447235922')
self.assertEqual(details.get('status'), 'AUTHORISED')

# After calling `check_payment_outcome`, there are still no recorded
# transactions in the database.
num_recorded_transactions = AdyenTransaction.objects.all().count()
self.assertEqual(num_recorded_transactions, 0)

self.request.GET = deepcopy(AUTHORISED_PAYMENT_PARAMS_GET)
success, status, details = self.scaffold.handle_payment_feedback(self.request)

Expand Down Expand Up @@ -441,3 +421,22 @@ def test_assess_notification_relevance(self):
self.request.POST['eventCode'] = 'REPORT_AVAILABLE'
must_process, must_ack = self.scaffold.assess_notification_relevance(self.request)
self.assertTupleEqual((must_process, must_ack), (False, True))

def test_assert_duplicate_notifications(self):
"""
This test tests that duplicate notifications are ignored.
"""
self.request.method = 'POST'
self.request.POST = deepcopy(AUTHORISED_PAYMENT_PARAMS_POST)

# We have a valid request. So let's confirm that we think we should process
# and acknowledge it.
assert (True, True) == self.scaffold.assess_notification_relevance(self.request)

# Let's process it then.
__, __, __ = self.scaffold.handle_payment_feedback(self.request)

# As we have already processed that request, we now shouldn't process the request
# any more. But we still acknowledge it.
assert (False, True) == self.scaffold.assess_notification_relevance(self.request)