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
相关产品推荐
相关产品推荐

