vatly-api-python 0.1.0a2__py3-none-any.whl
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.
- vatly/__init__.py +43 -0
- vatly/_model.py +13 -0
- vatly/_transport.py +284 -0
- vatly/_version.py +1 -0
- vatly/client.py +100 -0
- vatly/errors.py +86 -0
- vatly/models.py +3009 -0
- vatly/pagination.py +29 -0
- vatly/py.typed +1 -0
- vatly/resources.py +2003 -0
- vatly/webhooks.py +146 -0
- vatly_api_python-0.1.0a2.dist-info/METADATA +144 -0
- vatly_api_python-0.1.0a2.dist-info/RECORD +15 -0
- vatly_api_python-0.1.0a2.dist-info/WHEEL +4 -0
- vatly_api_python-0.1.0a2.dist-info/licenses/LICENSE +21 -0
vatly/models.py
ADDED
|
@@ -0,0 +1,3009 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: schema.json
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from datetime import date
|
|
8
|
+
from typing import Annotated, Any, Literal
|
|
9
|
+
|
|
10
|
+
from pydantic import AwareDatetime, ConfigDict, Field, RootModel
|
|
11
|
+
|
|
12
|
+
from vatly._model import VatlyModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Model(RootModel[Any]):
|
|
16
|
+
model_config = ConfigDict(
|
|
17
|
+
populate_by_name=True,
|
|
18
|
+
)
|
|
19
|
+
root: Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Money1(VatlyModel):
|
|
23
|
+
"""
|
|
24
|
+
Monetary value with currency
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
model_config = ConfigDict(
|
|
28
|
+
extra="allow",
|
|
29
|
+
populate_by_name=True,
|
|
30
|
+
)
|
|
31
|
+
value: Annotated[
|
|
32
|
+
str,
|
|
33
|
+
Field(
|
|
34
|
+
description='The amount as a decimal string. Sent and returned as a string to\npreserve precision for financial calculations.\n\nThe number of required decimal places is **currency-dependent**: it\nequals the currency\'s ISO 4217 minor-unit count — the same rule the\nserver enforces via [moneyphp/money](https://github.com/moneyphp/money)\'s\n`DecimalMoneyParser`. The `(value, currency)` pair is validated\ntogether (see the `oneOf` below); mixing precisions is rejected:\n\n- 2 decimals — EUR, USD (and most currencies): `"10.00"` ✓, `"10"` ✗, `"10.0"` ✗, `"10.000"` ✗\n- 0 decimals — e.g. JPY: `"1000"` ✓, `"1000.00"` ✗ *(not yet enabled)*\n- 3 decimals — e.g. BHD, KWD, OMR, TND: `"1.000"` ✓, `"1.00"` ✗ *(not yet enabled)*\n\nBecause the value is a decimal amount (not minor units), `"1000"`\nmeans one thousand units — valid only for a 0-decimal currency; for\nEUR/USD you must write `"1000.00"`.\n',
|
|
35
|
+
examples=["99.99"],
|
|
36
|
+
pattern="^\\d+\\.\\d{2}$",
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
currency: Annotated[
|
|
40
|
+
Literal["EUR", "USD"],
|
|
41
|
+
Field(
|
|
42
|
+
description="ISO 4217 currency code. Vatly aligns its supported currency set\nwith its payment provider's; currencies are enabled as PSP support and\ndownstream tax/FX coverage are in place. More currencies may be added\nlater.\n",
|
|
43
|
+
examples=["EUR"],
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Money(RootModel[Money1]):
|
|
49
|
+
model_config = ConfigDict(
|
|
50
|
+
populate_by_name=True,
|
|
51
|
+
)
|
|
52
|
+
root: Annotated[Money1, Field(description="Monetary value with currency")]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class EurMoney(VatlyModel):
|
|
56
|
+
"""
|
|
57
|
+
Monetary value in EUR. Used where the API currently accepts EUR prices
|
|
58
|
+
only: catalog prices and checkout price overrides. The `value` takes
|
|
59
|
+
exactly two decimals.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(
|
|
64
|
+
extra="allow",
|
|
65
|
+
populate_by_name=True,
|
|
66
|
+
)
|
|
67
|
+
value: Annotated[
|
|
68
|
+
str,
|
|
69
|
+
Field(
|
|
70
|
+
description="The amount as a decimal string with exactly two decimals.",
|
|
71
|
+
examples=["99.99"],
|
|
72
|
+
pattern="^\\d+\\.\\d{2}$",
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
currency: Annotated[
|
|
76
|
+
Literal["EUR"],
|
|
77
|
+
Field(
|
|
78
|
+
description="ISO 4217 currency code. Only `EUR` is accepted.",
|
|
79
|
+
examples=["EUR"],
|
|
80
|
+
),
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Link(VatlyModel):
|
|
85
|
+
"""
|
|
86
|
+
HATEOAS link to a related resource
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
model_config = ConfigDict(
|
|
90
|
+
extra="allow",
|
|
91
|
+
populate_by_name=True,
|
|
92
|
+
)
|
|
93
|
+
href: Annotated[
|
|
94
|
+
str,
|
|
95
|
+
Field(
|
|
96
|
+
description="URL of the linked resource",
|
|
97
|
+
examples=["https://api.vatly.com/v1/orders/order_Hn5xWqVfKm8RjTgYbUcP"],
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
type: Annotated[
|
|
101
|
+
Literal["application/json", "text/html"],
|
|
102
|
+
Field(
|
|
103
|
+
description="Content type of the linked resource.\n- `application/json` for API resources\n- `text/html` for hosted pages\n",
|
|
104
|
+
examples=["application/json"],
|
|
105
|
+
),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class BillingAddress(VatlyModel):
|
|
110
|
+
"""
|
|
111
|
+
Customer or merchant billing address
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
model_config = ConfigDict(
|
|
115
|
+
extra="allow",
|
|
116
|
+
populate_by_name=True,
|
|
117
|
+
)
|
|
118
|
+
full_name: Annotated[
|
|
119
|
+
str | None,
|
|
120
|
+
Field(
|
|
121
|
+
alias="fullName",
|
|
122
|
+
description="Full name of the person or contact",
|
|
123
|
+
examples=["John Doe"],
|
|
124
|
+
),
|
|
125
|
+
] = None
|
|
126
|
+
company_name: Annotated[
|
|
127
|
+
str | None,
|
|
128
|
+
Field(
|
|
129
|
+
alias="companyName",
|
|
130
|
+
description="Company or organization name",
|
|
131
|
+
examples=["Acme Corp"],
|
|
132
|
+
),
|
|
133
|
+
] = None
|
|
134
|
+
tax_id: Annotated[
|
|
135
|
+
str | None,
|
|
136
|
+
Field(
|
|
137
|
+
alias="taxId",
|
|
138
|
+
description="Tax identification number for tax purposes",
|
|
139
|
+
examples=["NL123456789B01"],
|
|
140
|
+
),
|
|
141
|
+
] = None
|
|
142
|
+
street_and_number: Annotated[
|
|
143
|
+
str | None,
|
|
144
|
+
Field(
|
|
145
|
+
alias="streetAndNumber",
|
|
146
|
+
description="Street address including building number",
|
|
147
|
+
examples=["123 Main Street"],
|
|
148
|
+
),
|
|
149
|
+
] = None
|
|
150
|
+
street_additional: Annotated[
|
|
151
|
+
str | None,
|
|
152
|
+
Field(
|
|
153
|
+
alias="streetAdditional",
|
|
154
|
+
description="Additional address information (apartment, suite, etc.)",
|
|
155
|
+
examples=["Suite 456"],
|
|
156
|
+
),
|
|
157
|
+
] = None
|
|
158
|
+
city: Annotated[str | None, Field(description="City name", examples=["Amsterdam"])] = None
|
|
159
|
+
region: Annotated[
|
|
160
|
+
str | None,
|
|
161
|
+
Field(description="State, province, or region", examples=["North Holland"]),
|
|
162
|
+
] = None
|
|
163
|
+
postal_code: Annotated[
|
|
164
|
+
str | None,
|
|
165
|
+
Field(alias="postalCode", description="Postal or ZIP code", examples=["1012 AB"]),
|
|
166
|
+
] = None
|
|
167
|
+
country: Annotated[
|
|
168
|
+
str | None,
|
|
169
|
+
Field(
|
|
170
|
+
description="ISO 3166-1 alpha-2 country code",
|
|
171
|
+
examples=["NL"],
|
|
172
|
+
max_length=2,
|
|
173
|
+
min_length=2,
|
|
174
|
+
),
|
|
175
|
+
] = None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class BillingDetails(VatlyModel):
|
|
179
|
+
"""
|
|
180
|
+
Complete billing details including address and contact information
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
model_config = ConfigDict(
|
|
184
|
+
extra="allow",
|
|
185
|
+
populate_by_name=True,
|
|
186
|
+
)
|
|
187
|
+
full_name: Annotated[
|
|
188
|
+
str | None,
|
|
189
|
+
Field(
|
|
190
|
+
alias="fullName",
|
|
191
|
+
description="Full name of the person or contact",
|
|
192
|
+
examples=["John Doe"],
|
|
193
|
+
),
|
|
194
|
+
] = None
|
|
195
|
+
company_name: Annotated[
|
|
196
|
+
str | None,
|
|
197
|
+
Field(
|
|
198
|
+
alias="companyName",
|
|
199
|
+
description="Company or organization name",
|
|
200
|
+
examples=["Acme Corp"],
|
|
201
|
+
),
|
|
202
|
+
] = None
|
|
203
|
+
tax_id: Annotated[
|
|
204
|
+
str | None,
|
|
205
|
+
Field(
|
|
206
|
+
alias="taxId",
|
|
207
|
+
description="Tax identification number",
|
|
208
|
+
examples=["NL123456789B01"],
|
|
209
|
+
),
|
|
210
|
+
] = None
|
|
211
|
+
street_and_number: Annotated[
|
|
212
|
+
str | None,
|
|
213
|
+
Field(
|
|
214
|
+
alias="streetAndNumber",
|
|
215
|
+
description="Street address including building number",
|
|
216
|
+
examples=["123 Main Street"],
|
|
217
|
+
),
|
|
218
|
+
] = None
|
|
219
|
+
street_additional: Annotated[
|
|
220
|
+
str | None,
|
|
221
|
+
Field(
|
|
222
|
+
alias="streetAdditional",
|
|
223
|
+
description="Additional address information",
|
|
224
|
+
examples=["Suite 456"],
|
|
225
|
+
),
|
|
226
|
+
] = None
|
|
227
|
+
city: Annotated[str | None, Field(description="City name", examples=["Amsterdam"])] = None
|
|
228
|
+
region: Annotated[
|
|
229
|
+
str | None,
|
|
230
|
+
Field(description="State, province, or region", examples=["North Holland"]),
|
|
231
|
+
] = None
|
|
232
|
+
postal_code: Annotated[
|
|
233
|
+
str | None,
|
|
234
|
+
Field(alias="postalCode", description="Postal or ZIP code", examples=["1012 AB"]),
|
|
235
|
+
] = None
|
|
236
|
+
country: Annotated[
|
|
237
|
+
str | None,
|
|
238
|
+
Field(description="ISO 3166-1 alpha-2 country code", examples=["NL"]),
|
|
239
|
+
] = None
|
|
240
|
+
email: Annotated[
|
|
241
|
+
str | None,
|
|
242
|
+
Field(
|
|
243
|
+
description="Email address.\nInternationalized domains must use their Punycode (ASCII) form\nper RFC 3492 — e.g. `billing@xn--mller-kva.de`.\n",
|
|
244
|
+
examples=["billing@acme.com"],
|
|
245
|
+
),
|
|
246
|
+
] = None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class Metadata(RootModel[Mapping[str, Any] | None]):
|
|
250
|
+
model_config = ConfigDict(
|
|
251
|
+
populate_by_name=True,
|
|
252
|
+
)
|
|
253
|
+
root: Annotated[
|
|
254
|
+
Mapping[str, Any] | None,
|
|
255
|
+
Field(
|
|
256
|
+
description="Arbitrary key-value metadata for your application.\nUp to 50 keys, with key names up to 40 characters and values up to 500 characters.\n",
|
|
257
|
+
examples=[{"orderId": "internal-12345", "campaign": "summer-sale"}],
|
|
258
|
+
),
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class PaginationLinks(VatlyModel):
|
|
263
|
+
"""
|
|
264
|
+
Pagination links for navigating list results
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
model_config = ConfigDict(
|
|
268
|
+
extra="allow",
|
|
269
|
+
populate_by_name=True,
|
|
270
|
+
)
|
|
271
|
+
self: Annotated[Link, Field(description="Link to the current page")]
|
|
272
|
+
next: Annotated[
|
|
273
|
+
Link | None,
|
|
274
|
+
Field(description="Link to the next page (null if on the last page)"),
|
|
275
|
+
] = None
|
|
276
|
+
prev: Annotated[
|
|
277
|
+
Link | None,
|
|
278
|
+
Field(description="Link to the previous page (null if on the first page)"),
|
|
279
|
+
] = None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Error(VatlyModel):
|
|
283
|
+
"""
|
|
284
|
+
Standard error response.
|
|
285
|
+
|
|
286
|
+
Every non-validation error path returns this shape — `message` is
|
|
287
|
+
always present, and `details` (if present) is an object carrying
|
|
288
|
+
structured context attached by the originating exception. HTTP
|
|
289
|
+
status code is the primary programmatic discriminator.
|
|
290
|
+
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
model_config = ConfigDict(
|
|
294
|
+
extra="allow",
|
|
295
|
+
populate_by_name=True,
|
|
296
|
+
)
|
|
297
|
+
message: Annotated[
|
|
298
|
+
str,
|
|
299
|
+
Field(
|
|
300
|
+
description="Human-readable error message",
|
|
301
|
+
examples=["The requested resource was not found."],
|
|
302
|
+
),
|
|
303
|
+
]
|
|
304
|
+
details: Annotated[
|
|
305
|
+
Mapping[str, Any] | None,
|
|
306
|
+
Field(
|
|
307
|
+
description="Optional structured context attached by the originating exception.\nShape depends on the specific error case; clients should treat\nunknown keys as forward-compatible additions and rely on the HTTP\nstatus code for branching logic.\n",
|
|
308
|
+
examples=[{"resource_type": "checkout", "resource_id": "checkout_abc123"}],
|
|
309
|
+
),
|
|
310
|
+
] = None
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class ValidationError(VatlyModel):
|
|
314
|
+
"""
|
|
315
|
+
Validation error response with field-specific errors
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
model_config = ConfigDict(
|
|
319
|
+
extra="allow",
|
|
320
|
+
populate_by_name=True,
|
|
321
|
+
)
|
|
322
|
+
message: Annotated[
|
|
323
|
+
str,
|
|
324
|
+
Field(
|
|
325
|
+
description="General validation error message",
|
|
326
|
+
examples=["The given data was invalid."],
|
|
327
|
+
),
|
|
328
|
+
]
|
|
329
|
+
errors: Annotated[
|
|
330
|
+
Mapping[str, Sequence[str]],
|
|
331
|
+
Field(
|
|
332
|
+
description="Field-specific validation errors.\nKeys are field names (using dot notation for nested fields).\nValues are arrays of error messages.\n",
|
|
333
|
+
examples=[
|
|
334
|
+
{
|
|
335
|
+
"email": ["The email field is required."],
|
|
336
|
+
"products.0.id": ["The selected products.0.id is invalid."],
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
),
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class Links(VatlyModel):
|
|
344
|
+
"""
|
|
345
|
+
HATEOAS links to related resources
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
model_config = ConfigDict(
|
|
349
|
+
extra="allow",
|
|
350
|
+
populate_by_name=True,
|
|
351
|
+
)
|
|
352
|
+
checkout_url: Annotated[
|
|
353
|
+
Link,
|
|
354
|
+
Field(alias="checkoutUrl", description="Hosted checkout page URL (text/html)"),
|
|
355
|
+
]
|
|
356
|
+
self: Annotated[Link, Field(description="Link to this checkout resource")]
|
|
357
|
+
order: Annotated[
|
|
358
|
+
Link | None,
|
|
359
|
+
Field(
|
|
360
|
+
description="Link to the created order. Always present; `null` until the checkout completes."
|
|
361
|
+
),
|
|
362
|
+
] = None
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class Checkout(VatlyModel):
|
|
366
|
+
"""
|
|
367
|
+
A checkout session represents a hosted payment flow.
|
|
368
|
+
Customers are redirected to the checkout URL to complete their purchase.
|
|
369
|
+
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
model_config = ConfigDict(
|
|
373
|
+
extra="allow",
|
|
374
|
+
populate_by_name=True,
|
|
375
|
+
)
|
|
376
|
+
id: Annotated[
|
|
377
|
+
str,
|
|
378
|
+
Field(
|
|
379
|
+
description="Unique identifier for the checkout",
|
|
380
|
+
examples=["checkout_QdEpFhdSrG4Y3DnfsdqsH"],
|
|
381
|
+
),
|
|
382
|
+
]
|
|
383
|
+
resource: Annotated[
|
|
384
|
+
Literal["checkout"],
|
|
385
|
+
Field(description="Resource type identifier", examples=["checkout"]),
|
|
386
|
+
]
|
|
387
|
+
customer_id: Annotated[
|
|
388
|
+
str | None,
|
|
389
|
+
Field(
|
|
390
|
+
alias="customerId",
|
|
391
|
+
description="ID of the customer associated with this checkout.\nNull until a customer has been associated (for an anonymous\ncheckout this happens when the buyer completes payment).\n",
|
|
392
|
+
examples=["customer_7kBmRtPvXw2NjLhYcZaE"],
|
|
393
|
+
),
|
|
394
|
+
] = None
|
|
395
|
+
order_id: Annotated[
|
|
396
|
+
str | None,
|
|
397
|
+
Field(
|
|
398
|
+
alias="orderId",
|
|
399
|
+
description="ID of the order created from this checkout. Always present in the\npayload; `null` until the checkout completes, then set to the created\norder id.\n",
|
|
400
|
+
examples=["order_Tk7mNvBxKw2RjTgYcZaE"],
|
|
401
|
+
),
|
|
402
|
+
] = None
|
|
403
|
+
testmode: Annotated[
|
|
404
|
+
bool,
|
|
405
|
+
Field(description="Whether this checkout is in test mode", examples=[False]),
|
|
406
|
+
]
|
|
407
|
+
redirect_url_success: Annotated[
|
|
408
|
+
str,
|
|
409
|
+
Field(
|
|
410
|
+
alias="redirectUrlSuccess",
|
|
411
|
+
description="URL to redirect customers after successful payment",
|
|
412
|
+
examples=["https://example.com/success"],
|
|
413
|
+
),
|
|
414
|
+
]
|
|
415
|
+
redirect_url_canceled: Annotated[
|
|
416
|
+
str,
|
|
417
|
+
Field(
|
|
418
|
+
alias="redirectUrlCanceled",
|
|
419
|
+
description="URL to redirect customers if they cancel the checkout",
|
|
420
|
+
examples=["https://example.com/canceled"],
|
|
421
|
+
),
|
|
422
|
+
]
|
|
423
|
+
metadata: Metadata | None = None
|
|
424
|
+
locale: Annotated[
|
|
425
|
+
Literal["en", "de", "fr", "nl", "es", "it", "pt", "pl"] | None,
|
|
426
|
+
Field(
|
|
427
|
+
description="The language you asked the hosted checkout to be presented in, as sent on\ncreation. `null` means you did not specify one, and the checkout picks a\nlanguage from the shopper's browser.\n",
|
|
428
|
+
examples=["de"],
|
|
429
|
+
),
|
|
430
|
+
]
|
|
431
|
+
status: Annotated[
|
|
432
|
+
Literal["created", "paid", "canceled", "failed", "expired"],
|
|
433
|
+
Field(
|
|
434
|
+
description="Current status of the checkout:\n- `created` - Checkout is active and awaiting payment\n- `paid` - Payment successful, order created\n- `canceled` - Checkout was canceled by the customer\n- `failed` - Payment failed\n- `expired` - Checkout expired without completion\n",
|
|
435
|
+
examples=["created"],
|
|
436
|
+
),
|
|
437
|
+
]
|
|
438
|
+
expires_at: Annotated[
|
|
439
|
+
AwareDatetime | None,
|
|
440
|
+
Field(
|
|
441
|
+
alias="expiresAt",
|
|
442
|
+
description="When this checkout will expire (ISO 8601 format)",
|
|
443
|
+
examples=["2024-01-15T14:30:00Z"],
|
|
444
|
+
),
|
|
445
|
+
] = None
|
|
446
|
+
created_at: Annotated[
|
|
447
|
+
AwareDatetime,
|
|
448
|
+
Field(
|
|
449
|
+
alias="createdAt",
|
|
450
|
+
description="When this checkout was created (ISO 8601 format)",
|
|
451
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
452
|
+
),
|
|
453
|
+
]
|
|
454
|
+
links: Annotated[Links, Field(description="HATEOAS links to related resources")]
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class CheckoutProduct(VatlyModel):
|
|
458
|
+
"""
|
|
459
|
+
A product to add to the checkout
|
|
460
|
+
"""
|
|
461
|
+
|
|
462
|
+
model_config = ConfigDict(
|
|
463
|
+
extra="allow",
|
|
464
|
+
populate_by_name=True,
|
|
465
|
+
)
|
|
466
|
+
id: Annotated[
|
|
467
|
+
str,
|
|
468
|
+
Field(
|
|
469
|
+
description="Product identifier. Must be either:\n- A one-off product ID (starts with `one_off_product_`)\n- A subscription plan ID (starts with `subscription_plan_`)\n",
|
|
470
|
+
examples=["one_off_product_Vr8kQdFhSrG4Y3DnfsdqH"],
|
|
471
|
+
pattern="^(one_off_product_|subscription_plan_)",
|
|
472
|
+
),
|
|
473
|
+
]
|
|
474
|
+
quantity: Annotated[
|
|
475
|
+
int | None, Field(description="Number of units to purchase", examples=[2], ge=1)
|
|
476
|
+
] = 1
|
|
477
|
+
price: Annotated[
|
|
478
|
+
EurMoney | None,
|
|
479
|
+
Field(
|
|
480
|
+
description="Custom price override for this product, in EUR. If not provided, the\nproduct's default price is used.\n\nCurrently only EUR is accepted; any other currency returns a `422`.\nWhen the customer pays in another currency, the amount is converted\nfrom this EUR price at checkout. For a subscription, the converted\namount and currency are then fixed, so renewals charge the same amount.\n"
|
|
481
|
+
),
|
|
482
|
+
] = None
|
|
483
|
+
trial_days: Annotated[
|
|
484
|
+
int | None,
|
|
485
|
+
Field(
|
|
486
|
+
alias="trialDays",
|
|
487
|
+
description="Trial period in days for subscription plans.\nOnly applicable when `id` references a subscription plan.\n",
|
|
488
|
+
examples=[14],
|
|
489
|
+
ge=0,
|
|
490
|
+
),
|
|
491
|
+
] = None
|
|
492
|
+
metadata: Metadata | None = None
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class CreateCheckoutRequest(VatlyModel):
|
|
496
|
+
"""
|
|
497
|
+
Request body for creating a new checkout
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
model_config = ConfigDict(
|
|
501
|
+
extra="allow",
|
|
502
|
+
populate_by_name=True,
|
|
503
|
+
)
|
|
504
|
+
redirect_url_success: Annotated[
|
|
505
|
+
str,
|
|
506
|
+
Field(
|
|
507
|
+
alias="redirectUrlSuccess",
|
|
508
|
+
description="URL to redirect customers after successful payment.\n\nYou may include the literal `{CHECKOUT_ID}` placeholder anywhere in the\nURL (e.g. `https://example.com/return?checkout_id={CHECKOUT_ID}`). Vatly\nsubstitutes it with this checkout's id at creation time, so the host can\nlearn the checkout id on the redirect back without any server-side token\nstore. Only `{CHECKOUT_ID}` is supported.\n",
|
|
509
|
+
examples=["https://example.com/return?checkout_id={CHECKOUT_ID}"],
|
|
510
|
+
),
|
|
511
|
+
]
|
|
512
|
+
redirect_url_canceled: Annotated[
|
|
513
|
+
str,
|
|
514
|
+
Field(
|
|
515
|
+
alias="redirectUrlCanceled",
|
|
516
|
+
description="URL to redirect customers if they cancel the checkout.\n\nSupports the same `{CHECKOUT_ID}` placeholder as `redirectUrlSuccess`.\n",
|
|
517
|
+
examples=["https://example.com/canceled?checkout_id={CHECKOUT_ID}"],
|
|
518
|
+
),
|
|
519
|
+
]
|
|
520
|
+
customer_id: Annotated[
|
|
521
|
+
str | None,
|
|
522
|
+
Field(
|
|
523
|
+
alias="customerId",
|
|
524
|
+
description="Existing customer ID to associate with this checkout.\nIf provided, the customer's email will be pre-filled.\nMust match the testmode of the API token.\n",
|
|
525
|
+
examples=["customer_7kBmRtPvXw2NjLhYcZaE"],
|
|
526
|
+
),
|
|
527
|
+
] = None
|
|
528
|
+
billing_address: Annotated[
|
|
529
|
+
BillingAddress | None,
|
|
530
|
+
Field(
|
|
531
|
+
alias="billingAddress",
|
|
532
|
+
description="Optional editable prefill for the billing address fields on the hosted\ncheckout. When `country` is supplied, it takes precedence over the\nshopper's IP address for determining their country. A supplied `taxId`\nis only a prefill; it is not registered until the buyer submits the\ncheckout.\n",
|
|
533
|
+
),
|
|
534
|
+
] = None
|
|
535
|
+
metadata: Metadata | None = None
|
|
536
|
+
locale: Annotated[
|
|
537
|
+
str | None,
|
|
538
|
+
Field(
|
|
539
|
+
description="Language to present the hosted checkout in, including its validation and\nerror messages. Set this when you already know the customer's language —\nit is a better signal than their browser's.\n\nSend a bare language code (`de`), a BCP 47 tag (`de-AT`), or a POSIX /\nISO 15897 locale (`de_DE`) — whichever your stack already stores. All\nthree are folded to the language, so `de`, `de-AT` and `de_DE` are the\nsame request. There are no region-specific variants: an Austrian and a\nGerman customer are both served `de`.\n\nThe language also carries through to the payment provider's own hosted\npage, in whatever format that provider expects.\n\nSupported languages: `en`, `de`, `fr`, `nl`, `es`, `it`, `pt`, `pl`.\nAnything else is a `422` naming the supported set.\n\nOmit it (or send `null`) to detect the language from the shopper's\n`Accept-Language` header, falling back to English. A shopper can always\noverride your choice with the checkout's own language switcher.\n\nThe response echoes back the folded language, not the string you sent.\n",
|
|
540
|
+
examples=["de"],
|
|
541
|
+
),
|
|
542
|
+
] = None
|
|
543
|
+
products: Annotated[
|
|
544
|
+
Sequence[CheckoutProduct],
|
|
545
|
+
Field(
|
|
546
|
+
description="Products to include in this checkout",
|
|
547
|
+
examples=[
|
|
548
|
+
[
|
|
549
|
+
{"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH", "quantity": 1},
|
|
550
|
+
{"id": "subscription_plan_Rk5pQrSvWm8NjLhYbUcP", "trialDays": 14},
|
|
551
|
+
]
|
|
552
|
+
],
|
|
553
|
+
min_length=1,
|
|
554
|
+
),
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
class Links1(VatlyModel):
|
|
559
|
+
"""
|
|
560
|
+
HATEOAS links to related resources
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
model_config = ConfigDict(
|
|
564
|
+
extra="allow",
|
|
565
|
+
populate_by_name=True,
|
|
566
|
+
)
|
|
567
|
+
self: Annotated[Link, Field(description="Link to this customer resource")]
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class Customer(VatlyModel):
|
|
571
|
+
"""
|
|
572
|
+
A customer represents a person or organization that can make purchases.
|
|
573
|
+
Email addresses are scoped to a storefront and testmode, so the same
|
|
574
|
+
address exists as separate customers in test and live mode.
|
|
575
|
+
|
|
576
|
+
"""
|
|
577
|
+
|
|
578
|
+
model_config = ConfigDict(
|
|
579
|
+
extra="allow",
|
|
580
|
+
populate_by_name=True,
|
|
581
|
+
)
|
|
582
|
+
id: Annotated[
|
|
583
|
+
str,
|
|
584
|
+
Field(
|
|
585
|
+
description="Unique identifier for the customer",
|
|
586
|
+
examples=["customer_7kBmRtPvXw2NjLhYcZaE"],
|
|
587
|
+
),
|
|
588
|
+
]
|
|
589
|
+
resource: Annotated[
|
|
590
|
+
Literal["customer"],
|
|
591
|
+
Field(description="Resource type identifier", examples=["customer"]),
|
|
592
|
+
]
|
|
593
|
+
testmode: Annotated[
|
|
594
|
+
bool,
|
|
595
|
+
Field(description="Whether this customer is in test mode", examples=[False]),
|
|
596
|
+
]
|
|
597
|
+
email: Annotated[
|
|
598
|
+
str,
|
|
599
|
+
Field(
|
|
600
|
+
description="Customer's email address.\nInternationalized domains are stored and returned in their\nPunycode (ASCII) form per UTS #46 — e.g. `user@xn--mller-kva.de`.\nConvert back to Unicode for display in your own UI.\n",
|
|
601
|
+
examples=["customer@example.com"],
|
|
602
|
+
),
|
|
603
|
+
]
|
|
604
|
+
name: Annotated[
|
|
605
|
+
str | None,
|
|
606
|
+
Field(
|
|
607
|
+
description="Customer's display / account-holder name. This is an identity field\nfor communication (dunning emails, dashboard) — it is distinct from,\nand does not affect, the billing name on invoices.\n",
|
|
608
|
+
examples=["John Doe"],
|
|
609
|
+
),
|
|
610
|
+
] = None
|
|
611
|
+
created_at: Annotated[
|
|
612
|
+
AwareDatetime,
|
|
613
|
+
Field(
|
|
614
|
+
alias="createdAt",
|
|
615
|
+
description="When this customer was created (ISO 8601 format)",
|
|
616
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
617
|
+
),
|
|
618
|
+
]
|
|
619
|
+
metadata: Metadata | None = None
|
|
620
|
+
links: Annotated[Links1, Field(description="HATEOAS links to related resources")]
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class CreateCustomerRequest(VatlyModel):
|
|
624
|
+
"""
|
|
625
|
+
Request body for creating a new customer
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
model_config = ConfigDict(
|
|
629
|
+
extra="allow",
|
|
630
|
+
populate_by_name=True,
|
|
631
|
+
)
|
|
632
|
+
email: Annotated[
|
|
633
|
+
str,
|
|
634
|
+
Field(
|
|
635
|
+
description="Customer's email address. Submit it in any form: the local part is\nlower-cased and an internationalized domain is converted to its\nPunycode (ASCII) form per UTS #46, so `User@Müller.de` is stored and\nreturned as `user@xn--mller-kva.de`.\n\nAn address another customer in this storefront and testmode already\nholds is not an error — that customer is returned with `200`.\n",
|
|
636
|
+
examples=["customer@example.com"],
|
|
637
|
+
),
|
|
638
|
+
]
|
|
639
|
+
name: Annotated[
|
|
640
|
+
str | None,
|
|
641
|
+
Field(
|
|
642
|
+
description="Customer's display / account-holder name.",
|
|
643
|
+
examples=["John Doe"],
|
|
644
|
+
max_length=255,
|
|
645
|
+
),
|
|
646
|
+
] = None
|
|
647
|
+
metadata: Metadata | None = None
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
class PendingUpdates(VatlyModel):
|
|
651
|
+
"""
|
|
652
|
+
The changes that will take effect once a submitted update is approved, or
|
|
653
|
+
`null` when there is no pending update. Only the fields that actually
|
|
654
|
+
differ from the live product are present.
|
|
655
|
+
|
|
656
|
+
"""
|
|
657
|
+
|
|
658
|
+
model_config = ConfigDict(
|
|
659
|
+
extra="allow",
|
|
660
|
+
populate_by_name=True,
|
|
661
|
+
)
|
|
662
|
+
name: Annotated[str | None, Field(examples=["Premium License v2"])] = None
|
|
663
|
+
description: Annotated[str | None, Field(examples=["Now with more premium"])] = None
|
|
664
|
+
base_price: Annotated[Money | None, Field(alias="basePrice")] = None
|
|
665
|
+
product_type: Annotated[Literal["saas", "ebook"] | None, Field(alias="productType")] = None
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class Links2(VatlyModel):
|
|
669
|
+
"""
|
|
670
|
+
HATEOAS links to related resources
|
|
671
|
+
"""
|
|
672
|
+
|
|
673
|
+
model_config = ConfigDict(
|
|
674
|
+
extra="allow",
|
|
675
|
+
populate_by_name=True,
|
|
676
|
+
)
|
|
677
|
+
self: Annotated[Link, Field(description="Link to this product resource")]
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
class OneOffProduct(VatlyModel):
|
|
681
|
+
"""
|
|
682
|
+
A one-off product represents a single-purchase item.
|
|
683
|
+
Products are configured in the Vatly dashboard and can be added to checkouts.
|
|
684
|
+
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
model_config = ConfigDict(
|
|
688
|
+
extra="allow",
|
|
689
|
+
populate_by_name=True,
|
|
690
|
+
)
|
|
691
|
+
id: Annotated[
|
|
692
|
+
str,
|
|
693
|
+
Field(
|
|
694
|
+
description="Unique identifier for the product (always starts with `one_off_product_`)",
|
|
695
|
+
examples=["one_off_product_Vr8kQdFhSrG4Y3DnfsdqH"],
|
|
696
|
+
pattern="^one_off_product_",
|
|
697
|
+
),
|
|
698
|
+
]
|
|
699
|
+
resource: Annotated[
|
|
700
|
+
Literal["one_off_product"],
|
|
701
|
+
Field(description="Resource type identifier", examples=["one_off_product"]),
|
|
702
|
+
]
|
|
703
|
+
testmode: Annotated[
|
|
704
|
+
bool,
|
|
705
|
+
Field(description="Whether this product is in test mode", examples=[False]),
|
|
706
|
+
]
|
|
707
|
+
name: Annotated[
|
|
708
|
+
str,
|
|
709
|
+
Field(description="Display name of the product", examples=["Premium License"]),
|
|
710
|
+
]
|
|
711
|
+
description: Annotated[
|
|
712
|
+
str,
|
|
713
|
+
Field(
|
|
714
|
+
description="Detailed description of the product",
|
|
715
|
+
examples=["Lifetime access to all premium features"],
|
|
716
|
+
),
|
|
717
|
+
]
|
|
718
|
+
base_price: Annotated[
|
|
719
|
+
Money,
|
|
720
|
+
Field(
|
|
721
|
+
alias="basePrice",
|
|
722
|
+
description="Default price of the product (can be overridden in checkout).\nInterpretation depends on `taxBehavior`:\n- `exclusive` → `basePrice` is the net amount; tax is added at checkout.\n- `inclusive` → `basePrice` already includes tax; net is back-computed from the buyer's jurisdiction.\n",
|
|
723
|
+
),
|
|
724
|
+
]
|
|
725
|
+
tax_behavior: Annotated[
|
|
726
|
+
Literal["exclusive", "inclusive"],
|
|
727
|
+
Field(
|
|
728
|
+
alias="taxBehavior",
|
|
729
|
+
description="Whether `basePrice` is tax-exclusive (B2B convention; tax added on top at checkout)\nor tax-inclusive (B2C convention; price already includes tax).\nImmutable after product creation. A checkout may not mix products with different\n`taxBehavior` values; doing so returns `422 Unprocessable Entity`.\n",
|
|
730
|
+
examples=["exclusive"],
|
|
731
|
+
),
|
|
732
|
+
]
|
|
733
|
+
product_type: Annotated[
|
|
734
|
+
str,
|
|
735
|
+
Field(
|
|
736
|
+
alias="productType",
|
|
737
|
+
description="What kind of product this is:\n- `saas` — software supplied as a service (hosted, licensed or otherwise\n delivered online)\n- `ebook` — an electronic publication supplied as a download or stream\n\nSet when the product is created. Subscription plans accept `saas` only;\nan e-book is a one-time purchase, so sell it as a one-off product.\n",
|
|
738
|
+
examples=["saas"],
|
|
739
|
+
),
|
|
740
|
+
]
|
|
741
|
+
status: Annotated[
|
|
742
|
+
Literal["active", "pending", "rejected"],
|
|
743
|
+
Field(
|
|
744
|
+
description="Current status of the product:\n- `active` - Product is active and can be purchased\n- `pending` - Product is awaiting approval\n- `rejected` - Product has been rejected\n",
|
|
745
|
+
examples=["active"],
|
|
746
|
+
),
|
|
747
|
+
]
|
|
748
|
+
archived_at: Annotated[
|
|
749
|
+
AwareDatetime | None,
|
|
750
|
+
Field(
|
|
751
|
+
alias="archivedAt",
|
|
752
|
+
description="When this product was archived (ISO 8601), or `null` while it is on\nsale. Always present. An archived product is hidden from\n`GET /v1/one-off-products` and refused by `POST /v1/checkouts`; restore\nit with `DELETE /v1/one-off-products/{oneOffProductId}/archive`.\n",
|
|
753
|
+
examples=[None],
|
|
754
|
+
),
|
|
755
|
+
] = None
|
|
756
|
+
pending_updates: Annotated[
|
|
757
|
+
PendingUpdates | None,
|
|
758
|
+
Field(
|
|
759
|
+
alias="pendingUpdates",
|
|
760
|
+
description="The changes that will take effect once a submitted update is approved, or\n`null` when there is no pending update. Only the fields that actually\ndiffer from the live product are present.\n",
|
|
761
|
+
examples=[{"basePrice": {"currency": "EUR", "value": "349.00"}}],
|
|
762
|
+
),
|
|
763
|
+
] = None
|
|
764
|
+
update_status: Annotated[
|
|
765
|
+
Literal["pending", "reviewing"] | None,
|
|
766
|
+
Field(
|
|
767
|
+
alias="updateStatus",
|
|
768
|
+
description="Lifecycle of a pending update, or `null` when there is none:\n- `pending` - An update was submitted and is awaiting review\n- `reviewing` - The update is being reviewed\n",
|
|
769
|
+
examples=[None],
|
|
770
|
+
),
|
|
771
|
+
] = None
|
|
772
|
+
created_at: Annotated[
|
|
773
|
+
AwareDatetime,
|
|
774
|
+
Field(
|
|
775
|
+
alias="createdAt",
|
|
776
|
+
description="When this product was created (ISO 8601 format)",
|
|
777
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
778
|
+
),
|
|
779
|
+
]
|
|
780
|
+
links: Annotated[Links2, Field(description="HATEOAS links to related resources")]
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
class CreateOneOffProductRequest(VatlyModel):
|
|
784
|
+
"""
|
|
785
|
+
Request body for creating a one-off product.
|
|
786
|
+
"""
|
|
787
|
+
|
|
788
|
+
model_config = ConfigDict(
|
|
789
|
+
extra="allow",
|
|
790
|
+
populate_by_name=True,
|
|
791
|
+
)
|
|
792
|
+
name: Annotated[
|
|
793
|
+
str,
|
|
794
|
+
Field(
|
|
795
|
+
description="Display name of the product.",
|
|
796
|
+
examples=["Premium License"],
|
|
797
|
+
max_length=255,
|
|
798
|
+
min_length=3,
|
|
799
|
+
),
|
|
800
|
+
]
|
|
801
|
+
description: Annotated[
|
|
802
|
+
str,
|
|
803
|
+
Field(
|
|
804
|
+
description="Detailed description of the product.",
|
|
805
|
+
examples=["Lifetime access to all premium features"],
|
|
806
|
+
),
|
|
807
|
+
]
|
|
808
|
+
base_price: Annotated[
|
|
809
|
+
EurMoney, Field(alias="basePrice", description="Price of the product, in EUR.")
|
|
810
|
+
]
|
|
811
|
+
product_type: Annotated[
|
|
812
|
+
Literal["saas", "ebook"],
|
|
813
|
+
Field(
|
|
814
|
+
alias="productType",
|
|
815
|
+
description="What kind of product this is:\n- `saas` — software supplied as a service (hosted, licensed or otherwise\n delivered online)\n- `ebook` — an electronic publication supplied as a download or stream\n\nSet when the product is created. Subscription plans accept `saas` only;\nan e-book is a one-time purchase, so sell it as a one-off product.\n",
|
|
816
|
+
examples=["saas"],
|
|
817
|
+
),
|
|
818
|
+
]
|
|
819
|
+
tax_behavior: Annotated[
|
|
820
|
+
Literal["exclusive", "inclusive"] | None,
|
|
821
|
+
Field(
|
|
822
|
+
alias="taxBehavior",
|
|
823
|
+
description="Whether `basePrice` is tax-exclusive (B2B convention) or tax-inclusive\n(B2C convention). Optional; defaults to `exclusive`. Immutable after\nproduct creation.\n",
|
|
824
|
+
examples=["exclusive"],
|
|
825
|
+
),
|
|
826
|
+
] = "exclusive"
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
class PendingUpdates1(VatlyModel):
|
|
830
|
+
"""
|
|
831
|
+
The changes that will take effect once a submitted update is approved, or
|
|
832
|
+
`null` when there is no pending update. Only the fields that actually
|
|
833
|
+
differ from the live plan are present.
|
|
834
|
+
|
|
835
|
+
"""
|
|
836
|
+
|
|
837
|
+
model_config = ConfigDict(
|
|
838
|
+
extra="allow",
|
|
839
|
+
populate_by_name=True,
|
|
840
|
+
)
|
|
841
|
+
name: Annotated[str | None, Field(examples=["Pro Weekly"])] = None
|
|
842
|
+
description: Annotated[str | None, Field(examples=["Billed weekly"])] = None
|
|
843
|
+
base_price: Annotated[Money | None, Field(alias="basePrice")] = None
|
|
844
|
+
interval: Literal["day", "week", "month", "year"] | None = None
|
|
845
|
+
interval_count: Annotated[int | None, Field(alias="intervalCount", ge=1)] = None
|
|
846
|
+
product_type: Annotated[Literal["saas"] | None, Field(alias="productType")] = None
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
class Links3(VatlyModel):
|
|
850
|
+
"""
|
|
851
|
+
HATEOAS links to related resources
|
|
852
|
+
"""
|
|
853
|
+
|
|
854
|
+
model_config = ConfigDict(
|
|
855
|
+
extra="allow",
|
|
856
|
+
populate_by_name=True,
|
|
857
|
+
)
|
|
858
|
+
self: Annotated[Link, Field(description="Link to this plan resource")]
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
class SubscriptionPlan(VatlyModel):
|
|
862
|
+
"""
|
|
863
|
+
A subscription plan defines recurring billing terms.
|
|
864
|
+
Plans are configured in the Vatly dashboard and can be added to checkouts.
|
|
865
|
+
|
|
866
|
+
"""
|
|
867
|
+
|
|
868
|
+
model_config = ConfigDict(
|
|
869
|
+
extra="allow",
|
|
870
|
+
populate_by_name=True,
|
|
871
|
+
)
|
|
872
|
+
id: Annotated[
|
|
873
|
+
str,
|
|
874
|
+
Field(
|
|
875
|
+
description="Unique identifier for the plan (always starts with `subscription_plan_`)",
|
|
876
|
+
examples=["subscription_plan_Wt5mNvBxKw7YcZaEjLhR"],
|
|
877
|
+
pattern="^subscription_plan_",
|
|
878
|
+
),
|
|
879
|
+
]
|
|
880
|
+
resource: Annotated[
|
|
881
|
+
Literal["subscription_plan"],
|
|
882
|
+
Field(description="Resource type identifier", examples=["subscription_plan"]),
|
|
883
|
+
]
|
|
884
|
+
testmode: Annotated[
|
|
885
|
+
bool, Field(description="Whether this plan is in test mode", examples=[False])
|
|
886
|
+
]
|
|
887
|
+
name: Annotated[str, Field(description="Display name of the plan", examples=["Pro Monthly"])]
|
|
888
|
+
description: Annotated[
|
|
889
|
+
str,
|
|
890
|
+
Field(
|
|
891
|
+
description="Detailed description of the plan",
|
|
892
|
+
examples=["Full access to all Pro features, billed monthly"],
|
|
893
|
+
),
|
|
894
|
+
]
|
|
895
|
+
base_price: Annotated[
|
|
896
|
+
Money,
|
|
897
|
+
Field(
|
|
898
|
+
alias="basePrice",
|
|
899
|
+
description="Price per billing interval.\nInterpretation depends on `taxBehavior`:\n- `exclusive` → `basePrice` is the net amount; tax is added at checkout.\n- `inclusive` → `basePrice` already includes tax; net is back-computed from the buyer's jurisdiction.\n",
|
|
900
|
+
),
|
|
901
|
+
]
|
|
902
|
+
tax_behavior: Annotated[
|
|
903
|
+
Literal["exclusive", "inclusive"],
|
|
904
|
+
Field(
|
|
905
|
+
alias="taxBehavior",
|
|
906
|
+
description="Whether `basePrice` is tax-exclusive (B2B convention) or tax-inclusive (B2C convention).\nImmutable after plan creation — create a new plan for the other mode. A checkout may not\nmix products with different `taxBehavior` values; doing so returns\n`422 Unprocessable Entity`.\n",
|
|
907
|
+
examples=["exclusive"],
|
|
908
|
+
),
|
|
909
|
+
]
|
|
910
|
+
interval: Annotated[
|
|
911
|
+
Literal["day", "week", "month", "year"],
|
|
912
|
+
Field(
|
|
913
|
+
description="Billing interval unit:\n- `day` - Daily billing\n- `week` - Weekly billing\n- `month` - Monthly billing\n- `year` - Yearly billing\n",
|
|
914
|
+
examples=["month"],
|
|
915
|
+
),
|
|
916
|
+
]
|
|
917
|
+
interval_count: Annotated[
|
|
918
|
+
int,
|
|
919
|
+
Field(
|
|
920
|
+
alias="intervalCount",
|
|
921
|
+
description="Number of interval units between billing cycles.\nFor example, `interval: month` with `intervalCount: 3` bills every 3 months.\n",
|
|
922
|
+
examples=[1],
|
|
923
|
+
ge=1,
|
|
924
|
+
),
|
|
925
|
+
]
|
|
926
|
+
product_type: Annotated[
|
|
927
|
+
Literal["saas"],
|
|
928
|
+
Field(
|
|
929
|
+
alias="productType",
|
|
930
|
+
description="What kind of product this plan sells. Only `saas` is available on a\nsubscription plan — an e-book is a one-time purchase, so sell it as a\none-off product instead.\n\nSet when the plan is created.\n",
|
|
931
|
+
examples=["saas"],
|
|
932
|
+
),
|
|
933
|
+
]
|
|
934
|
+
status: Annotated[
|
|
935
|
+
Literal["active", "pending", "rejected"],
|
|
936
|
+
Field(
|
|
937
|
+
description="Current status of the plan:\n- `active` - Plan is active and can be subscribed to\n- `pending` - Plan is awaiting approval\n- `rejected` - Plan has been rejected\n",
|
|
938
|
+
examples=["active"],
|
|
939
|
+
),
|
|
940
|
+
]
|
|
941
|
+
archived_at: Annotated[
|
|
942
|
+
AwareDatetime | None,
|
|
943
|
+
Field(
|
|
944
|
+
alias="archivedAt",
|
|
945
|
+
description="When this plan was archived (ISO 8601), or `null` while it is open to\nnew business. Always present. An archived plan is hidden from\n`GET /v1/subscription-plans`, refused by `POST /v1/checkouts`, and\ncannot be switched to from `PATCH /v1/subscriptions/{subscriptionId}`.\nSubscribers already on the plan keep billing unchanged. Restore it with\n`DELETE /v1/subscription-plans/{subscriptionPlanId}/archive`.\n",
|
|
946
|
+
examples=[None],
|
|
947
|
+
),
|
|
948
|
+
] = None
|
|
949
|
+
pending_updates: Annotated[
|
|
950
|
+
PendingUpdates1 | None,
|
|
951
|
+
Field(
|
|
952
|
+
alias="pendingUpdates",
|
|
953
|
+
description="The changes that will take effect once a submitted update is approved, or\n`null` when there is no pending update. Only the fields that actually\ndiffer from the live plan are present.\n",
|
|
954
|
+
examples=[{"interval": "week", "intervalCount": 1}],
|
|
955
|
+
),
|
|
956
|
+
] = None
|
|
957
|
+
update_status: Annotated[
|
|
958
|
+
Literal["pending", "reviewing"] | None,
|
|
959
|
+
Field(
|
|
960
|
+
alias="updateStatus",
|
|
961
|
+
description="Lifecycle of a pending update, or `null` when there is none:\n- `pending` - An update was submitted and is awaiting review\n- `reviewing` - The update is being reviewed\n",
|
|
962
|
+
examples=[None],
|
|
963
|
+
),
|
|
964
|
+
] = None
|
|
965
|
+
created_at: Annotated[
|
|
966
|
+
AwareDatetime,
|
|
967
|
+
Field(
|
|
968
|
+
alias="createdAt",
|
|
969
|
+
description="When this plan was created (ISO 8601 format)",
|
|
970
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
971
|
+
),
|
|
972
|
+
]
|
|
973
|
+
links: Annotated[Links3, Field(description="HATEOAS links to related resources")]
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
class CreateSubscriptionPlanRequest(VatlyModel):
|
|
977
|
+
"""
|
|
978
|
+
Request body for creating a subscription plan.
|
|
979
|
+
"""
|
|
980
|
+
|
|
981
|
+
model_config = ConfigDict(
|
|
982
|
+
extra="allow",
|
|
983
|
+
populate_by_name=True,
|
|
984
|
+
)
|
|
985
|
+
name: Annotated[
|
|
986
|
+
str,
|
|
987
|
+
Field(
|
|
988
|
+
description="Display name of the plan.",
|
|
989
|
+
examples=["Pro Monthly"],
|
|
990
|
+
max_length=255,
|
|
991
|
+
min_length=3,
|
|
992
|
+
),
|
|
993
|
+
]
|
|
994
|
+
description: Annotated[
|
|
995
|
+
str,
|
|
996
|
+
Field(
|
|
997
|
+
description="Detailed description of the plan.",
|
|
998
|
+
examples=["Full access to all Pro features, billed monthly"],
|
|
999
|
+
),
|
|
1000
|
+
]
|
|
1001
|
+
base_price: Annotated[
|
|
1002
|
+
EurMoney,
|
|
1003
|
+
Field(alias="basePrice", description="Price per billing interval, in EUR."),
|
|
1004
|
+
]
|
|
1005
|
+
product_type: Annotated[
|
|
1006
|
+
Literal["saas"],
|
|
1007
|
+
Field(
|
|
1008
|
+
alias="productType",
|
|
1009
|
+
description="What kind of product this plan sells. Only `saas` is available on a\nsubscription plan — an e-book is a one-time purchase, so sell it as a\none-off product instead.\n\nSet when the plan is created.\n",
|
|
1010
|
+
examples=["saas"],
|
|
1011
|
+
),
|
|
1012
|
+
]
|
|
1013
|
+
tax_behavior: Annotated[
|
|
1014
|
+
Literal["exclusive", "inclusive"] | None,
|
|
1015
|
+
Field(
|
|
1016
|
+
alias="taxBehavior",
|
|
1017
|
+
description="Whether `basePrice` is tax-exclusive (B2B convention) or tax-inclusive\n(B2C convention). Optional; defaults to `exclusive`. Immutable after\nplan creation — create a new plan for the other mode.\n",
|
|
1018
|
+
examples=["exclusive"],
|
|
1019
|
+
),
|
|
1020
|
+
] = "exclusive"
|
|
1021
|
+
interval: Annotated[
|
|
1022
|
+
Literal["day", "week", "month", "year"],
|
|
1023
|
+
Field(
|
|
1024
|
+
description="Billing interval unit. `day` is sandbox-only (test mode); live plans\nsupport `week`, `month`, and `year`.\n",
|
|
1025
|
+
examples=["month"],
|
|
1026
|
+
),
|
|
1027
|
+
]
|
|
1028
|
+
interval_count: Annotated[
|
|
1029
|
+
int,
|
|
1030
|
+
Field(
|
|
1031
|
+
alias="intervalCount",
|
|
1032
|
+
description="Number of interval units between billing cycles. Bounded per unit:\nup to 365 days, 52 weeks, or 12 months. For `year`, billing is always\nannual and this value is ignored.\n",
|
|
1033
|
+
examples=[1],
|
|
1034
|
+
ge=1,
|
|
1035
|
+
),
|
|
1036
|
+
]
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
class Links4(VatlyModel):
|
|
1040
|
+
"""
|
|
1041
|
+
HATEOAS links to related resources
|
|
1042
|
+
"""
|
|
1043
|
+
|
|
1044
|
+
model_config = ConfigDict(
|
|
1045
|
+
extra="allow",
|
|
1046
|
+
populate_by_name=True,
|
|
1047
|
+
)
|
|
1048
|
+
self: Annotated[Link, Field(description="Link to this order resource")]
|
|
1049
|
+
customer: Annotated[
|
|
1050
|
+
Link | None,
|
|
1051
|
+
Field(
|
|
1052
|
+
description="Link to the customer who made this purchase. Null for orders with no associated customer."
|
|
1053
|
+
),
|
|
1054
|
+
] = None
|
|
1055
|
+
customer_invoice: Annotated[
|
|
1056
|
+
Link,
|
|
1057
|
+
Field(
|
|
1058
|
+
alias="customerInvoice",
|
|
1059
|
+
description="Link to the customer-facing invoice page in the customer portal (text/html).\nOn a refund or chargeback order this page is the credit note. The link is signed\nand expires after 72 hours; an expired link lets the customer request a fresh one by email.\n",
|
|
1060
|
+
),
|
|
1061
|
+
]
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
class InvoiceUpdateLink(VatlyModel):
|
|
1065
|
+
"""
|
|
1066
|
+
A signed URL for the customer to update invoice details (billing address, VAT number, company name)
|
|
1067
|
+
"""
|
|
1068
|
+
|
|
1069
|
+
model_config = ConfigDict(
|
|
1070
|
+
extra="allow",
|
|
1071
|
+
populate_by_name=True,
|
|
1072
|
+
)
|
|
1073
|
+
href: Annotated[
|
|
1074
|
+
str,
|
|
1075
|
+
Field(
|
|
1076
|
+
description="Signed URL to the address update page.\nValid for a limited time (typically 24 hours).\n",
|
|
1077
|
+
examples=[
|
|
1078
|
+
"https://vatly.com/invoices/order_Hn5xWqVfKm8RjTgYbUcP/edit?signature=abc123..."
|
|
1079
|
+
],
|
|
1080
|
+
),
|
|
1081
|
+
]
|
|
1082
|
+
type: Annotated[
|
|
1083
|
+
Literal["text/html"],
|
|
1084
|
+
Field(description="Content type of the linked page", examples=["text/html"]),
|
|
1085
|
+
]
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
class Links5(VatlyModel):
|
|
1089
|
+
"""
|
|
1090
|
+
HATEOAS links to related resources
|
|
1091
|
+
"""
|
|
1092
|
+
|
|
1093
|
+
model_config = ConfigDict(
|
|
1094
|
+
extra="allow",
|
|
1095
|
+
populate_by_name=True,
|
|
1096
|
+
)
|
|
1097
|
+
self: Annotated[Link, Field(description="Link to this refund resource")]
|
|
1098
|
+
original_order: Annotated[
|
|
1099
|
+
Link, Field(alias="originalOrder", description="Link to the original order")
|
|
1100
|
+
]
|
|
1101
|
+
order: Annotated[
|
|
1102
|
+
Link | None,
|
|
1103
|
+
Field(
|
|
1104
|
+
description="Link to the credit note order. Always present; `null` until the credit-note order is created."
|
|
1105
|
+
),
|
|
1106
|
+
] = None
|
|
1107
|
+
customer_invoice: Annotated[
|
|
1108
|
+
Link | None,
|
|
1109
|
+
Field(
|
|
1110
|
+
alias="customerInvoice",
|
|
1111
|
+
description="Link to this refund's credit note in the customer portal (text/html). This is the\nsame page as the credit-note order's `customerInvoice` link, not the original order's invoice.\nAlways present; `null` until the refund is processed. The link is signed and\nexpires after 72 hours; an expired link lets the customer request a fresh one by email.\n",
|
|
1112
|
+
),
|
|
1113
|
+
] = None
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
class CreateFullRefundRequest(VatlyModel):
|
|
1117
|
+
"""
|
|
1118
|
+
Request body for creating a full refund
|
|
1119
|
+
"""
|
|
1120
|
+
|
|
1121
|
+
model_config = ConfigDict(
|
|
1122
|
+
extra="allow",
|
|
1123
|
+
populate_by_name=True,
|
|
1124
|
+
)
|
|
1125
|
+
metadata: Metadata | None = None
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
class Links6(VatlyModel):
|
|
1129
|
+
"""
|
|
1130
|
+
HATEOAS links to related resources
|
|
1131
|
+
"""
|
|
1132
|
+
|
|
1133
|
+
model_config = ConfigDict(
|
|
1134
|
+
extra="allow",
|
|
1135
|
+
populate_by_name=True,
|
|
1136
|
+
)
|
|
1137
|
+
self: Annotated[Link, Field(description="Link to this chargeback resource")]
|
|
1138
|
+
original_order: Annotated[
|
|
1139
|
+
Link, Field(alias="originalOrder", description="Link to the original order")
|
|
1140
|
+
]
|
|
1141
|
+
order: Annotated[
|
|
1142
|
+
Link | None,
|
|
1143
|
+
Field(
|
|
1144
|
+
description="Link to the credit note order. Always present; `null` until the credit-note order is created."
|
|
1145
|
+
),
|
|
1146
|
+
] = None
|
|
1147
|
+
customer_invoice: Annotated[
|
|
1148
|
+
Link | None,
|
|
1149
|
+
Field(
|
|
1150
|
+
alias="customerInvoice",
|
|
1151
|
+
description="Link to this chargeback's credit note in the customer portal (text/html). This is the\nsame page as the credit-note order's `customerInvoice` link, not the original order's invoice.\nAlways present; `null` until the lost chargeback is processed. The link is signed and\nexpires after 72 hours; an expired link lets the customer request a fresh one by email.\n",
|
|
1152
|
+
),
|
|
1153
|
+
] = None
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
class Links7(VatlyModel):
|
|
1157
|
+
"""
|
|
1158
|
+
HATEOAS links to related resources. Always present on the persisted event.
|
|
1159
|
+
"""
|
|
1160
|
+
|
|
1161
|
+
model_config = ConfigDict(
|
|
1162
|
+
extra="allow",
|
|
1163
|
+
populate_by_name=True,
|
|
1164
|
+
)
|
|
1165
|
+
self: Annotated[Link, Field(description="Link to this webhook event resource")]
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
class Links8(VatlyModel):
|
|
1169
|
+
"""
|
|
1170
|
+
HATEOAS links to related resources
|
|
1171
|
+
"""
|
|
1172
|
+
|
|
1173
|
+
model_config = ConfigDict(
|
|
1174
|
+
extra="allow",
|
|
1175
|
+
populate_by_name=True,
|
|
1176
|
+
)
|
|
1177
|
+
self: Annotated[Link, Field(description="Link to this webhook endpoint resource")]
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
class WebhookEndpoint(VatlyModel):
|
|
1181
|
+
"""
|
|
1182
|
+
A registered webhook endpoint. Vatly POSTs event deliveries to its `url`
|
|
1183
|
+
and signs each one with the secret you set (see the `Vatly-Signature`
|
|
1184
|
+
header).
|
|
1185
|
+
|
|
1186
|
+
A storefront can have up to five endpoints in each mode. URLs must be
|
|
1187
|
+
unique within the storefront and mode. Each endpoint receives only the
|
|
1188
|
+
public event names in `enabledEvents`; an empty list makes it dormant.
|
|
1189
|
+
|
|
1190
|
+
The signing secret is write-only: you provide it on creation (and may rotate
|
|
1191
|
+
it via update) but it is never returned in any response. Store the value you
|
|
1192
|
+
send; the API will not give it back.
|
|
1193
|
+
|
|
1194
|
+
"""
|
|
1195
|
+
|
|
1196
|
+
model_config = ConfigDict(
|
|
1197
|
+
extra="allow",
|
|
1198
|
+
populate_by_name=True,
|
|
1199
|
+
)
|
|
1200
|
+
id: Annotated[
|
|
1201
|
+
str,
|
|
1202
|
+
Field(
|
|
1203
|
+
description="Unique identifier for the webhook endpoint",
|
|
1204
|
+
examples=["webhook_QdEpFhdSrG4Y3DnfsdqsH"],
|
|
1205
|
+
),
|
|
1206
|
+
]
|
|
1207
|
+
resource: Annotated[
|
|
1208
|
+
Literal["webhook_endpoint"],
|
|
1209
|
+
Field(description="Resource type identifier", examples=["webhook_endpoint"]),
|
|
1210
|
+
]
|
|
1211
|
+
testmode: Annotated[
|
|
1212
|
+
bool,
|
|
1213
|
+
Field(
|
|
1214
|
+
description="Whether this endpoint receives test-mode events",
|
|
1215
|
+
examples=[False],
|
|
1216
|
+
),
|
|
1217
|
+
]
|
|
1218
|
+
url: Annotated[
|
|
1219
|
+
str,
|
|
1220
|
+
Field(
|
|
1221
|
+
description="The HTTPS URL deliveries are POSTed to",
|
|
1222
|
+
examples=["https://merchant.example/webhooks/vatly"],
|
|
1223
|
+
),
|
|
1224
|
+
]
|
|
1225
|
+
enabled_events: Annotated[
|
|
1226
|
+
Sequence[str],
|
|
1227
|
+
Field(
|
|
1228
|
+
alias="enabledEvents",
|
|
1229
|
+
description="The endpoint's persisted subscription names. Current names in this list\nare delivered; a retired public name can remain visible for stable\nreadback but is inert. New event names are not added automatically;\nupdate this list to opt in. An empty list means no domain events are\ndelivered, although `webhook.setup` is still sent whenever Vatly\nverifies the endpoint configuration.\n",
|
|
1230
|
+
examples=[["order.paid", "refund.completed"]],
|
|
1231
|
+
),
|
|
1232
|
+
]
|
|
1233
|
+
created_at: Annotated[
|
|
1234
|
+
AwareDatetime,
|
|
1235
|
+
Field(
|
|
1236
|
+
alias="createdAt",
|
|
1237
|
+
description="When this endpoint was created (ISO 8601 format)",
|
|
1238
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
1239
|
+
),
|
|
1240
|
+
]
|
|
1241
|
+
links: Annotated[Links8, Field(description="HATEOAS links to related resources")]
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
class Links9(VatlyModel):
|
|
1245
|
+
"""
|
|
1246
|
+
HATEOAS links to related resources
|
|
1247
|
+
"""
|
|
1248
|
+
|
|
1249
|
+
model_config = ConfigDict(
|
|
1250
|
+
extra="allow",
|
|
1251
|
+
populate_by_name=True,
|
|
1252
|
+
)
|
|
1253
|
+
self: Annotated[Link, Field(description="Link to this subscription resource")]
|
|
1254
|
+
customer: Annotated[Link, Field(description="Link to the customer who owns this subscription")]
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
class ScheduledUpdate(VatlyModel):
|
|
1258
|
+
"""
|
|
1259
|
+
The target values for a change scheduled to take effect at the subscription's next
|
|
1260
|
+
billing cycle — set by `PATCH /subscriptions/{subscriptionId}` with
|
|
1261
|
+
`applyImmediately: false`. The rest of the object is the subscription's *current*
|
|
1262
|
+
state; `scheduledUpdate` is what it will become.
|
|
1263
|
+
|
|
1264
|
+
Null when nothing is pending. Always present on both the REST resource and webhook
|
|
1265
|
+
deliveries, so this is the authoritative way to reconcile a pending change — do not
|
|
1266
|
+
depend on having received the `subscription.update_scheduled` webhook, which is
|
|
1267
|
+
delivered once.
|
|
1268
|
+
|
|
1269
|
+
Cleared when the change is applied at renewal, and when it is discarded because the
|
|
1270
|
+
subscription was canceled before that renewal happened.
|
|
1271
|
+
|
|
1272
|
+
"""
|
|
1273
|
+
|
|
1274
|
+
model_config = ConfigDict(
|
|
1275
|
+
extra="allow",
|
|
1276
|
+
populate_by_name=True,
|
|
1277
|
+
)
|
|
1278
|
+
subscription_plan_id: Annotated[
|
|
1279
|
+
str,
|
|
1280
|
+
Field(
|
|
1281
|
+
alias="subscriptionPlanId",
|
|
1282
|
+
description="ID of the plan the subscription will switch to",
|
|
1283
|
+
examples=["subscription_plan_Wt5mNvBxKw7YcZaEjLhR"],
|
|
1284
|
+
),
|
|
1285
|
+
]
|
|
1286
|
+
name: Annotated[str, Field(description="Name of the scheduled plan", examples=["Pro Annual"])]
|
|
1287
|
+
description: Annotated[str, Field(description="Description of the scheduled plan")]
|
|
1288
|
+
base_price: Annotated[
|
|
1289
|
+
Money,
|
|
1290
|
+
Field(
|
|
1291
|
+
alias="basePrice",
|
|
1292
|
+
description="Scheduled price per billing cycle before taxes",
|
|
1293
|
+
),
|
|
1294
|
+
]
|
|
1295
|
+
quantity: Annotated[int, Field(description="Scheduled quantity", examples=[1], ge=1)]
|
|
1296
|
+
interval: Annotated[
|
|
1297
|
+
Literal["day", "week", "month", "year"],
|
|
1298
|
+
Field(description="Scheduled billing interval unit", examples=["month"]),
|
|
1299
|
+
]
|
|
1300
|
+
interval_count: Annotated[
|
|
1301
|
+
int,
|
|
1302
|
+
Field(
|
|
1303
|
+
alias="intervalCount",
|
|
1304
|
+
description="Scheduled number of interval units between billing cycles",
|
|
1305
|
+
examples=[1],
|
|
1306
|
+
ge=1,
|
|
1307
|
+
),
|
|
1308
|
+
]
|
|
1309
|
+
effective_at: Annotated[
|
|
1310
|
+
AwareDatetime | None,
|
|
1311
|
+
Field(
|
|
1312
|
+
alias="effectiveAt",
|
|
1313
|
+
description="When the change takes effect — the subscription's next renewal date. Null if the\nsubscription has no scheduled renewal.\n",
|
|
1314
|
+
examples=["2024-03-15T10:30:00Z"],
|
|
1315
|
+
),
|
|
1316
|
+
] = None
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
class Price(VatlyModel):
|
|
1320
|
+
"""
|
|
1321
|
+
Change the recurring price, keeping the current plan. Provide
|
|
1322
|
+
`price` without a `subscriptionPlanId` to change the price in place;
|
|
1323
|
+
combine it with `subscriptionPlanId` to switch plans at a custom
|
|
1324
|
+
price. The currency must match the subscription's currency. The
|
|
1325
|
+
`prorate`, `applyImmediately`, `invoiceImmediately` and anchor
|
|
1326
|
+
options apply exactly as they do for a plan or quantity change.
|
|
1327
|
+
|
|
1328
|
+
"""
|
|
1329
|
+
|
|
1330
|
+
model_config = ConfigDict(
|
|
1331
|
+
extra="allow",
|
|
1332
|
+
populate_by_name=True,
|
|
1333
|
+
)
|
|
1334
|
+
value: Annotated[
|
|
1335
|
+
str,
|
|
1336
|
+
Field(
|
|
1337
|
+
description="Price amount as a decimal string, with the exact number of decimals for the currency.",
|
|
1338
|
+
examples=["99.99"],
|
|
1339
|
+
),
|
|
1340
|
+
]
|
|
1341
|
+
currency: Annotated[
|
|
1342
|
+
Literal["EUR", "USD"],
|
|
1343
|
+
Field(
|
|
1344
|
+
description="Must match the subscription's own currency (a subscription's\ncurrency is fixed at checkout and cannot be changed via an update).\n",
|
|
1345
|
+
examples=["EUR"],
|
|
1346
|
+
),
|
|
1347
|
+
]
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
class UpdateSubscriptionRequest(VatlyModel):
|
|
1351
|
+
"""
|
|
1352
|
+
Request body for updating a subscription.
|
|
1353
|
+
At least one of `subscriptionPlanId`, `quantity`, or `price` must be provided.
|
|
1354
|
+
|
|
1355
|
+
"""
|
|
1356
|
+
|
|
1357
|
+
model_config = ConfigDict(
|
|
1358
|
+
extra="allow",
|
|
1359
|
+
populate_by_name=True,
|
|
1360
|
+
)
|
|
1361
|
+
subscription_plan_id: Annotated[
|
|
1362
|
+
str | None,
|
|
1363
|
+
Field(
|
|
1364
|
+
alias="subscriptionPlanId",
|
|
1365
|
+
description="ID of the new subscription plan.\nMust match the testmode of the current subscription.\n",
|
|
1366
|
+
examples=["subscription_plan_Wt5mNvBxKw7YcZaEjLhR"],
|
|
1367
|
+
pattern="^subscription_plan_",
|
|
1368
|
+
),
|
|
1369
|
+
] = None
|
|
1370
|
+
quantity: Annotated[
|
|
1371
|
+
int | None,
|
|
1372
|
+
Field(
|
|
1373
|
+
description="The new total quantity (e.g. number of seats). This sets the\nquantity to the given value — it is not added to the current\nquantity. Omit it to leave the quantity unchanged.\n",
|
|
1374
|
+
examples=[5],
|
|
1375
|
+
ge=1,
|
|
1376
|
+
),
|
|
1377
|
+
] = None
|
|
1378
|
+
price: Annotated[
|
|
1379
|
+
Price | None,
|
|
1380
|
+
Field(
|
|
1381
|
+
description="Change the recurring price, keeping the current plan. Provide\n`price` without a `subscriptionPlanId` to change the price in place;\ncombine it with `subscriptionPlanId` to switch plans at a custom\nprice. The currency must match the subscription's currency. The\n`prorate`, `applyImmediately`, `invoiceImmediately` and anchor\noptions apply exactly as they do for a plan or quantity change.\n"
|
|
1382
|
+
),
|
|
1383
|
+
] = None
|
|
1384
|
+
prorate: Annotated[
|
|
1385
|
+
bool | None,
|
|
1386
|
+
Field(
|
|
1387
|
+
description="How an immediate change is priced.\n- `true`: the new price for the time left on the cycle, minus a credit\n for the old price over that same time\n- `false`: the full new price for the current cycle, on top of what was\n already paid. This does not skip the charge; use\n `prorationCharge: false` for that\n\nIgnored when `prorationCharge` is `false`.\n",
|
|
1388
|
+
examples=[True],
|
|
1389
|
+
),
|
|
1390
|
+
] = True
|
|
1391
|
+
proration_charge: Annotated[
|
|
1392
|
+
bool | None,
|
|
1393
|
+
Field(
|
|
1394
|
+
alias="prorationCharge",
|
|
1395
|
+
description="Whether an immediate change is charged at all.\n- `true`: charged as set by `prorate` and `invoiceImmediately`\n- `false`: the change applies now and nothing is charged for it. The\n next renewal bills the new price, unless a change is already\n scheduled for that renewal: that change still applies. A downgrade\n gets no credit. During a trial the trial keeps running and its end\n bills the new plan\n\nWith `false`, `invoiceImmediately: true`, `anchor`, `resetAnchor`,\n`trialUntil` and a change to a plan with a different interval are\nrejected with `422`: each would start or bill a new cycle now.\n",
|
|
1396
|
+
examples=[False],
|
|
1397
|
+
),
|
|
1398
|
+
] = True
|
|
1399
|
+
apply_immediately: Annotated[
|
|
1400
|
+
bool | None,
|
|
1401
|
+
Field(
|
|
1402
|
+
alias="applyImmediately",
|
|
1403
|
+
description="Whether to apply changes immediately or at the end of the current billing period.\n- `true`: Changes take effect immediately (with proration if enabled)\n- `false`: Changes apply when the current period ends\n\nDuring a trial, `true` ends the trial and starts the first paid cycle,\ncharged right away. Without `anchor` the cycle starts that day at the\nfull new price; with `anchor` it runs to that date, priced per\n`prorate`. A subscription that is `on_grace_period` starts no paid\ncycle and still ends on its communicated date.\n",
|
|
1404
|
+
examples=[True],
|
|
1405
|
+
),
|
|
1406
|
+
] = False
|
|
1407
|
+
invoice_immediately: Annotated[
|
|
1408
|
+
bool | None,
|
|
1409
|
+
Field(
|
|
1410
|
+
alias="invoiceImmediately",
|
|
1411
|
+
description="Whether to generate and charge an invoice immediately for proration.\nOnly applies when `applyImmediately` and `prorate` are both true.\n\nWhen `false` (the default) the proration delta is parked on the current\ncycle and billed as a line on the next renewal invoice — the customer\ngets one invoice and one payment covering both. If the subscription is\ncancelled before that renewal there is no next invoice and the parked\namount is waived, so a customer is never charged at the moment they\ncancel.\n\n**Set this to `true` on yearly and other long-interval plans.** The\ndefault suits monthly billing, where the delta is collected within\nweeks. On a yearly plan an upgrade in month 2 is not billed until\nmonth 12, and is waived entirely if the customer cancels at any point\nin between — so the longer the interval, the more the deferral costs\nyou. Charging immediately trades that for a separate invoice and\npayment (and its transaction fee) per change.\n\nAn immediate update during a trial is always charged right away,\nwhatever this is set to, because it starts the first paid cycle. This\ndoes not apply to a subscription that is `on_grace_period`.\n",
|
|
1412
|
+
examples=[False],
|
|
1413
|
+
),
|
|
1414
|
+
] = False
|
|
1415
|
+
anchor: Annotated[
|
|
1416
|
+
date | None,
|
|
1417
|
+
Field(
|
|
1418
|
+
description="Set the billing anchor to a specific calendar date (YYYY-MM-DD,\ninterpreted in UTC). The new billing cycle is recalculated around\nthis anchor. Date-only by design — the anchor identifies a day,\nnot an instant, so daylight-saving transitions don't shift it.\nCannot be combined with `resetAnchor`.\n",
|
|
1419
|
+
examples=["2024-02-01"],
|
|
1420
|
+
),
|
|
1421
|
+
] = None
|
|
1422
|
+
reset_anchor: Annotated[
|
|
1423
|
+
bool | None,
|
|
1424
|
+
Field(
|
|
1425
|
+
alias="resetAnchor",
|
|
1426
|
+
description='Reset the billing anchor to "now" — the moment the update\nis processed becomes the new cycle anchor, and future\nrenewals align to it. Useful when a customer upgrades\nmid-cycle and you want the new plan\'s billing period to\nstart fresh from today.\nCannot be combined with `anchor`.\n',
|
|
1427
|
+
examples=[True],
|
|
1428
|
+
),
|
|
1429
|
+
] = False
|
|
1430
|
+
trial_until: Annotated[
|
|
1431
|
+
AwareDatetime | None,
|
|
1432
|
+
Field(
|
|
1433
|
+
alias="trialUntil",
|
|
1434
|
+
description="Extend or set a trial period until this date (ISO 8601 format).\nCannot be combined with `anchor`.\n",
|
|
1435
|
+
examples=["2024-02-15T00:00:00Z"],
|
|
1436
|
+
),
|
|
1437
|
+
] = None
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
class UpdateSubscriptionBillingRequest(VatlyModel):
|
|
1441
|
+
"""
|
|
1442
|
+
Request body for updating subscription billing details via hosted flow
|
|
1443
|
+
"""
|
|
1444
|
+
|
|
1445
|
+
model_config = ConfigDict(
|
|
1446
|
+
extra="allow",
|
|
1447
|
+
populate_by_name=True,
|
|
1448
|
+
)
|
|
1449
|
+
redirect_url_success: Annotated[
|
|
1450
|
+
str,
|
|
1451
|
+
Field(
|
|
1452
|
+
alias="redirectUrlSuccess",
|
|
1453
|
+
description="URL to redirect after successful billing update",
|
|
1454
|
+
examples=["https://example.com/billing-updated"],
|
|
1455
|
+
),
|
|
1456
|
+
]
|
|
1457
|
+
redirect_url_canceled: Annotated[
|
|
1458
|
+
str,
|
|
1459
|
+
Field(
|
|
1460
|
+
alias="redirectUrlCanceled",
|
|
1461
|
+
description="URL to redirect if customer cancels the update",
|
|
1462
|
+
examples=["https://example.com/billing-canceled"],
|
|
1463
|
+
),
|
|
1464
|
+
]
|
|
1465
|
+
billing_address: Annotated[
|
|
1466
|
+
BillingAddress | None,
|
|
1467
|
+
Field(
|
|
1468
|
+
alias="billingAddress",
|
|
1469
|
+
description="Optional editable prefill for the hosted billing update form. When\nsupplied, it replaces the subscription's stored billing details as the\nprefill, so send every field you want shown; empty strings give a blank\nform. Omit it, or send `null` or `{}`, to prefill the stored details.\n`country` must be an ISO 3166-1 alpha-2 code. A supplied `taxId` is\nonly a prefill; it is not registered until the customer submits the\nform.\n",
|
|
1470
|
+
),
|
|
1471
|
+
] = None
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
class Mandate(VatlyModel):
|
|
1475
|
+
"""
|
|
1476
|
+
A mandate represents an authorization to charge a customer's payment method
|
|
1477
|
+
on a recurring basis (cards, SEPA Direct Debit, PayPal, Bacs).
|
|
1478
|
+
|
|
1479
|
+
This shape is the inline summary exposed on subscriptions — enough to
|
|
1480
|
+
render "Visa ending in 4242" in a billing portal without an extra round
|
|
1481
|
+
trip. A dedicated `/v1/mandates/{id}` endpoint with richer detail (status,
|
|
1482
|
+
validity timestamps, billing snapshot) is on the roadmap; fields here are
|
|
1483
|
+
forward-compatible additions to that future schema.
|
|
1484
|
+
|
|
1485
|
+
"""
|
|
1486
|
+
|
|
1487
|
+
model_config = ConfigDict(
|
|
1488
|
+
extra="allow",
|
|
1489
|
+
populate_by_name=True,
|
|
1490
|
+
)
|
|
1491
|
+
method: Annotated[
|
|
1492
|
+
Literal["card", "sepa_debit", "paypal", "bacs_debit"],
|
|
1493
|
+
Field(
|
|
1494
|
+
description="Normalized payment method category, provider-agnostic:\n- `card` — credit/debit card (incl. Apple Pay, Google Pay wallets)\n- `sepa_debit` — SEPA Direct Debit (incl. iDEAL, Bancontact, KBC and\n other rail aliases that settle into a directdebit mandate)\n- `paypal` — PayPal billing agreement\n- `bacs_debit` — UK Bacs Direct Debit\n",
|
|
1495
|
+
examples=["card"],
|
|
1496
|
+
),
|
|
1497
|
+
]
|
|
1498
|
+
masked_identifier: Annotated[
|
|
1499
|
+
str | None,
|
|
1500
|
+
Field(
|
|
1501
|
+
alias="maskedIdentifier",
|
|
1502
|
+
description='Customer-facing masked identifier for the payment method on file:\nthe last 4 of a card (`"4242"`), a masked IBAN (`"NL91****4300"`),\na masked PayPal email (`"j***@example.com"`), or last 4 of a Bacs\naccount (`"****5678"`).\n\nMay briefly be `null` for freshly-subscribed customers when the\nunderlying authorization event doesn\'t carry the masked identifier\ninline — in that case it lands on a follow-up sync (runs at most\nevery 5 minutes) and the field updates from `null` to the resolved\nvalue once the sync completes.\n',
|
|
1503
|
+
examples=["4242"],
|
|
1504
|
+
),
|
|
1505
|
+
] = None
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
class UpdateCustomerRequest(VatlyModel):
|
|
1509
|
+
"""
|
|
1510
|
+
Request body for updating a customer's identity fields. Only `name` and
|
|
1511
|
+
`email` may be changed here — both are optional (send either or both).
|
|
1512
|
+
|
|
1513
|
+
Billing-address fields (company name, tax ID, street, city, country, etc.)
|
|
1514
|
+
are not supported by this endpoint and are ignored; amend those through the
|
|
1515
|
+
hosted billing-update flow, which validates tax-relevant data centrally.
|
|
1516
|
+
|
|
1517
|
+
"""
|
|
1518
|
+
|
|
1519
|
+
model_config = ConfigDict(
|
|
1520
|
+
extra="allow",
|
|
1521
|
+
populate_by_name=True,
|
|
1522
|
+
)
|
|
1523
|
+
name: Annotated[
|
|
1524
|
+
str | None,
|
|
1525
|
+
Field(
|
|
1526
|
+
description="Customer's display / account-holder name.",
|
|
1527
|
+
examples=["John Doe"],
|
|
1528
|
+
max_length=255,
|
|
1529
|
+
),
|
|
1530
|
+
] = None
|
|
1531
|
+
email: Annotated[
|
|
1532
|
+
str | None,
|
|
1533
|
+
Field(
|
|
1534
|
+
description="Customer's email address, canonicalized on the way in exactly as on\ncreate (lower-cased local part, Punycode domain per UTS #46).\n\nUnlike create, a collision is rejected here: moving this customer onto\nan address another customer in the same storefront and testmode holds\nreturns `422`. Creating with a known address is a recovery; moving one\ncustomer onto another's address is a conflict.\n",
|
|
1535
|
+
examples=["new.email@example.com"],
|
|
1536
|
+
),
|
|
1537
|
+
] = None
|
|
1538
|
+
|
|
1539
|
+
|
|
1540
|
+
class CreatePortalSessionRequest(VatlyModel):
|
|
1541
|
+
"""
|
|
1542
|
+
Optional settings for a short-lived hosted customer portal session. The body
|
|
1543
|
+
may be omitted or sent as `{}`. `returnUrl` must be sent here in the JSON
|
|
1544
|
+
body; query-string input is rejected.
|
|
1545
|
+
|
|
1546
|
+
"""
|
|
1547
|
+
|
|
1548
|
+
model_config = ConfigDict(
|
|
1549
|
+
extra="allow",
|
|
1550
|
+
populate_by_name=True,
|
|
1551
|
+
)
|
|
1552
|
+
return_url: Annotated[
|
|
1553
|
+
str | None,
|
|
1554
|
+
Field(
|
|
1555
|
+
alias="returnUrl",
|
|
1556
|
+
description="Absolute HTTPS URL without embedded credentials, rendered as a return\nlink in the hosted portal. The runtime limit is 2,048 UTF-8 bytes.\nVatly never fetches or automatically redirects to this URL.\n",
|
|
1557
|
+
examples=["https://merchant.example/account/billing"],
|
|
1558
|
+
max_length=2048,
|
|
1559
|
+
pattern="^[Hh][Tt][Tt][Pp][Ss]://",
|
|
1560
|
+
),
|
|
1561
|
+
] = None
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
class PortalSession(VatlyModel):
|
|
1565
|
+
"""
|
|
1566
|
+
A short-lived, single-use entry link to the hosted customer portal. Redirect
|
|
1567
|
+
the customer's browser to `url`; do not store it as a long-lived account
|
|
1568
|
+
link.
|
|
1569
|
+
|
|
1570
|
+
"""
|
|
1571
|
+
|
|
1572
|
+
model_config = ConfigDict(
|
|
1573
|
+
extra="allow",
|
|
1574
|
+
populate_by_name=True,
|
|
1575
|
+
)
|
|
1576
|
+
url: Annotated[
|
|
1577
|
+
str,
|
|
1578
|
+
Field(
|
|
1579
|
+
description="Single-use HTTPS URL to redirect the customer to.",
|
|
1580
|
+
examples=["https://billing.vatly.com/authenticate?credential=..."],
|
|
1581
|
+
),
|
|
1582
|
+
]
|
|
1583
|
+
expires_at: Annotated[
|
|
1584
|
+
AwareDatetime,
|
|
1585
|
+
Field(
|
|
1586
|
+
alias="expiresAt",
|
|
1587
|
+
description="Expiry of the one-time entry link, not of the resulting browser session.",
|
|
1588
|
+
examples=["2026-09-01T12:15:00Z"],
|
|
1589
|
+
),
|
|
1590
|
+
]
|
|
1591
|
+
return_url: Annotated[
|
|
1592
|
+
str | None,
|
|
1593
|
+
Field(
|
|
1594
|
+
alias="returnUrl",
|
|
1595
|
+
description="The validated absolute HTTPS return URL supplied in the request, or\nnull. It contains no embedded credentials and is limited to 2,048\nUTF-8 bytes.\n",
|
|
1596
|
+
examples=["https://merchant.example/account/billing"],
|
|
1597
|
+
max_length=2048,
|
|
1598
|
+
pattern="^[Hh][Tt][Tt][Pp][Ss]://",
|
|
1599
|
+
),
|
|
1600
|
+
] = None
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
class UpdateOneOffProductRequest(VatlyModel):
|
|
1604
|
+
"""
|
|
1605
|
+
Request body for submitting an update to a live one-off product. Each request
|
|
1606
|
+
is the **complete set of changes** relative to the current live product, and
|
|
1607
|
+
must contain at least one field. A field submitted equal to the live value is
|
|
1608
|
+
ignored; a submission that nets to no change clears any pending update. The
|
|
1609
|
+
update enters review (`updateStatus: pending`) before it takes effect (in test
|
|
1610
|
+
mode it is approved automatically).
|
|
1611
|
+
|
|
1612
|
+
"""
|
|
1613
|
+
|
|
1614
|
+
model_config = ConfigDict(
|
|
1615
|
+
extra="allow",
|
|
1616
|
+
populate_by_name=True,
|
|
1617
|
+
)
|
|
1618
|
+
name: Annotated[
|
|
1619
|
+
str | None,
|
|
1620
|
+
Field(
|
|
1621
|
+
description="New display name of the product.",
|
|
1622
|
+
examples=["Premium License v2"],
|
|
1623
|
+
max_length=255,
|
|
1624
|
+
min_length=3,
|
|
1625
|
+
),
|
|
1626
|
+
] = None
|
|
1627
|
+
description: Annotated[
|
|
1628
|
+
str | None,
|
|
1629
|
+
Field(
|
|
1630
|
+
description="New description of the product.",
|
|
1631
|
+
examples=["Now with more premium"],
|
|
1632
|
+
),
|
|
1633
|
+
] = None
|
|
1634
|
+
base_price: Annotated[
|
|
1635
|
+
EurMoney | None,
|
|
1636
|
+
Field(alias="basePrice", description="New price of the product, in EUR."),
|
|
1637
|
+
] = None
|
|
1638
|
+
product_type: Annotated[
|
|
1639
|
+
Literal["saas", "ebook"] | None,
|
|
1640
|
+
Field(
|
|
1641
|
+
alias="productType",
|
|
1642
|
+
description="What kind of product this is:\n- `saas` — software supplied as a service (hosted, licensed or otherwise\n delivered online)\n- `ebook` — an electronic publication supplied as a download or stream\n\nSet when the product is created. Subscription plans accept `saas` only;\nan e-book is a one-time purchase, so sell it as a one-off product.\n",
|
|
1643
|
+
examples=["saas"],
|
|
1644
|
+
),
|
|
1645
|
+
] = None
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
class UpdateSubscriptionPlanRequest(VatlyModel):
|
|
1649
|
+
"""
|
|
1650
|
+
Request body for submitting an update to a live subscription plan. Each
|
|
1651
|
+
request is the **complete set of changes** relative to the current live plan,
|
|
1652
|
+
and must contain at least one field. A field submitted equal to the live value
|
|
1653
|
+
is ignored; a submission that nets to no change clears any pending update. The
|
|
1654
|
+
update enters review (`updateStatus: pending`) before it takes effect (in test
|
|
1655
|
+
mode it is approved automatically).
|
|
1656
|
+
|
|
1657
|
+
The interval cannot be changed once the plan has ever been used by a
|
|
1658
|
+
subscription, active or not. The price stays changeable.
|
|
1659
|
+
|
|
1660
|
+
"""
|
|
1661
|
+
|
|
1662
|
+
model_config = ConfigDict(
|
|
1663
|
+
extra="allow",
|
|
1664
|
+
populate_by_name=True,
|
|
1665
|
+
)
|
|
1666
|
+
name: Annotated[
|
|
1667
|
+
str | None,
|
|
1668
|
+
Field(
|
|
1669
|
+
description="New display name of the plan.",
|
|
1670
|
+
examples=["Pro Weekly"],
|
|
1671
|
+
max_length=255,
|
|
1672
|
+
min_length=3,
|
|
1673
|
+
),
|
|
1674
|
+
] = None
|
|
1675
|
+
description: Annotated[
|
|
1676
|
+
str | None,
|
|
1677
|
+
Field(description="New description of the plan.", examples=["Billed weekly"]),
|
|
1678
|
+
] = None
|
|
1679
|
+
base_price: Annotated[
|
|
1680
|
+
EurMoney | None,
|
|
1681
|
+
Field(alias="basePrice", description="New price per billing interval, in EUR."),
|
|
1682
|
+
] = None
|
|
1683
|
+
product_type: Annotated[
|
|
1684
|
+
Literal["saas"] | None, Field(alias="productType", examples=["saas"])
|
|
1685
|
+
] = None
|
|
1686
|
+
interval: Annotated[
|
|
1687
|
+
Literal["day", "week", "month", "year"] | None, Field(examples=["week"])
|
|
1688
|
+
] = None
|
|
1689
|
+
interval_count: Annotated[
|
|
1690
|
+
int | None,
|
|
1691
|
+
Field(
|
|
1692
|
+
alias="intervalCount",
|
|
1693
|
+
description="Required when `interval` is provided.",
|
|
1694
|
+
examples=[1],
|
|
1695
|
+
ge=1,
|
|
1696
|
+
),
|
|
1697
|
+
] = None
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
class TaxSummaryRate(VatlyModel):
|
|
1701
|
+
"""
|
|
1702
|
+
Simplified tax rate information
|
|
1703
|
+
"""
|
|
1704
|
+
|
|
1705
|
+
model_config = ConfigDict(
|
|
1706
|
+
extra="allow",
|
|
1707
|
+
populate_by_name=True,
|
|
1708
|
+
)
|
|
1709
|
+
name: Annotated[str, Field(description="Human-readable name of the tax rate", examples=["VAT"])]
|
|
1710
|
+
percentage: Annotated[float, Field(description="Tax rate percentage", examples=[21])]
|
|
1711
|
+
taxable_percentage: Annotated[
|
|
1712
|
+
float,
|
|
1713
|
+
Field(
|
|
1714
|
+
alias="taxablePercentage",
|
|
1715
|
+
description="Percentage of the amount that is taxable",
|
|
1716
|
+
examples=[100],
|
|
1717
|
+
),
|
|
1718
|
+
]
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
class TaxSummaryItem(VatlyModel):
|
|
1722
|
+
"""
|
|
1723
|
+
A single tax rate entry with its calculated amount
|
|
1724
|
+
"""
|
|
1725
|
+
|
|
1726
|
+
model_config = ConfigDict(
|
|
1727
|
+
extra="allow",
|
|
1728
|
+
populate_by_name=True,
|
|
1729
|
+
)
|
|
1730
|
+
tax_rate: Annotated[TaxSummaryRate, Field(alias="taxRate")]
|
|
1731
|
+
amount: Annotated[Money, Field(description="Tax amount for this rate")]
|
|
1732
|
+
|
|
1733
|
+
|
|
1734
|
+
class RefundItem(VatlyModel):
|
|
1735
|
+
"""
|
|
1736
|
+
An item to refund from an order
|
|
1737
|
+
"""
|
|
1738
|
+
|
|
1739
|
+
model_config = ConfigDict(
|
|
1740
|
+
extra="allow",
|
|
1741
|
+
populate_by_name=True,
|
|
1742
|
+
)
|
|
1743
|
+
item_id: Annotated[
|
|
1744
|
+
str,
|
|
1745
|
+
Field(
|
|
1746
|
+
alias="itemId",
|
|
1747
|
+
description="ID of the order line item to refund",
|
|
1748
|
+
examples=["order_item_Jk4pQrSvWm8NjLhYbUcP"],
|
|
1749
|
+
),
|
|
1750
|
+
]
|
|
1751
|
+
amount: Annotated[
|
|
1752
|
+
Money,
|
|
1753
|
+
Field(
|
|
1754
|
+
description="Amount to refund for this item (before taxes).\nCannot exceed the remaining refundable amount for the item.\n"
|
|
1755
|
+
),
|
|
1756
|
+
]
|
|
1757
|
+
description: Annotated[
|
|
1758
|
+
str | None,
|
|
1759
|
+
Field(
|
|
1760
|
+
description="Custom description for this refund line",
|
|
1761
|
+
examples=["Partial refund for service issue"],
|
|
1762
|
+
),
|
|
1763
|
+
] = None
|
|
1764
|
+
description_additional_line: Annotated[
|
|
1765
|
+
str | None,
|
|
1766
|
+
Field(
|
|
1767
|
+
alias="descriptionAdditionalLine",
|
|
1768
|
+
description="Additional description line for the refund",
|
|
1769
|
+
examples=["Ticket #12345"],
|
|
1770
|
+
),
|
|
1771
|
+
] = None
|
|
1772
|
+
|
|
1773
|
+
|
|
1774
|
+
class WebhookSubscriptionEventName(
|
|
1775
|
+
RootModel[
|
|
1776
|
+
Literal[
|
|
1777
|
+
"checkout.canceled",
|
|
1778
|
+
"checkout.expired",
|
|
1779
|
+
"checkout.failed",
|
|
1780
|
+
"checkout.paid",
|
|
1781
|
+
"one_off_product.archived",
|
|
1782
|
+
"one_off_product.unarchived",
|
|
1783
|
+
"one_off_product.update_approved",
|
|
1784
|
+
"one_off_product.update_rejected",
|
|
1785
|
+
"one_off_product.update_submitted",
|
|
1786
|
+
"order.canceled",
|
|
1787
|
+
"order.chargeback_received",
|
|
1788
|
+
"order.chargeback_reversed",
|
|
1789
|
+
"order.paid",
|
|
1790
|
+
"order.payment_failed",
|
|
1791
|
+
"refund.canceled",
|
|
1792
|
+
"refund.completed",
|
|
1793
|
+
"refund.failed",
|
|
1794
|
+
"subscription.billing_updated",
|
|
1795
|
+
"subscription.canceled_for_nonpayment",
|
|
1796
|
+
"subscription.canceled_immediately",
|
|
1797
|
+
"subscription.canceled_with_grace_period",
|
|
1798
|
+
"subscription.cancellation_grace_period_completed",
|
|
1799
|
+
"subscription.resumed",
|
|
1800
|
+
"subscription.started",
|
|
1801
|
+
"subscription.update_scheduled",
|
|
1802
|
+
"subscription.updated",
|
|
1803
|
+
"subscription_plan.archived",
|
|
1804
|
+
"subscription_plan.unarchived",
|
|
1805
|
+
"subscription_plan.update_approved",
|
|
1806
|
+
"subscription_plan.update_rejected",
|
|
1807
|
+
"subscription_plan.update_submitted",
|
|
1808
|
+
]
|
|
1809
|
+
]
|
|
1810
|
+
):
|
|
1811
|
+
model_config = ConfigDict(
|
|
1812
|
+
populate_by_name=True,
|
|
1813
|
+
)
|
|
1814
|
+
root: Annotated[
|
|
1815
|
+
Literal[
|
|
1816
|
+
"checkout.canceled",
|
|
1817
|
+
"checkout.expired",
|
|
1818
|
+
"checkout.failed",
|
|
1819
|
+
"checkout.paid",
|
|
1820
|
+
"one_off_product.archived",
|
|
1821
|
+
"one_off_product.unarchived",
|
|
1822
|
+
"one_off_product.update_approved",
|
|
1823
|
+
"one_off_product.update_rejected",
|
|
1824
|
+
"one_off_product.update_submitted",
|
|
1825
|
+
"order.canceled",
|
|
1826
|
+
"order.chargeback_received",
|
|
1827
|
+
"order.chargeback_reversed",
|
|
1828
|
+
"order.paid",
|
|
1829
|
+
"order.payment_failed",
|
|
1830
|
+
"refund.canceled",
|
|
1831
|
+
"refund.completed",
|
|
1832
|
+
"refund.failed",
|
|
1833
|
+
"subscription.billing_updated",
|
|
1834
|
+
"subscription.canceled_for_nonpayment",
|
|
1835
|
+
"subscription.canceled_immediately",
|
|
1836
|
+
"subscription.canceled_with_grace_period",
|
|
1837
|
+
"subscription.cancellation_grace_period_completed",
|
|
1838
|
+
"subscription.resumed",
|
|
1839
|
+
"subscription.started",
|
|
1840
|
+
"subscription.update_scheduled",
|
|
1841
|
+
"subscription.updated",
|
|
1842
|
+
"subscription_plan.archived",
|
|
1843
|
+
"subscription_plan.unarchived",
|
|
1844
|
+
"subscription_plan.update_approved",
|
|
1845
|
+
"subscription_plan.update_rejected",
|
|
1846
|
+
"subscription_plan.update_submitted",
|
|
1847
|
+
],
|
|
1848
|
+
Field(
|
|
1849
|
+
description="A public event name that can be enabled for a webhook endpoint. The\n`webhook.setup` verification event is deliberately excluded: Vatly sends\nthat event whenever it verifies endpoint configuration, regardless of the\nendpoint's subscriptions.\n",
|
|
1850
|
+
examples=["order.paid"],
|
|
1851
|
+
),
|
|
1852
|
+
]
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
class FastForwardSubscriptionRenewalRequest(VatlyModel):
|
|
1856
|
+
model_config = ConfigDict(
|
|
1857
|
+
extra="allow",
|
|
1858
|
+
populate_by_name=True,
|
|
1859
|
+
)
|
|
1860
|
+
payment_status: Annotated[
|
|
1861
|
+
Literal["paid", "failed"] | None,
|
|
1862
|
+
Field(
|
|
1863
|
+
alias="paymentStatus",
|
|
1864
|
+
description="Deterministic outcome to simulate for the test-mode renewal payment. The\nhelper uses a local payment and does not submit a charge to the provider\nsandbox, so a provider response cannot override this requested status.\n\nOmit it to advance the billing cycle and leave that local payment\npending, which is this endpoint's original behaviour.\n\n`failed` declines the payment and starts a payment recovery for the\nrenewal order, so `order.payment_failed` is delivered to your webhook\nendpoint. `paid` settles it.\n",
|
|
1865
|
+
examples=["failed"],
|
|
1866
|
+
),
|
|
1867
|
+
] = None
|
|
1868
|
+
failure_reason: Annotated[
|
|
1869
|
+
Literal[
|
|
1870
|
+
"insufficient_funds",
|
|
1871
|
+
"temporary_decline",
|
|
1872
|
+
"general_failure",
|
|
1873
|
+
"invalid_mandate",
|
|
1874
|
+
"mandate_canceled",
|
|
1875
|
+
"account_closed",
|
|
1876
|
+
"card_expired",
|
|
1877
|
+
"card_lost_or_stolen",
|
|
1878
|
+
"invalid_card_details",
|
|
1879
|
+
"authentication_failed",
|
|
1880
|
+
"fraud_suspected",
|
|
1881
|
+
]
|
|
1882
|
+
| None,
|
|
1883
|
+
Field(
|
|
1884
|
+
alias="failureReason",
|
|
1885
|
+
description="Which decline to simulate. Only valid alongside `paymentStatus: failed`;\nsending it with `paid` is rejected.\n\nThe reason decides how the payment recovery proceeds. A soft decline\n(`insufficient_funds`, `temporary_decline`, `general_failure`) means the\npayment method is still good, so recovery retries on a multi-week\ntimeline. Every other value is a hard decline: the payment method is\ndead, retrying is pointless, and recovery instead drives the customer to\nsupply a new one on a short timeline.\n\nDefaults to `general_failure`, which is soft.\n",
|
|
1886
|
+
examples=["card_expired"],
|
|
1887
|
+
),
|
|
1888
|
+
] = "general_failure"
|
|
1889
|
+
|
|
1890
|
+
|
|
1891
|
+
class SimulateOrderPaymentRequest(VatlyModel):
|
|
1892
|
+
model_config = ConfigDict(
|
|
1893
|
+
extra="allow",
|
|
1894
|
+
populate_by_name=True,
|
|
1895
|
+
)
|
|
1896
|
+
payment_status: Annotated[
|
|
1897
|
+
Literal["paid", "failed"],
|
|
1898
|
+
Field(
|
|
1899
|
+
alias="paymentStatus",
|
|
1900
|
+
description="Outcome to force on the order's pending payment.\n\n`paid` settles it, so `order.paid` is delivered to your webhook endpoint.\n`failed` declines it and starts a payment recovery, so\n`order.payment_failed` is delivered.\n",
|
|
1901
|
+
examples=["paid"],
|
|
1902
|
+
),
|
|
1903
|
+
]
|
|
1904
|
+
failure_reason: Annotated[
|
|
1905
|
+
Literal[
|
|
1906
|
+
"insufficient_funds",
|
|
1907
|
+
"temporary_decline",
|
|
1908
|
+
"general_failure",
|
|
1909
|
+
"invalid_mandate",
|
|
1910
|
+
"mandate_canceled",
|
|
1911
|
+
"account_closed",
|
|
1912
|
+
"card_expired",
|
|
1913
|
+
"card_lost_or_stolen",
|
|
1914
|
+
"invalid_card_details",
|
|
1915
|
+
"authentication_failed",
|
|
1916
|
+
"fraud_suspected",
|
|
1917
|
+
]
|
|
1918
|
+
| None,
|
|
1919
|
+
Field(
|
|
1920
|
+
alias="failureReason",
|
|
1921
|
+
description="Which decline to simulate. Only valid alongside `paymentStatus: failed`;\nsending it with `paid` is rejected.\n\nThe reason decides how the payment recovery proceeds. A soft decline\n(`insufficient_funds`, `temporary_decline`, `general_failure`) means the\npayment method is still good, so recovery retries on a multi-week\ntimeline. Every other value is a hard decline: the payment method is\ndead, retrying is pointless, and recovery instead drives the customer to\nsupply a new one on a short timeline.\n\nDefaults to `general_failure`, which is soft.\n",
|
|
1922
|
+
examples=["card_expired"],
|
|
1923
|
+
),
|
|
1924
|
+
] = "general_failure"
|
|
1925
|
+
|
|
1926
|
+
|
|
1927
|
+
class Webhook(VatlyModel):
|
|
1928
|
+
"""
|
|
1929
|
+
A registered webhook endpoint configuration — the subject of a `webhook.setup`
|
|
1930
|
+
verification call. Never includes the signing secret.
|
|
1931
|
+
|
|
1932
|
+
"""
|
|
1933
|
+
|
|
1934
|
+
model_config = ConfigDict(
|
|
1935
|
+
extra="allow",
|
|
1936
|
+
populate_by_name=True,
|
|
1937
|
+
)
|
|
1938
|
+
resource: Annotated[Literal["webhook"], Field(examples=["webhook"])]
|
|
1939
|
+
id: Annotated[
|
|
1940
|
+
str,
|
|
1941
|
+
Field(
|
|
1942
|
+
description="The webhook endpoint identifier.",
|
|
1943
|
+
examples=["webhook_QdEpFhdSrG4Y3DnfsdqsH"],
|
|
1944
|
+
),
|
|
1945
|
+
]
|
|
1946
|
+
url: Annotated[
|
|
1947
|
+
str,
|
|
1948
|
+
Field(
|
|
1949
|
+
description="The endpoint the verification call was sent to.",
|
|
1950
|
+
examples=["https://merchant.example/webhooks/vatly"],
|
|
1951
|
+
),
|
|
1952
|
+
]
|
|
1953
|
+
testmode: Annotated[bool, Field(examples=[True])]
|
|
1954
|
+
|
|
1955
|
+
|
|
1956
|
+
class Links10(VatlyModel):
|
|
1957
|
+
"""
|
|
1958
|
+
HATEOAS links to related resources. Present on event deliveries; omitted on
|
|
1959
|
+
the `webhook.setup` verification call, which is not a persisted resource.
|
|
1960
|
+
|
|
1961
|
+
"""
|
|
1962
|
+
|
|
1963
|
+
model_config = ConfigDict(
|
|
1964
|
+
extra="allow",
|
|
1965
|
+
populate_by_name=True,
|
|
1966
|
+
)
|
|
1967
|
+
self: Annotated[Link, Field(description="Link to this webhook event resource")]
|
|
1968
|
+
|
|
1969
|
+
|
|
1970
|
+
class ListCheckoutsParams(VatlyModel):
|
|
1971
|
+
model_config = ConfigDict(
|
|
1972
|
+
extra="allow",
|
|
1973
|
+
populate_by_name=True,
|
|
1974
|
+
)
|
|
1975
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
1976
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
1977
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
1978
|
+
|
|
1979
|
+
|
|
1980
|
+
class ListCustomersParams(VatlyModel):
|
|
1981
|
+
model_config = ConfigDict(
|
|
1982
|
+
extra="allow",
|
|
1983
|
+
populate_by_name=True,
|
|
1984
|
+
)
|
|
1985
|
+
email: str | None = None
|
|
1986
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
1987
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
1988
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
1989
|
+
|
|
1990
|
+
|
|
1991
|
+
class ListCustomerSubscriptionsParams(VatlyModel):
|
|
1992
|
+
model_config = ConfigDict(
|
|
1993
|
+
extra="allow",
|
|
1994
|
+
populate_by_name=True,
|
|
1995
|
+
)
|
|
1996
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
1997
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
1998
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
1999
|
+
|
|
2000
|
+
|
|
2001
|
+
class ListOneOffProductsParams(VatlyModel):
|
|
2002
|
+
model_config = ConfigDict(
|
|
2003
|
+
extra="allow",
|
|
2004
|
+
populate_by_name=True,
|
|
2005
|
+
)
|
|
2006
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2007
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2008
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2009
|
+
include_archived: Annotated[bool | None, Field(alias="includeArchived")] = False
|
|
2010
|
+
|
|
2011
|
+
|
|
2012
|
+
class ListSubscriptionPlansParams(VatlyModel):
|
|
2013
|
+
model_config = ConfigDict(
|
|
2014
|
+
extra="allow",
|
|
2015
|
+
populate_by_name=True,
|
|
2016
|
+
)
|
|
2017
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2018
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2019
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2020
|
+
include_archived: Annotated[bool | None, Field(alias="includeArchived")] = False
|
|
2021
|
+
|
|
2022
|
+
|
|
2023
|
+
class ListOrdersParams(VatlyModel):
|
|
2024
|
+
model_config = ConfigDict(
|
|
2025
|
+
extra="allow",
|
|
2026
|
+
populate_by_name=True,
|
|
2027
|
+
)
|
|
2028
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2029
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2030
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2031
|
+
|
|
2032
|
+
|
|
2033
|
+
class ListOrderRefundsParams(VatlyModel):
|
|
2034
|
+
model_config = ConfigDict(
|
|
2035
|
+
extra="allow",
|
|
2036
|
+
populate_by_name=True,
|
|
2037
|
+
)
|
|
2038
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2039
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2040
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
class ListOrderChargebacksParams(VatlyModel):
|
|
2044
|
+
model_config = ConfigDict(
|
|
2045
|
+
extra="allow",
|
|
2046
|
+
populate_by_name=True,
|
|
2047
|
+
)
|
|
2048
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2049
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2050
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
class ListRefundsParams(VatlyModel):
|
|
2054
|
+
model_config = ConfigDict(
|
|
2055
|
+
extra="allow",
|
|
2056
|
+
populate_by_name=True,
|
|
2057
|
+
)
|
|
2058
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2059
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2060
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2061
|
+
|
|
2062
|
+
|
|
2063
|
+
class ListChargebacksParams(VatlyModel):
|
|
2064
|
+
model_config = ConfigDict(
|
|
2065
|
+
extra="allow",
|
|
2066
|
+
populate_by_name=True,
|
|
2067
|
+
)
|
|
2068
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2069
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2070
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2071
|
+
|
|
2072
|
+
|
|
2073
|
+
class ListWebhookEndpointsParams(VatlyModel):
|
|
2074
|
+
model_config = ConfigDict(
|
|
2075
|
+
extra="allow",
|
|
2076
|
+
populate_by_name=True,
|
|
2077
|
+
)
|
|
2078
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2079
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2080
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2081
|
+
|
|
2082
|
+
|
|
2083
|
+
class ListSubscriptionsParams(VatlyModel):
|
|
2084
|
+
model_config = ConfigDict(
|
|
2085
|
+
extra="allow",
|
|
2086
|
+
populate_by_name=True,
|
|
2087
|
+
)
|
|
2088
|
+
limit: Annotated[int | None, Field(ge=1, le=100)] = 10
|
|
2089
|
+
starting_after: Annotated[str | None, Field(alias="startingAfter")] = None
|
|
2090
|
+
ending_before: Annotated[str | None, Field(alias="endingBefore")] = None
|
|
2091
|
+
|
|
2092
|
+
|
|
2093
|
+
class CancelSubscriptionParams(VatlyModel):
|
|
2094
|
+
model_config = ConfigDict(
|
|
2095
|
+
extra="allow",
|
|
2096
|
+
populate_by_name=True,
|
|
2097
|
+
)
|
|
2098
|
+
immediately: bool | None = False
|
|
2099
|
+
|
|
2100
|
+
|
|
2101
|
+
class CreateSubscriptionBillingUpdateLinkResponse(VatlyModel):
|
|
2102
|
+
model_config = ConfigDict(
|
|
2103
|
+
extra="allow",
|
|
2104
|
+
populate_by_name=True,
|
|
2105
|
+
)
|
|
2106
|
+
href: Annotated[
|
|
2107
|
+
str,
|
|
2108
|
+
Field(
|
|
2109
|
+
description="URL to redirect customer for billing update",
|
|
2110
|
+
examples=[
|
|
2111
|
+
"https://vatly.com/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE/billing?token=xyz..."
|
|
2112
|
+
],
|
|
2113
|
+
),
|
|
2114
|
+
]
|
|
2115
|
+
type: Annotated[Literal["text/html"], Field(examples=["text/html"])]
|
|
2116
|
+
|
|
2117
|
+
|
|
2118
|
+
class OrderLine(VatlyModel):
|
|
2119
|
+
"""
|
|
2120
|
+
A line item in an order
|
|
2121
|
+
"""
|
|
2122
|
+
|
|
2123
|
+
model_config = ConfigDict(
|
|
2124
|
+
extra="allow",
|
|
2125
|
+
populate_by_name=True,
|
|
2126
|
+
)
|
|
2127
|
+
id: Annotated[
|
|
2128
|
+
str,
|
|
2129
|
+
Field(
|
|
2130
|
+
description="Unique identifier for this line item",
|
|
2131
|
+
examples=["order_item_Jk4pQrSvWm8NjLhYbUcP"],
|
|
2132
|
+
),
|
|
2133
|
+
]
|
|
2134
|
+
resource: Annotated[
|
|
2135
|
+
Literal["orderline"],
|
|
2136
|
+
Field(description="Resource type identifier", examples=["orderline"]),
|
|
2137
|
+
]
|
|
2138
|
+
description: Annotated[
|
|
2139
|
+
str,
|
|
2140
|
+
Field(description="Description of the item", examples=["Pro Monthly Subscription"]),
|
|
2141
|
+
]
|
|
2142
|
+
quantity: Annotated[int, Field(description="Number of units", examples=[1], ge=1)]
|
|
2143
|
+
base_price: Annotated[
|
|
2144
|
+
Money, Field(alias="basePrice", description="Price per unit before taxes")
|
|
2145
|
+
]
|
|
2146
|
+
total: Annotated[
|
|
2147
|
+
Money,
|
|
2148
|
+
Field(description="Total price including taxes (basePrice * quantity + taxes)"),
|
|
2149
|
+
]
|
|
2150
|
+
subtotal: Annotated[Money, Field(description="Subtotal before taxes (basePrice * quantity)")]
|
|
2151
|
+
taxes: Annotated[
|
|
2152
|
+
Sequence[TaxSummaryItem],
|
|
2153
|
+
Field(description="Tax breakdown by rate for this line"),
|
|
2154
|
+
]
|
|
2155
|
+
product_type: Annotated[
|
|
2156
|
+
Literal[
|
|
2157
|
+
"one_off_product",
|
|
2158
|
+
"subscription_plan",
|
|
2159
|
+
"subscription",
|
|
2160
|
+
"billing_details_verification",
|
|
2161
|
+
"auth_request_product",
|
|
2162
|
+
]
|
|
2163
|
+
| None,
|
|
2164
|
+
Field(
|
|
2165
|
+
alias="productType",
|
|
2166
|
+
description="The product type this line links to; null when the line has no product link.",
|
|
2167
|
+
examples=["subscription"],
|
|
2168
|
+
),
|
|
2169
|
+
] = None
|
|
2170
|
+
product_id: Annotated[
|
|
2171
|
+
str | None,
|
|
2172
|
+
Field(
|
|
2173
|
+
alias="productId",
|
|
2174
|
+
description="The linked product's id; for a `subscription` line this is the public `subscription_…` id.",
|
|
2175
|
+
examples=["subscription_Hn5xWqVfKm8RjTgYbUcP"],
|
|
2176
|
+
),
|
|
2177
|
+
] = None
|
|
2178
|
+
|
|
2179
|
+
|
|
2180
|
+
class RefundLine(VatlyModel):
|
|
2181
|
+
"""
|
|
2182
|
+
A line item in a refund
|
|
2183
|
+
"""
|
|
2184
|
+
|
|
2185
|
+
model_config = ConfigDict(
|
|
2186
|
+
extra="allow",
|
|
2187
|
+
populate_by_name=True,
|
|
2188
|
+
)
|
|
2189
|
+
id: Annotated[
|
|
2190
|
+
str,
|
|
2191
|
+
Field(
|
|
2192
|
+
description="Unique identifier for this refund line",
|
|
2193
|
+
examples=["refund_item_Tk7mNvBxKw2RjTgYcZaE"],
|
|
2194
|
+
),
|
|
2195
|
+
]
|
|
2196
|
+
resource: Annotated[
|
|
2197
|
+
Literal["refundline"],
|
|
2198
|
+
Field(description="Resource type identifier", examples=["refundline"]),
|
|
2199
|
+
]
|
|
2200
|
+
description: Annotated[
|
|
2201
|
+
str,
|
|
2202
|
+
Field(
|
|
2203
|
+
description="Description of the refunded item",
|
|
2204
|
+
examples=["Pro Monthly Subscription (Refund)"],
|
|
2205
|
+
),
|
|
2206
|
+
]
|
|
2207
|
+
quantity: Annotated[
|
|
2208
|
+
int, Field(description="Number of units being refunded", examples=[1], ge=1)
|
|
2209
|
+
]
|
|
2210
|
+
base_price: Annotated[
|
|
2211
|
+
Money,
|
|
2212
|
+
Field(alias="basePrice", description="Refund amount per unit before taxes"),
|
|
2213
|
+
]
|
|
2214
|
+
total: Annotated[Money, Field(description="Total refund amount including taxes")]
|
|
2215
|
+
subtotal: Annotated[Money, Field(description="Refund subtotal before taxes")]
|
|
2216
|
+
taxes: Annotated[
|
|
2217
|
+
Sequence[TaxSummaryItem],
|
|
2218
|
+
Field(description="Tax breakdown by rate being refunded"),
|
|
2219
|
+
]
|
|
2220
|
+
|
|
2221
|
+
|
|
2222
|
+
class CreateRefundRequest(VatlyModel):
|
|
2223
|
+
"""
|
|
2224
|
+
Request body for creating a partial refund
|
|
2225
|
+
"""
|
|
2226
|
+
|
|
2227
|
+
model_config = ConfigDict(
|
|
2228
|
+
extra="allow",
|
|
2229
|
+
populate_by_name=True,
|
|
2230
|
+
)
|
|
2231
|
+
items: Annotated[
|
|
2232
|
+
Sequence[RefundItem],
|
|
2233
|
+
Field(
|
|
2234
|
+
description="Items to refund",
|
|
2235
|
+
examples=[
|
|
2236
|
+
[
|
|
2237
|
+
{
|
|
2238
|
+
"itemId": "order_item_Jk4pQrSvWm8NjLhYbUcP",
|
|
2239
|
+
"amount": {"value": "15.00", "currency": "EUR"},
|
|
2240
|
+
"description": "Partial refund",
|
|
2241
|
+
}
|
|
2242
|
+
]
|
|
2243
|
+
],
|
|
2244
|
+
min_length=1,
|
|
2245
|
+
),
|
|
2246
|
+
]
|
|
2247
|
+
metadata: Metadata | None = None
|
|
2248
|
+
|
|
2249
|
+
|
|
2250
|
+
class Chargeback(VatlyModel):
|
|
2251
|
+
"""
|
|
2252
|
+
A chargeback represents a payment dispute initiated by a customer through their bank or card issuer.
|
|
2253
|
+
Chargebacks are created automatically when the payment provider notifies Vatly of a dispute.
|
|
2254
|
+
|
|
2255
|
+
"""
|
|
2256
|
+
|
|
2257
|
+
model_config = ConfigDict(
|
|
2258
|
+
extra="allow",
|
|
2259
|
+
populate_by_name=True,
|
|
2260
|
+
)
|
|
2261
|
+
id: Annotated[
|
|
2262
|
+
str,
|
|
2263
|
+
Field(
|
|
2264
|
+
description="Unique identifier for the chargeback",
|
|
2265
|
+
examples=["chargeback_Mn6xBtPvKw2RjTgYcZaE"],
|
|
2266
|
+
),
|
|
2267
|
+
]
|
|
2268
|
+
resource: Annotated[
|
|
2269
|
+
Literal["chargeback"],
|
|
2270
|
+
Field(description="Resource type identifier", examples=["chargeback"]),
|
|
2271
|
+
]
|
|
2272
|
+
customer_id: Annotated[
|
|
2273
|
+
str,
|
|
2274
|
+
Field(
|
|
2275
|
+
alias="customerId",
|
|
2276
|
+
description="ID of the customer the chargeback was raised against",
|
|
2277
|
+
examples=["customer_Lp3mNvBxKw7RjTgYcZaE"],
|
|
2278
|
+
),
|
|
2279
|
+
]
|
|
2280
|
+
testmode: Annotated[
|
|
2281
|
+
bool,
|
|
2282
|
+
Field(description="Whether this chargeback is in test mode", examples=[False]),
|
|
2283
|
+
]
|
|
2284
|
+
created_at: Annotated[
|
|
2285
|
+
AwareDatetime,
|
|
2286
|
+
Field(
|
|
2287
|
+
alias="createdAt",
|
|
2288
|
+
description="When this chargeback was created (ISO 8601 format)",
|
|
2289
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
2290
|
+
),
|
|
2291
|
+
]
|
|
2292
|
+
status: Annotated[
|
|
2293
|
+
Literal[
|
|
2294
|
+
"pending",
|
|
2295
|
+
"requires_amount_review",
|
|
2296
|
+
"accepted",
|
|
2297
|
+
"rejected",
|
|
2298
|
+
"evidence_submitted",
|
|
2299
|
+
"won",
|
|
2300
|
+
"lost",
|
|
2301
|
+
],
|
|
2302
|
+
Field(
|
|
2303
|
+
description="Current status of the chargeback dispute:\n- `pending` - Dispute received, awaiting action\n- `requires_amount_review` - Provider amount exceeds the original order; financial completion is quarantined pending review\n- `accepted` - Merchant accepted the dispute\n- `rejected` - Dispute rejected\n- `evidence_submitted` - Evidence submitted, awaiting decision\n- `won` - Dispute resolved in the merchant's favor\n- `lost` - Dispute resolved in the customer's favor\n",
|
|
2304
|
+
examples=["pending"],
|
|
2305
|
+
),
|
|
2306
|
+
]
|
|
2307
|
+
amount: Annotated[Money, Field(description="Amount of the chargeback")]
|
|
2308
|
+
settlement_amount: Annotated[
|
|
2309
|
+
Money,
|
|
2310
|
+
Field(
|
|
2311
|
+
alias="settlementAmount",
|
|
2312
|
+
description="Amount that was deducted from the merchant's settlement.\nMay differ from `amount` due to currency conversion or fees.\n",
|
|
2313
|
+
),
|
|
2314
|
+
]
|
|
2315
|
+
total: Annotated[Money, Field(description="Total chargeback amount including taxes")]
|
|
2316
|
+
subtotal: Annotated[Money, Field(description="Chargeback subtotal before taxes")]
|
|
2317
|
+
tax_summary: Annotated[
|
|
2318
|
+
Sequence[TaxSummaryItem],
|
|
2319
|
+
Field(alias="taxSummary", description="Tax breakdown by rate for the chargeback"),
|
|
2320
|
+
]
|
|
2321
|
+
reason: Annotated[
|
|
2322
|
+
str,
|
|
2323
|
+
Field(
|
|
2324
|
+
description="Reason code or description for the chargeback.\nCommon reasons include:\n- `fraud` - Unauthorized transaction\n- `product_not_received` - Customer claims non-delivery\n- `product_unacceptable` - Customer claims product was defective\n- `duplicate` - Customer claims duplicate charge\n- `subscription_canceled` - Customer claims subscription was canceled\n",
|
|
2325
|
+
examples=["fraud"],
|
|
2326
|
+
),
|
|
2327
|
+
]
|
|
2328
|
+
original_order_id: Annotated[
|
|
2329
|
+
str,
|
|
2330
|
+
Field(
|
|
2331
|
+
alias="originalOrderId",
|
|
2332
|
+
description="ID of the original order that was charged back",
|
|
2333
|
+
examples=["order_Fp2kQrSvWm8NjLhYbUcP"],
|
|
2334
|
+
),
|
|
2335
|
+
]
|
|
2336
|
+
order_id: Annotated[
|
|
2337
|
+
str | None,
|
|
2338
|
+
Field(
|
|
2339
|
+
alias="orderId",
|
|
2340
|
+
description="ID of the credit note order created for this chargeback. Always present\nin the payload; `null` until the chargeback is processed, then set to the\ncredit-note order id.\n",
|
|
2341
|
+
examples=["order_Rk5pQrSvWm8NjLhYbUcP"],
|
|
2342
|
+
),
|
|
2343
|
+
] = None
|
|
2344
|
+
links: Annotated[Links6, Field(description="HATEOAS links to related resources")]
|
|
2345
|
+
|
|
2346
|
+
|
|
2347
|
+
class CreateWebhookEndpointRequest(VatlyModel):
|
|
2348
|
+
"""
|
|
2349
|
+
Request body for registering a new webhook endpoint
|
|
2350
|
+
"""
|
|
2351
|
+
|
|
2352
|
+
model_config = ConfigDict(
|
|
2353
|
+
extra="allow",
|
|
2354
|
+
populate_by_name=True,
|
|
2355
|
+
)
|
|
2356
|
+
url: Annotated[
|
|
2357
|
+
str,
|
|
2358
|
+
Field(
|
|
2359
|
+
description="The HTTPS URL deliveries are POSTed to. Must be publicly reachable and\npresent a valid SSL certificate — Vatly sends a `webhook.setup`\nverification ping on registration and rejects the request if it fails.\n`localhost` and loopback addresses are not allowed.\n",
|
|
2360
|
+
examples=["https://merchant.example/webhooks/vatly"],
|
|
2361
|
+
),
|
|
2362
|
+
]
|
|
2363
|
+
secret: Annotated[
|
|
2364
|
+
str,
|
|
2365
|
+
Field(
|
|
2366
|
+
description="Signing secret for this endpoint, used to compute the `Vatly-Signature`\nHMAC on every delivery. You choose it (e.g. pinned from an environment\nvariable) and use it to verify inbound webhooks. It is write-only — keep\nthis value, as the API never returns it.\n",
|
|
2367
|
+
examples=["whsec_3f9a1c7e2d4f7b9c5a2c1d5b7e9f3a8d"],
|
|
2368
|
+
min_length=10,
|
|
2369
|
+
),
|
|
2370
|
+
]
|
|
2371
|
+
enabled_events: Annotated[
|
|
2372
|
+
Sequence[WebhookSubscriptionEventName] | None,
|
|
2373
|
+
Field(
|
|
2374
|
+
alias="enabledEvents",
|
|
2375
|
+
description="The explicit events delivered to this endpoint. If omitted, Vatly\nstores all event names available at registration time. New event names\nadded later are not enabled automatically. Send an empty list to create\na dormant endpoint. `webhook.setup` cannot be selected and remains an\nunconditional configuration-verification event.\n",
|
|
2376
|
+
examples=[["order.paid", "refund.completed"]],
|
|
2377
|
+
),
|
|
2378
|
+
] = None
|
|
2379
|
+
|
|
2380
|
+
|
|
2381
|
+
class UpdateWebhookEndpointRequest(VatlyModel):
|
|
2382
|
+
"""
|
|
2383
|
+
Request body for updating a webhook endpoint. Send `url` to repoint the
|
|
2384
|
+
endpoint, `secret` to rotate the signing secret, and/or `enabledEvents` to
|
|
2385
|
+
replace its full subscription set. Omitting all three is a no-op that
|
|
2386
|
+
returns the current endpoint.
|
|
2387
|
+
|
|
2388
|
+
"""
|
|
2389
|
+
|
|
2390
|
+
model_config = ConfigDict(
|
|
2391
|
+
extra="allow",
|
|
2392
|
+
populate_by_name=True,
|
|
2393
|
+
)
|
|
2394
|
+
url: Annotated[
|
|
2395
|
+
str | None,
|
|
2396
|
+
Field(
|
|
2397
|
+
description="New HTTPS delivery URL. Revalidated for reachability and SSL the same\nway as on creation.\n",
|
|
2398
|
+
examples=["https://merchant.example/webhooks/vatly-v2"],
|
|
2399
|
+
),
|
|
2400
|
+
] = None
|
|
2401
|
+
secret: Annotated[
|
|
2402
|
+
str | None,
|
|
2403
|
+
Field(
|
|
2404
|
+
description="New signing secret for this endpoint. Write-only — keep the value, as\nthe API never returns it.\n",
|
|
2405
|
+
examples=["whsec_a1b2c3d4e5f60718293a4b5c6d7e8f90"],
|
|
2406
|
+
min_length=10,
|
|
2407
|
+
),
|
|
2408
|
+
] = None
|
|
2409
|
+
enabled_events: Annotated[
|
|
2410
|
+
Sequence[WebhookSubscriptionEventName] | None,
|
|
2411
|
+
Field(
|
|
2412
|
+
alias="enabledEvents",
|
|
2413
|
+
description="Replaces the endpoint's complete event subscription set. Omit this\nfield to preserve the current subscriptions; send an empty list to make\nthe endpoint dormant. New event names are never added automatically.\n`webhook.setup` is not subscribable.\n",
|
|
2414
|
+
examples=[["checkout.paid", "order.paid"]],
|
|
2415
|
+
),
|
|
2416
|
+
] = None
|
|
2417
|
+
|
|
2418
|
+
|
|
2419
|
+
class Subscription(VatlyModel):
|
|
2420
|
+
"""
|
|
2421
|
+
A subscription represents a recurring billing relationship with a customer.
|
|
2422
|
+
Subscriptions automatically renew according to their billing interval.
|
|
2423
|
+
|
|
2424
|
+
"""
|
|
2425
|
+
|
|
2426
|
+
model_config = ConfigDict(
|
|
2427
|
+
extra="allow",
|
|
2428
|
+
populate_by_name=True,
|
|
2429
|
+
)
|
|
2430
|
+
id: Annotated[
|
|
2431
|
+
str,
|
|
2432
|
+
Field(
|
|
2433
|
+
description="Unique identifier for the subscription",
|
|
2434
|
+
examples=["subscription_Lp3mNvBxKw7RjTgYcZaE"],
|
|
2435
|
+
),
|
|
2436
|
+
]
|
|
2437
|
+
resource: Annotated[
|
|
2438
|
+
Literal["subscription"],
|
|
2439
|
+
Field(description="Resource type identifier", examples=["subscription"]),
|
|
2440
|
+
]
|
|
2441
|
+
customer_id: Annotated[
|
|
2442
|
+
str,
|
|
2443
|
+
Field(
|
|
2444
|
+
alias="customerId",
|
|
2445
|
+
description="ID of the customer who owns this subscription",
|
|
2446
|
+
examples=["customer_Lp3mNvBxKw7RjTgYcZaE"],
|
|
2447
|
+
),
|
|
2448
|
+
]
|
|
2449
|
+
subscription_plan_id: Annotated[
|
|
2450
|
+
str,
|
|
2451
|
+
Field(
|
|
2452
|
+
alias="subscriptionPlanId",
|
|
2453
|
+
description="ID of the subscription plan this subscription is based on",
|
|
2454
|
+
examples=["subscription_plan_Wt5mNvBxKw7YcZaEjLhR"],
|
|
2455
|
+
),
|
|
2456
|
+
]
|
|
2457
|
+
testmode: Annotated[
|
|
2458
|
+
bool,
|
|
2459
|
+
Field(description="Whether this subscription is in test mode", examples=[False]),
|
|
2460
|
+
]
|
|
2461
|
+
name: Annotated[
|
|
2462
|
+
str,
|
|
2463
|
+
Field(
|
|
2464
|
+
description="Name of the subscription (from the plan)",
|
|
2465
|
+
examples=["Pro Monthly"],
|
|
2466
|
+
),
|
|
2467
|
+
]
|
|
2468
|
+
description: Annotated[
|
|
2469
|
+
str,
|
|
2470
|
+
Field(
|
|
2471
|
+
description="Description of the subscription",
|
|
2472
|
+
examples=["Full access to all Pro features"],
|
|
2473
|
+
),
|
|
2474
|
+
]
|
|
2475
|
+
billing_address: Annotated[
|
|
2476
|
+
BillingAddress,
|
|
2477
|
+
Field(
|
|
2478
|
+
alias="billingAddress",
|
|
2479
|
+
description="Customer's billing address for this subscription",
|
|
2480
|
+
),
|
|
2481
|
+
]
|
|
2482
|
+
base_price: Annotated[
|
|
2483
|
+
Money,
|
|
2484
|
+
Field(alias="basePrice", description="Price per billing cycle before taxes"),
|
|
2485
|
+
]
|
|
2486
|
+
quantity: Annotated[
|
|
2487
|
+
int,
|
|
2488
|
+
Field(description="Number of subscription units (e.g., seats)", examples=[1], ge=1),
|
|
2489
|
+
]
|
|
2490
|
+
interval: Annotated[
|
|
2491
|
+
Literal["day", "week", "month", "year"],
|
|
2492
|
+
Field(description="Billing interval unit", examples=["month"]),
|
|
2493
|
+
]
|
|
2494
|
+
interval_count: Annotated[
|
|
2495
|
+
int,
|
|
2496
|
+
Field(
|
|
2497
|
+
alias="intervalCount",
|
|
2498
|
+
description="Number of interval units between billing cycles",
|
|
2499
|
+
examples=[1],
|
|
2500
|
+
ge=1,
|
|
2501
|
+
),
|
|
2502
|
+
]
|
|
2503
|
+
status: Annotated[
|
|
2504
|
+
Literal["active", "created", "trial", "on_grace_period", "paused", "canceled"],
|
|
2505
|
+
Field(
|
|
2506
|
+
description="Current status of the subscription:\n- `active` - Subscription is active and will renew\n- `created` - Subscription has been created but not yet started\n- `trial` - Subscription is in trial period\n- `on_grace_period` - Subscription is canceled but still active until period ends\n- `paused` - Subscription is temporarily paused\n- `canceled` - Subscription has been canceled\n",
|
|
2507
|
+
examples=["active"],
|
|
2508
|
+
),
|
|
2509
|
+
]
|
|
2510
|
+
cancellation_reason: Annotated[
|
|
2511
|
+
Literal["payment_failure", "merchant_request", "customer_request"] | None,
|
|
2512
|
+
Field(
|
|
2513
|
+
alias="cancellationReason",
|
|
2514
|
+
description="Why the subscription was canceled. Null unless a cancellation has been\nrequested. Public values are intentionally decoupled from internal domain\nreason names:\n- `payment_failure` - Payment recovery was exhausted after failed renewals\n- `merchant_request` - The merchant explicitly canceled the subscription\n- `customer_request` - The customer canceled from the self-service portal\n",
|
|
2515
|
+
examples=[None],
|
|
2516
|
+
),
|
|
2517
|
+
]
|
|
2518
|
+
mandate: Annotated[
|
|
2519
|
+
Mandate | None,
|
|
2520
|
+
Field(
|
|
2521
|
+
description='Payment method on file for this subscription, as an inline summary\nintended for billing-portal rendering ("Visa ending in 4242"). May\nbe `null` when the subscription has no mandate yet (e.g. ended /\ncanceled-before-payment states).\n'
|
|
2522
|
+
),
|
|
2523
|
+
] = None
|
|
2524
|
+
started_at: Annotated[
|
|
2525
|
+
AwareDatetime | None,
|
|
2526
|
+
Field(
|
|
2527
|
+
alias="startedAt",
|
|
2528
|
+
description="When the subscription started (ISO 8601 format)",
|
|
2529
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
2530
|
+
),
|
|
2531
|
+
] = None
|
|
2532
|
+
ended_at: Annotated[
|
|
2533
|
+
AwareDatetime | None,
|
|
2534
|
+
Field(alias="endedAt", description="When the subscription ended (ISO 8601 format)"),
|
|
2535
|
+
] = None
|
|
2536
|
+
canceled_at: Annotated[
|
|
2537
|
+
AwareDatetime | None,
|
|
2538
|
+
Field(
|
|
2539
|
+
alias="canceledAt",
|
|
2540
|
+
description="When the subscription was canceled (ISO 8601 format)",
|
|
2541
|
+
),
|
|
2542
|
+
] = None
|
|
2543
|
+
renewed_at: Annotated[
|
|
2544
|
+
AwareDatetime | None,
|
|
2545
|
+
Field(
|
|
2546
|
+
alias="renewedAt",
|
|
2547
|
+
description="When the subscription was last renewed (ISO 8601 format)",
|
|
2548
|
+
examples=["2024-02-15T10:30:00Z"],
|
|
2549
|
+
),
|
|
2550
|
+
] = None
|
|
2551
|
+
renewed_until: Annotated[
|
|
2552
|
+
AwareDatetime | None,
|
|
2553
|
+
Field(
|
|
2554
|
+
alias="renewedUntil",
|
|
2555
|
+
description="Current billing period end date (ISO 8601 format)",
|
|
2556
|
+
examples=["2024-03-15T10:30:00Z"],
|
|
2557
|
+
),
|
|
2558
|
+
] = None
|
|
2559
|
+
next_renewal_at: Annotated[
|
|
2560
|
+
AwareDatetime | None,
|
|
2561
|
+
Field(
|
|
2562
|
+
alias="nextRenewalAt",
|
|
2563
|
+
description="When the next renewal will be attempted (ISO 8601 format).\nNull if subscription is canceled or ended.\n",
|
|
2564
|
+
examples=["2024-03-15T10:30:00Z"],
|
|
2565
|
+
),
|
|
2566
|
+
] = None
|
|
2567
|
+
trial_until: Annotated[
|
|
2568
|
+
AwareDatetime | None,
|
|
2569
|
+
Field(
|
|
2570
|
+
alias="trialUntil",
|
|
2571
|
+
description="When the trial period ends (ISO 8601 format).\nNull if not in trial or trial has ended.\n",
|
|
2572
|
+
),
|
|
2573
|
+
] = None
|
|
2574
|
+
links: Annotated[Links9, Field(description="HATEOAS links to related resources")]
|
|
2575
|
+
scheduled_update: Annotated[
|
|
2576
|
+
ScheduledUpdate | None,
|
|
2577
|
+
Field(
|
|
2578
|
+
alias="scheduledUpdate",
|
|
2579
|
+
description="The target values for a change scheduled to take effect at the subscription's next\nbilling cycle — set by `PATCH /subscriptions/{subscriptionId}` with\n`applyImmediately: false`. The rest of the object is the subscription's *current*\nstate; `scheduledUpdate` is what it will become.\n\nNull when nothing is pending. Always present on both the REST resource and webhook\ndeliveries, so this is the authoritative way to reconcile a pending change — do not\ndepend on having received the `subscription.update_scheduled` webhook, which is\ndelivered once.\n\nCleared when the change is applied at renewal, and when it is discarded because the\nsubscription was canceled before that renewal happened.\n",
|
|
2580
|
+
),
|
|
2581
|
+
] = None
|
|
2582
|
+
|
|
2583
|
+
|
|
2584
|
+
class Order(VatlyModel):
|
|
2585
|
+
"""
|
|
2586
|
+
An order represents a completed purchase.
|
|
2587
|
+
Orders are created when checkouts complete or subscriptions renew.
|
|
2588
|
+
|
|
2589
|
+
"""
|
|
2590
|
+
|
|
2591
|
+
model_config = ConfigDict(
|
|
2592
|
+
extra="allow",
|
|
2593
|
+
populate_by_name=True,
|
|
2594
|
+
)
|
|
2595
|
+
id: Annotated[
|
|
2596
|
+
str,
|
|
2597
|
+
Field(
|
|
2598
|
+
description="Unique identifier for the order",
|
|
2599
|
+
examples=["order_Hn5xWqVfKm8RjTgYbUcP"],
|
|
2600
|
+
),
|
|
2601
|
+
]
|
|
2602
|
+
resource: Annotated[
|
|
2603
|
+
Literal["order"],
|
|
2604
|
+
Field(description="Resource type identifier", examples=["order"]),
|
|
2605
|
+
]
|
|
2606
|
+
customer_id: Annotated[
|
|
2607
|
+
str | None,
|
|
2608
|
+
Field(
|
|
2609
|
+
alias="customerId",
|
|
2610
|
+
description="ID of the customer who made this purchase. Always present; `null` for orders with no associated customer.",
|
|
2611
|
+
examples=["customer_Lp3mNvBxKw7RjTgYcZaE"],
|
|
2612
|
+
),
|
|
2613
|
+
] = None
|
|
2614
|
+
testmode: Annotated[
|
|
2615
|
+
bool, Field(description="Whether this order is in test mode", examples=[False])
|
|
2616
|
+
]
|
|
2617
|
+
metadata: Metadata | None = None
|
|
2618
|
+
payment_method: Annotated[
|
|
2619
|
+
str | None,
|
|
2620
|
+
Field(
|
|
2621
|
+
alias="paymentMethod",
|
|
2622
|
+
description="Payment method used for this order.\nExamples: `ideal`, `creditcard`, `bancontact`, `paypal`\n",
|
|
2623
|
+
examples=["ideal"],
|
|
2624
|
+
),
|
|
2625
|
+
] = None
|
|
2626
|
+
created_at: Annotated[
|
|
2627
|
+
AwareDatetime,
|
|
2628
|
+
Field(
|
|
2629
|
+
alias="createdAt",
|
|
2630
|
+
description="When this order was created (ISO 8601 format)",
|
|
2631
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
2632
|
+
),
|
|
2633
|
+
]
|
|
2634
|
+
status: Annotated[
|
|
2635
|
+
Literal["created", "pending", "paid", "canceled", "expired"],
|
|
2636
|
+
Field(
|
|
2637
|
+
description="Current status of the order:\n- `created` - Order has just been created\n- `pending` - Payment is being processed\n- `paid` - Payment successful\n- `canceled` - Order has been canceled\n- `expired` - Order has expired\n",
|
|
2638
|
+
examples=["paid"],
|
|
2639
|
+
),
|
|
2640
|
+
]
|
|
2641
|
+
cancellation_reason: Annotated[
|
|
2642
|
+
Literal["payment_failure"] | None,
|
|
2643
|
+
Field(
|
|
2644
|
+
alias="cancellationReason",
|
|
2645
|
+
description="Why the order was canceled. Null unless the status is `canceled`, and null\nfor orders canceled before this field existed.\n- `payment_failure` - Payment recovery for this unpaid order was exhausted\n",
|
|
2646
|
+
examples=[None],
|
|
2647
|
+
),
|
|
2648
|
+
]
|
|
2649
|
+
invoice_number: Annotated[
|
|
2650
|
+
str | None,
|
|
2651
|
+
Field(
|
|
2652
|
+
alias="invoiceNumber",
|
|
2653
|
+
description="Invoice number for this order (assigned after payment)",
|
|
2654
|
+
examples=["INV-2024-0001"],
|
|
2655
|
+
),
|
|
2656
|
+
] = None
|
|
2657
|
+
total: Annotated[Money, Field(description="Total amount including taxes")]
|
|
2658
|
+
subtotal: Annotated[Money, Field(description="Subtotal amount before taxes")]
|
|
2659
|
+
reversed_subtotal: Annotated[
|
|
2660
|
+
Money,
|
|
2661
|
+
Field(
|
|
2662
|
+
alias="reversedSubtotal",
|
|
2663
|
+
description="Amount of the captured payment returned to the customer, before taxes, via refund or chargeback (settled)",
|
|
2664
|
+
),
|
|
2665
|
+
]
|
|
2666
|
+
refundable_subtotal: Annotated[
|
|
2667
|
+
Money,
|
|
2668
|
+
Field(
|
|
2669
|
+
alias="refundableSubtotal",
|
|
2670
|
+
description="Remaining amount that can still be refunded, before taxes.",
|
|
2671
|
+
),
|
|
2672
|
+
]
|
|
2673
|
+
tax_summary: Annotated[
|
|
2674
|
+
Sequence[TaxSummaryItem],
|
|
2675
|
+
Field(alias="taxSummary", description="Tax breakdown by rate"),
|
|
2676
|
+
]
|
|
2677
|
+
lines: Annotated[Sequence[OrderLine], Field(description="Line items in this order")]
|
|
2678
|
+
customer_details: Annotated[
|
|
2679
|
+
BillingDetails,
|
|
2680
|
+
Field(alias="customerDetails", description="Customer billing details (buyer)"),
|
|
2681
|
+
]
|
|
2682
|
+
links: Annotated[Links4, Field(description="HATEOAS links to related resources")]
|
|
2683
|
+
|
|
2684
|
+
|
|
2685
|
+
class Refund(VatlyModel):
|
|
2686
|
+
"""
|
|
2687
|
+
A refund represents a partial or full reversal of an order payment.
|
|
2688
|
+
Refunds are processed through the original payment provider.
|
|
2689
|
+
|
|
2690
|
+
"""
|
|
2691
|
+
|
|
2692
|
+
model_config = ConfigDict(
|
|
2693
|
+
extra="allow",
|
|
2694
|
+
populate_by_name=True,
|
|
2695
|
+
)
|
|
2696
|
+
id: Annotated[
|
|
2697
|
+
str,
|
|
2698
|
+
Field(
|
|
2699
|
+
description="Unique identifier for the refund",
|
|
2700
|
+
examples=["refund_Xk9pQrSvWm4NjLhYbUcP"],
|
|
2701
|
+
),
|
|
2702
|
+
]
|
|
2703
|
+
resource: Annotated[
|
|
2704
|
+
Literal["refund"],
|
|
2705
|
+
Field(description="Resource type identifier", examples=["refund"]),
|
|
2706
|
+
]
|
|
2707
|
+
order_id: Annotated[
|
|
2708
|
+
str | None,
|
|
2709
|
+
Field(
|
|
2710
|
+
alias="orderId",
|
|
2711
|
+
description="ID of the credit note order created for this refund. Always present in\nthe payload; `null` until the refund is processed, then set to the\ncredit-note order id.\n",
|
|
2712
|
+
examples=["order_Vr8kQdFhSrG4Y3DnfsdqH"],
|
|
2713
|
+
),
|
|
2714
|
+
] = None
|
|
2715
|
+
customer_id: Annotated[
|
|
2716
|
+
str,
|
|
2717
|
+
Field(
|
|
2718
|
+
alias="customerId",
|
|
2719
|
+
description="ID of the customer receiving the refund",
|
|
2720
|
+
examples=["customer_Lp3mNvBxKw7RjTgYcZaE"],
|
|
2721
|
+
),
|
|
2722
|
+
]
|
|
2723
|
+
testmode: Annotated[
|
|
2724
|
+
bool, Field(description="Whether this refund is in test mode", examples=[False])
|
|
2725
|
+
]
|
|
2726
|
+
created_at: Annotated[
|
|
2727
|
+
AwareDatetime,
|
|
2728
|
+
Field(
|
|
2729
|
+
alias="createdAt",
|
|
2730
|
+
description="When this refund was created (ISO 8601 format)",
|
|
2731
|
+
examples=["2024-01-15T10:30:00Z"],
|
|
2732
|
+
),
|
|
2733
|
+
]
|
|
2734
|
+
status: Annotated[
|
|
2735
|
+
Literal["pending", "queued", "processing", "refunded", "failed", "canceled"],
|
|
2736
|
+
Field(
|
|
2737
|
+
description="Current status of the refund:\n- `pending` - Refund is ready to be sent to the bank\n- `queued` - Refund is queued due to a lack of balance\n- `processing` - Refund is being processed (cancellation no longer possible)\n- `refunded` - Refund has been processed successfully\n- `failed` - Refund has failed after processing\n- `canceled` - Refund was canceled\n",
|
|
2738
|
+
examples=["refunded"],
|
|
2739
|
+
),
|
|
2740
|
+
]
|
|
2741
|
+
original_order_id: Annotated[
|
|
2742
|
+
str,
|
|
2743
|
+
Field(
|
|
2744
|
+
alias="originalOrderId",
|
|
2745
|
+
description="ID of the original order being refunded",
|
|
2746
|
+
examples=["order_Fp2kQrSvWm8NjLhYbUcP"],
|
|
2747
|
+
),
|
|
2748
|
+
]
|
|
2749
|
+
total: Annotated[Money, Field(description="Total refund amount including taxes")]
|
|
2750
|
+
subtotal: Annotated[Money, Field(description="Refund subtotal before taxes")]
|
|
2751
|
+
tax_summary: Annotated[
|
|
2752
|
+
Sequence[TaxSummaryItem],
|
|
2753
|
+
Field(alias="taxSummary", description="Tax breakdown by rate being refunded"),
|
|
2754
|
+
]
|
|
2755
|
+
lines: Annotated[Sequence[RefundLine], Field(description="Refund line items")]
|
|
2756
|
+
links: Annotated[Links5, Field(description="HATEOAS links to related resources")]
|
|
2757
|
+
|
|
2758
|
+
|
|
2759
|
+
class WebhookEvent(VatlyModel):
|
|
2760
|
+
"""
|
|
2761
|
+
A persisted webhook event — the record returned by `GET /webhook-events/{eventId}`.
|
|
2762
|
+
It captures a domain event that was delivered to your endpoint, including the full
|
|
2763
|
+
resource payload at the time the event occurred.
|
|
2764
|
+
|
|
2765
|
+
This is the *stored* resource. For the shape Vatly POSTs to your endpoint on each
|
|
2766
|
+
live delivery (which additionally includes the `webhook.setup` verification call),
|
|
2767
|
+
see `WebhookDelivery`.
|
|
2768
|
+
|
|
2769
|
+
"""
|
|
2770
|
+
|
|
2771
|
+
model_config = ConfigDict(
|
|
2772
|
+
extra="allow",
|
|
2773
|
+
populate_by_name=True,
|
|
2774
|
+
)
|
|
2775
|
+
id: Annotated[
|
|
2776
|
+
str,
|
|
2777
|
+
Field(
|
|
2778
|
+
description="Unique identifier for the webhook event",
|
|
2779
|
+
examples=["webhook_event_Qk8pRtSvWm2NjLhYcZaE"],
|
|
2780
|
+
),
|
|
2781
|
+
]
|
|
2782
|
+
resource: Annotated[
|
|
2783
|
+
Literal["webhook_event"],
|
|
2784
|
+
Field(description="Resource type identifier", examples=["webhook_event"]),
|
|
2785
|
+
]
|
|
2786
|
+
event_name: Annotated[
|
|
2787
|
+
Literal[
|
|
2788
|
+
"order.paid",
|
|
2789
|
+
"order.canceled",
|
|
2790
|
+
"order.chargeback_received",
|
|
2791
|
+
"order.chargeback_reversed",
|
|
2792
|
+
"order.payment_failed",
|
|
2793
|
+
"refund.completed",
|
|
2794
|
+
"refund.failed",
|
|
2795
|
+
"refund.canceled",
|
|
2796
|
+
"subscription.started",
|
|
2797
|
+
"subscription.canceled_immediately",
|
|
2798
|
+
"subscription.canceled_for_nonpayment",
|
|
2799
|
+
"subscription.canceled_with_grace_period",
|
|
2800
|
+
"subscription.cancellation_grace_period_completed",
|
|
2801
|
+
"subscription.resumed",
|
|
2802
|
+
"subscription.billing_updated",
|
|
2803
|
+
"subscription.updated",
|
|
2804
|
+
"subscription.update_scheduled",
|
|
2805
|
+
"checkout.paid",
|
|
2806
|
+
"checkout.failed",
|
|
2807
|
+
"checkout.canceled",
|
|
2808
|
+
"checkout.expired",
|
|
2809
|
+
"one_off_product.update_submitted",
|
|
2810
|
+
"one_off_product.update_approved",
|
|
2811
|
+
"one_off_product.update_rejected",
|
|
2812
|
+
"one_off_product.archived",
|
|
2813
|
+
"one_off_product.unarchived",
|
|
2814
|
+
"subscription_plan.update_submitted",
|
|
2815
|
+
"subscription_plan.update_approved",
|
|
2816
|
+
"subscription_plan.update_rejected",
|
|
2817
|
+
"subscription_plan.archived",
|
|
2818
|
+
"subscription_plan.unarchived",
|
|
2819
|
+
],
|
|
2820
|
+
Field(
|
|
2821
|
+
alias="eventName",
|
|
2822
|
+
description="Name of the event that triggered this webhook:\n- `order.paid` - Order payment was successful\n- `order.canceled` - Order was canceled\n- `order.chargeback_received` - Chargeback was received for an order\n- `order.chargeback_reversed` - Chargeback was reversed\n- `order.payment_failed` - A payment failed and the order entered payment recovery (dunning); sent once per failed order\n- `refund.completed` - Refund was processed successfully\n- `refund.failed` - Refund processing failed\n- `refund.canceled` - Refund was canceled\n- `subscription.started` - Subscription was started\n- `subscription.canceled_immediately` - Subscription was canceled immediately\n- `subscription.canceled_for_nonpayment` - Subscription was canceled because payment recovery was exhausted\n- `subscription.canceled_with_grace_period` - Subscription was canceled with a grace period\n- `subscription.cancellation_grace_period_completed` - Subscription grace period ended\n- `subscription.resumed` - A canceled subscription was resumed during its grace period\n- `subscription.billing_updated` - A subscription's billing details changed; in practice today the mandate (payment method on file), e.g. to refresh the masked payment method identifier shown to the customer\n- `subscription.updated` - A subscription changed: on an immediate change, when a scheduled change takes effect at renewal, or when a renewal payment is confirmed; see `reason`\n- `subscription.update_scheduled` - A plan/price/quantity change was scheduled for the next billing cycle; the target values are in `object.scheduledUpdate`\n- `checkout.paid` - Checkout was paid successfully\n- `checkout.failed` - Checkout payment failed\n- `checkout.canceled` - Checkout was canceled\n- `checkout.expired` - Checkout session expired\n\n`webhook.setup` is intentionally absent: the verification handshake is not\npersisted and so never appears on this resource (see `WebhookDelivery`).\n\nThis list is kept in sync with the webhook transformer registry by\n`php artisan openapi:audit-webhook-drift`.\n",
|
|
2823
|
+
examples=["order.paid"],
|
|
2824
|
+
),
|
|
2825
|
+
]
|
|
2826
|
+
reason: Annotated[
|
|
2827
|
+
str | None,
|
|
2828
|
+
Field(
|
|
2829
|
+
description="Why the event fired, for events that have more than one cause. Always\npresent; `null` for every event other than `subscription.updated`.\n\nFor `subscription.updated`:\n- `updated_immediately` - A plan, price, interval or quantity change took effect immediately\n- `updated_on_renewal` - A scheduled plan, price, interval or quantity change took effect at a renewal\n- `renewed` - A renewal payment was confirmed; `renewedAt` and `renewedUntil` moved\n\nNew values may be added; treat an unknown value as a generic change.\n",
|
|
2830
|
+
examples=["renewed"],
|
|
2831
|
+
),
|
|
2832
|
+
] = None
|
|
2833
|
+
entity_type: Annotated[
|
|
2834
|
+
str,
|
|
2835
|
+
Field(
|
|
2836
|
+
alias="entityType",
|
|
2837
|
+
description="Type of the resource this event relates to",
|
|
2838
|
+
examples=["order"],
|
|
2839
|
+
),
|
|
2840
|
+
]
|
|
2841
|
+
entity_id: Annotated[
|
|
2842
|
+
str,
|
|
2843
|
+
Field(
|
|
2844
|
+
alias="entityId",
|
|
2845
|
+
description="ID of the resource this event relates to",
|
|
2846
|
+
examples=["order_Hn5xWqVfKm8RjTgYbUcP"],
|
|
2847
|
+
),
|
|
2848
|
+
]
|
|
2849
|
+
object: Annotated[
|
|
2850
|
+
Order | Chargeback | Refund | Subscription | Checkout | OneOffProduct | SubscriptionPlan,
|
|
2851
|
+
Field(
|
|
2852
|
+
description="The full resource payload at the time of the event, serialized identically\nto the corresponding REST resource. The concrete shape is selected by the\npayload's own `resource` field (the discriminator):\n - `order.paid`, `order.canceled`, `order.payment_failed` → Order\n - `order.chargeback_received`, `order.chargeback_reversed` → Chargeback\n - `refund.*` → Refund\n - `subscription.*` → Subscription\n - `checkout.*` → Checkout\n - `one_off_product.*` → OneOffProduct\n - `subscription_plan.*` → SubscriptionPlan\n\nNote `object.resource` may differ from `entityType`: chargeback events carry\n`entityType: order` (the order the chargeback belongs to) but a Chargeback object.\n",
|
|
2853
|
+
discriminator="resource",
|
|
2854
|
+
),
|
|
2855
|
+
]
|
|
2856
|
+
created_at: Annotated[
|
|
2857
|
+
AwareDatetime,
|
|
2858
|
+
Field(
|
|
2859
|
+
alias="createdAt",
|
|
2860
|
+
description="ISO 8601 timestamp of when the webhook event was created.",
|
|
2861
|
+
examples=["2026-05-21T14:32:08+00:00"],
|
|
2862
|
+
),
|
|
2863
|
+
]
|
|
2864
|
+
testmode: Annotated[
|
|
2865
|
+
bool,
|
|
2866
|
+
Field(
|
|
2867
|
+
description="Whether the event originated from a test-mode resource.\n`true` for events created with a `test_` prefixed token, `false` for live events.\n",
|
|
2868
|
+
examples=[False],
|
|
2869
|
+
),
|
|
2870
|
+
]
|
|
2871
|
+
links: Annotated[
|
|
2872
|
+
Links7,
|
|
2873
|
+
Field(
|
|
2874
|
+
description="HATEOAS links to related resources. Always present on the persisted event."
|
|
2875
|
+
),
|
|
2876
|
+
]
|
|
2877
|
+
|
|
2878
|
+
|
|
2879
|
+
class WebhookDelivery(VatlyModel):
|
|
2880
|
+
"""
|
|
2881
|
+
The envelope Vatly POSTs to your configured webhook endpoint for each delivery.
|
|
2882
|
+
|
|
2883
|
+
Identical to the persisted `WebhookEvent`, with two differences that exist only on
|
|
2884
|
+
the wire:
|
|
2885
|
+
- it additionally covers the `webhook.setup` verification call (`entityType`
|
|
2886
|
+
`webhook`, `object` is the secret-free endpoint config), and
|
|
2887
|
+
- that setup call is not persisted and omits `links`, so `links` is not required
|
|
2888
|
+
on a delivery.
|
|
2889
|
+
|
|
2890
|
+
Every delivery is signed: the `Vatly-Signature` header carries an HMAC-SHA256 of
|
|
2891
|
+
the raw body and `Vatly-Event-Id` carries a retry-stable dedup key (see the
|
|
2892
|
+
operation parameters).
|
|
2893
|
+
|
|
2894
|
+
"""
|
|
2895
|
+
|
|
2896
|
+
model_config = ConfigDict(
|
|
2897
|
+
extra="allow",
|
|
2898
|
+
populate_by_name=True,
|
|
2899
|
+
)
|
|
2900
|
+
id: Annotated[
|
|
2901
|
+
str,
|
|
2902
|
+
Field(
|
|
2903
|
+
description="Unique identifier for the webhook event",
|
|
2904
|
+
examples=["webhook_event_Qk8pRtSvWm2NjLhYcZaE"],
|
|
2905
|
+
),
|
|
2906
|
+
]
|
|
2907
|
+
resource: Annotated[
|
|
2908
|
+
Literal["webhook_event"],
|
|
2909
|
+
Field(description="Resource type identifier", examples=["webhook_event"]),
|
|
2910
|
+
]
|
|
2911
|
+
event_name: Annotated[
|
|
2912
|
+
Literal[
|
|
2913
|
+
"order.paid",
|
|
2914
|
+
"order.canceled",
|
|
2915
|
+
"order.chargeback_received",
|
|
2916
|
+
"order.chargeback_reversed",
|
|
2917
|
+
"order.payment_failed",
|
|
2918
|
+
"refund.completed",
|
|
2919
|
+
"refund.failed",
|
|
2920
|
+
"refund.canceled",
|
|
2921
|
+
"subscription.started",
|
|
2922
|
+
"subscription.canceled_immediately",
|
|
2923
|
+
"subscription.canceled_for_nonpayment",
|
|
2924
|
+
"subscription.canceled_with_grace_period",
|
|
2925
|
+
"subscription.cancellation_grace_period_completed",
|
|
2926
|
+
"subscription.resumed",
|
|
2927
|
+
"subscription.billing_updated",
|
|
2928
|
+
"subscription.updated",
|
|
2929
|
+
"subscription.update_scheduled",
|
|
2930
|
+
"checkout.paid",
|
|
2931
|
+
"checkout.failed",
|
|
2932
|
+
"checkout.canceled",
|
|
2933
|
+
"checkout.expired",
|
|
2934
|
+
"one_off_product.update_submitted",
|
|
2935
|
+
"one_off_product.update_approved",
|
|
2936
|
+
"one_off_product.update_rejected",
|
|
2937
|
+
"one_off_product.archived",
|
|
2938
|
+
"one_off_product.unarchived",
|
|
2939
|
+
"subscription_plan.update_submitted",
|
|
2940
|
+
"subscription_plan.update_approved",
|
|
2941
|
+
"subscription_plan.update_rejected",
|
|
2942
|
+
"subscription_plan.archived",
|
|
2943
|
+
"subscription_plan.unarchived",
|
|
2944
|
+
"webhook.setup",
|
|
2945
|
+
],
|
|
2946
|
+
Field(
|
|
2947
|
+
alias="eventName",
|
|
2948
|
+
description="Name of the event that triggered this webhook:\n- `order.paid` - Order payment was successful\n- `order.canceled` - Order was canceled\n- `order.chargeback_received` - Chargeback was received for an order\n- `order.chargeback_reversed` - Chargeback was reversed\n- `order.payment_failed` - A payment failed and the order entered payment recovery (dunning); sent once per failed order\n- `refund.completed` - Refund was processed successfully\n- `refund.failed` - Refund processing failed\n- `refund.canceled` - Refund was canceled\n- `subscription.started` - Subscription was started\n- `subscription.canceled_immediately` - Subscription was canceled immediately\n- `subscription.canceled_for_nonpayment` - Subscription was canceled because payment recovery was exhausted\n- `subscription.canceled_with_grace_period` - Subscription was canceled with a grace period\n- `subscription.cancellation_grace_period_completed` - Subscription grace period ended\n- `subscription.resumed` - A canceled subscription was resumed during its grace period\n- `subscription.billing_updated` - A subscription's billing details changed; in practice today the mandate (payment method on file), e.g. to refresh the masked payment method identifier shown to the customer\n- `subscription.updated` - A subscription changed: on an immediate change, when a scheduled change takes effect at renewal, or when a renewal payment is confirmed; see `reason`\n- `subscription.update_scheduled` - A plan/price/quantity change was scheduled for the next billing cycle; the target values are in `object.scheduledUpdate`\n- `checkout.paid` - Checkout was paid successfully\n- `checkout.failed` - Checkout payment failed\n- `checkout.canceled` - Checkout was canceled\n- `checkout.expired` - Checkout session expired\n- `webhook.setup` - Verification call sent when an endpoint is registered or its URL changes (`entityType` is `webhook`)\n\nThis list is the single source of truth for SDK generators and is kept in\nsync with the webhook transformer registry by `php artisan openapi:audit-webhook-drift`.\n",
|
|
2949
|
+
examples=["order.paid"],
|
|
2950
|
+
),
|
|
2951
|
+
]
|
|
2952
|
+
reason: Annotated[
|
|
2953
|
+
str | None,
|
|
2954
|
+
Field(
|
|
2955
|
+
description="Why the event fired, for events that have more than one cause. Always\npresent; `null` for every event other than `subscription.updated`.\n\nFor `subscription.updated`:\n- `updated_immediately` - A plan, price, interval or quantity change took effect immediately\n- `updated_on_renewal` - A scheduled plan, price, interval or quantity change took effect at a renewal\n- `renewed` - A renewal payment was confirmed; `renewedAt` and `renewedUntil` moved\n\nNew values may be added; treat an unknown value as a generic change.\n",
|
|
2956
|
+
examples=["renewed"],
|
|
2957
|
+
),
|
|
2958
|
+
] = None
|
|
2959
|
+
entity_type: Annotated[
|
|
2960
|
+
str,
|
|
2961
|
+
Field(
|
|
2962
|
+
alias="entityType",
|
|
2963
|
+
description="Type of the resource this event relates to",
|
|
2964
|
+
examples=["order"],
|
|
2965
|
+
),
|
|
2966
|
+
]
|
|
2967
|
+
entity_id: Annotated[
|
|
2968
|
+
str,
|
|
2969
|
+
Field(
|
|
2970
|
+
alias="entityId",
|
|
2971
|
+
description="ID of the resource this event relates to",
|
|
2972
|
+
examples=["order_Hn5xWqVfKm8RjTgYbUcP"],
|
|
2973
|
+
),
|
|
2974
|
+
]
|
|
2975
|
+
object: Annotated[
|
|
2976
|
+
Order
|
|
2977
|
+
| Chargeback
|
|
2978
|
+
| Refund
|
|
2979
|
+
| Subscription
|
|
2980
|
+
| Checkout
|
|
2981
|
+
| OneOffProduct
|
|
2982
|
+
| SubscriptionPlan
|
|
2983
|
+
| Webhook,
|
|
2984
|
+
Field(
|
|
2985
|
+
description="The full resource payload at the time of the event, serialized identically\nto the corresponding REST resource. The concrete shape is selected by the\npayload's own `resource` field (the discriminator):\n - `order.paid`, `order.canceled`, `order.payment_failed` → Order\n - `order.chargeback_received`, `order.chargeback_reversed` → Chargeback\n - `refund.*` → Refund\n - `subscription.*` → Subscription\n - `checkout.*` → Checkout\n - `webhook.setup` → Webhook (the endpoint config; verification handshake)\n - `one_off_product.*` → OneOffProduct\n - `subscription_plan.*` → SubscriptionPlan\n\nNote `object.resource` may differ from `entityType`: chargeback events carry\n`entityType: order` (the order the chargeback belongs to) but a Chargeback object.\n",
|
|
2986
|
+
discriminator="resource",
|
|
2987
|
+
),
|
|
2988
|
+
]
|
|
2989
|
+
created_at: Annotated[
|
|
2990
|
+
AwareDatetime,
|
|
2991
|
+
Field(
|
|
2992
|
+
alias="createdAt",
|
|
2993
|
+
description="ISO 8601 timestamp of when the webhook event was created.",
|
|
2994
|
+
examples=["2026-05-21T14:32:08+00:00"],
|
|
2995
|
+
),
|
|
2996
|
+
]
|
|
2997
|
+
testmode: Annotated[
|
|
2998
|
+
bool,
|
|
2999
|
+
Field(
|
|
3000
|
+
description="Whether the event originated from a test-mode resource.\n`true` for events created with a `test_` prefixed token, `false` for live events.\n",
|
|
3001
|
+
examples=[False],
|
|
3002
|
+
),
|
|
3003
|
+
]
|
|
3004
|
+
links: Annotated[
|
|
3005
|
+
Links10 | None,
|
|
3006
|
+
Field(
|
|
3007
|
+
description="HATEOAS links to related resources. Present on event deliveries; omitted on\nthe `webhook.setup` verification call, which is not a persisted resource.\n"
|
|
3008
|
+
),
|
|
3009
|
+
] = None
|