Coverage for polar/checkout_link/schemas.py: 98%
61 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 16:17 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 16:17 +0000
1from typing import Annotated, Literal 1a
3from pydantic import UUID4, AliasPath, Field, HttpUrl, computed_field 1a
4from pydantic.json_schema import SkipJsonSchema 1a
6from polar.config import settings 1a
7from polar.discount.schemas import DiscountMinimal 1a
8from polar.enums import PaymentProcessor 1a
9from polar.kit.metadata import ( 1a
10 MetadataInputMixin,
11 MetadataOutputMixin,
12)
13from polar.kit.schemas import ( 1a
14 IDSchema,
15 MergeJSONSchema,
16 Schema,
17 SetSchemaReference,
18 TimestampedSchema,
19)
20from polar.kit.trial import TrialConfigurationInputMixin, TrialConfigurationOutputMixin 1a
21from polar.organization.schemas import OrganizationID 1a
22from polar.product.schemas import ( 1a
23 BenefitPublicList,
24 ProductBase,
25 ProductMediaList,
26 ProductPrice,
27 ProductPriceList,
28)
30SuccessURL = Annotated[ 1a
31 HttpUrl | None,
32 Field(
33 description=(
34 "URL where the customer will be redirected after a successful payment."
35 "You can add the `checkout_id={CHECKOUT_ID}` query parameter "
36 "to retrieve the checkout session id."
37 )
38 ),
39]
41_allow_discount_codes_description = ( 1a
42 "Whether to allow the customer to apply discount codes. "
43 "If you apply a discount through `discount_id`, it'll still be applied, "
44 "but the customer won't be able to change it."
45)
46_require_billing_address_description = ( 1a
47 "Whether to require the customer to fill their full billing address, instead of "
48 "just the country. "
49 "Customers in the US will always be required to fill their full address, "
50 "regardless of this setting."
51)
52_discount_id_description = ( 1a
53 "ID of the discount to apply to the checkout. "
54 "If the discount is not applicable anymore when opening the checkout link, "
55 "it'll be ignored."
56)
59class CheckoutLinkCreateBase(TrialConfigurationInputMixin, MetadataInputMixin, Schema): 1a
60 payment_processor: Literal[PaymentProcessor.stripe] = Field( 1a
61 description="Payment processor to use. Currently only Stripe is supported."
62 )
63 label: str | None = Field( 1a
64 description="Optional label to distinguish links internally", default=None
65 )
66 allow_discount_codes: bool = Field( 1a
67 default=True, description=_allow_discount_codes_description
68 )
69 require_billing_address: bool = Field( 1a
70 default=False, description=_require_billing_address_description
71 )
72 discount_id: UUID4 | None = Field( 1a
73 default=None, description=_discount_id_description
74 )
75 success_url: SuccessURL = None 1a
78class CheckoutLinkCreateProductPrice(CheckoutLinkCreateBase): 1a
79 """
80 Schema to create a new checkout link from a a single product price.
82 **Deprecated**: Use `CheckoutLinkCreateProducts` instead.
83 """
85 product_price_id: UUID4 1a
88class CheckoutLinkCreateProduct(CheckoutLinkCreateBase): 1a
89 """
90 Schema to create a new checkout link from a a single product.
92 **Deprecated**: Use `CheckoutLinkCreateProducts` instead.
93 """
95 product_id: UUID4 1a
98class CheckoutLinkCreateProducts(CheckoutLinkCreateBase): 1a
99 """Schema to create a new checkout link."""
101 products: list[UUID4] = Field( 1a
102 description="List of products that will be available to select at checkout.",
103 min_length=1,
104 )
107CheckoutLinkCreate = Annotated[ 1a
108 CheckoutLinkCreateProductPrice
109 | CheckoutLinkCreateProduct
110 | CheckoutLinkCreateProducts,
111 SetSchemaReference("CheckoutLinkCreate"),
112]
115class CheckoutLinkUpdate(MetadataInputMixin, TrialConfigurationInputMixin): 1a
116 """Schema to update an existing checkout link."""
118 products: list[UUID4] | None = Field( 1a
119 default=None,
120 description="List of products that will be available to select at checkout.",
121 min_length=1,
122 )
123 label: str | None = None 1a
124 allow_discount_codes: bool | None = Field( 1a
125 default=None, description=_allow_discount_codes_description
126 )
127 require_billing_address: bool | None = Field( 1a
128 default=None, description=_require_billing_address_description
129 )
130 discount_id: UUID4 | None = Field( 1a
131 default=None, description=_discount_id_description
132 )
133 success_url: SuccessURL = None 1a
136class CheckoutLinkBase( 1a
137 MetadataOutputMixin, TrialConfigurationOutputMixin, TimestampedSchema, IDSchema
138):
139 payment_processor: PaymentProcessor = Field(description="Payment processor used.") 1a
140 client_secret: str = Field( 1a
141 description="Client secret used to access the checkout link."
142 )
143 success_url: str | None = Field( 1a
144 description=(
145 "URL where the customer will be redirected after a successful payment."
146 )
147 )
148 label: str | None = Field( 1a
149 description="Optional label to distinguish links internally"
150 )
151 allow_discount_codes: bool = Field(description=_allow_discount_codes_description) 1a
152 require_billing_address: bool = Field( 1a
153 description=_require_billing_address_description
154 )
155 discount_id: UUID4 | None = Field(description=_discount_id_description) 1a
156 organization_id: OrganizationID 1a
158 @computed_field # type: ignore[prop-decorator] 1a
159 @property 1a
160 def url(self) -> str: 1a
161 return settings.CHECKOUT_BASE_URL.format(client_secret=self.client_secret)
164class CheckoutLinkProduct(ProductBase, MetadataOutputMixin): 1a
165 """Product data for a checkout link."""
167 prices: ProductPriceList 1a
168 benefits: BenefitPublicList 1a
169 medias: ProductMediaList 1a
172CheckoutLinkDiscount = Annotated[ 1a
173 DiscountMinimal, MergeJSONSchema({"title": "CheckoutLinkDiscount"})
174]
177class CheckoutLink(CheckoutLinkBase): 1a
178 """Checkout link data."""
180 products: list[CheckoutLinkProduct] 1a
181 discount: CheckoutLinkDiscount | None 1a
183 # Deprecated fields for backward compatibility
184 product_id: SkipJsonSchema[UUID4] = Field( 1a
185 validation_alias=AliasPath("products", 0, "id"),
186 deprecated="Use `products` instead.",
187 )
188 product_price_id: SkipJsonSchema[UUID4] = Field( 1a
189 validation_alias=AliasPath("products", 0, "prices", 0, "id"),
190 deprecated="Use `products` instead.",
191 )
192 product: SkipJsonSchema[CheckoutLinkProduct] = Field( 1a
193 validation_alias=AliasPath("products", 0),
194 deprecated="Use `products` instead.",
195 )
196 product_price: SkipJsonSchema[ProductPrice] = Field( 1a
197 validation_alias=AliasPath("products", 0, "prices", 0),
198 deprecated="Use `products` instead.",
199 )