Double Increment Issue in TotalVote Count After Payment in django App

51 Views Asked by At

I'm currently developing a talent application where participants can receive votes from voters, and each vote incurs a cost. The collected funds from these votes are meant to be credited to the respective contestants. For instance, if someone casts a vote for a contestant with a count of 10 votes, the contestant's total vote count should increase by the number of votes received.

However, I've encountered an issue after a user casts a vote and makes a payment. It appears that the total vote count is incrementing by twice the intended value. For example, if a user submits a vote of 10, the contestant's total vote count is increasing by 20, which is unexpected and seems incorrect. Any assistance in resolving this issue would be greatly appreciated. Please find my code below:

def contestant_detail_payment(request, slug, *args, **kwargs):
    contestant = get_object_or_404(Contestant, slug=slug)
    vote_charge = contestant.get_vote_per_charge()

    if request.method == "POST":
        email = request.POST.get("email", "")
        vote = request.POST.get("vote", "")
        amount = int(request.POST["amount"].replace("₦", ""))

        pk = settings.PAYSTACK_PUBLIC_KEY

        payment = Payment.objects.create(
            contestant=contestant,
            amount=amount,
            email=email,
            vote=vote,
        )

        
        payment.save()

        context = {
            "payment": payment,
            "field_values": request.POST,
            "paystack_pub_key": pk,
            "amount_value": payment.amount_value(),
            "contestant": contestant,
        }

        return render(request, "competitions/make_payment.html", context)
    context = {
        "contestant": contestant,
        "vote_charge": vote_charge,
    }

    return render(request, "competitions/profile.html", context)


@transaction.atomic
def verify_payment(request, ref):
    print(f"verify_payment called for reference: {ref}")
    try:
        payment = Payment.objects.get(ref=ref)
    except Payment.DoesNotExist:
        # Handle the case where the payment with the given ref doesn't exist
        print("Invalid payment reference:", ref)
        return render(
            request,
            "competitions/error.html",
            {"error_message": "Invalid payment reference."},
        )

    verified = payment.verify_payment()

    if verified:
        contestant = payment.contestant


        profile_obj = payment.contestant.user.profile

        profile_obj.totalVote += payment.vote


        profile_obj.save()

        print(f"Payment verified for with total votes of {contestant.user.profile.totalVote}.")

        return render(request, "competitions/success.html")
    else:
        return render(
            request,
            "competitions/error.html",
            {"error_message": "Payment verification failed."},
        )


class Payment(models.Model):
    contestant = models.ForeignKey(Contestant, on_delete=models.CASCADE)
    email = models.EmailField(max_length=254, blank=True, null=True)
    amount = models.PositiveIntegerField()
    vote = models.IntegerField()
    ref = models.CharField(max_length=200)
    verified = models.BooleanField(default=False)
    date_created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ("-date_created",)

    def __str__(self):
        return "{} voted for {} with {} votes".format(
            self.email, self.contestant.user.first_name, self.vote
        )

    def save(self, *args, **kwargs):
        while not self.ref:
            ref = secrets.token_urlsafe(50)
            object_with_similar_ref = Payment.objects.filter(ref=ref)
            if not object_with_similar_ref:
                self.ref = ref

        super().save(*args, **kwargs)

    def amount_value(self):
        return int(self.amount) * 100

    def verify_payment(self):
        if not self.verified:
            paystack = Paystack()
            status, result = paystack.verify_payment(self.ref, self.amount)
            if status:
                if result["amount"] / 100 == self.amount:
                    self.verified = True
                    self.save()
                   
        return self.verified


I have tried refactoriing where the payment iss been verified and yet no head way. I have also moved tthe totalVote from where it was to the user profile model hoping that could be the issue and that didnt work a well.

1

There are 1 best solutions below

2
Denys Danov On

Generally, you should avoid mathematical expressions working with model's fields. It may(and will) cause race condition. Use model.field_name = F("field_name") + something instead of model.field_name += something.

More about race conditions: https://bovage.hashnode.dev/how-to-avoid-race-condition-in-django

More about F(): https://docs.djangoproject.com/en/4.2/ref/models/expressions/#f-expressions

But honestly, try to log these additions and see where it works in unexpected way. First things to log are previous state, current state and what you've added. It may help you