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.
- checkout_intents-0.2.0/.release-please-manifest.json +3 -0
- checkout_intents-0.2.0/CHANGELOG.md +43 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/CONTRIBUTING.md +12 -12
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/PKG-INFO +113 -1
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/README.md +112 -0
- checkout_intents-0.2.0/examples/complete-checkout-intent.py +78 -0
- checkout_intents-0.2.0/examples/error-handling.py +83 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/pyproject.toml +10 -33
- checkout_intents-0.2.0/requirements-dev.lock +144 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/__init__.py +2 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_client.py +65 -14
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_exceptions.py +34 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_models.py +31 -14
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_compat.py +1 -1
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_utils.py +3 -3
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_version.py +1 -1
- checkout_intents-0.2.0/src/checkout_intents/resources/checkout_intents.py +1258 -0
- checkout_intents-0.2.0/tests/test_environment_inference.py +163 -0
- checkout_intents-0.2.0/tests/test_polling.py +695 -0
- checkout_intents-0.2.0/uv.lock +1808 -0
- checkout_intents-0.1.0/.release-please-manifest.json +0 -3
- checkout_intents-0.1.0/CHANGELOG.md +0 -17
- checkout_intents-0.1.0/requirements-dev.lock +0 -137
- checkout_intents-0.1.0/src/checkout_intents/resources/checkout_intents.py +0 -480
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/.gitignore +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/LICENSE +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/SECURITY.md +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/api.md +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/bin/check-release-environment +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/bin/publish-pypi +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/examples/.keep +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/noxfile.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/release-please-config.json +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/requirements.lock +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_base_client.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_compat.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_constants.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_files.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_qs.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_resource.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_response.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_streaming.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_types.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/__init__.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_datetime_parse.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_logs.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_proxy.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_reflection.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_resources_proxy.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_streams.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_sync.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_transform.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/_utils/_typing.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/lib/.keep +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/py.typed +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/resources/__init__.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/resources/brands.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/__init__.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/base_checkout_intent.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/brand_retrieve_response.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/buyer.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/buyer_param.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent_add_payment_params.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent_confirm_params.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/checkout_intent_create_params.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/money.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/offer.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/payment_method.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/payment_method_param.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/variant_selection.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/src/checkout_intents/types/variant_selection_param.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/__init__.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/api_resources/__init__.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/api_resources/test_brands.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/api_resources/test_checkout_intents.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/conftest.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/sample_file.txt +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_client.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_deepcopy.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_extract_files.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_files.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_models.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_qs.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_required_args.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_response.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_streaming.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_transform.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_utils/test_datetime_parse.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_utils/test_proxy.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/test_utils/test_typing.py +0 -0
- {checkout_intents-0.1.0 → checkout_intents-0.2.0}/tests/utils.py +0 -0
|
@@ -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
|
|
3
|
+
### With `uv`
|
|
4
4
|
|
|
5
|
-
We use [
|
|
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
|
|
11
|
+
Or [install uv manually](https://docs.astral.sh/uv/getting-started/installation/) and run:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
$
|
|
14
|
+
$ uv sync --all-extras
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
You can then run scripts using `
|
|
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
|
-
#
|
|
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 `
|
|
23
|
+
# now you can omit the `uv run` prefix
|
|
24
24
|
$ python script.py
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
### Without
|
|
27
|
+
### Without `uv`
|
|
28
28
|
|
|
29
|
-
Alternatively if you don't want to install `
|
|
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
|
|
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 you
|
|
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
|
-
$
|
|
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.
|
|
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.
|
|
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.
|
|
43
|
+
[tool.uv]
|
|
44
44
|
managed = true
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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"]
|