Skip to content
This repository has been archived by the owner on Jun 27, 2024. It is now read-only.

Commit

Permalink
PayPal: Save captured_amount when processing data
Browse files Browse the repository at this point in the history
Partially fixes: jazzband#309
  • Loading branch information
radekholy24 committed May 16, 2024
1 parent f655fb2 commit ceecbad
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ v3.0.0
- Added support for Python 3.11, Django 4.1 and Django 4.2.
- Stripe backends now supports webhooks
- New :ref:`webhook settings <webhooks>`
- Fixed PayPal backends not saving captured_amount when processing data.

v2.0.0
------
Expand Down
3 changes: 3 additions & 0 deletions payments/paypal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ def process_data(self, payment, request):
payment.attrs.payer_info = executed_payment["payer"]["payer_info"]
if self._capture:
payment.captured_amount = payment.total
type(payment).objects.filter(pk=payment.pk).update(
captured_amount=payment.captured_amount
)
payment.change_status(PaymentStatus.CONFIRMED)
else:
payment.change_status(PaymentStatus.PREAUTH)
Expand Down
115 changes: 114 additions & 1 deletion payments/paypal/test_paypal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import json
from copy import deepcopy
from datetime import date
from decimal import Decimal
from unittest import TestCase
Expand Down Expand Up @@ -33,7 +34,58 @@
}


class PaymentQuerySet(Mock):
__payments = {}

def create(self, **kwargs):
if kwargs:
raise NotImplementedError(f"arguments not supported yet: {kwargs}")
id_ = max(self.__payments) + 1 if self.__payments else 1
self.__payments[id_] = {}
payment = Payment()
payment.id = id_
payment.save()
return payment

def get(self, *args, **kwargs):
if args or kwargs:
return self.filter(*args, **kwargs).get()
payment = Payment()
(payment_fields,) = self.__payments.values()
for payment_field_name, payment_field_value in payment_fields.items():
setattr(payment, payment_field_name, deepcopy(payment_field_value))
return payment

def filter(self, *args, pk=None, **kwargs):
if args or kwargs:
raise NotImplementedError(f"arguments not supported yet: {args}, {kwargs}")
if pk is not None:
return PaymentQuerySet(
{pk_: payment for pk_, payment in self.__payments.items() if pk_ == pk}
)
return self

def update(self, **kwargs):
for payment in self.__payments.values():
for field_name, field_value in kwargs.items():
if not any(
field.name == field_name
for field in Payment._meta.get_fields(
include_parents=True, include_hidden=True
)
):
raise NotImplementedError(
f"updating unknown field not supported yet: {field_name}"
)
payment[field_name] = deepcopy(field_value)

def delete(self):
self.__payments.clear()


class Payment(Mock):
objects = PaymentQuerySet()

id = 1
description = "payment"
currency = "USD"
Expand All @@ -57,9 +109,14 @@ class Payment(Mock):
}
)

@property
def pk(self):
return self.id

def change_status(self, status, message=""):
self.status = status
self.message = message
self.save(update_fields=["status", "message"])

def get_failure_url(self):
return "http://cancel.com"
Expand All @@ -77,10 +134,58 @@ def get_purchased_items(self):
def get_success_url(self):
return "http://success.com"

def save(self, *args, update_fields=None, **kwargs):
if args or kwargs:
raise NotImplementedError(f"arguments not supported yet: {args}, {kwargs}")
if update_fields is None:
update_fields = {
field.name
for field in self._meta.get_fields(
include_parents=True, include_hidden=True
)
}
Payment.objects.filter(pk=self.pk).update(
**{field: getattr(self, field) for field in update_fields}
)

def refresh_from_db(self, *args, **kwargs):
if args or kwargs:
raise NotImplementedError(f"arguments not supported yet: {args}, {kwargs}")
payment_from_db = Payment.objects.get(pk=self.pk)
for field in self._meta.get_fields(include_parents=True, include_hidden=True):
field_value_from_db = getattr(payment_from_db, field.name)
setattr(self, field.name, field_value_from_db)

class Meta(Mock):
def get_fields(self, include_parents=True, include_hidden=False):
fields = []
for field_name in {
"id",
"description",
"currency",
"delivery",
"status",
"tax",
"token",
"total",
"captured_amount",
"variant",
"transaction_id",
"message",
"extra_data",
}:
field = Mock()
field.name = field_name
fields.append(field)
return tuple(fields)

_meta = Meta()


class TestPaypalProvider(TestCase):
def setUp(self):
self.payment = Payment()
Payment.objects.delete()
self.payment = Payment.objects.create()
self.provider = PaypalProvider(secret=SECRET, client_id=CLIENT_ID)

def test_provider_raises_redirect_needed_on_success(self):
Expand Down Expand Up @@ -171,6 +276,9 @@ def test_provider_redirects_on_success_captured_payment(

self.assertEqual(self.payment.status, PaymentStatus.CONFIRMED)
self.assertEqual(self.payment.captured_amount, self.payment.total)
self.payment.refresh_from_db()
self.assertEqual(self.payment.status, PaymentStatus.CONFIRMED)
self.assertEqual(self.payment.captured_amount, self.payment.total)

@patch("requests.post")
@patch("payments.paypal.redirect")
Expand Down Expand Up @@ -202,6 +310,9 @@ def test_provider_redirects_on_success_preauth_payment(

self.assertEqual(self.payment.status, PaymentStatus.PREAUTH)
self.assertEqual(self.payment.captured_amount, Decimal("0"))
self.payment.refresh_from_db()
self.assertEqual(self.payment.status, PaymentStatus.PREAUTH)
self.assertEqual(self.payment.captured_amount, Decimal("0"))

@patch("payments.paypal.redirect")
def test_provider_request_without_payerid_redirects_on_failure(
Expand All @@ -211,6 +322,8 @@ def test_provider_request_without_payerid_redirects_on_failure(
request.GET = {"token": "test", "PayerID": None}
self.provider.process_data(self.payment, request)
self.assertEqual(self.payment.status, PaymentStatus.REJECTED)
self.payment.refresh_from_db()
self.assertEqual(self.payment.status, PaymentStatus.REJECTED)

@patch("requests.post")
def test_provider_renews_access_token(self, mocked_post):
Expand Down

0 comments on commit ceecbad

Please sign in to comment.