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.contrib.auth.models import Group
from django.db import models from django.db import models
from wagtail.admin.panels import FieldPanel from wagtail.admin.panels import FieldPanel
from modelcluster.fields import ParentalKey
from wagtail.models import Orderable
GITEA_ORG_NAME = "Studio77" GITEA_ORG_NAME = "Studio77"
logger = lg.getLogger(__name__) logger = lg.getLogger(__name__)
@@ -136,14 +138,14 @@ class CoursePurchase(models.Model):
self.add_to_gitea_team() 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. """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. 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. 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) description = models.TextField(blank=True)
price_cents = models.PositiveIntegerField(help_text="Price in cents") price_cents = models.PositiveIntegerField(help_text="Price in cents")
currency = models.CharField( currency = models.CharField(
@@ -151,13 +153,33 @@ class PurchasableProduct(models.Model):
) )
stripe_product_id = models.CharField(max_length=255, blank=True, null=True) 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_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) 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 = [ panels = [
FieldPanel("price_cents"), FieldPanel("price_cents"),
FieldPanel("currency"), 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): def __str__(self):
return f"{self.name} ({self.price_cents / 100:.2f} {self.currency.upper()})" return f"{self.name} ({self.price_cents / 100:.2f} {self.currency.upper()})"
@@ -180,6 +202,15 @@ class PurchasableProduct(models.Model):
except self.__class__.DoesNotExist: except self.__class__.DoesNotExist:
previous = None 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 # Persist local changes first so we have an id
super().save(*args, **kwargs) super().save(*args, **kwargs)
@@ -222,6 +253,32 @@ class PurchasableProduct(models.Model):
f"Created Stripe price {self.stripe_price_id} for PurchasableProduct {self.id}" 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 this is an update (we had previous state) perform updates
if previous: if previous:
# Update product metadata/name/description if they changed # Update product metadata/name/description if they changed