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

1from typing import TYPE_CHECKING 

2from uuid import UUID 

3 

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 

7 

8from polar.enums import PaymentProcessor 

9from polar.kit.db.models import RecordModel 

10from polar.kit.metadata import MetadataMixin 

11from polar.kit.trial import TrialConfigurationMixin 

12 

13from .discount import Discount 

14from .product import Product 

15 

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 

19 

20 

21class CheckoutLink(TrialConfigurationMixin, MetadataMixin, RecordModel): 

22 __tablename__ = "checkout_links" 

23 

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 ) 

33 

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 ) 

41 

42 discount_id: Mapped[UUID | None] = mapped_column( 

43 Uuid, ForeignKey("discounts.id", ondelete="set null"), nullable=True 

44 ) 

45 

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") 

50 

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 ) 

59 

60 products: AssociationProxy[list["Product"]] = association_proxy( 

61 "checkout_link_products", "product" 

62 ) 

63 

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 ) 

72 

73 @declared_attr 

74 def organization(cls) -> Mapped["Organization"]: 

75 return relationship("Organization", lazy="raise") 

76 

77 @property 

78 def success_url(self) -> str | None: 

79 return self._success_url 

80 

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