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

如何在Django REST中区分基础/付费用户以实现付费专属功能?

Django + Stripe 订阅制付费的通用实现方案

你的初始思路存在几个关键缺陷:依赖前端跳转更新状态不可靠、单一布尔字段无法处理订阅到期/续费等复杂场景、没有和Stripe的客户体系绑定。下面是行业通用的实现方案,直接解决你提出的问题:

一、模型设计:不要用单一布尔字段,关联Stripe订阅信息

修改你的User模型,添加以下字段来和Stripe的订阅体系对齐:

from django.contrib.auth.models import AbstractUser
from django.db import models
from datetime import datetime

class User(AbstractUser):
    # 关联Stripe客户ID
    stripe_customer_id = models.CharField(max_length=255, blank=True, null=True)
    # 关联Stripe订阅ID
    stripe_subscription_id = models.CharField(max_length=255, blank=True, null=True)
    # 标记是否为付费用户(结合到期时间判断)
    is_premium = models.BooleanField(default=False)
    # 订阅到期时间,用于校验是否过期
    subscription_end_date = models.DateTimeField(blank=True, null=True)

二、核心:用Stripe Webhook处理所有订阅状态变更

绝对不要依赖前端success_url更新用户状态——用户可能在支付成功后关闭页面,或者跳转过程中出错,导致状态不同步。正确的方式是通过Stripe Webhook接收事件,主动更新后端状态。

1. 配置Webhook视图

在Django中创建一个无CSRF保护的视图,用于接收Stripe的事件,并验证签名(防止伪造请求):

import stripe
from django.http import HttpResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from .models import User
from datetime import datetime

stripe.api_key = settings.STRIPE_SECRET_KEY

@csrf_exempt
@require_POST
def stripe_webhook(request):
    payload = request.body
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
    event = None

    # 验证Stripe事件签名
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return HttpResponse(status=400)

    # 处理不同类型的事件
    if event['type'] == 'checkout.session.completed':
        # 用户首次完成订阅支付
        session = event['data']['object']
        # 从session的metadata中获取你的用户ID(创建checkout session时要传入)
        user_id = session['metadata']['user_id']
        user = User.objects.get(id=user_id)
        
        # 获取订阅详情
        subscription = stripe.Subscription.retrieve(session['subscription'])
        user.stripe_customer_id = session['customer']
        user.stripe_subscription_id = subscription.id
        user.is_premium = True
        user.subscription_end_date = datetime.fromtimestamp(subscription.current_period_end)
        user.save()

    elif event['type'] == 'invoice.paid':
        # 用户续费成功
        invoice = event['data']['object']
        subscription_id = invoice['subscription']
        if subscription_id:
            subscription = stripe.Subscription.retrieve(subscription_id)
            user = User.objects.get(stripe_subscription_id=subscription_id)
            user.subscription_end_date = datetime.fromtimestamp(subscription.current_period_end)
            user.is_premium = True
            user.save()

    elif event['type'] == 'customer.subscription.deleted':
        # 用户主动取消订阅
        subscription = event['data']['object']
        user = User.objects.get(stripe_subscription_id=subscription.id)
        user.is_premium = False
        user.subscription_end_date = None
        user.stripe_subscription_id = None
        user.save()

    elif event['type'] == 'invoice.payment_failed':
        # 用户续费失败(比如信用卡过期)
        invoice = event['data']['object']
        subscription_id = invoice['subscription']
        if subscription_id:
            user = User.objects.get(stripe_subscription_id=subscription_id)
            user.is_premium = False
            user.save()

    return HttpResponse(status=200)

2. 在Stripe后台配置Webhook

在Stripe控制台中添加你的webhook URL(比如https://your-domain.com/api/stripe/webhook/),并订阅以下事件:

  • checkout.session.completed
  • invoice.paid
  • customer.subscription.deleted
  • invoice.payment_failed

三、权限校验:结合订阅状态和到期时间

写一个自定义权限类,确保只有有效订阅的用户才能访问付费功能:

from rest_framework.permissions import BasePermission
from datetime import datetime

class IsPremiumUser(BasePermission):
    def has_permission(self, request, view):
        if not request.user.is_authenticated:
            return False
        # 校验是否为付费用户,且订阅未到期
        return request.user.is_premium and (
            request.user.subscription_end_date is None 
            or request.user.subscription_end_date > datetime.now()
        )

在付费专属API视图中使用这个权限:

from rest_framework.views import APIView
from rest_framework.response import Response
from .permissions import IsPremiumUser

class PremiumWritingToolAPI(APIView):
    permission_classes = [IsPremiumUser]

    def get(self, request):
        # 处理付费专属逻辑
        return Response({"data": "Premium writing features activated"})

四、你的问题逐一解答

  1. 用户停止付费后如何更新状态?
    通过Webhook监听customer.subscription.deleted(主动取消)和invoice.payment_failed(续费失败)事件,在处理函数中将is_premium设为False,清空订阅相关字段。

  2. 如何检测支付成功事件?
    依赖Stripe Webhook的checkout.session.completed(首次订阅)和invoice.paid(续费)事件,这些事件由Stripe主动推送到你的后端,可靠性远高于前端跳转。

  3. 是否应该用Stripe客户对象?
    必须用。将stripe_customer_id绑定到你的User模型,能让你将平台用户和Stripe的客户体系一一对应,方便后续查询订阅状态、管理发票、处理用户的订阅变更请求,避免重复创建Stripe客户。

额外提示

  • 创建Stripe Checkout Session时,一定要在metadata中传入你的用户ID,这样Webhook才能找到对应的用户。
  • 定期同步订阅状态:可以写一个定时任务(比如用Celery),每天拉取Stripe的订阅数据,和本地数据库对比,避免Webhook漏发导致的状态不一致。

内容的提问来源于stack exchange,提问作者Tyler Kim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 17:45:42