You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django应用支付后TotalVote计数翻倍问题求助

问题:付费投票后参赛者票数翻倍增长

我正在开发一款才艺类应用,参赛者可获得投票者的付费投票,投票所得资金计入对应参赛者账户。例如用户为某选手投10票,选手的totalVote计数应增加10,但目前出现异常:用户完成支付后,totalVote会翻倍增长,投10票实际增加20。我已尝试重构支付验证逻辑、将totalVote字段迁移至用户Profile模型,但问题仍未解决,相关代码如下:

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:
        print("Invalid payment reference:", ref)
        return render(
            request,
            "competitions/error.html",
            {"error_message": "Invalid payment reference."},
        )

    verified = payment.verify_payment()

    if verified:
        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

排查与修复方案

1. 防止验证接口重复触发

verify_payment接口可能被多次调用(前端重复请求、Paystack webhook重复通知),当前逻辑未限制票数仅更新一次。

修复:
给Payment模型新增vote_processed字段标记是否已处理票数更新,仅在未处理时执行计数操作:

# 给Payment模型添加字段
vote_processed = models.BooleanField(default=False)

# 修改verify_payment视图
@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:
        print("Invalid payment reference:", ref)
        return render(
            request,
            "competitions/error.html",
            {"error_message": "Invalid payment reference."},
        )

    verified = payment.verify_payment()

    if verified and not payment.vote_processed:
        profile_obj = payment.contestant.user.profile
        profile_obj.totalVote += payment.vote
        profile_obj.save()
        # 标记该支付已处理票数
        payment.vote_processed = True
        payment.save()
        print(f"Payment verified, total votes updated to {profile_obj.totalVote}.")
        return render(request, "competitions/success.html")
    elif verified and payment.vote_processed:
        # 已处理过直接返回成功
        return render(request, "competitions/success.html")
    else:
        return render(
            request,
            "competitions/error.html",
            {"error_message": "Payment verification failed."},
        )

2. 解决并发更新问题

并发场景下,多次读取totalVote旧值再更新会导致重复计数,使用Django的F()表达式实现原子更新:

from django.db.models import F

# 修改票数更新代码
profile_obj.totalVote = F('totalVote') + payment.vote
profile_obj.save()
# 刷新对象获取最新值
profile_obj.refresh_from_db()
print(f"Payment verified, total votes updated to {profile_obj.totalVote}.")

3. 检查前端重复提交

排查前端是否存在重复点击支付按钮、重复调用验证接口的情况,可添加按钮置灰、请求防抖等防重复提交逻辑。


内容的提问来源于stack exchange,提问作者Dennis Akagha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 04:22:04