Skip to content

Paypal not saving changes made on signal #436

@vhsantos

Description

@vhsantos

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:

  1. User creates a transaction/payment
  2. Initial state: is_paid=False, date_paid=Null, is_cancelled=False
  3. User proceeds to PayPal and completes the payment
  4. PayPal sends a callback, triggering process_data in django-payments
  5. process_data updates the status to CONFIRMED (payment.change_status(PaymentStatus.CONFIRMED))
  6. The Transaction model receives the pre_save signal
  7. pre_save updates the fields: is_paid=True, date_paid=Now(), is_cancelled=False
  8. process_data redirects to success_url
  9. Issue: When checking the database, the fields are incorrect: is_paid=False, date_paid=Null, is_cancelled=False
  10. 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 ?

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions