Skip to content

Commit

Permalink
Use a JSONField for BasePayment.extra_data
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Osvaldo Barrera authored and WhyNotHugo committed Oct 14, 2023
1 parent 9855b63 commit f89f664
Show file tree
Hide file tree
Showing 10 changed files with 45 additions and 39 deletions.
3 changes: 2 additions & 1 deletion payments/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import re
from importlib import import_module
from typing import TYPE_CHECKING
from urllib.parse import urlencode
from urllib.parse import urljoin
Expand Down Expand Up @@ -167,7 +168,7 @@ def _default_provider_factory(variant: str, payment: BasePayment | None = None):
raise ValueError(f"Payment variant does not exist: {variant}")
if variant not in PROVIDER_CACHE:
module_path, class_name = handler.rsplit(".", 1)
module = __import__(str(module_path), globals(), locals(), [str(class_name)])
module = import_module(module_path)
class_ = getattr(module, class_name)
PROVIDER_CACHE[variant] = class_(**config)
return PROVIDER_CACHE[variant]
Expand Down
18 changes: 9 additions & 9 deletions payments/cybersource/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ def charge(self, payment, data):
else:
params = self._prepare_preauth(payment, data)
response = self._make_request(payment, params)
payment.attrs.capture = self._capture
payment.extra_data.capture = self._capture
payment.transaction_id = response.requestID
if response.reasonCode == AUTHENTICATE_REQUIRED:
xid = response.payerAuthEnrollReply.xid
payment.attrs.xid = xid
payment.extra_data.xid = xid
payment.change_status(
PaymentStatus.WAITING, message=_("3-D Secure verification in progress")
)
Expand Down Expand Up @@ -276,8 +276,8 @@ def _get_params_for_new_payment(self, payment):
"merchantReferenceCode": payment.id,
}
try:
fingerprint_id = payment.attrs.fingerprint_session_id
except AttributeError:
fingerprint_id = payment.extra_data["fingerprint_session_id"]
except KeyError:
pass
else:
params["deviceFingerprintID"] = fingerprint_id
Expand All @@ -288,7 +288,7 @@ def _get_params_for_new_payment(self, payment):

def _make_request(self, payment, params):
response = self.client.service.runTransaction(**params)
payment.attrs.last_response = self._serialize_response(response)
payment.extra_data.last_response = self._serialize_response(response)
return response

def _prepare_payer_auth_validation_check(self, payment, card_data, pa_response):
Expand All @@ -297,7 +297,7 @@ def _prepare_payer_auth_validation_check(self, payment, card_data, pa_response):
check_service.signedPARes = pa_response
params = self._get_params_for_new_payment(payment)
params["payerAuthValidateService"] = check_service
if payment.attrs.capture:
if payment.extra_data.capture:
service = self.client.factory.create("data:CCCreditService")
service._run = "true"
params["ccCreditService"] = service
Expand Down Expand Up @@ -440,8 +440,8 @@ def _prepare_items(self, payment):

def _prepare_merchant_defined_data(self, payment):
try:
merchant_defined_data = payment.attrs.merchant_defined_data
except AttributeError:
merchant_defined_data = payment.extra_data["merchant_defined_data"]
except KeyError:
return None
else:
data = self.client.factory.create("data:MerchantDefinedData")
Expand Down Expand Up @@ -471,7 +471,7 @@ def _serialize_response(self, response):

def process_data(self, payment, request):
xid = request.POST.get("MD")
if xid != payment.attrs.xid:
if xid != payment.extra_data.xid:
return redirect(payment.get_failure_url())
if payment.status in [PaymentStatus.CONFIRMED, PaymentStatus.PREAUTH]:
return redirect(payment.get_success_url())
Expand Down
4 changes: 2 additions & 2 deletions payments/cybersource/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.provider.org_id:
try:
fingerprint_id = self.payment.attrs.fingerprint_session_id
fingerprint_id = self.payment.extra_data.fingerprint_session_id
except KeyError:
fingerprint_id = str(uuid4())
self.fields["fingerprint"] = FingerprintInput(
Expand All @@ -57,7 +57,7 @@ def clean(self):
if not self.errors:
if self.provider.org_id:
fingerprint = cleaned_data["fingerprint"]
self.payment.attrs.fingerprint_session_id = fingerprint
self.payment.extra_data.fingerprint_session_id = fingerprint
if not self.payment.transaction_id:
try:
self.provider.charge(self.payment, cleaned_data)
Expand Down
6 changes: 3 additions & 3 deletions payments/cybersource/test_cybersource.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def test_provider_redirects_on_success_captured_payment(
):
transaction_id = 1234
xid = "abc"
self.payment.attrs.xid = xid
self.payment.extra_data.xid = xid

response = MagicMock()
response.requestID = transaction_id
Expand Down Expand Up @@ -188,7 +188,7 @@ def test_provider_redirects_on_success_preauth_payment(
)
transaction_id = 1234
xid = "abc"
self.payment.attrs.xid = xid
self.payment.extra_data.xid = xid

response = MagicMock()
response.requestID = transaction_id
Expand Down Expand Up @@ -218,7 +218,7 @@ def test_provider_redirects_on_success_preauth_payment(
def test_provider_redirects_on_failure(self, mocked_request, mocked_redirect):
transaction_id = 1234
xid = "abc"
self.payment.attrs.xid = xid
self.payment.extra_data.xid = xid

response = MagicMock()
response.requestID = transaction_id
Expand Down
6 changes: 3 additions & 3 deletions payments/mercadopago/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def create_preference(self, payment: BasePayment):
if payment.transaction_id:
raise ValueError("This payment already has a preference.")

payment.attrs.external_reference = uuid4().hex
payment.extra_data.external_reference = uuid4().hex

payload = {
"auto_return": "all",
Expand All @@ -89,7 +89,7 @@ def create_preference(self, payment: BasePayment):
}
for item in payment.get_purchased_items()
],
"external_reference": payment.attrs.external_reference,
"external_reference": payment.extra_data.external_reference,
"back_urls": {
"success": self.get_return_url(payment),
"pending": self.get_return_url(payment),
Expand Down Expand Up @@ -218,7 +218,7 @@ def poll_for_updates(self, payment: BasePayment):
"""
data = self.client.payment().search(
{
"external_reference": payment.attrs.external_reference,
"external_reference": payment.extra_data.external_reference,
}
)

Expand Down
29 changes: 17 additions & 12 deletions payments/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import json
import warnings
from typing import Iterable
from uuid import uuid4

Expand Down Expand Up @@ -80,7 +81,7 @@ class BasePayment(models.Model):
billing_email = models.EmailField(blank=True)
billing_phone = PhoneNumberField(blank=True)
customer_ip_address = models.GenericIPAddressField(blank=True, null=True)
extra_data = models.TextField(blank=True, default="")
extra_data = models.JsonField(blank=True, default=dict)
message = models.TextField(blank=True, default="")
token = models.CharField(max_length=36, blank=True, default="")
captured_amount = models.DecimalField(max_digits=9, decimal_places=2, default="0.0")
Expand Down Expand Up @@ -226,14 +227,18 @@ def refund(self, amount=None):

@property
def attrs(self):
"""A JSON-serialised wrapper around `extra_data`.
This property exposes a a dict or list which is serialised into the `extra_data`
text field. Usage of this wrapper is preferred over accessing the underlying
field directly.
You may think of this as a `JSONField` which is saved to the `extra_data`
column.
"""
# TODO: Deprecate in favour of JSONField when we drop support for django 2.2.
return PaymentAttributeProxy(self)
warnings.warn(
"Using BasePayment.attrs is deprecated. Use BasePayment.extra_data instead",
DeprecationWarning,
stacklevel=2,
)
return self.extra_data

@attrs.setter
def attrs(self, value):
warnings.warn(
"Using BasePayment.attrs is deprecated. Use BasePayment.extra_data instead",
DeprecationWarning,
stacklevel=2,
)
self.extra_data = value
2 changes: 1 addition & 1 deletion payments/paypal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def process_data(self, payment, request):
except PaymentError:
return redirect(failure_url)
self.set_response_links(payment, executed_payment)
payment.attrs.payer_info = executed_payment["payer"]["payer_info"]
payment.extra_data.payer_info = executed_payment["payer"]["payer_info"]
if self._capture:
payment.captured_amount = payment.total
payment.change_status(PaymentStatus.CONFIRMED)
Expand Down
6 changes: 3 additions & 3 deletions payments/stripe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,19 @@ def capture(self, payment, amount=None):
except stripe.InvalidRequestError as e:
payment.change_status(PaymentStatus.REFUNDED)
raise PaymentError("Payment already refunded") from e
payment.attrs.capture = json.dumps(charge)
payment.extra_data.capture = json.dumps(charge)
return Decimal(amount) / 100

def release(self, payment):
charge = stripe.Charge.retrieve(payment.transaction_id)
charge.refund()
payment.attrs.release = json.dumps(charge)
payment.extra_data.release = json.dumps(charge)

def refund(self, payment, amount=None):
amount = int((amount or payment.total) * 100)
charge = stripe.Charge.retrieve(payment.transaction_id)
charge.refund(amount=amount)
payment.attrs.refund = json.dumps(charge)
payment.extra_data.refund = json.dumps(charge)
return Decimal(amount) / 100


Expand Down
2 changes: 1 addition & 1 deletion payments/stripe/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def clean(self):

def save(self):
self.payment.transaction_id = self.charge.id
self.payment.attrs.charge = json.dumps(self.charge)
self.payment.extra_data.charge = json.dumps(self.charge)
self.payment.change_status(PaymentStatus.PREAUTH)
if self.provider._capture:
self.payment.capture()
Expand Down
8 changes: 4 additions & 4 deletions payments/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ class Payment(BasePayment):
class TestBasePayment(TestCase):
def test_payment_attributes(self):
payment = Payment(extra_data='{"attr1": "test1", "attr2": "test2"}')
self.assertEqual(payment.attrs.attr1, "test1")
self.assertEqual(payment.attrs.attr2, "test2")
self.assertEqual(getattr(payment.attrs, "attr5", None), None)
self.assertEqual(hasattr(payment.attrs, "attr7"), False)
self.assertEqual(payment.extra_data.attr1, "test1")
self.assertEqual(payment.extra_data.attr2, "test2")
self.assertEqual(getattr(payment.extra_data, "attr5", None), None)
self.assertEqual(hasattr(payment.extra_data, "attr7"), False)

def test_capture_with_wrong_status(self):
payment = Payment(variant="default", status=PaymentStatus.WAITING)
Expand Down

0 comments on commit f89f664

Please sign in to comment.