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 intermittent PDT issues #245

Closed
wants to merge 2 commits into from
Closed
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
8 changes: 8 additions & 0 deletions paypal/standard/pdt/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django import forms

from paypal.standard.forms import PayPalStandardBaseForm
from paypal.standard.pdt.models import PayPalPDT

Expand All @@ -12,3 +14,9 @@ class Meta:
exclude = ['ipaddress', 'flag', 'flag_code',
'flag_info', 'query', 'response',
'created_at', 'updated', 'form_view']


class PayPalPDTCallbackForm(forms.ModelForm):
class Meta:
model = PayPalPDT
fields = ['amt', 'cm', 'tx', 'st']
Copy link
Owner

@spookylukey spookylukey Mar 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I've read it correctly, the only piece of information that we use is tx (looking at PayPalPDT._postback(). Do you agree, if so, should we make that the only field? Are all the other fields set from the postback call? My reading of the PayPal docs suggests that it is actually just the transaction ID we are looking for at this point.

(Please forgive my ignorance, I've only ever personally used IPN)

It would be great, either way, if we could have a reason for the specific set of fields included here, and a comment on PayPalPDTCallbackForm that explained its purpose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember my logic, but I assume I chose these four to match the PDT-specific fields here:

amt = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
cm = models.CharField(max_length=255, blank=True)
sig = models.CharField(max_length=255, blank=True)
tx = models.CharField(max_length=255, blank=True)
st = models.CharField(max_length=32, blank=True)

I suspect I omitted sig because it doesn't appear in either the old-style or new-style query parameters.

As you say, tx is the only field used explicitly, but the others are saved to the instance, and I wanted to avoid introducing any changes in behaviour in this PR.

Having said all that, I'm not convinced that PayPalPDT needs these custom fields at all — all the interesting information comes from the postback and is saved to fields defined in PayPalStandardBase.

40 changes: 40 additions & 0 deletions paypal/standard/pdt/tests/test_pdt.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ def test_custom_passthrough(self):
pdt_obj = PayPalPDT.objects.all()[0]
self.assertEqual(pdt_obj.custom, self.get_params['cm'])

def test_pdt_full_params(self):
# New callback parameters as of May 2021
self.assertEqual(len(PayPalPDT.objects.all()), 0)
params = {
"PayerID": "8MZ9FQTSAMUPJ",
"st": "Completed",
"tx": "4WJ86550014687441",
"cc": "EUR",
"amt": "225.00",
"cm": "a3e192b8-8fea-4a86-b2e8-d5bf502e36be",
"payer_email": "buyer_1239119200_per%40yoursite.com",
"payer_id": "8MZ9FQTSAMUPJ",
"payer_status": "VERIFIED",
"first_name": "Test",
"last_name": "User",
"txn_id": "1ED550410S3402306",
"mc_currency": "EUR",
"mc_fee": "6.88",
"mc_gross": "225.00",
"protection_eligibility": "Ineligible",
"payment_fee": "6.88",
"payment_gross": "5.00",
"payment_status": "Completed",
"payment_type": "instant",
"handling_amount": "0.00",
"shipping": "0.00",
"item_name": "Example",
"quantity": "1",
"txn_type": "web_accept",
"payment_date": "2021-11-05T10:23:28Z",
"business": "test@example.com",
"receiver_id": "746LDC2EQAP4W",
"notify_version": "UNVERSIONED",
"custom": "ABC123",
"verify_sign": "ABC123",
}
paypal_response = self.client.get("/pdt/", params)
self.assertContains(paypal_response, 'Transaction complete', status_code=200)
self.assertEqual(len(PayPalPDT.objects.all()), 1)


class MockedResponse:
content = 'test'
Expand Down
4 changes: 2 additions & 2 deletions paypal/standard/pdt/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from paypal.standard.pdt.forms import PayPalPDTForm
from paypal.standard.pdt.forms import PayPalPDTCallbackForm
from paypal.standard.pdt.models import PayPalPDT
from paypal.utils import warn_untested

Expand Down Expand Up @@ -32,7 +32,7 @@ def process_pdt(request):
pass

if pdt_obj is None:
form = PayPalPDTForm(request.GET)
form = PayPalPDTCallbackForm(request.GET)
if form.is_valid():
try:
pdt_obj = form.save(commit=False)
Expand Down