feat(purchase/models.py): create payment links and sync name and description from coursepage

This commit is contained in:
2026-05-18 18:06:16 +02:00
parent a3cd8d42fa
commit 211dcc4f67

View File

@@ -7,6 +7,8 @@ from django.conf import settings
from django.contrib.auth.models import Group
from django.db import models
from wagtail.admin.panels import FieldPanel
from modelcluster.fields import ParentalKey
from wagtail.models import Orderable
GITEA_ORG_NAME = "Studio77"
logger = lg.getLogger(__name__)
@@ -136,14 +138,14 @@ class CoursePurchase(models.Model):
self.add_to_gitea_team()
class PurchasableProduct(models.Model):
class PurchasableProduct(Orderable, models.Model):
"""A product that can be purchased. When created it will create a Stripe Product and Price.
On delete it will try to deactivate the Price and delete the Product in Stripe.
The code is defensive: if STRIPE_API_KEY is not configured the model will still work locally.
"""
name = models.CharField(max_length=255)
name = models.CharField(max_length=255, blank=True)
description = models.TextField(blank=True)
price_cents = models.PositiveIntegerField(help_text="Price in cents")
currency = models.CharField(
@@ -151,13 +153,33 @@ class PurchasableProduct(models.Model):
)
stripe_product_id = models.CharField(max_length=255, blank=True, null=True)
stripe_price_id = models.CharField(max_length=255, blank=True, null=True)
stripe_payment_url = models.URLField(
blank=True,
null=True,
help_text="Stripe Checkout URL for this product",
)
created_at = models.DateTimeField(auto_now_add=True)
course = ParentalKey(
"home.CoursePage",
on_delete=models.CASCADE,
related_name="purchasable_products",
null=True,
blank=True,
)
panels = [
FieldPanel("price_cents"),
FieldPanel("currency"),
FieldPanel("stripe_product_id", read_only=True),
FieldPanel("stripe_price_id", read_only=True),
FieldPanel("stripe_payment_url", read_only=True),
]
@property
def price(self):
return self.price_cents / 100
def __str__(self):
return f"{self.name} ({self.price_cents / 100:.2f} {self.currency.upper()})"
@@ -180,6 +202,15 @@ class PurchasableProduct(models.Model):
except self.__class__.DoesNotExist:
previous = None
# Get name, description and image from the linked course if not set explicitly on the product
if self.course:
course_name = self.course.title
course_description = self.course.description or ""
if not self.name:
self.name = course_name
if not self.description:
self.description = course_description
# Persist local changes first so we have an id
super().save(*args, **kwargs)
@@ -222,6 +253,32 @@ class PurchasableProduct(models.Model):
f"Created Stripe price {self.stripe_price_id} for PurchasableProduct {self.id}"
)
# Create Stripe Payment Link if missing or if price changed
if not self.stripe_payment_url and self.stripe_price_id:
try:
payment_link = stripe.PaymentLink.create(
line_items=[{"price": self.stripe_price_id, "quantity": 1}],
after_completion={
"type": "redirect",
"redirect": {
"url": getattr(
settings,
"STRIPE_SUCCESS_URL",
"https://example.com/success",
)
},
},
)
self.stripe_payment_url = payment_link.url
changed_fields.append("stripe_payment_url")
logger.info(
f"Created Stripe payment link {self.stripe_payment_url} for PurchasableProduct {self.id}"
)
except Exception as e:
logger.exception(
f"Failed to create Stripe payment link for PurchasableProduct {self.id}: {e}"
)
# If this is an update (we had previous state) perform updates
if previous:
# Update product metadata/name/description if they changed