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

Django支付返回successful时如何将课程购买数据存入数据库

实现方案

当前逻辑的核心断档点:支付网关回调时仅返回status和tx_ref两个参数,你没有提前把交易编号和对应的用户、课程做绑定,所以回调阶段拿不到需要关联的主体数据,同时现有代码存在缩进、参数不匹配的语法问题,先按以下步骤修改:

第一步:新增交易记录模型

用于绑定支付交易号、用户、课程的关联关系,避免依赖session(跨设备跳转时session会失效),在models.py中添加如下模型:

class PaymentTransaction(models.Model):
    PENDING = 'pending'
    SUCCESS = 'success'
    FAILED = 'failed'
    STATUS_CHOICES = (
        (PENDING, '待支付'),
        (SUCCESS, '支付成功'),
        (FAILED, '支付失败'),
    )
    tx_ref = models.CharField(max_length=50, unique=True, verbose_name="交易流水号")
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="支付用户")
    course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="购买课程")
    amount = models.IntegerField(verbose_name="支付金额")
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=PENDING, verbose_name="支付状态")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

添加完成后执行python manage.py makemigrations和python manage.py migrate完成数据表创建。

第二步:修正结算页与支付发起逻辑

原有代码存在缩进错误、参数不匹配问题,将views.py中对应代码替换为如下内容:

import math
import random
import requests
from django.conf import settings
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .models import Course, UserCourse, PaymentTransaction

@login_required
def course_checkout(request, slug):
    course = Course.objects.get(slug=slug)
    user = request.user
    
    if request.method == "POST":
        name = request.POST.get("name")
        email = request.POST.get("email")
        # 正式环境不要取前端传的amount,直接用course.price防止篡改
        amount = course.price
        # 免费课直接关联
        if amount == 0:
            UserCourse.objects.get_or_create(user=user, course=course)
            return redirect('course:course-content', course.slug)
        # 付费课发起支付
        payment_link = process_payment(request, name, email, amount, course, user)
        return redirect(payment_link)
    
    return render(request, "course_checkout.html", {"course": course})

def process_payment(request, name, email, amount, course, user):
    auth_token = settings.FLUTTER_SECRET_KEY
    hed = {'Authorization': 'Bearer ' + auth_token}
    # 生成唯一交易流水号
    tx_ref = str(math.floor(1000000 + random.random()*9000000))
    # 提前写入待支付交易记录,绑定用户、课程、流水号的关联关系
    PaymentTransaction.objects.create(
        tx_ref=tx_ref,
        user=user,
        course=course,
        amount=amount
    )
    data = {
        "tx_ref": tx_ref,
        "amount": amount,
        "currency": "USD",
        "redirect_url": f"{request.scheme}://{request.get_host()}/callback",
        "payment_options": "card",
        "customer": {
            "email": email,
            "name": name
        },
        "customizations": {
            "title": "DexxaEd",
            "description": "Limitless Learning For Everyone"
        }
    }
    # 支付网关请求地址
    url = 'https://api.flutterwave.com/v3/payments'
    response = requests.post(url, json=data, headers=hed)
    response_data = response.json()
    return response_data['data']['link']

第三步:修改回调视图写入关联数据

回调阶段通过tx_ref查询对应的交易记录,校验支付状态后写入UserCourse关联,同时加幂等判断避免重复写入,替换payment_response视图代码:

from django.views.decorators.http import require_http_methods

@require_http_methods(['GET', 'POST'])
def payment_response(request):
    status = request.GET.get('status', None)
    tx_ref = request.GET.get('tx_ref', None)
    
    # 流水号不存在直接返回失败页
    try:
        transaction = PaymentTransaction.objects.get(tx_ref=tx_ref)
    except PaymentTransaction.DoesNotExist:
        return render(request, "payment/payment-failed.html")
    
    if status == "successful":
        # 幂等校验:交易未处理过才更新状态、写入关联
        if transaction.status != PaymentTransaction.SUCCESS:
            transaction.status = PaymentTransaction.SUCCESS
            transaction.save()
            # get_or_create避免重复生成关联记录
            UserCourse.objects.get_or_create(user=transaction.user, course=transaction.course)
        return render(request, "payment/payment-success.html", {"course": transaction.course})
    
    if status == "cancelled":
        transaction.status = PaymentTransaction.FAILED
        transaction.save()
        return render(request, "payment/payment-failed.html")

注意事项

  • 正式环境必须加支付签名校验,不要完全信任前端回调带的status参数,建议回调时服务端主动调用支付网关的查询接口,用tx_ref核实真实支付状态,避免伪造回调
  • 免费课、支付成功写入关联时都要用get_or_create,避免用户重复点击生成重复的购课记录
  • 原有course_details视图缺少返回逻辑,访问会报错,记得补充对应的render响应

内容的提问来源于stack exchange,提问作者Destiny Franks

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 15:36:22