Coverage for polar/models/checkout_link.py: 88%
38 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 12:42 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 12:42 +0000
1from typing import TYPE_CHECKING
2from uuid import UUID
4from sqlalchemy import Boolean, ForeignKey, String, Uuid
5from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
6from sqlalchemy.orm import Mapped, declared_attr, mapped_column, relationship
8from polar.enums import PaymentProcessor
9from polar.kit.db.models import RecordModel
10from polar.kit.metadata import MetadataMixin
11from polar.kit.trial import TrialConfigurationMixin
13from .discount import Discount
14from .product import Product
16if TYPE_CHECKING: 16 ↛ 17line 16 didn't jump to line 17 because the condition on line 16 was never true
17 from .checkout_link_product import CheckoutLinkProduct
18 from .organization import Organization
21class CheckoutLink(TrialConfigurationMixin, MetadataMixin, RecordModel):
22 __tablename__ = "checkout_links"
24 payment_processor: Mapped[PaymentProcessor] = mapped_column(
25 String, nullable=False, default=PaymentProcessor.stripe, index=True
26 )
27 client_secret: Mapped[str] = mapped_column(
28 String, index=True, nullable=False, unique=True
29 )
30 _success_url: Mapped[str | None] = mapped_column(
31 "success_url", String, nullable=True, default=None
32 )
34 label: Mapped[UUID] = mapped_column(String, nullable=True)
35 allow_discount_codes: Mapped[bool] = mapped_column(
36 Boolean, nullable=False, default=True
37 )
38 require_billing_address: Mapped[bool] = mapped_column(
39 Boolean, nullable=False, default=False
40 )
42 discount_id: Mapped[UUID | None] = mapped_column(
43 Uuid, ForeignKey("discounts.id", ondelete="set null"), nullable=True
44 )
46 @declared_attr
47 def discount(cls) -> Mapped[Discount | None]:
48 # Eager loading makes sense here because we always need the discount when present
49 return relationship(Discount, lazy="joined")
51 checkout_link_products: Mapped[list["CheckoutLinkProduct"]] = relationship(
52 "CheckoutLinkProduct",
53 back_populates="checkout_link",
54 cascade="all, delete-orphan",
55 order_by="CheckoutLinkProduct.order",
56 # Products are almost always needed, so eager loading makes sense
57 lazy="selectin",
58 )
60 products: AssociationProxy[list["Product"]] = association_proxy(
61 "checkout_link_products", "product"
62 )
64 # Denormalize organization_id to help with validation
65 # when updating products or discount
66 organization_id: Mapped[UUID] = mapped_column(
67 Uuid,
68 ForeignKey("organizations.id", ondelete="cascade"),
69 nullable=False,
70 index=True,
71 )
73 @declared_attr
74 def organization(cls) -> Mapped["Organization"]:
75 return relationship("Organization", lazy="raise")
77 @property
78 def success_url(self) -> str | None:
79 return self._success_url
81 @success_url.setter
82 def success_url(self, value: str | None) -> None:
83 self._success_url = str(value) if value is not None else None