-
-
Notifications
You must be signed in to change notification settings - Fork 288
Description
Hello,
Today, I'm troubleshooting an issue with the PayPal variant. My setup:
myapp/models.py
class Transaction(AbstractBaseModel, BasePayment):
user = models.ForeignKey(
get_user_model(),
null=True,
on_delete=models.SET_NULL,
related_name="subscription_transactions",
)
date_paid = models.DateTimeField(blank=True,null=True,)
is_paid = models.BooleanField(default=False, verbose_name=_("Paid"))
is_cancelled = models.BooleanField(default=False,verbose_name=_("Cancelled"))
def get_failure_url(self):
return f"{PAYMENT_PROTOCOL}://{PAYMENT_HOST}/subscriptions/payment-failure"
def get_success_url(self):
return f"{PAYMENT_PROTOCOL}://{PAYMENT_HOST}/subscriptions/payment-success"
def get_process_url(self) -> str:
return reverse("plan_subscriptions:process_payment", kwargs={"token": self.token})
myapp/signals.py
@receiver(pre_save, sender=Transaction)
def update_date_paid(sender, instance, **kwargs):
"""
Signal handler to update transaction status and handle related logic.
This function ensures that a transaction is not marked as both paid and cancelled,
and updates the date_paid field based on the transaction status.
"""
# Ensure the transaction is not both paid and cancelled
if instance.is_paid and instance.is_cancelled:
raise ValidationError(_("A transaction cannot be both paid and cancelled."))
# Update date_paid based on the payment status
if instance.status == PaymentStatus.CONFIRMED and not instance.date_paid:
# Mark transaction as paid by setting the date_paid
instance.is_paid = True
instance.date_paid = timezone.now()
instance.is_cancelled = False
elif instance.status != PaymentStatus.CONFIRMED and instance.date_paid:
instance.is_paid = False
instance.date_paid = None
# Handle cancellation logic
if instance.is_cancelled:
instance.is_paid = False
instance.date_paid = None
@receiver(post_save, sender=Transaction)
def change_user_plan_on_transaction_paid(sender, instance=None, created=False, **kwargs):
# A lot of others stuffs making changes in another models, not important now
The signals are working correctly:
- When I update a field in my
Transaction
(payment) model, both signals are triggered. - When processing a payment via Stripe, the signals execute as expected.
However, when processing a payment via PayPal, the PayPal response is received, and both signals are triggered (I can confirm the changes to instance.is_paid, instance.date_paid, and instance.is_cancelled in debug mode). Yet, when checking the database after, the field values are incorrect:
Worklow:
- User creates a transaction/payment
- Initial state: is_paid=False, date_paid=Null, is_cancelled=False
- User proceeds to PayPal and completes the payment
- PayPal sends a callback, triggering
process_data
in django-payments process_data
updates the status to CONFIRMED (payment.change_status(PaymentStatus.CONFIRMED)
)- The
Transaction
model receives thepre_save
signal pre_save
updates the fields: is_paid=True, date_paid=Now(), is_cancelled=Falseprocess_data
redirects to success_url- Issue: When checking the database, the fields are incorrect: is_paid=False, date_paid=Null, is_cancelled=False
- Expected values: is_paid=True, date_paid=Now(), is_cancelled=False
I'm quite certain I'm missing something on my end because otherwise, someone else would have reported this issue already.
Nevertheless, after reviewing the PayPal process_data
I found that adding a save() call at the end resolves the issue and ensures the signals behave as expected:
def process_data(self, payment, request):
[...]
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)
payment.save()
return redirect(success_url)
It is a bug ?? If not, what I'm doing wrong ?