Coverage for polar/checkout_link/schemas.py: 98%

61 statements  

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

1from typing import Annotated, Literal 1a

2 

3from pydantic import UUID4, AliasPath, Field, HttpUrl, computed_field 1a

4from pydantic.json_schema import SkipJsonSchema 1a

5 

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) 

29 

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] 

40 

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) 

57 

58 

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

76 

77 

78class CheckoutLinkCreateProductPrice(CheckoutLinkCreateBase): 1a

79 """ 

80 Schema to create a new checkout link from a a single product price. 

81 

82 **Deprecated**: Use `CheckoutLinkCreateProducts` instead. 

83 """ 

84 

85 product_price_id: UUID4 1a

86 

87 

88class CheckoutLinkCreateProduct(CheckoutLinkCreateBase): 1a

89 """ 

90 Schema to create a new checkout link from a a single product. 

91 

92 **Deprecated**: Use `CheckoutLinkCreateProducts` instead. 

93 """ 

94 

95 product_id: UUID4 1a

96 

97 

98class CheckoutLinkCreateProducts(CheckoutLinkCreateBase): 1a

99 """Schema to create a new checkout link.""" 

100 

101 products: list[UUID4] = Field( 1a

102 description="List of products that will be available to select at checkout.", 

103 min_length=1, 

104 ) 

105 

106 

107CheckoutLinkCreate = Annotated[ 1a

108 CheckoutLinkCreateProductPrice 

109 | CheckoutLinkCreateProduct 

110 | CheckoutLinkCreateProducts, 

111 SetSchemaReference("CheckoutLinkCreate"), 

112] 

113 

114 

115class CheckoutLinkUpdate(MetadataInputMixin, TrialConfigurationInputMixin): 1a

116 """Schema to update an existing checkout link.""" 

117 

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

134 

135 

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

157 

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) 

162 

163 

164class CheckoutLinkProduct(ProductBase, MetadataOutputMixin): 1a

165 """Product data for a checkout link.""" 

166 

167 prices: ProductPriceList 1a

168 benefits: BenefitPublicList 1a

169 medias: ProductMediaList 1a

170 

171 

172CheckoutLinkDiscount = Annotated[ 1a

173 DiscountMinimal, MergeJSONSchema({"title": "CheckoutLinkDiscount"}) 

174] 

175 

176 

177class CheckoutLink(CheckoutLinkBase): 1a

178 """Checkout link data.""" 

179 

180 products: list[CheckoutLinkProduct] 1a

181 discount: CheckoutLinkDiscount | None 1a

182 

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 )