Skip to content

Commit 6db4b0c

Browse files
Hugo Osvaldo BarreraWhyNotHugo
authored andcommitted
Use a JSONField for BasePayment.extra_data
1 parent 1cd04c5 commit 6db4b0c

File tree

18 files changed

+97
-115
lines changed

18 files changed

+97
-115
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ releases, in reverse chronological order.
77
v3.0.0
88
------
99
- Dropped support for Django 2.2.
10+
- ``BasePayment.extra_data`` is now a JSONField and django will handle the
11+
serialisation. Due to this, usage of the ``BasePayment.attrs`` proxy has been
12+
deprecated. A migration needs to be generated to update this column in place.
13+
Application code needs to be updated from ``payment.extra_data.field`` to
14+
``payment.extra_data["field"]``.
1015

1116
v2.0.1
1217
------

docs/backends.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ about the payment or the order, such as an order number, additional customer
108108
information, or a special comment or request from the customer. This can be
109109
accomplished by passing your data to the :class:`Payment` instance::
110110

111-
>>> payment.attrs.merchant_defined_data = {'01': 'foo', '02': 'bar'}
111+
>>> payment.extra_data["merchant_defined_data"] = {'01': 'foo', '02': 'bar'}
112112

113113
Fingerprinting::
114114

payments/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
from importlib import import_module
23
from typing import TYPE_CHECKING
34
from typing import Dict
45
from typing import Optional
@@ -155,7 +156,7 @@ def _default_provider_factory(variant: str, payment: Optional["BasePayment"] = N
155156
raise ValueError("Payment variant does not exist: {}".format(variant))
156157
if variant not in PROVIDER_CACHE:
157158
module_path, class_name = handler.rsplit(".", 1)
158-
module = __import__(str(module_path), globals(), locals(), [str(class_name)])
159+
module = import_module(module_path)
159160
class_ = getattr(module, class_name)
160161
PROVIDER_CACHE[variant] = class_(**config)
161162
return PROVIDER_CACHE[variant]

payments/cybersource/__init__.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,11 @@ def charge(self, payment, data):
166166
else:
167167
params = self._prepare_preauth(payment, data)
168168
response = self._make_request(payment, params)
169-
payment.attrs.capture = self._capture
169+
payment.extra_data["capture"] = self._capture
170170
payment.transaction_id = response.requestID
171171
if response.reasonCode == AUTHENTICATE_REQUIRED:
172172
xid = response.payerAuthEnrollReply.xid
173-
payment.attrs.xid = xid
173+
payment.extra_data["xid"] = xid
174174
payment.change_status(
175175
PaymentStatus.WAITING, message=_("3-D Secure verification in progress")
176176
)
@@ -279,8 +279,8 @@ def _get_params_for_new_payment(self, payment):
279279
"merchantReferenceCode": payment.id,
280280
}
281281
try:
282-
fingerprint_id = payment.attrs.fingerprint_session_id
283-
except AttributeError:
282+
fingerprint_id = payment.extra_data["fingerprint_session_id"]
283+
except KeyError:
284284
pass
285285
else:
286286
params["deviceFingerprintID"] = fingerprint_id
@@ -291,7 +291,7 @@ def _get_params_for_new_payment(self, payment):
291291

292292
def _make_request(self, payment, params):
293293
response = self.client.service.runTransaction(**params)
294-
payment.attrs.last_response = self._serialize_response(response)
294+
payment.extra_data["last_response"] = self._serialize_response(response)
295295
return response
296296

297297
def _prepare_payer_auth_validation_check(self, payment, card_data, pa_response):
@@ -300,7 +300,7 @@ def _prepare_payer_auth_validation_check(self, payment, card_data, pa_response):
300300
check_service.signedPARes = pa_response
301301
params = self._get_params_for_new_payment(payment)
302302
params["payerAuthValidateService"] = check_service
303-
if payment.attrs.capture:
303+
if payment.extra_data["capture"]:
304304
service = self.client.factory.create("data:CCCreditService")
305305
service._run = "true"
306306
params["ccCreditService"] = service
@@ -445,8 +445,8 @@ def _prepare_items(self, payment):
445445

446446
def _prepare_merchant_defined_data(self, payment):
447447
try:
448-
merchant_defined_data = payment.attrs.merchant_defined_data
449-
except AttributeError:
448+
merchant_defined_data = payment.extra_data["merchant_defined_data"]
449+
except KeyError:
450450
return
451451
else:
452452
data = self.client.factory.create("data:MerchantDefinedData")
@@ -476,7 +476,7 @@ def _serialize_response(self, response):
476476

477477
def process_data(self, payment, request):
478478
xid = request.POST.get("MD")
479-
if xid != payment.attrs.xid:
479+
if xid != payment.extra_data["xid"]:
480480
return redirect(payment.get_failure_url())
481481
if payment.status in [PaymentStatus.CONFIRMED, PaymentStatus.PREAUTH]:
482482
return redirect(payment.get_success_url())

payments/cybersource/forms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def __init__(self, *args, **kwargs):
3939
super().__init__(*args, **kwargs)
4040
if self.provider.org_id:
4141
try:
42-
fingerprint_id = self.payment.attrs.fingerprint_session_id
42+
fingerprint_id = self.payment.extra_data["fingerprint_session_id"]
4343
except KeyError:
4444
fingerprint_id = str(uuid4())
4545
self.fields["fingerprint"] = FingerprintInput(
@@ -55,7 +55,7 @@ def clean(self):
5555
if not self.errors:
5656
if self.provider.org_id:
5757
fingerprint = cleaned_data["fingerprint"]
58-
self.payment.attrs.fingerprint_session_id = fingerprint
58+
self.payment.extra_data["fingerprint_session_id"] = fingerprint
5959
if not self.payment.transaction_id:
6060
try:
6161
self.provider.charge(self.payment, cleaned_data)

payments/cybersource/test_cybersource.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from decimal import Decimal
2-
from typing import Dict
32
from unittest import TestCase
43
from unittest.mock import MagicMock
54
from unittest.mock import Mock
@@ -38,10 +37,10 @@ class Payment(Mock):
3837
transaction_id = None
3938
captured_amount = 0
4039
message = ""
41-
42-
class attrs:
43-
fingerprint_session_id = "fake"
44-
merchant_defined_data: Dict[str, str] = {}
40+
extra_data = {
41+
"fingerprint_session_id": "fake",
42+
"merchant_defined_data": {},
43+
}
4544

4645
def get_process_url(self):
4746
return "http://example.com"
@@ -152,7 +151,7 @@ def test_provider_redirects_on_success_captured_payment(
152151
):
153152
transaction_id = 1234
154153
xid = "abc"
155-
self.payment.attrs.xid = xid
154+
self.payment.extra_data["xid"] = xid
156155

157156
response = MagicMock()
158157
response.requestID = transaction_id
@@ -187,7 +186,7 @@ def test_provider_redirects_on_success_preauth_payment(
187186
)
188187
transaction_id = 1234
189188
xid = "abc"
190-
self.payment.attrs.xid = xid
189+
self.payment.extra_data["xid"] = xid
191190

192191
response = MagicMock()
193192
response.requestID = transaction_id
@@ -217,7 +216,7 @@ def test_provider_redirects_on_success_preauth_payment(
217216
def test_provider_redirects_on_failure(self, mocked_request, mocked_redirect):
218217
transaction_id = 1234
219218
xid = "abc"
220-
self.payment.attrs.xid = xid
219+
self.payment.extra_data["xid"] = xid
221220

222221
response = MagicMock()
223222
response.requestID = transaction_id

payments/mercadopago/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def create_preference(self, payment: BasePayment):
7171
if payment.transaction_id:
7272
raise ValueError("This payment already has a preference.")
7373

74-
payment.attrs.external_reference = uuid4().hex
74+
payment.extra_data["external_reference"] = uuid4().hex
7575

7676
payload = {
7777
"auto_return": "all",
@@ -86,7 +86,7 @@ def create_preference(self, payment: BasePayment):
8686
}
8787
for item in payment.get_purchased_items()
8888
],
89-
"external_reference": payment.attrs.external_reference,
89+
"external_reference": payment.extra_data["external_reference"],
9090
"back_urls": {
9191
"success": self.get_return_url(payment),
9292
"pending": self.get_return_url(payment),
@@ -214,7 +214,7 @@ def poll_for_updates(self, payment: BasePayment):
214214
"""
215215
data = self.client.payment().search(
216216
{
217-
"external_reference": payment.attrs.external_reference,
217+
"external_reference": payment.extra_data["external_reference"],
218218
}
219219
)
220220

payments/mercadopago/test_mercadopago.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Payment(Mock):
2525
captured_amount = 0
2626
transaction_id: Optional[str] = None
2727
billing_email = "[email protected]"
28+
extra_data: dict = {}
2829

2930
def change_status(self, status, message=""):
3031
self.status = status

payments/models.py

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import json
1+
import warnings
22
from typing import Iterable
33
from typing import Union
44
from uuid import uuid4
@@ -14,29 +14,6 @@
1414
from .core import provider_factory
1515

1616

17-
class PaymentAttributeProxy:
18-
def __init__(self, payment):
19-
self._payment = payment
20-
super().__init__()
21-
22-
def __getattr__(self, item):
23-
data = json.loads(self._payment.extra_data or "{}")
24-
try:
25-
return data[item]
26-
except KeyError as e:
27-
raise AttributeError(*e.args)
28-
29-
def __setattr__(self, key, value):
30-
if key == "_payment":
31-
return super().__setattr__(key, value)
32-
try:
33-
data = json.loads(self._payment.extra_data)
34-
except ValueError:
35-
data = {}
36-
data[key] = value
37-
self._payment.extra_data = json.dumps(data)
38-
39-
4017
class BasePayment(models.Model):
4118
"""
4219
Represents a single transaction. Each instance has one or more PaymentItem.
@@ -78,7 +55,7 @@ class BasePayment(models.Model):
7855
billing_email = models.EmailField(blank=True)
7956
billing_phone = PhoneNumberField(blank=True)
8057
customer_ip_address = models.GenericIPAddressField(blank=True, null=True)
81-
extra_data = models.TextField(blank=True, default="")
58+
extra_data = models.JSONField(blank=True, default=dict)
8259
message = models.TextField(blank=True, default="")
8360
token = models.CharField(max_length=36, blank=True, default="")
8461
captured_amount = models.DecimalField(max_digits=9, decimal_places=2, default="0.0")
@@ -225,14 +202,18 @@ def refund(self, amount=None):
225202

226203
@property
227204
def attrs(self):
228-
"""A JSON-serialised wrapper around `extra_data`.
229-
230-
This property exposes a a dict or list which is serialised into the `extra_data`
231-
text field. Usage of this wrapper is preferred over accessing the underlying
232-
field directly.
233-
234-
You may think of this as a `JSONField` which is saved to the `extra_data`
235-
column.
236-
"""
237-
# TODO: Deprecate in favour of JSONField when we drop support for django 2.2.
238-
return PaymentAttributeProxy(self)
205+
warnings.warn(
206+
"Using BasePayment.attrs is deprecated. Use BasePayment.extra_data instead",
207+
DeprecationWarning,
208+
stacklevel=2,
209+
)
210+
return self.extra_data
211+
212+
@attrs.setter
213+
def attrs(self, value):
214+
warnings.warn(
215+
"Using BasePayment.attrs is deprecated. Use BasePayment.extra_data instead",
216+
DeprecationWarning,
217+
stacklevel=2,
218+
)
219+
self.extra_data = value

payments/paypal/__init__.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,34 +77,34 @@ def __init__(
7777
super().__init__(capture=capture)
7878

7979
def set_response_data(self, payment, response, is_auth=False):
80-
extra_data = json.loads(payment.extra_data or "{}")
80+
extra_data = payment.extra_data or {}
8181
if is_auth:
8282
extra_data["auth_response"] = response
8383
else:
8484
extra_data["response"] = response
8585
if "links" in response:
8686
extra_data["links"] = {link["rel"]: link for link in response["links"]}
87-
payment.extra_data = json.dumps(extra_data)
87+
payment.extra_data = extra_data
8888
payment.save()
8989

9090
def set_response_links(self, payment, response):
9191
transaction = response["transactions"][0]
9292
related_resources = transaction["related_resources"][0]
9393
resource_key = "sale" if self._capture else "authorization"
9494
links = related_resources[resource_key]["links"]
95-
extra_data = json.loads(payment.extra_data or "{}")
95+
extra_data = payment.extra_data or {}
9696
extra_data["links"] = {link["rel"]: link for link in links}
97-
payment.extra_data = json.dumps(extra_data)
97+
payment.extra_data = extra_data
9898
payment.save()
9999

100100
def set_error_data(self, payment, error):
101-
extra_data = json.loads(payment.extra_data or "{}")
101+
extra_data = payment.extra_data or {}
102102
extra_data["error"] = error
103-
payment.extra_data = json.dumps(extra_data)
103+
payment.extra_data = extra_data
104104
payment.save()
105105

106106
def _get_links(self, payment):
107-
extra_data = json.loads(payment.extra_data or "{}")
107+
extra_data = payment.extra_data or {}
108108
links = extra_data.get("links", {})
109109
return links
110110

@@ -141,7 +141,7 @@ def post(self, payment, *args, **kwargs):
141141
return data
142142

143143
def get_last_response(self, payment, is_auth=False):
144-
extra_data = json.loads(payment.extra_data or "{}")
144+
extra_data = payment.extra_data or {}
145145
if is_auth:
146146
return extra_data.get("auth_response", {})
147147
return extra_data.get("response", {})
@@ -249,7 +249,7 @@ def process_data(self, payment, request):
249249
except PaymentError:
250250
return redirect(failure_url)
251251
self.set_response_links(payment, executed_payment)
252-
payment.attrs.payer_info = executed_payment["payer"]["payer_info"]
252+
payment.extra_data["payer_info"] = executed_payment["payer"]["payer_info"]
253253
if self._capture:
254254
payment.captured_amount = payment.total
255255
payment.change_status(PaymentStatus.CONFIRMED)

0 commit comments

Comments
 (0)