Skip to content
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

Paypal not saving changes made on signal #436

Open
vhsantos opened this issue Feb 11, 2025 · 3 comments
Open

Paypal not saving changes made on signal #436

vhsantos opened this issue Feb 11, 2025 · 3 comments
Assignees
Labels

Comments

@vhsantos
Copy link
Contributor

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 ?

@mariofix
Copy link
Member

And if you notice on the other providers, it's customary to save the payment object before sending the client (as is the person who clicks the pay button) outside the django application.

@vhsantos
Copy link
Contributor Author

vhsantos commented Feb 12, 2025

Hi @mariofix ,

I don't understand why you closed the issue, if paypal provider are missing the save() on the process_data as you have commented.

@mariofix mariofix reopened this Feb 12, 2025
@mariofix
Copy link
Member

Wrong button, thanks for the heads up

@mariofix mariofix self-assigned this Feb 12, 2025
@mariofix mariofix added the bug label Feb 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants