Skip to content

Commit cdb7281

Browse files
radekholy24WhyNotHugo
authored andcommitted
PayPal: Save captured_amount when processing data
Partially fixes: #309
1 parent f655fb2 commit cdb7281

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

CHANGELOG.rst

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ v3.0.0
1313
- Added support for Python 3.11, Django 4.1 and Django 4.2.
1414
- Stripe backends now supports webhooks
1515
- New :ref:`webhook settings <webhooks>`
16+
- Fixed PayPal backends not saving captured_amount when processing data.
1617

1718
v2.0.0
1819
------

payments/paypal/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ def process_data(self, payment, request):
252252
payment.attrs.payer_info = executed_payment["payer"]["payer_info"]
253253
if self._capture:
254254
payment.captured_amount = payment.total
255+
type(payment).objects.filter(pk=payment.pk).update(
256+
captured_amount=payment.captured_amount
257+
)
255258
payment.change_status(PaymentStatus.CONFIRMED)
256259
else:
257260
payment.change_status(PaymentStatus.PREAUTH)

payments/paypal/test_paypal.py

+114-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import json
4+
from copy import deepcopy
45
from datetime import date
56
from decimal import Decimal
67
from unittest import TestCase
@@ -33,7 +34,58 @@
3334
}
3435

3536

37+
class PaymentQuerySet(Mock):
38+
__payments = {}
39+
40+
def create(self, **kwargs):
41+
if kwargs:
42+
raise NotImplementedError(f"arguments not supported yet: {kwargs}")
43+
id_ = max(self.__payments) + 1 if self.__payments else 1
44+
self.__payments[id_] = {}
45+
payment = Payment()
46+
payment.id = id_
47+
payment.save()
48+
return payment
49+
50+
def get(self, *args, **kwargs):
51+
if args or kwargs:
52+
return self.filter(*args, **kwargs).get()
53+
payment = Payment()
54+
(payment_fields,) = self.__payments.values()
55+
for payment_field_name, payment_field_value in payment_fields.items():
56+
setattr(payment, payment_field_name, deepcopy(payment_field_value))
57+
return payment
58+
59+
def filter(self, *args, pk=None, **kwargs):
60+
if args or kwargs:
61+
raise NotImplementedError(f"arguments not supported yet: {args}, {kwargs}")
62+
if pk is not None:
63+
return PaymentQuerySet(
64+
{pk_: payment for pk_, payment in self.__payments.items() if pk_ == pk}
65+
)
66+
return self
67+
68+
def update(self, **kwargs):
69+
for payment in self.__payments.values():
70+
for field_name, field_value in kwargs.items():
71+
if not any(
72+
field.name == field_name
73+
for field in Payment._meta.get_fields(
74+
include_parents=True, include_hidden=True
75+
)
76+
):
77+
raise NotImplementedError(
78+
f"updating unknown field not supported yet: {field_name}"
79+
)
80+
payment[field_name] = deepcopy(field_value)
81+
82+
def delete(self):
83+
self.__payments.clear()
84+
85+
3686
class Payment(Mock):
87+
objects = PaymentQuerySet()
88+
3789
id = 1
3890
description = "payment"
3991
currency = "USD"
@@ -57,9 +109,14 @@ class Payment(Mock):
57109
}
58110
)
59111

112+
@property
113+
def pk(self):
114+
return self.id
115+
60116
def change_status(self, status, message=""):
61117
self.status = status
62118
self.message = message
119+
self.save(update_fields=["status", "message"])
63120

64121
def get_failure_url(self):
65122
return "http://cancel.com"
@@ -77,10 +134,58 @@ def get_purchased_items(self):
77134
def get_success_url(self):
78135
return "http://success.com"
79136

137+
def save(self, *args, update_fields=None, **kwargs):
138+
if args or kwargs:
139+
raise NotImplementedError(f"arguments not supported yet: {args}, {kwargs}")
140+
if update_fields is None:
141+
update_fields = {
142+
field.name
143+
for field in self._meta.get_fields(
144+
include_parents=True, include_hidden=True
145+
)
146+
}
147+
Payment.objects.filter(pk=self.pk).update(
148+
**{field: getattr(self, field) for field in update_fields}
149+
)
150+
151+
def refresh_from_db(self, *args, **kwargs):
152+
if args or kwargs:
153+
raise NotImplementedError(f"arguments not supported yet: {args}, {kwargs}")
154+
payment_from_db = Payment.objects.get(pk=self.pk)
155+
for field in self._meta.get_fields(include_parents=True, include_hidden=True):
156+
field_value_from_db = getattr(payment_from_db, field.name)
157+
setattr(self, field.name, field_value_from_db)
158+
159+
class Meta(Mock):
160+
def get_fields(self, include_parents=True, include_hidden=False):
161+
fields = []
162+
for field_name in {
163+
"id",
164+
"description",
165+
"currency",
166+
"delivery",
167+
"status",
168+
"tax",
169+
"token",
170+
"total",
171+
"captured_amount",
172+
"variant",
173+
"transaction_id",
174+
"message",
175+
"extra_data",
176+
}:
177+
field = Mock()
178+
field.name = field_name
179+
fields.append(field)
180+
return tuple(fields)
181+
182+
_meta = Meta()
183+
80184

81185
class TestPaypalProvider(TestCase):
82186
def setUp(self):
83-
self.payment = Payment()
187+
Payment.objects.delete()
188+
self.payment = Payment.objects.create()
84189
self.provider = PaypalProvider(secret=SECRET, client_id=CLIENT_ID)
85190

86191
def test_provider_raises_redirect_needed_on_success(self):
@@ -171,6 +276,9 @@ def test_provider_redirects_on_success_captured_payment(
171276

172277
self.assertEqual(self.payment.status, PaymentStatus.CONFIRMED)
173278
self.assertEqual(self.payment.captured_amount, self.payment.total)
279+
self.payment.refresh_from_db()
280+
self.assertEqual(self.payment.status, PaymentStatus.CONFIRMED)
281+
self.assertEqual(self.payment.captured_amount, self.payment.total)
174282

175283
@patch("requests.post")
176284
@patch("payments.paypal.redirect")
@@ -202,6 +310,9 @@ def test_provider_redirects_on_success_preauth_payment(
202310

203311
self.assertEqual(self.payment.status, PaymentStatus.PREAUTH)
204312
self.assertEqual(self.payment.captured_amount, Decimal("0"))
313+
self.payment.refresh_from_db()
314+
self.assertEqual(self.payment.status, PaymentStatus.PREAUTH)
315+
self.assertEqual(self.payment.captured_amount, Decimal("0"))
205316

206317
@patch("payments.paypal.redirect")
207318
def test_provider_request_without_payerid_redirects_on_failure(
@@ -211,6 +322,8 @@ def test_provider_request_without_payerid_redirects_on_failure(
211322
request.GET = {"token": "test", "PayerID": None}
212323
self.provider.process_data(self.payment, request)
213324
self.assertEqual(self.payment.status, PaymentStatus.REJECTED)
325+
self.payment.refresh_from_db()
326+
self.assertEqual(self.payment.status, PaymentStatus.REJECTED)
214327

215328
@patch("requests.post")
216329
def test_provider_renews_access_token(self, mocked_post):

0 commit comments

Comments
 (0)