Coverage for polar/checkout/schemas.py: 95%

188 statements  

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

1from datetime import datetime 1a

2from typing import Annotated, Any, Literal 1a

3 

4from annotated_types import Ge, Le 1a

5from pydantic import ( 1a

6 UUID4, 

7 AliasChoices, 

8 Discriminator, 

9 Field, 

10 HttpUrl, 

11 IPvAnyAddress, 

12 Tag, 

13 computed_field, 

14) 

15from pydantic.json_schema import SkipJsonSchema 1a

16 

17from polar.custom_field.data import ( 1a

18 CustomFieldDataInputMixin, 

19 CustomFieldDataOutputMixin, 

20) 

21from polar.custom_field.schemas import AttachedCustomField 1a

22from polar.customer.schemas.customer import CustomerNameInput 1a

23from polar.discount.schemas import ( 1a

24 DiscountFixedBase, 

25 DiscountOnceForeverDurationBase, 

26 DiscountPercentageBase, 

27 DiscountRepeatDurationBase, 

28) 

29from polar.enums import PaymentProcessor 1a

30from polar.kit.address import Address, AddressInput 1a

31from polar.kit.email import EmailStrDNS 1a

32from polar.kit.metadata import ( 1a

33 METADATA_DESCRIPTION, 

34 MetadataField, 

35 MetadataInputMixin, 

36 MetadataOutputMixin, 

37) 

38from polar.kit.schemas import ( 1a

39 EmptyStrToNoneValidator, 

40 HttpUrlToStr, 

41 IDSchema, 

42 Schema, 

43 SetSchemaReference, 

44 TimestampedSchema, 

45) 

46from polar.kit.trial import ( 1a

47 TrialConfigurationInputMixin, 

48 TrialConfigurationOutputMixin, 

49 TrialInterval, 

50) 

51from polar.models.checkout import ( 1a

52 CheckoutBillingAddressFields, 

53 CheckoutCustomerBillingAddressFields, 

54 CheckoutStatus, 

55) 

56from polar.models.discount import DiscountDuration, DiscountType 1a

57from polar.organization.schemas import OrganizationPublicBase 1a

58from polar.product.schemas import ( 1a

59 BenefitPublicList, 

60 ProductBase, 

61 ProductMediaList, 

62 ProductPrice, 

63 ProductPriceCreateList, 

64 ProductPriceList, 

65) 

66 

67# Ref: https://stripe.com/docs/api/payment_intents/object#payment_intent_object-amount 

68MAXIMUM_PRICE_AMOUNT = 99999999 1a

69MINIMUM_PRICE_AMOUNT = 50 1a

70 

71Amount = Annotated[ 1a

72 int, 

73 Field( 

74 description=( 

75 "Amount in cents, before discounts and taxes. " 

76 "Only useful for custom prices, it'll be ignored for fixed and free prices." 

77 ) 

78 ), 

79 Ge(MINIMUM_PRICE_AMOUNT), 

80 Le(MAXIMUM_PRICE_AMOUNT), 

81] 

82CustomerEmail = Annotated[ 1a

83 EmailStrDNS, 

84 Field(description="Email address of the customer."), 

85] 

86CustomerIPAddress = Annotated[ 1a

87 IPvAnyAddress, 

88 Field( 

89 description="IP address of the customer. Used to detect tax location.", 

90 ), 

91] 

92CustomerBillingAddressInput = Annotated[ 1a

93 AddressInput, Field(description="Billing address of the customer.") 

94] 

95CustomerBillingAddress = Annotated[ 1a

96 Address, Field(description="Billing address of the customer.") 

97] 

98SuccessURL = Annotated[ 1a

99 HttpUrl | None, 

100 Field( 

101 description=( 

102 "URL where the customer will be redirected after a successful payment." 

103 "You can add the `checkout_id={CHECKOUT_ID}` query parameter " 

104 "to retrieve the checkout session id." 

105 ) 

106 ), 

107] 

108ReturnURL = Annotated[ 1a

109 HttpUrlToStr | None, 

110 Field( 

111 description=( 

112 "When set, a back button will be shown in the checkout " 

113 "to return to this URL." 

114 ) 

115 ), 

116] 

117EmbedOrigin = Annotated[ 1a

118 str | None, 

119 Field( 

120 description=( 

121 "If you plan to embed the checkout session, " 

122 "set this to the Origin of the embedding page. " 

123 "It'll allow the Polar iframe to communicate with the parent page." 

124 ), 

125 ), 

126] 

127 

128_external_customer_id_description = ( 1a

129 "ID of the customer in your system. " 

130 "If a matching customer exists on Polar, the resulting order " 

131 "will be linked to this customer. " 

132 "Otherwise, a new customer will be created with this external ID set." 

133) 

134_allow_discount_codes_description = ( 1a

135 "Whether to allow the customer to apply discount codes. " 

136 "If you apply a discount through `discount_id`, it'll still be applied, " 

137 "but the customer won't be able to change it." 

138) 

139_require_billing_address_description = ( 1a

140 "Whether to require the customer to fill their full billing address, instead of " 

141 "just the country. " 

142 "Customers in the US will always be required to fill their full address, " 

143 "regardless of this setting. " 

144 "If you preset the billing address, this setting will be automatically set to " 

145 "`true`." 

146) 

147_allow_trial_description = ( 1a

148 "Whether to enable the trial period for the checkout session. " 

149 "If `false`, the trial period will be disabled, even if the selected product " 

150 "has a trial configured." 

151) 

152_is_business_customer_description = ( 1a

153 "Whether the customer is a business or an individual. " 

154 "If `true`, the customer will be required to fill their full billing address " 

155 "and billing name." 

156) 

157_customer_metadata_description = METADATA_DESCRIPTION.format( 1a

158 heading=( 

159 "Key-value object allowing you to store additional information " 

160 "that'll be copied to the created customer." 

161 ) 

162) 

163 

164 

165class CheckoutCreateBase( 1a

166 CustomFieldDataInputMixin, MetadataInputMixin, TrialConfigurationInputMixin, Schema 

167): 

168 """ 

169 Create a new checkout session. 

170 

171 Metadata set on the checkout will be copied 

172 to the resulting order and/or subscription. 

173 """ 

174 

175 discount_id: UUID4 | None = Field( 1a

176 default=None, description="ID of the discount to apply to the checkout." 

177 ) 

178 allow_discount_codes: bool = Field( 1a

179 default=True, description=_allow_discount_codes_description 

180 ) 

181 require_billing_address: bool = Field( 1a

182 default=False, description=_require_billing_address_description 

183 ) 

184 amount: Amount | None = None 1a

185 seats: int | None = Field( 1a

186 default=None, 

187 ge=1, 

188 le=1000, 

189 description="Number of seats for seat-based pricing. Required for seat-based products.", 

190 ) 

191 allow_trial: bool = Field(default=True, description=_allow_trial_description) 1a

192 customer_id: UUID4 | None = Field( 1a

193 default=None, 

194 description=( 

195 "ID of an existing customer in the organization. " 

196 "The customer data will be pre-filled in the checkout form. " 

197 "The resulting order will be linked to this customer." 

198 ), 

199 ) 

200 is_business_customer: bool = Field( 1a

201 default=False, description=_is_business_customer_description 

202 ) 

203 external_customer_id: str | None = Field( 1a

204 default=None, 

205 description=_external_customer_id_description, 

206 validation_alias=AliasChoices("external_customer_id", "customer_external_id"), 

207 ) 

208 customer_name: CustomerNameInput | None = None 1a

209 customer_email: CustomerEmail | None = None 1a

210 customer_ip_address: CustomerIPAddress | None = None 1a

211 customer_billing_name: Annotated[str | None, EmptyStrToNoneValidator] = None 1a

212 customer_billing_address: CustomerBillingAddressInput | None = None 1a

213 customer_tax_id: Annotated[str | None, EmptyStrToNoneValidator] = None 1a

214 customer_metadata: MetadataField = Field( 1a

215 default_factory=dict, description=_customer_metadata_description 

216 ) 

217 subscription_id: UUID4 | None = Field( 1a

218 default=None, 

219 description=( 

220 "ID of a subscription to upgrade. It must be on a free pricing. " 

221 "If checkout is successful, metadata set on this checkout " 

222 "will be copied to the subscription, and existing keys will be overwritten." 

223 ), 

224 ) 

225 success_url: SuccessURL = None 1a

226 return_url: ReturnURL = None 1a

227 embed_origin: EmbedOrigin = None 1a

228 

229 

230class CheckoutPriceCreate(CheckoutCreateBase): 1a

231 """ 

232 Create a new checkout session from a product price. 

233 

234 **Deprecated**: Use `CheckoutProductsCreate` instead. 

235 

236 Metadata set on the checkout will be copied 

237 to the resulting order and/or subscription. 

238 """ 

239 

240 product_price_id: UUID4 = Field(description="ID of the product price to checkout.") 1a

241 

242 

243class CheckoutProductCreate(CheckoutCreateBase): 1a

244 """ 

245 Create a new checkout session from a product. 

246 

247 **Deprecated**: Use `CheckoutProductsCreate` instead. 

248 

249 Metadata set on the checkout will be copied 

250 to the resulting order and/or subscription. 

251 """ 

252 

253 product_id: UUID4 = Field( 1a

254 description=( 

255 "ID of the product to checkout. First available price will be selected." 

256 ) 

257 ) 

258 

259 

260class CheckoutProductsCreate(CheckoutCreateBase): 1a

261 """ 

262 Create a new checkout session from a list of products. 

263 Customers will be able to switch between those products. 

264 

265 Metadata set on the checkout will be copied 

266 to the resulting order and/or subscription. 

267 """ 

268 

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

270 description=( 

271 "List of product IDs available to select at that checkout. " 

272 "The first one will be selected by default." 

273 ), 

274 min_length=1, 

275 ) 

276 prices: dict[UUID4, ProductPriceCreateList] | None = Field( 1a

277 default=None, 

278 description=( 

279 "Optional mapping of product IDs to a list of ad-hoc prices to create for that product. " 

280 "If not set, catalog prices of the product will be used." 

281 ), 

282 ) 

283 

284 

285CheckoutCreate = Annotated[ 1a

286 CheckoutProductsCreate 

287 | SkipJsonSchema[CheckoutProductCreate] 

288 | SkipJsonSchema[CheckoutPriceCreate], 

289 SetSchemaReference("CheckoutCreate"), 

290] 

291 

292 

293class CheckoutCreatePublic(Schema): 1a

294 """Create a new checkout session from a client.""" 

295 

296 product_id: UUID4 = Field(description="ID of the product to checkout.") 1a

297 seats: int | None = Field( 1a

298 default=None, 

299 ge=1, 

300 le=1000, 

301 description="Number of seats for seat-based pricing.", 

302 ) 

303 customer_email: CustomerEmail | None = None 1a

304 subscription_id: UUID4 | None = Field( 1a

305 default=None, 

306 description=( 

307 "ID of a subscription to upgrade. It must be on a free pricing. " 

308 "If checkout is successful, metadata set on this checkout " 

309 "will be copied to the subscription, and existing keys will be overwritten." 

310 ), 

311 ) 

312 

313 

314class CheckoutUpdateBase(CustomFieldDataInputMixin, Schema): 1a

315 product_id: UUID4 | None = Field( 1a

316 default=None, 

317 description=( 

318 "ID of the product to checkout. " 

319 "Must be present in the checkout's product list." 

320 ), 

321 ) 

322 product_price_id: UUID4 | None = Field( 1a

323 default=None, 

324 description=( 

325 "ID of the product price to checkout. " 

326 "Must correspond to a price present in the checkout's product list." 

327 ), 

328 deprecated=( 

329 "Use `product_id` unless you have a product with legacy pricing " 

330 "including several recurring intervals." 

331 ), 

332 ) 

333 amount: Amount | None = None 1a

334 seats: int | None = Field( 1a

335 default=None, 

336 ge=1, 

337 le=1000, 

338 description="Number of seats for seat-based pricing.", 

339 ) 

340 is_business_customer: bool | None = None 1a

341 customer_name: CustomerNameInput | None = None 1a

342 customer_email: CustomerEmail | None = None 1a

343 customer_billing_name: Annotated[str | None, EmptyStrToNoneValidator] = None 1a

344 customer_billing_address: CustomerBillingAddressInput | None = None 1a

345 customer_tax_id: Annotated[str | None, EmptyStrToNoneValidator] = None 1a

346 

347 

348class CheckoutUpdate( 1a

349 MetadataInputMixin, TrialConfigurationInputMixin, CheckoutUpdateBase 

350): 

351 """Update an existing checkout session using an access token.""" 

352 

353 discount_id: UUID4 | None = Field( 1a

354 default=None, description="ID of the discount to apply to the checkout." 

355 ) 

356 allow_discount_codes: bool | None = Field( 1a

357 default=None, description=_allow_discount_codes_description 

358 ) 

359 require_billing_address: bool | None = Field( 1a

360 default=None, description=_require_billing_address_description 

361 ) 

362 allow_trial: bool | None = Field(default=None, description=_allow_trial_description) 1a

363 customer_ip_address: CustomerIPAddress | None = None 1a

364 customer_metadata: MetadataField | None = Field( 1a

365 default=None, description=_customer_metadata_description 

366 ) 

367 success_url: SuccessURL = None 1a

368 return_url: ReturnURL = None 1a

369 embed_origin: EmbedOrigin = None 1a

370 

371 

372class CheckoutUpdatePublic(CheckoutUpdateBase): 1a

373 """Update an existing checkout session using the client secret.""" 

374 

375 discount_code: str | None = Field( 1a

376 default=None, description="Discount code to apply to the checkout." 

377 ) 

378 allow_trial: Literal[False] | None = Field( 1a

379 default=None, 

380 description=( 

381 "Disable the trial period for the checkout session. " 

382 "It's mainly useful when the trial is blocked because the customer " 

383 "already redeemed one." 

384 ), 

385 ) 

386 

387 

388class CheckoutConfirmBase(CheckoutUpdatePublic): ... 1a

389 

390 

391class CheckoutConfirmStripe(CheckoutConfirmBase): 1a

392 """Confirm a checkout session using a Stripe confirmation token.""" 

393 

394 confirmation_token_id: str | None = Field( 1a

395 None, 

396 description=( 

397 "ID of the Stripe confirmation token. " 

398 "Required for fixed prices and custom prices." 

399 ), 

400 ) 

401 

402 

403CheckoutConfirm = CheckoutConfirmStripe 1a

404 

405 

406class CheckoutBase(CustomFieldDataOutputMixin, TimestampedSchema, IDSchema): 1a

407 payment_processor: PaymentProcessor = Field(description="Payment processor used.") 1a

408 status: CheckoutStatus = Field( 1a

409 description=""" 

410 Status of the checkout session. 

411 

412 - Open: the checkout session was opened. 

413 - Expired: the checkout session was expired and is no more accessible. 

414 - Confirmed: the user on the checkout session clicked Pay. This is not indicative of the payment's success status. 

415 - Failed: the checkout definitely failed for technical reasons and cannot be retried. In most cases, this state is never reached. 

416 - Succeeded: the payment on the checkout was performed successfully. 

417 """ 

418 ) 

419 client_secret: str = Field( 1a

420 description=( 

421 "Client secret used to update and complete " 

422 "the checkout session from the client." 

423 ) 

424 ) 

425 url: str = Field( 1a

426 description="URL where the customer can access the checkout session." 

427 ) 

428 expires_at: datetime = Field( 1a

429 description="Expiration date and time of the checkout session." 

430 ) 

431 success_url: str = Field( 1a

432 description=( 

433 "URL where the customer will be redirected after a successful payment." 

434 ) 

435 ) 

436 return_url: str | None = Field( 1a

437 description=( 

438 "When set, a back button will be shown in the checkout " 

439 "to return to this URL." 

440 ) 

441 ) 

442 embed_origin: str | None = Field( 1a

443 description=( 

444 "When checkout is embedded, " 

445 "represents the Origin of the page embedding the checkout. " 

446 "Used as a security measure to send messages only to the embedding page." 

447 ) 

448 ) 

449 amount: int = Field(description="Amount in cents, before discounts and taxes.") 1a

450 seats: int | None = Field( 1a

451 default=None, description="Number of seats for seat-based pricing." 

452 ) 

453 price_per_seat: int | None = Field( 1a

454 default=None, 

455 description=( 

456 "Price per seat in cents for the current seat count, " 

457 "based on the applicable tier. Only relevant for seat-based pricing." 

458 ), 

459 ) 

460 discount_amount: int = Field(description="Discount amount in cents.") 1a

461 net_amount: int = Field( 1a

462 description="Amount in cents, after discounts but before taxes." 

463 ) 

464 tax_amount: int | None = Field( 1a

465 description=( 

466 "Sales tax amount in cents. " 

467 "If `null`, it means there is no enough information yet to calculate it." 

468 ) 

469 ) 

470 total_amount: int = Field(description="Amount in cents, after discounts and taxes.") 1a

471 currency: str = Field(description="Currency code of the checkout session.") 1a

472 

473 allow_trial: bool | None = Field(description=_allow_trial_description) 1a

474 active_trial_interval: TrialInterval | None = Field( 1a

475 description=( 

476 "Interval unit of the trial period, if any. " 

477 "This value is either set from the checkout, if `trial_interval` is set, " 

478 "or from the selected product." 

479 ) 

480 ) 

481 active_trial_interval_count: int | None = Field( 1a

482 description=( 

483 "Number of interval units of the trial period, if any. " 

484 "This value is either set from the checkout, if `trial_interval_count` " 

485 "is set, or from the selected product." 

486 ) 

487 ) 

488 trial_end: datetime | None = Field( 1a

489 description="End date and time of the trial period, if any." 

490 ) 

491 

492 organization_id: UUID4 = Field( 1a

493 description="ID of the organization owning the checkout session." 

494 ) 

495 product_id: UUID4 | None = Field(description="ID of the product to checkout.") 1a

496 product_price_id: UUID4 | None = Field( 1a

497 description="ID of the product price to checkout.", deprecated=True 

498 ) 

499 discount_id: UUID4 | None = Field( 1a

500 description="ID of the discount applied to the checkout." 

501 ) 

502 allow_discount_codes: bool = Field(description=_allow_discount_codes_description) 1a

503 require_billing_address: bool = Field( 1a

504 description=_require_billing_address_description 

505 ) 

506 is_discount_applicable: bool = Field( 1a

507 description=( 

508 "Whether the discount is applicable to the checkout. " 

509 "Typically, free and custom prices are not discountable." 

510 ) 

511 ) 

512 is_free_product_price: bool = Field( 1a

513 description="Whether the product price is free, regardless of discounts." 

514 ) 

515 is_payment_required: bool = Field( 1a

516 description=( 

517 "Whether the checkout requires payment, e.g. in case of free products " 

518 "or discounts that cover the total amount." 

519 ) 

520 ) 

521 is_payment_setup_required: bool = Field( 1a

522 description=( 

523 "Whether the checkout requires setting up a payment method, " 

524 "regardless of the amount, e.g. subscriptions that have first free cycles." 

525 ) 

526 ) 

527 is_payment_form_required: bool = Field( 1a

528 description=( 

529 "Whether the checkout requires a payment form, " 

530 "whether because of a payment or payment method setup." 

531 ) 

532 ) 

533 

534 customer_id: UUID4 | None 1a

535 is_business_customer: bool = Field(description=_is_business_customer_description) 1a

536 customer_name: str | None = Field(description="Name of the customer.") 1a

537 customer_email: str | None = Field(description="Email address of the customer.") 1a

538 customer_ip_address: CustomerIPAddress | None 1a

539 customer_billing_name: str | None 1a

540 customer_billing_address: CustomerBillingAddress | None 1a

541 customer_tax_id: str | None = Field( 1a

542 validation_alias=AliasChoices("customer_tax_id_number", "customer_tax_id") 

543 ) 

544 

545 payment_processor_metadata: dict[str, str] 1a

546 

547 customer_billing_address_fields: SkipJsonSchema[ 1a

548 CheckoutCustomerBillingAddressFields 

549 ] = Field( 

550 deprecated="Use `billing_address_fields` instead.", 

551 ) 

552 billing_address_fields: CheckoutBillingAddressFields = Field( 1a

553 description=( 

554 "Determine which billing address fields " 

555 "should be disabled, optional or required in the checkout form." 

556 ) 

557 ) 

558 

559 @computed_field(deprecated="Use `net_amount`.") 1a

560 def subtotal_amount(self) -> SkipJsonSchema[int | None]: 1a

561 return self.net_amount 

562 

563 

564class CheckoutOrganization(OrganizationPublicBase): ... 1a

565 

566 

567class CheckoutProduct(ProductBase): 1a

568 """Product data for a checkout session.""" 

569 

570 prices: ProductPriceList 1a

571 benefits: BenefitPublicList 1a

572 medias: ProductMediaList 1a

573 

574 

575class CheckoutDiscountBase(IDSchema): 1a

576 name: str 1a

577 type: DiscountType 1a

578 duration: DiscountDuration 1a

579 code: str | None 1a

580 

581 

582class CheckoutDiscountFixedOnceForeverDuration( 1a

583 CheckoutDiscountBase, DiscountFixedBase, DiscountOnceForeverDurationBase 

584): 

585 """Schema for a fixed amount discount that is applied once or forever.""" 

586 

587 

588class CheckoutDiscountFixedRepeatDuration( 1a

589 CheckoutDiscountBase, DiscountFixedBase, DiscountRepeatDurationBase 

590): 

591 """ 

592 Schema for a fixed amount discount that is applied on every invoice 

593 for a certain number of months. 

594 """ 

595 

596 

597class CheckoutDiscountPercentageOnceForeverDuration( 1a

598 CheckoutDiscountBase, DiscountPercentageBase, DiscountOnceForeverDurationBase 

599): 

600 """Schema for a percentage discount that is applied once or forever.""" 

601 

602 

603class CheckoutDiscountPercentageRepeatDuration( 1a

604 CheckoutDiscountBase, DiscountPercentageBase, DiscountRepeatDurationBase 

605): 

606 """ 

607 Schema for a percentage discount that is applied on every invoice 

608 for a certain number of months. 

609 """ 

610 

611 

612def get_discount_discriminator_value(v: Any) -> str: 1a

613 if isinstance(v, dict): 

614 type = v["type"] 

615 duration = v["duration"] 

616 else: 

617 type = getattr(v, "type") 

618 duration = getattr(v, "duration") 

619 duration_tag = ( 

620 "once_forever" 

621 if duration in {DiscountDuration.once, DiscountDuration.forever} 

622 else "repeat" 

623 ) 

624 return f"{type}.{duration_tag}" 

625 

626 

627CheckoutDiscount = Annotated[ 1a

628 Annotated[CheckoutDiscountFixedOnceForeverDuration, Tag("fixed.once_forever")] 

629 | Annotated[CheckoutDiscountFixedRepeatDuration, Tag("fixed.repeat")] 

630 | Annotated[ 

631 CheckoutDiscountPercentageOnceForeverDuration, Tag("percentage.once_forever") 

632 ] 

633 | Annotated[CheckoutDiscountPercentageRepeatDuration, Tag("percentage.repeat")], 

634 Discriminator(get_discount_discriminator_value), 

635] 

636 

637 

638class Checkout(MetadataOutputMixin, TrialConfigurationOutputMixin, CheckoutBase): 1a

639 """Checkout session data retrieved using an access token.""" 

640 

641 external_customer_id: str | None = Field( 1a

642 description=_external_customer_id_description, 

643 validation_alias=AliasChoices("external_customer_id", "customer_external_id"), 

644 ) 

645 customer_external_id: str | None = Field( 1a

646 validation_alias=AliasChoices("external_customer_id", "customer_external_id"), 

647 deprecated="Use `external_customer_id` instead.", 

648 ) 

649 products: list[CheckoutProduct] = Field( 1a

650 description="List of products available to select." 

651 ) 

652 product: CheckoutProduct | None = Field(description="Product selected to checkout.") 1a

653 product_price: ProductPrice | None = Field( 1a

654 description="Price of the selected product.", deprecated=True 

655 ) 

656 prices: dict[UUID4, ProductPriceList] | None = Field( 1a

657 description=("Mapping of product IDs to their list of prices.") 

658 ) 

659 discount: CheckoutDiscount | None 1a

660 subscription_id: UUID4 | None 1a

661 attached_custom_fields: list[AttachedCustomField] | None 1a

662 customer_metadata: dict[str, str | int | bool] 1a

663 

664 

665class CheckoutPublic(CheckoutBase): 1a

666 """Checkout session data retrieved using the client secret.""" 

667 

668 products: list[CheckoutProduct] = Field( 1a

669 description="List of products available to select." 

670 ) 

671 product: CheckoutProduct | None = Field(description="Product selected to checkout.") 1a

672 product_price: ProductPrice | None = Field( 1a

673 description="Price of the selected product.", deprecated=True 

674 ) 

675 prices: dict[UUID4, ProductPriceList] | None = Field( 1a

676 description=("Mapping of product IDs to their list of prices.") 

677 ) 

678 discount: CheckoutDiscount | None 1a

679 organization: CheckoutOrganization 1a

680 attached_custom_fields: list[AttachedCustomField] | None 1a

681 

682 

683class CheckoutPublicConfirmed(CheckoutPublic): 1a

684 """ 

685 Checkout session data retrieved using the client secret after confirmation. 

686 

687 It contains a customer session token to retrieve order information 

688 right after the checkout. 

689 """ 

690 

691 status: Literal[CheckoutStatus.confirmed] 1a

692 customer_session_token: str 1a