checkout-intents 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checkout_intents-0.2.0/.release-please-manifest.json +3 -0
  2. checkout_intents-0.2.0/CHANGELOG.md +43 -0
  3. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/CONTRIBUTING.md +12 -12
  4. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/PKG-INFO +113 -1
  5. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/README.md +112 -0
  6. checkout_intents-0.2.0/examples/complete-checkout-intent.py +78 -0
  7. checkout_intents-0.2.0/examples/error-handling.py +83 -0
  8. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/pyproject.toml +10 -33
  9. checkout_intents-0.2.0/requirements-dev.lock +144 -0
  10. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/__init__.py +2 -0
  11. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_client.py +65 -14
  12. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_exceptions.py +34 -0
  13. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_models.py +31 -14
  14. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_compat.py +1 -1
  15. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_utils.py +3 -3
  16. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_version.py +1 -1
  17. checkout_intents-0.2.0/src/checkout_intents/resources/checkout_intents.py +1258 -0
  18. checkout_intents-0.2.0/tests/test_environment_inference.py +163 -0
  19. checkout_intents-0.2.0/tests/test_polling.py +695 -0
  20. checkout_intents-0.2.0/uv.lock +1808 -0
  21. checkout_intents-0.1.0/.release-please-manifest.json +0 -3
  22. checkout_intents-0.1.0/CHANGELOG.md +0 -17
  23. checkout_intents-0.1.0/requirements-dev.lock +0 -137
  24. checkout_intents-0.1.0/src/checkout_intents/resources/checkout_intents.py +0 -480
  25. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/.gitignore +0 -0
  26. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/LICENSE +0 -0
  27. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/SECURITY.md +0 -0
  28. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/api.md +0 -0
  29. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/bin/check-release-environment +0 -0
  30. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/bin/publish-pypi +0 -0
  31. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/examples/.keep +0 -0
  32. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/noxfile.py +0 -0
  33. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/release-please-config.json +0 -0
  34. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/requirements.lock +0 -0
  35. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_base_client.py +0 -0
  36. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_compat.py +0 -0
  37. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_constants.py +0 -0
  38. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_files.py +0 -0
  39. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_qs.py +0 -0
  40. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_resource.py +0 -0
  41. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_response.py +0 -0
  42. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_streaming.py +0 -0
  43. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_types.py +0 -0
  44. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/__init__.py +0 -0
  45. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_datetime_parse.py +0 -0
  46. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_logs.py +0 -0
  47. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_proxy.py +0 -0
  48. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_reflection.py +0 -0
  49. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_resources_proxy.py +0 -0
  50. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_streams.py +0 -0
  51. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_sync.py +0 -0
  52. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_transform.py +0 -0
  53. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_typing.py +0 -0
  54. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/lib/.keep +0 -0
  55. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/py.typed +0 -0
  56. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/resources/__init__.py +0 -0
  57. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/resources/brands.py +0 -0
  58. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/__init__.py +0 -0
  59. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/base_checkout_intent.py +0 -0
  60. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/brand_retrieve_response.py +0 -0
  61. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/buyer.py +0 -0
  62. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/buyer_param.py +0 -0
  63. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent.py +0 -0
  64. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent_add_payment_params.py +0 -0
  65. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent_confirm_params.py +0 -0
  66. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent_create_params.py +0 -0
  67. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/money.py +0 -0
  68. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/offer.py +0 -0
  69. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/payment_method.py +0 -0
  70. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/payment_method_param.py +0 -0
  71. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/variant_selection.py +0 -0
  72. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/variant_selection_param.py +0 -0
  73. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/__init__.py +0 -0
  74. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/api_resources/__init__.py +0 -0
  75. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/api_resources/test_brands.py +0 -0
  76. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/api_resources/test_checkout_intents.py +0 -0
  77. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/conftest.py +0 -0
  78. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/sample_file.txt +0 -0
  79. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_client.py +0 -0
  80. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_deepcopy.py +0 -0
  81. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_extract_files.py +0 -0
  82. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_files.py +0 -0
  83. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_models.py +0 -0
  84. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_qs.py +0 -0
  85. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_required_args.py +0 -0
  86. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_response.py +0 -0
  87. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_streaming.py +0 -0
  88. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_transform.py +0 -0
  89. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_utils/test_datetime_parse.py +0 -0
  90. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_utils/test_proxy.py +0 -0
  91. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_utils/test_typing.py +0 -0
  92. {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.0"
3
+ }
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 (2025-11-13)
4
+
5
+ Full Changelog: [v0.1.0...v0.2.0](https://github.com/rye-com/checkout-intents-python/compare/v0.1.0...v0.2.0)
6
+
7
+ ### Features
8
+
9
+ * **api:** add polling helpers ([35dfc75](https://github.com/rye-com/checkout-intents-python/commit/35dfc75a2335fabb2ad1bab4b14f3f231deca600))
10
+ * **api:** infer environment from api key ([341d678](https://github.com/rye-com/checkout-intents-python/commit/341d6781d5275abec09fcc6d4634d3725f096674))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([004dd94](https://github.com/rye-com/checkout-intents-python/commit/004dd94cb5ec8647b21ba2568744bbb3e850c132))
16
+
17
+
18
+ ### Chores
19
+
20
+ * **internal:** add type ignore annotations ([0d0990e](https://github.com/rye-com/checkout-intents-python/commit/0d0990e8b9f83614725366b69df65ca2c9aec402))
21
+ * **internal:** replace rye with uv ([6cc9fcc](https://github.com/rye-com/checkout-intents-python/commit/6cc9fcc05af9040b863187affc79323812af3d83))
22
+
23
+
24
+ ### Documentation
25
+
26
+ * **api:** add polling helpers ([7bd9f19](https://github.com/rye-com/checkout-intents-python/commit/7bd9f19fbec2bdc289cc3ace4edfa10e0914b3a2))
27
+ * **internal:** replace rye with uv ([7fbabe6](https://github.com/rye-com/checkout-intents-python/commit/7fbabe69d822fc3577a1762804dae36e9ea7385a))
28
+
29
+ ## 0.1.0 (2025-11-11)
30
+
31
+ Full Changelog: [v0.0.1...v0.1.0](https://github.com/rye-com/checkout-intents-python/compare/v0.0.1...v0.1.0)
32
+
33
+ ### Features
34
+
35
+ * **api:** api update ([e4a0e20](https://github.com/rye-com/checkout-intents-python/commit/e4a0e206d7566f904ac22caea8954990ad5c7271))
36
+ * **api:** api update ([7d95f0d](https://github.com/rye-com/checkout-intents-python/commit/7d95f0db63098d4edf209c7291959eb5f08df44b))
37
+
38
+
39
+ ### Chores
40
+
41
+ * configure new SDK language ([a8f36d4](https://github.com/rye-com/checkout-intents-python/commit/a8f36d46dc5c0d3e868d289132bb83465736d0f5))
42
+ * update SDK settings ([5271e8a](https://github.com/rye-com/checkout-intents-python/commit/5271e8aa9f149e67b203919039afb2f61deca5e2))
43
+ * update SDK settings ([949efc6](https://github.com/rye-com/checkout-intents-python/commit/949efc6b67a53d856d11214bc6a924d879c2dfab))
@@ -1,32 +1,32 @@
1
1
  ## Setting up the environment
2
2
 
3
- ### With Rye
3
+ ### With `uv`
4
4
 
5
- We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run:
5
+ We use [uv](https://docs.astral.sh/uv/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run:
6
6
 
7
7
  ```sh
8
8
  $ ./scripts/bootstrap
9
9
  ```
10
10
 
11
- Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run:
11
+ Or [install uv manually](https://docs.astral.sh/uv/getting-started/installation/) and run:
12
12
 
13
13
  ```sh
14
- $ rye sync --all-features
14
+ $ uv sync --all-extras
15
15
  ```
16
16
 
17
- You can then run scripts using `rye run python script.py` or by activating the virtual environment:
17
+ You can then run scripts using `uv run python script.py` or by manually activating the virtual environment:
18
18
 
19
19
  ```sh
20
- # Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work
20
+ # manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work
21
21
  $ source .venv/bin/activate
22
22
 
23
- # now you can omit the `rye run` prefix
23
+ # now you can omit the `uv run` prefix
24
24
  $ python script.py
25
25
  ```
26
26
 
27
- ### Without Rye
27
+ ### Without `uv`
28
28
 
29
- Alternatively if you don't want to install `Rye`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command:
29
+ Alternatively if you don't want to install `uv`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command:
30
30
 
31
31
  ```sh
32
32
  $ pip install -r requirements-dev.lock
@@ -45,7 +45,7 @@ All files in the `examples/` directory are not modified by the generator and can
45
45
  ```py
46
46
  # add an example to examples/<your-example>.py
47
47
 
48
- #!/usr/bin/env -S rye run python
48
+ #!/usr/bin/env -S uv run python
49
49
 
50
50
  ```
51
51
 
@@ -57,7 +57,7 @@ $ ./examples/<your-example>.py
57
57
 
58
58
  ## Using the repository from source
59
59
 
60
- If youd like to use the repository from source, you can either install from git or link to a cloned repository:
60
+ If you'd like to use the repository from source, you can either install from git or link to a cloned repository:
61
61
 
62
62
  To install via git:
63
63
 
@@ -72,7 +72,7 @@ Building this package will create two files in the `dist/` directory, a `.tar.gz
72
72
  To create a distributable version of the library, all you have to do is run this command:
73
73
 
74
74
  ```sh
75
- $ rye build
75
+ $ uv build
76
76
  # or
77
77
  $ python -m build
78
78
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: checkout-intents
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: The official Python library for the Checkout Intents API
5
5
  Project-URL: Homepage, https://github.com/rye-com/checkout-intents-python
6
6
  Project-URL: Repository, https://github.com/rye-com/checkout-intents-python
@@ -88,6 +88,98 @@ we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
88
88
  to add `CHECKOUT_INTENTS_API_KEY="My API Key"` to your `.env` file
89
89
  so that your API Key is not stored in source control.
90
90
 
91
+ ### Polling Helpers
92
+
93
+ This SDK includes helper methods for the asynchronous checkout flow. The recommended pattern follows Rye's two-phase checkout:
94
+
95
+ ```python
96
+ from checkout_intents import CheckoutIntents
97
+
98
+ client = CheckoutIntents()
99
+
100
+ # Phase 1: Create and wait for offer
101
+ intent = client.checkout_intents.create_and_poll(
102
+ buyer={
103
+ "address1": "123 Main St",
104
+ "city": "New York",
105
+ "country": "US",
106
+ "email": "john.doe@example.com",
107
+ "first_name": "John",
108
+ "last_name": "Doe",
109
+ "phone": "5555555555",
110
+ "postal_code": "10001",
111
+ "province": "NY",
112
+ },
113
+ product_url="https://example.com/product",
114
+ quantity=1,
115
+ )
116
+
117
+ # Handle failure during offer retrieval
118
+ if intent.state == "failed":
119
+ print(f"Failed: {intent.failure_reason}")
120
+ else:
121
+ # Review pricing with user
122
+ print(f"Total: {intent.offer.cost.total}")
123
+
124
+ # Phase 2: Confirm and wait for completion
125
+ completed = client.checkout_intents.confirm_and_poll(
126
+ intent.id,
127
+ payment_method={
128
+ "type": "stripe_token",
129
+ "stripe_token": "tok_visa",
130
+ },
131
+ )
132
+
133
+ print(f"Status: {completed.state}")
134
+ ```
135
+
136
+ For more examples, see the [`examples/`](https://github.com/rye-com/checkout-intents-python/tree/main/./examples) directory:
137
+
138
+ - [`complete-checkout-intent.py`](https://github.com/rye-com/checkout-intents-python/tree/main/./examples/complete-checkout-intent.py) - Recommended two-phase flow
139
+ - [`error-handling.py`](https://github.com/rye-com/checkout-intents-python/tree/main/./examples/error-handling.py) - Timeout and error handling with `PollTimeoutError`
140
+
141
+ Available polling methods:
142
+
143
+ - `create_and_poll()` - Create and poll until offer is ready (awaiting_confirmation or failed)
144
+ - `confirm_and_poll()` - Confirm and poll until completion (completed or failed)
145
+ - `poll_until_completed()` - Poll until completed or failed
146
+ - `poll_until_awaiting_confirmation()` - Poll until offer is ready or failed
147
+
148
+ All polling methods support customizable timeouts:
149
+
150
+ ```python
151
+ # Configure polling behavior
152
+ intent = client.checkout_intents.poll_until_completed(
153
+ intent_id,
154
+ poll_interval=5.0, # Poll every 5 seconds (default)
155
+ max_attempts=120, # Try up to 120 times, ~10 minutes (default)
156
+ )
157
+ ```
158
+
159
+ #### Handling Polling Timeouts
160
+
161
+ When polling operations exceed `max_attempts`, a `PollTimeoutError` is raised with helpful context:
162
+
163
+ ```python
164
+ from checkout_intents import CheckoutIntents, PollTimeoutError
165
+
166
+ client = CheckoutIntents()
167
+
168
+ try:
169
+ intent = client.checkout_intents.poll_until_completed(
170
+ intent_id,
171
+ poll_interval=5.0,
172
+ max_attempts=60,
173
+ )
174
+ except PollTimeoutError as e:
175
+ print(f"Polling timed out for intent: {e.intent_id}")
176
+ print(f"Attempted {e.attempts} times over {(e.attempts * e.poll_interval) / 1000}s")
177
+
178
+ # You can retrieve the current state manually
179
+ current_intent = client.checkout_intents.retrieve(e.intent_id)
180
+ print(f"Current state: {current_intent.state}")
181
+ ```
182
+
91
183
  ## Async usage
92
184
 
93
185
  Simply import `AsyncCheckoutIntents` instead of `CheckoutIntents` and use `await` with each API call:
@@ -258,6 +350,26 @@ Error codes are as follows:
258
350
  | 429 | `RateLimitError` |
259
351
  | >=500 | `InternalServerError` |
260
352
  | N/A | `APIConnectionError` |
353
+ | N/A | `PollTimeoutError` |
354
+
355
+ ### Polling Timeout Errors
356
+
357
+ When using polling helper methods, if the operation exceeds the configured `max_attempts`, a `PollTimeoutError` is raised. This error includes detailed context about the timeout:
358
+
359
+ ```python
360
+ from checkout_intents import CheckoutIntents, PollTimeoutError
361
+
362
+ try:
363
+ intent = client.checkout_intents.poll_until_completed("intent_id")
364
+ except PollTimeoutError as e:
365
+ # Access timeout details
366
+ print(f"Intent ID: {e.intent_id}")
367
+ print(f"Attempts: {e.attempts}")
368
+ print(f"Poll interval: {e.poll_interval}s")
369
+ print(f"Max attempts: {e.max_attempts}")
370
+ ```
371
+
372
+ See the [error-handling.py example](https://github.com/rye-com/checkout-intents-python/tree/main/./examples/error-handling.py) for more detailed timeout handling patterns.
261
373
 
262
374
  ### Retries
263
375
 
@@ -54,6 +54,98 @@ we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
54
54
  to add `CHECKOUT_INTENTS_API_KEY="My API Key"` to your `.env` file
55
55
  so that your API Key is not stored in source control.
56
56
 
57
+ ### Polling Helpers
58
+
59
+ This SDK includes helper methods for the asynchronous checkout flow. The recommended pattern follows Rye's two-phase checkout:
60
+
61
+ ```python
62
+ from checkout_intents import CheckoutIntents
63
+
64
+ client = CheckoutIntents()
65
+
66
+ # Phase 1: Create and wait for offer
67
+ intent = client.checkout_intents.create_and_poll(
68
+ buyer={
69
+ "address1": "123 Main St",
70
+ "city": "New York",
71
+ "country": "US",
72
+ "email": "john.doe@example.com",
73
+ "first_name": "John",
74
+ "last_name": "Doe",
75
+ "phone": "5555555555",
76
+ "postal_code": "10001",
77
+ "province": "NY",
78
+ },
79
+ product_url="https://example.com/product",
80
+ quantity=1,
81
+ )
82
+
83
+ # Handle failure during offer retrieval
84
+ if intent.state == "failed":
85
+ print(f"Failed: {intent.failure_reason}")
86
+ else:
87
+ # Review pricing with user
88
+ print(f"Total: {intent.offer.cost.total}")
89
+
90
+ # Phase 2: Confirm and wait for completion
91
+ completed = client.checkout_intents.confirm_and_poll(
92
+ intent.id,
93
+ payment_method={
94
+ "type": "stripe_token",
95
+ "stripe_token": "tok_visa",
96
+ },
97
+ )
98
+
99
+ print(f"Status: {completed.state}")
100
+ ```
101
+
102
+ For more examples, see the [`examples/`](./examples) directory:
103
+
104
+ - [`complete-checkout-intent.py`](./examples/complete-checkout-intent.py) - Recommended two-phase flow
105
+ - [`error-handling.py`](./examples/error-handling.py) - Timeout and error handling with `PollTimeoutError`
106
+
107
+ Available polling methods:
108
+
109
+ - `create_and_poll()` - Create and poll until offer is ready (awaiting_confirmation or failed)
110
+ - `confirm_and_poll()` - Confirm and poll until completion (completed or failed)
111
+ - `poll_until_completed()` - Poll until completed or failed
112
+ - `poll_until_awaiting_confirmation()` - Poll until offer is ready or failed
113
+
114
+ All polling methods support customizable timeouts:
115
+
116
+ ```python
117
+ # Configure polling behavior
118
+ intent = client.checkout_intents.poll_until_completed(
119
+ intent_id,
120
+ poll_interval=5.0, # Poll every 5 seconds (default)
121
+ max_attempts=120, # Try up to 120 times, ~10 minutes (default)
122
+ )
123
+ ```
124
+
125
+ #### Handling Polling Timeouts
126
+
127
+ When polling operations exceed `max_attempts`, a `PollTimeoutError` is raised with helpful context:
128
+
129
+ ```python
130
+ from checkout_intents import CheckoutIntents, PollTimeoutError
131
+
132
+ client = CheckoutIntents()
133
+
134
+ try:
135
+ intent = client.checkout_intents.poll_until_completed(
136
+ intent_id,
137
+ poll_interval=5.0,
138
+ max_attempts=60,
139
+ )
140
+ except PollTimeoutError as e:
141
+ print(f"Polling timed out for intent: {e.intent_id}")
142
+ print(f"Attempted {e.attempts} times over {(e.attempts * e.poll_interval) / 1000}s")
143
+
144
+ # You can retrieve the current state manually
145
+ current_intent = client.checkout_intents.retrieve(e.intent_id)
146
+ print(f"Current state: {current_intent.state}")
147
+ ```
148
+
57
149
  ## Async usage
58
150
 
59
151
  Simply import `AsyncCheckoutIntents` instead of `CheckoutIntents` and use `await` with each API call:
@@ -224,6 +316,26 @@ Error codes are as follows:
224
316
  | 429 | `RateLimitError` |
225
317
  | >=500 | `InternalServerError` |
226
318
  | N/A | `APIConnectionError` |
319
+ | N/A | `PollTimeoutError` |
320
+
321
+ ### Polling Timeout Errors
322
+
323
+ When using polling helper methods, if the operation exceeds the configured `max_attempts`, a `PollTimeoutError` is raised. This error includes detailed context about the timeout:
324
+
325
+ ```python
326
+ from checkout_intents import CheckoutIntents, PollTimeoutError
327
+
328
+ try:
329
+ intent = client.checkout_intents.poll_until_completed("intent_id")
330
+ except PollTimeoutError as e:
331
+ # Access timeout details
332
+ print(f"Intent ID: {e.intent_id}")
333
+ print(f"Attempts: {e.attempts}")
334
+ print(f"Poll interval: {e.poll_interval}s")
335
+ print(f"Max attempts: {e.max_attempts}")
336
+ ```
337
+
338
+ See the [error-handling.py example](./examples/error-handling.py) for more detailed timeout handling patterns.
227
339
 
228
340
  ### Retries
229
341
 
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env -S uv run python
2
+
3
+ """
4
+ Recommended checkout flow (two-phase):
5
+ 1. Create checkout intent and poll until offer is ready
6
+ 2. Review pricing with user
7
+ 3. Confirm with payment and poll until completion
8
+
9
+ This follows the Rye documented checkout flow.
10
+ """
11
+
12
+ import os
13
+
14
+ from checkout_intents import CheckoutIntents
15
+
16
+
17
+ def main() -> None:
18
+ """Demonstrate the complete two-phase checkout flow."""
19
+ client = CheckoutIntents(
20
+ api_key=os.getenv("CHECKOUT_INTENTS_API_KEY"),
21
+ )
22
+
23
+ # Phase 1: Create checkout intent and wait for offer
24
+ print("Creating checkout intent...")
25
+
26
+ intent = client.checkout_intents.create_and_poll(
27
+ buyer={
28
+ "address1": "123 Main St",
29
+ "city": "New York",
30
+ "country": "US",
31
+ "email": "john.doe@example.com",
32
+ "first_name": "John",
33
+ "last_name": "Doe",
34
+ "phone": "5555555555",
35
+ "postal_code": "10001",
36
+ "province": "NY",
37
+ },
38
+ product_url="https://flybyjing.com/collections/shop/products/sichuan-chili-crisp",
39
+ quantity=1,
40
+ )
41
+
42
+ print(f"Checkout intent created: {intent.id}")
43
+ print(f"State: {intent.state}")
44
+
45
+ # Handle failure during offer retrieval
46
+ if intent.state == "failed":
47
+ print(f"Failed to retrieve offer: {intent.failure_reason}")
48
+ return
49
+
50
+ # Review the offer with the user before confirming
51
+ if hasattr(intent, "offer"):
52
+ print("\nOffer Details:")
53
+ print(f" Subtotal: {intent.offer.cost.subtotal}")
54
+ print(f" Shipping: {intent.offer.cost.shipping or intent.offer.shipping}")
55
+ print(f" Tax: {intent.offer.cost.tax}")
56
+ print(f" Total: {intent.offer.cost.total}")
57
+
58
+ # Phase 2: User confirms, complete the checkout
59
+ print("\nConfirming checkout...")
60
+
61
+ completed = client.checkout_intents.confirm_and_poll(
62
+ intent.id,
63
+ payment_method={
64
+ "type": "stripe_token",
65
+ "stripe_token": "tok_visa", # Use tok_visa in staging
66
+ },
67
+ )
68
+
69
+ print(f"Final state: {completed.state}")
70
+
71
+ if completed.state == "completed":
72
+ print("✓ Order placed successfully!")
73
+ elif completed.state == "failed":
74
+ print(f"✗ Order failed: {completed.failure_reason}")
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env -S uv run python
2
+
3
+ """
4
+ Handling polling timeouts and errors.
5
+
6
+ This example demonstrates:
7
+ - How to catch PollTimeoutError specifically
8
+ - How to access timeout error details
9
+ - How to configure polling parameters
10
+ - How to handle different error scenarios
11
+ - Best practices for error handling
12
+ """
13
+
14
+ import os
15
+
16
+ from checkout_intents import CheckoutIntents, PollTimeoutError, CheckoutIntentsError
17
+
18
+
19
+ def main() -> None:
20
+ """Demonstrate error handling for polling operations."""
21
+ client = CheckoutIntents(
22
+ api_key=os.getenv("CHECKOUT_INTENTS_API_KEY"),
23
+ )
24
+
25
+ print("Creating checkout intent...")
26
+
27
+ intent = client.checkout_intents.create(
28
+ buyer={
29
+ "address1": "123 Main St",
30
+ "city": "New York",
31
+ "country": "US",
32
+ "email": "john.doe@example.com",
33
+ "first_name": "John",
34
+ "last_name": "Doe",
35
+ "phone": "5555555555",
36
+ "postal_code": "10001",
37
+ "province": "NY",
38
+ },
39
+ product_url="https://flybyjing.com/collections/shop/products/sichuan-chili-crisp",
40
+ quantity=1,
41
+ )
42
+
43
+ print(f"Created checkout intent: {intent.id}")
44
+
45
+ try:
46
+ print("\nPolling with immediate timeout (will timeout)...")
47
+
48
+ # Poll with immediate timeout to demonstrate error handling
49
+ client.checkout_intents.poll_until_awaiting_confirmation(
50
+ intent.id,
51
+ poll_interval=0,
52
+ max_attempts=1,
53
+ )
54
+
55
+ print("✓ Polling succeeded (unexpected)")
56
+
57
+ except PollTimeoutError as e:
58
+ print("✗ Polling timed out as expected")
59
+ print(f"\nPollTimeoutError details:")
60
+ print(f" Intent ID: {e.intent_id}")
61
+ print(f" Attempts made: {e.attempts}")
62
+ print(f" Time elapsed: {e.attempts * e.poll_interval}s")
63
+ print(f" Poll interval: {e.poll_interval}s")
64
+ print(f" Max attempts: {e.max_attempts}")
65
+ print(f"\nError message:\n {e}")
66
+
67
+ print("\nTo fix this:")
68
+ print(" - Increase max_attempts (e.g., 60 for ~5 minutes with 5s interval)")
69
+ print(" - Increase poll_interval if you want less frequent checks")
70
+ print(" - Use appropriate polling method (poll_until_awaiting_confirmation or poll_until_completed)")
71
+
72
+ # You can also retrieve the intent manually to check its current state
73
+ print(f"\nManually checking intent state...")
74
+ current_intent = client.checkout_intents.retrieve(e.intent_id)
75
+ print(f" Current state: {current_intent.state}")
76
+
77
+ except CheckoutIntentsError as e:
78
+ # Catch other SDK errors (API errors, network errors, etc.)
79
+ print(f"✗ Other error: {e}")
80
+
81
+
82
+ if __name__ == "__main__":
83
+ main()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "checkout-intents"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "The official Python library for the Checkout Intents API"
5
5
  dynamic = ["readme"]
6
6
  license = "MIT"
@@ -40,10 +40,13 @@ Repository = "https://github.com/rye-com/checkout-intents-python"
40
40
  [project.optional-dependencies]
41
41
  aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]
42
42
 
43
- [tool.rye]
43
+ [tool.uv]
44
44
  managed = true
45
- # version pins are in requirements-dev.lock
46
- dev-dependencies = [
45
+ required-version = ">=0.5.0"
46
+
47
+ [dependency-groups]
48
+ # version pins are in uv.lock
49
+ dev = [
47
50
  "pyright==1.1.399",
48
51
  "mypy",
49
52
  "respx",
@@ -57,35 +60,9 @@ dev-dependencies = [
57
60
  "rich>=13.7.1",
58
61
  "pytest-xdist>=3.6.1",
59
62
  ]
60
-
61
- [tool.rye.scripts]
62
- format = { chain = [
63
- "format:ruff",
64
- "format:docs",
65
- "fix:ruff",
66
- # run formatting again to fix any inconsistencies when imports are stripped
67
- "format:ruff",
68
- ]}
69
- "format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md"
70
- "format:ruff" = "ruff format"
71
-
72
- "lint" = { chain = [
73
- "check:ruff",
74
- "typecheck",
75
- "check:importable",
76
- ]}
77
- "check:ruff" = "ruff check ."
78
- "fix:ruff" = "ruff check --fix ."
79
-
80
- "check:importable" = "python -c 'import checkout_intents'"
81
-
82
- typecheck = { chain = [
83
- "typecheck:pyright",
84
- "typecheck:mypy"
85
- ]}
86
- "typecheck:pyright" = "pyright"
87
- "typecheck:verify-types" = "pyright --verifytypes checkout_intents --ignoreexternal"
88
- "typecheck:mypy" = "mypy ."
63
+ pydantic-v1 = [
64
+ "pydantic>=1.9.0, <3",
65
+ ]
89
66
 
90
67
  [build-system]
91
68
  requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]