Coverage for polar/models/checkout_link.py: 88%

38 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-12-05 15:52 +0000

1from typing import TYPE_CHECKING 1ab

2from uuid import UUID 1ab

3 

4from sqlalchemy import Boolean, ForeignKey, String, Uuid 1ab

5from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy 1ab

6from sqlalchemy.orm import Mapped, declared_attr, mapped_column, relationship 1ab

7 

8from polar.enums import PaymentProcessor 1ab

9from polar.kit.db.models import RecordModel 1ab

10from polar.kit.metadata import MetadataMixin 1ab

11from polar.kit.trial import TrialConfigurationMixin 1ab

12 

13from .discount import Discount 1ab

14from .product import Product 1ab

15 

16if TYPE_CHECKING: 16 ↛ 17line 16 didn't jump to line 17 because the condition on line 16 was never true1ab

17 from .checkout_link_product import CheckoutLinkProduct 

18 from .organization import Organization 

19 

20 

21class CheckoutLink(TrialConfigurationMixin, MetadataMixin, RecordModel): 1ab

22 __tablename__ = "checkout_links" 1ab

23 

24 payment_processor: Mapped[PaymentProcessor] = mapped_column( 1ab

25 String, nullable=False, default=PaymentProcessor.stripe, index=True 

26 ) 

27 client_secret: Mapped[str] = mapped_column( 1ab

28 String, index=True, nullable=False, unique=True 

29 ) 

30 _success_url: Mapped[str | None] = mapped_column( 1ab

31 "success_url", String, nullable=True, default=None 

32 ) 

33 

34 label: Mapped[UUID] = mapped_column(String, nullable=True) 1ab

35 allow_discount_codes: Mapped[bool] = mapped_column( 1ab

36 Boolean, nullable=False, default=True 

37 ) 

38 require_billing_address: Mapped[bool] = mapped_column( 1ab

39 Boolean, nullable=False, default=False 

40 ) 

41 

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

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

44 ) 

45 

46 @declared_attr 1ab

47 def discount(cls) -> Mapped[Discount | None]: 1ab

48 # Eager loading makes sense here because we always need the discount when present 

49 return relationship(Discount, lazy="joined") 1ab

50 

51 checkout_link_products: Mapped[list["CheckoutLinkProduct"]] = relationship( 1ab

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( 1ab

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( 1ab

67 Uuid, 

68 ForeignKey("organizations.id", ondelete="cascade"), 

69 nullable=False, 

70 index=True, 

71 ) 

72 

73 @declared_attr 1ab

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

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

76 

77 @property 1ab

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

79 return self._success_url 

80 

81 @success_url.setter 1ab

82 def success_url(self, value: str | None) -> None: 1ab

83 self._success_url = str(value) if value is not None else None