Skip to content

Added suport for transactions refund #7

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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: 7 additions & 1 deletion paypal/pro/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,13 @@ def manangeRecurringPaymentsProfileStatus(self, params, fail_silently=False):
return nvp_obj

def refundTransaction(self, params):
raise NotImplementedError
defaults = {"method": "refundTransaction", "refundtype": 'Full'}
required = L("transactionid")

nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj

def _is_recurring(self, params):
"""Returns True if the item passed is a recurring transaction."""
Expand Down
7 changes: 6 additions & 1 deletion paypal/standard/ipn/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ def _verify_postback(self):
def send_signals(self):
"""Shout for the world to hear whether a txn was successful."""
# Transaction signals:
if self.is_transaction():
if self.is_transaction() and not self.is_recurring():
if self.flag:
payment_was_flagged.send(sender=self)
elif self.is_refund():
payment_refunded.send(sender=self)
else:
payment_was_successful.send(sender=self)
# Recurring payment signals:
Expand All @@ -38,6 +40,9 @@ def send_signals(self):
recurring_payment.send(sender=self)
elif self.is_recurring_cancel():
recurring_cancel.send(sender=self)
elif self.is_refund():
recurring_refunded.send(sender=self)

# Subscription signals:
else:
if self.is_subscription_cancellation():
Expand Down
7 changes: 6 additions & 1 deletion paypal/standard/ipn/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# Sent when a payment is flagged.
payment_was_flagged = Signal()

# Sent when a payment is refunded
payment_refunded = Signal()

# Sent when a subscription was cancelled.
subscription_cancel = Signal()

Expand All @@ -30,4 +33,6 @@
# recurring_payment
recurring_payment = Signal()

recurring_cancel = Signal()
recurring_cancel = Signal()

recurring_refunded = Signal()
36 changes: 29 additions & 7 deletions paypal/standard/ipn/tests/test_ipn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
from django.test import TestCase
from django.test.client import Client

from paypal.standard.models import ST_PP_CANCELLED
from paypal.standard.models import ST_PP_CANCELLED, ST_PP_REFUNDED
from paypal.standard.ipn.models import PayPalIPN
from paypal.standard.ipn.signals import (payment_was_successful,
payment_was_flagged)
payment_was_flagged, recurring_refunded, payment_refunded)


IPN_POST_PARAMS = {
"protection_eligibility": "Ineligible",
"last_name": "User",
"txn_id": "51403485VH153354B",
"receiver_email": settings.PAYPAL_RECEIVER_EMAIL,
"receiver_email": str(settings.PAYPAL_RECEIVER_EMAIL),
"payment_status": "Completed",
"payment_gross": "10.00",
"tax": "0.00",
Expand Down Expand Up @@ -60,7 +60,7 @@ def tearDown(self):
settings.DEBUG = self.old_debug
PayPalIPN._postback = self.old_postback

def assertGotSignal(self, signal, flagged):
def assertGotSignal(self, signal, flagged, params=IPN_POST_PARAMS):
# Check the signal was sent. These get lost if they don't reference self.
self.got_signal = False
self.signal_obj = None
Expand All @@ -70,7 +70,7 @@ def handle_signal(sender, **kwargs):
self.signal_obj = sender
signal.connect(handle_signal)

response = self.client.post("/ipn/", IPN_POST_PARAMS)
response = self.client.post("/ipn/", params)
self.assertEqual(response.status_code, 200)
ipns = PayPalIPN.objects.all()
self.assertEqual(len(ipns), 1)
Expand Down Expand Up @@ -115,11 +115,33 @@ def test_vaid_payment_status_cancelled(self):
ipn_obj = PayPalIPN.objects.all()[0]
self.assertEqual(ipn_obj.flag, False)


def test_duplicate_txn_id(self):
self.client.post("/ipn/", IPN_POST_PARAMS)
self.client.post("/ipn/", IPN_POST_PARAMS)
self.assertEqual(len(PayPalIPN.objects.all()), 2)
ipn_obj = PayPalIPN.objects.order_by('-created_at')[0]
self.assertEqual(ipn_obj.flag, True)
self.assertEqual(ipn_obj.flag_info, "Duplicate txn_id. (51403485VH153354B)")
self.assertEqual(ipn_obj.flag_info, "Duplicate txn_id. (51403485VH153354B)")

def test_refund_for_recurring_payment(self):
update = {
"payment_status": ST_PP_REFUNDED,
"reason_code": "refund",
"parent_txn_id": "1NK420530S625752Y",
"recurring_payment_id": "I-N8KNB3KKD9NF"
}
params = IPN_POST_PARAMS.copy()
params.update(update)

self.assertGotSignal(recurring_refunded, False, params)

def test_refund_for_non_recurring_payment(self):
update = {
"payment_status": ST_PP_REFUNDED,
"reason_code": "refund",
"parent_txn_id": "1NK420530S625752Y"
}
params = IPN_POST_PARAMS.copy()
params.update(update)

self.assertGotSignal(payment_refunded, False, params)
6 changes: 5 additions & 1 deletion paypal/standard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ST_PP_PENDING = 'Pending'
ST_PP_PROCESSED = 'Processed'
ST_PP_REFUSED = 'Refused'
ST_PP_REFUNDED = 'Refunded'
ST_PP_REVERSED = 'Reversed'
ST_PP_REWARDED = 'Rewarded'
ST_PP_UNCLAIMED = 'Unclaimed'
Expand All @@ -29,7 +30,7 @@ class PayPalStandardBase(Model):
# @@@ Might want to add all these one distant day.
# FLAG_CODE_CHOICES = (
# PAYMENT_STATUS_CHOICES = "Canceled_ Reversal Completed Denied Expired Failed Pending Processed Refunded Reversed Voided".split()
PAYMENT_STATUS_CHOICES = (ST_PP_ACTIVE, ST_PP_CANCELLED, ST_PP_CLEARED, ST_PP_COMPLETED, ST_PP_DENIED, ST_PP_PAID, ST_PP_PENDING, ST_PP_PROCESSED, ST_PP_REFUSED, ST_PP_REVERSED, ST_PP_REWARDED, ST_PP_UNCLAIMED, ST_PP_UNCLEARED)
PAYMENT_STATUS_CHOICES = (ST_PP_ACTIVE, ST_PP_CANCELLED, ST_PP_CLEARED, ST_PP_COMPLETED, ST_PP_DENIED, ST_PP_PAID, ST_PP_PENDING, ST_PP_PROCESSED, ST_PP_REFUSED, ST_PP_REFUNDED, ST_PP_REVERSED, ST_PP_REWARDED, ST_PP_UNCLAIMED, ST_PP_UNCLEARED)
# AUTH_STATUS_CHOICES = "Completed Pending Voided".split()
# ADDRESS_STATUS_CHOICES = "confirmed unconfirmed".split()
# PAYER_STATUS_CHOICES = "verified / unverified".split()
Expand Down Expand Up @@ -219,6 +220,9 @@ def is_recurring_payment(self):

def is_recurring_cancel(self):
return self.txn_type == "recurring_payment_profile_cancel"

def is_refund(self):
return self.reason_code == "refund"

def set_flag(self, info, code=None):
"""Sets a flag on the transaction and also sets a reason."""
Expand Down