Django投票平台Nomination模型votes字段值未正确更新问题求助
问题根因
你的投票平台票数累加错误的核心原因是支付回调环节没有拿到用户实际购买的票数,直接使用金额字段换算值进行累加,流程中缺失了购买票数的传递/存储逻辑。
排查步骤
- 定位异常代码位置:查看
process_payment函数中票数更新逻辑:
Nomination.objects.filter(slug=slug).update(votes=int(nomination[0]['votes']) + int(amount[:-2]))
这里的amount是你在nomination_payView中生成的12位补零金额字符串(比如2票、单票5美元的场景下,金额处理后为000000001000,取[:-2]后得到数值10,直接累加就会出现原有3票变成13票的错误)。
2. 确认流程缺失:你在nomination_payView的POST请求中已经通过request.POST['votes']拿到了用户购买的票数,但没有将该值存储或传递到支付回调环节,导致回调时无法获取正确的累加数值。
修复方案
提供两种可落地的修复方案,可根据业务场景选择:
方案1:金额反算票数(适合单票价格固定的场景)
通过回调拿到的金额,结合单票固定价格反算出实际购买的票数再累加:
- 首先确认单票价格和前端传入的
price值一致,建议配置在Django settings中避免硬编码。 - 修改
process_payment的票数更新逻辑:
from django.conf import settings from django.db.models import F from django.shortcuts import get_object_or_404 def process_payment(request, slug, amount): trans_id = request.GET.get('transaction_id') status = request.GET.get('status') reason = request.GET.get('reason') transaction_id = request.GET.get('transaction_id') if status == 'Approved': transaction = SuccessfulTransactionHistory( nominee_name=slug, transaction_id=transaction_id, amount=amount ) transaction.save() # 新增逻辑:金额反算票数 # 去掉金额的前导零,转成单位为分的整数 amount_cent = int(amount.lstrip('0')) # 转成元单位,除以单票价格得到购买票数 bought_votes = int((amount_cent / 100) / settings.PER_VOTE_PRICE) # 用F表达式避免并发脏读 Nomination.objects.filter(slug=slug).update(votes=F('votes') + bought_votes) return redirect('/success') else: context = { 'error': reason } transaction = FailedTransactionHistory( nominee_name=slug, transaction_id=transaction_id, amount=amount ) transaction.save() return render(request, 'payment_error.html', context=context)
方案2:预存交易记录传递票数(适用所有场景,更稳妥)
支付前先将购买票数和交易ID绑定存储,回调时通过交易ID直接取到购买票数:
- 新增待支付交易模型,存储交易ID、票数、对应提名slug:
# models.py新增 class PendingTransaction(models.Model): transaction_id = models.CharField(max_length=12, unique=True) slug = models.SlugField(max_length=150) bought_votes = models.IntegerField() amount = models.CharField(max_length=12) create_time = models.DateTimeField(auto_now_add=True)
- 修改
nomination_payView的POST逻辑,生成交易ID后预存待支付记录:
elif request.method == 'POST': bought_votes = int(request.POST['votes']) price = float(request.POST['price']) amount = (str(bought_votes * price) + '0').replace('.', '') zeros = '0' * (12 - len(amount)) amount = zeros + amount email = request.POST['email'] url = 'https://test.theteller.net/checkout/initiate' transaction_id = random.randint(100000000000, 999999999999) # 新增:预存待支付交易 PendingTransaction.objects.create( transaction_id=str(transaction_id), slug=slug, bought_votes=bought_votes, amount=amount ) # 剩余原有请求支付网关的逻辑保持不变
- 修改回调逻辑,通过交易ID取票数累加:
from django.db.models import F from django.shortcuts import get_object_or_404 def process_payment(request, slug, amount): trans_id = request.GET.get('transaction_id') status = request.GET.get('status') reason = request.GET.get('reason') if status == 'Approved': # 查待支付记录拿购买票数 pending_trans = get_object_or_404(PendingTransaction, transaction_id=trans_id) bought_votes = pending_trans.bought_votes # 存成功交易记录 transaction = SuccessfulTransactionHistory( nominee_name=slug, transaction_id=trans_id, amount=amount ) transaction.save() # 更新票数 Nomination.objects.filter(slug=slug).update(votes=F('votes') + bought_votes) # 删除已完成的待支付记录 pending_trans.delete() return redirect('/success') else: # 失败逻辑可自行增加待支付记录清理逻辑 context = { 'error': reason } transaction = FailedTransactionHistory( nominee_name=slug, transaction_id=trans_id, amount=amount ) transaction.save() return render(request, 'payment_error.html', context=context)
额外优化建议
- 票数更新建议统一使用Django的
F表达式,避免多用户同时支付时出现的脏读导致票数统计错误。 - 交易ID建议使用全局唯一的生成规则,避免随机生成出现重复冲突。
内容的提问来源于stack exchange,提问作者spiritman
相关产品推荐
相关产品推荐

