Models
Stripe data models mirrored in Django.
The subscriptions module mirrors Stripe's data model in Django, keeping a local copy of products, prices, and subscriptions for fast access.
Model Relationships
StripeUser (1:1 with Django User)
│
├── Subscription (1:N)
│ │
│ └── SubscriptionItem (1:N)
│ │
│ └── Price (FK)
│ │
│ └── Product (FK)
│ │
│ └── ProductFeature (M2M) → Feature
│
└── Properties:
├── subscribed_products → Set[Product]
└── subscribed_features → Set[Feature]StripeUser
class StripeUser(models.Model):
user = models.OneToOneField(AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
customer_id = models.CharField(max_length=128, null=True, blank=True)The customer_id is assigned when a Stripe customer is created (during checkout or management command sync). Key computed properties:
@property
def current_subscription_items(self):
"""SubscriptionItems with access-granting status (active, trialing, past_due)."""
return SubscriptionItem.objects.filter(
subscription__stripe_user=self,
subscription__status__in=ACCESS_GRANTING_STATUSES,
)
@property
def subscribed_products(self):
"""Set of Products the user currently has access to."""
return {item.price.product for item in self.current_subscription_items}
@property
def subscribed_features(self):
"""Set of Features derived from subscribed products."""
features = set()
for product in self.subscribed_products:
features.update(
Feature.objects.filter(linked_products__product=product)
)
return featuresProduct
class Product(models.Model):
product_id = models.CharField(max_length=256, primary_key=True)
active = models.BooleanField()
description = models.CharField(max_length=1024, null=True, blank=True)
name = models.CharField(max_length=256, null=True, blank=True)Synced from Stripe via webhooks (product.created, product.updated, product.deleted).
Price
class Price(models.Model):
price_id = models.CharField(max_length=256, primary_key=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="prices")
nickname = models.CharField(max_length=256, null=True, blank=True)
price = models.PositiveIntegerField() # Amount in cents (e.g., 2900 = $29.00)
freq = models.CharField(max_length=64, null=True, blank=True) # "month_1", "year_1"
active = models.BooleanField()
currency = models.CharField(max_length=3) # "usd", "eur", etc.The freq field is derived from Stripe's price.recurring.interval and interval_count:
month_1= monthly billingyear_1= annual billingmonth_3= quarterly billing
Feature
class Feature(models.Model):
feature_id = models.CharField(max_length=64, primary_key=True)
description = models.CharField(max_length=256, null=True, blank=True)Features are defined in Stripe Product metadata. Add a features key with space-delimited feature IDs:
Product metadata in Stripe Dashboard:
features: "chat rag export analytics"The sync process creates Feature records and ProductFeature associations automatically.
Subscription
class Subscription(models.Model):
subscription_id = models.CharField(max_length=256, primary_key=True)
stripe_user = models.ForeignKey(StripeUser, on_delete=models.CASCADE, related_name="subscriptions")
period_start = models.DateTimeField(null=True)
period_end = models.DateTimeField(null=True)
cancel_at = models.DateTimeField(null=True)
cancel_at_period_end = models.BooleanField()
ended_at = models.DateTimeField(null=True)
status = models.CharField(max_length=64)
trial_start = models.DateTimeField(null=True)
trial_end = models.DateTimeField(null=True)SubscriptionItem
class SubscriptionItem(models.Model):
sub_item_id = models.CharField(max_length=256, primary_key=True)
subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE, related_name="items")
price = models.ForeignKey(Price, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()Query Helpers
The module provides query functions for common access patterns:
# List user's active subscriptions
list_user_subscriptions(user_id, current=True)
# List user's active subscription items (with product/price details)
list_user_subscription_items(user_id, current=True)
# Get products the user is currently subscribed to
list_user_subscription_products(user_id, current=True)
# Get prices the user can subscribe to (excludes current)
list_subscribable_product_prices_to_user(user_id)
# Get all active prices (public, for pricing page)
list_all_available_product_prices(expand=None)When current=True, queries filter by ACCESS_GRANTING_STATUSES: active, past_due, trialing.
Pydantic Validation Models
Stripe API responses are validated with Pydantic models before being processed:
| Model | Purpose |
|---|---|
StripeEvent | Webhook event payload |
StripeSubscription | Subscription data with items |
StripeProduct | Product data with metadata |
StripePrice | Price data with recurring info |
StripeCustomer | Customer data |
StripeInvoice | Invoice with line items |
StripeCurrency | 140+ currency code enum |
These models ensure type safety when processing webhook payloads.