fazercards-python-sdk 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. fazercards_python_sdk-0.1.1/.gitignore +14 -0
  2. fazercards_python_sdk-0.1.1/CHANGELOG.md +19 -0
  3. fazercards_python_sdk-0.1.1/LICENSE +21 -0
  4. fazercards_python_sdk-0.1.1/PKG-INFO +169 -0
  5. fazercards_python_sdk-0.1.1/README.md +127 -0
  6. fazercards_python_sdk-0.1.1/examples/fastapi_webhook.py +30 -0
  7. fazercards_python_sdk-0.1.1/examples/fazercards.toml +5 -0
  8. fazercards_python_sdk-0.1.1/pyproject.toml +66 -0
  9. fazercards_python_sdk-0.1.1/src/fazercards_sdk/__init__.py +102 -0
  10. fazercards_python_sdk-0.1.1/src/fazercards_sdk/_core.py +308 -0
  11. fazercards_python_sdk-0.1.1/src/fazercards_sdk/_version.py +1 -0
  12. fazercards_python_sdk-0.1.1/src/fazercards_sdk/client.py +58 -0
  13. fazercards_python_sdk-0.1.1/src/fazercards_sdk/config.py +104 -0
  14. fazercards_python_sdk-0.1.1/src/fazercards_sdk/enums.py +90 -0
  15. fazercards_python_sdk-0.1.1/src/fazercards_sdk/errors.py +104 -0
  16. fazercards_python_sdk-0.1.1/src/fazercards_sdk/messages.py +60 -0
  17. fazercards_python_sdk-0.1.1/src/fazercards_sdk/messages.yaml +48 -0
  18. fazercards_python_sdk-0.1.1/src/fazercards_sdk/models.py +515 -0
  19. fazercards_python_sdk-0.1.1/src/fazercards_sdk/money.py +41 -0
  20. fazercards_python_sdk-0.1.1/src/fazercards_sdk/py.typed +0 -0
  21. fazercards_python_sdk-0.1.1/src/fazercards_sdk/resources.py +313 -0
  22. fazercards_python_sdk-0.1.1/src/fazercards_sdk/webhooks.py +63 -0
  23. fazercards_python_sdk-0.1.1/tests/__init__.py +0 -0
  24. fazercards_python_sdk-0.1.1/tests/conftest.py +131 -0
  25. fazercards_python_sdk-0.1.1/tests/test_async.py +41 -0
  26. fazercards_python_sdk-0.1.1/tests/test_catalog.py +100 -0
  27. fazercards_python_sdk-0.1.1/tests/test_config.py +49 -0
  28. fazercards_python_sdk-0.1.1/tests/test_orders.py +236 -0
  29. fazercards_python_sdk-0.1.1/tests/test_safety.py +135 -0
  30. fazercards_python_sdk-0.1.1/tests/test_webhooks.py +73 -0
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ htmlcov/
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ - `orders.get` leaves the id format to the API and only refuses an empty id or one that would change the path
6
+ - Steam amounts always go out with two decimals
7
+
8
+ ## 0.1.0
9
+
10
+ - Sync `FazerCards` and async `AsyncFazerCards`
11
+ - Top-ups, gift cards, Telegram Stars and Premium, Steam wallet, orders, balance, account and webhook settings
12
+ - Cursor paged categories and numbered order pages, with helpers that read every page
13
+ - Top-up, gift card and Steam orders always carry an Idempotency-Key, with an optional shop prefix
14
+ - Orders are never resent after an ambiguous failure; `OutcomeUnknownError` carries the key
15
+ - A reused key that returns another item raises `KeyCollisionError`
16
+ - Gift card orders that come back completed without codes are read again
17
+ - Reads retry with backoff, 429 waits for `Retry-After`, connection failures retry because nothing was sent
18
+ - Webhook signature check in constant time, with a body size limit
19
+ - goodconf settings, endpoint paths and messages are configurable
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qiyascc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.5
2
+ Name: fazercards-python-sdk
3
+ Version: 0.1.1
4
+ Summary: Typed Python client for the FazerCards reseller API: game top-ups, gift cards, Telegram Stars and Premium, Steam wallet. Sync and async.
5
+ Project-URL: Homepage, https://theqiyas.cc/projects/fazercards-sdk/
6
+ Project-URL: Source, https://github.com/qiyascc/fazercards-python-sdk
7
+ Project-URL: Author, https://theqiyas.cc
8
+ Project-URL: Issues, https://github.com/qiyascc/fazercards-python-sdk/issues
9
+ Project-URL: Changelog, https://github.com/qiyascc/fazercards-python-sdk/blob/main/CHANGELOG.md
10
+ Author-email: qiyascc <qiyasturk@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: api,fazercards,gift cards,reseller,sdk,steam,telegram stars,top-up
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Office/Business :: Financial
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: goodconf>=7.1
26
+ Requires-Dist: httpx<1,>=0.24
27
+ Requires-Dist: pydantic>=2.7
28
+ Requires-Dist: pyyaml>=6
29
+ Provides-Extra: dev
30
+ Requires-Dist: goodconf[toml]>=7.1; extra == 'dev'
31
+ Requires-Dist: goodconf[yaml]>=7.1; extra == 'dev'
32
+ Requires-Dist: mypy>=1.8; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
34
+ Requires-Dist: pytest>=7; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4; extra == 'dev'
36
+ Requires-Dist: types-pyyaml; extra == 'dev'
37
+ Provides-Extra: toml
38
+ Requires-Dist: goodconf[toml]>=7.1; extra == 'toml'
39
+ Provides-Extra: yaml
40
+ Requires-Dist: goodconf[yaml]>=7.1; extra == 'yaml'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # fazercards-python-sdk
44
+
45
+ [![Python](https://img.shields.io/badge/python-3.10%20to%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org)
46
+ [![FazerCards](https://img.shields.io/badge/api-FazerCards%20v2-7C3AED)](https://api.fzr.cards/api/v2)
47
+ [![Typed](https://img.shields.io/badge/typing-mypy-2A6DB2)](https://mypy-lang.org)
48
+ [![Async](https://img.shields.io/badge/sync%20%2B%20async-httpx-5A45FF)](https://www.python-httpx.org)
49
+ [![Ruff](https://img.shields.io/badge/lint-ruff-D7FF64?logo=ruff&logoColor=black)](https://docs.astral.sh/ruff)
50
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/qiyascc/fazercards-python-sdk/blob/main/LICENSE)
51
+
52
+ Typed Python client for the FazerCards reseller API. Game top-ups, gift cards, Telegram Stars and Premium, Steam wallet, orders and signed webhooks. Sync and async, with retry rules built so an order is never placed twice.
53
+
54
+ ```bash
55
+ pip install fazercards-python-sdk
56
+ ```
57
+
58
+ ## Top up a game
59
+
60
+ ```python
61
+ from fazercards_sdk import FazerCards
62
+
63
+ with FazerCards("API_KEY") as fc:
64
+ check = fc.topups.validate("free_fire", "123456789")
65
+ if check.valid:
66
+ order = fc.topups.order("free_fire_cis", "110_diamonds", "123456789", idempotency_key="ORDER-1042")
67
+ ```
68
+
69
+ `order.status` moves from `created` to `processing` to `completed`, or to `failed` / `refund` with `order.fail_reason`. Follow it by webhook, or with `fc.orders.wait(order.id)`.
70
+
71
+ ## Sell a gift card
72
+
73
+ ```python
74
+ order = fc.giftcards.order("playstation_tr", "250_try", quantity=1, idempotency_key="ORDER-1043")
75
+ order.codes
76
+ ```
77
+
78
+ FazerCards can answer a gift card order with `completed` and an empty `cards` list, then fill the list a moment later. The client reads the order again before it returns (`code_checks`, default 3). If the codes still have not arrived, `order.awaiting_codes` is `True`: keep the order open and read it later, never show the customer an empty result.
79
+
80
+ ## Telegram and Steam
81
+
82
+ ```python
83
+ fc.telegram.stars_quote().price(500)
84
+ fc.telegram.buy_stars("@username", 500)
85
+ fc.telegram.buy_premium("username", months=12)
86
+
87
+ if fc.steam.check_login("steam_login").valid:
88
+ fc.steam.order("steam_login", "19.50", "USD", idempotency_key="ORDER-1044")
89
+ ```
90
+
91
+ ## Catalog
92
+
93
+ ```python
94
+ games = fc.topups.all_categories()
95
+ cards = fc.giftcards.all_categories()
96
+ offers = fc.topups.offers("free_fire_cis")
97
+ offers.get("110_diamonds").price
98
+ offers.fields
99
+ fc.giftcards.cards("playstation_tr").offers[0].stock
100
+ ```
101
+
102
+ Categories come in cursor pages of up to 500 (`fc.topups.categories(limit, cursor)`); `all_categories()` follows the cursor. Orders come in numbered pages of up to 100 (`fc.orders.list(page, limit)`, `fc.orders.recent(count)`).
103
+
104
+ ## Webhooks
105
+
106
+ ```python
107
+ event = fc.verify_webhook(request.body, headers=request.headers)
108
+ if event.needs_lookup:
109
+ order = fc.orders.get(event.order_id)
110
+ ```
111
+
112
+ The body is checked against `X-Webhook-Signature` (HMAC SHA256 of the raw body, `sha256=` prefix optional) in constant time. Set the address with `fc.account.set_webhook("https://shop.example/webhook/fazercards")`. See [examples/fastapi_webhook.py](https://github.com/qiyascc/fazercards-python-sdk/blob/main/examples/fastapi_webhook.py).
113
+
114
+ ## Safety
115
+
116
+ | Situation | What the client does |
117
+ | --- | --- |
118
+ | Connection refused, nothing sent | Retries, then `NotSentError` |
119
+ | Timeout, cut connection or 5xx on an order | No retry, `OutcomeUnknownError` with the `idempotency_key` |
120
+ | Same failure on a read | Retries with backoff, then `TransportError` |
121
+ | 429 | Waits `Retry-After` and retries, the request was not processed |
122
+ | `resend_keyed_orders=True` | An order with an Idempotency-Key is resent once with the same key |
123
+ | Telegram purchase | Takes no key on the API side, so it is never resent |
124
+ | Reused key that returns another item | `KeyCollisionError`, never counted as success |
125
+ | Gift card `completed` without codes | Read again, then `order.awaiting_codes` |
126
+ | Quantity, months, amount like `1.005` or `True` | `InvalidRequestError` before anything is sent |
127
+ | `base_url` over plain http | `ConfigurationError`, except localhost or `allow_insecure_http` |
128
+ | Webhook body over 256 KB | Rejected before hashing |
129
+
130
+ Every top-up, gift card and Steam order carries an `Idempotency-Key`. Pass your own order number, so a double click or a crash can not buy twice. With several shops on one account set `idempotency_prefix`. Prices are `Decimal` end to end. `fc.rate_limit` keeps the last `X-RateLimit-*` headers.
131
+
132
+ ## Endpoints
133
+
134
+ | Namespace | Methods |
135
+ | --- | --- |
136
+ | `topups` | `categories` `all_categories` `offers` `validation_games` `validate` `order` |
137
+ | `giftcards` | `categories` `all_categories` `cards` `order` |
138
+ | `orders` | `get` `list` `recent` `wait` |
139
+ | `telegram` | `stars_quote` `premium_quote` `buy_stars` `buy_premium` |
140
+ | `steam` | `rates` `check_login` `order` |
141
+ | `account` | `me` `balance` `webhook` `set_webhook` |
142
+ | client | `balance` `verify_webhook` `request` |
143
+
144
+ `AsyncFazerCards` has the same methods, awaited.
145
+
146
+ ## Errors
147
+
148
+ `FazerCardsError` is the base. API answers raise `APIError` subclasses by status: `BadRequestError` (400), `AuthenticationError` (401, 403), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `ServerError` (5xx). Each carries `status_code`, `message` and the raw `body`.
149
+
150
+ ## Config
151
+
152
+ Pass values in code, or set `FAZERCARDS_API_KEY`, `FAZERCARDS_WEBHOOK_SECRET`, `FAZERCARDS_BASE_URL`, `FAZERCARDS_IDEMPOTENCY_PREFIX`, `FAZERCARDS_TIMEOUT`, `FAZERCARDS_MAX_RETRIES`, or put them in `fazercards.toml` ([example](https://github.com/qiyascc/fazercards-python-sdk/blob/main/examples/fazercards.toml)). Every path is a setting (`FAZERCARDS_ENDPOINTS__BALANCE=...`). Messages live in `messages.yaml` and can be replaced with `messages.use(...)`.
153
+
154
+ ## Develop
155
+
156
+ ```bash
157
+ pip install -e ".[dev]" && pytest && ruff check . && mypy src
158
+ ```
159
+
160
+ ## Links
161
+
162
+ - Source code: [github.com/qiyascc/fazercards-python-sdk](https://github.com/qiyascc/fazercards-python-sdk)
163
+ - PyPI: [pypi.org/project/fazercards-python-sdk](https://pypi.org/project/fazercards-python-sdk/)
164
+ - Project page: [theqiyas.cc/projects/fazercards-sdk](https://theqiyas.cc/projects/fazercards-sdk/)
165
+ - Author: [Qiyas Sadıqov](https://theqiyas.cc), more projects at [theqiyas.cc](https://theqiyas.cc)
166
+
167
+ ## License
168
+
169
+ MIT
@@ -0,0 +1,127 @@
1
+ # fazercards-python-sdk
2
+
3
+ [![Python](https://img.shields.io/badge/python-3.10%20to%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org)
4
+ [![FazerCards](https://img.shields.io/badge/api-FazerCards%20v2-7C3AED)](https://api.fzr.cards/api/v2)
5
+ [![Typed](https://img.shields.io/badge/typing-mypy-2A6DB2)](https://mypy-lang.org)
6
+ [![Async](https://img.shields.io/badge/sync%20%2B%20async-httpx-5A45FF)](https://www.python-httpx.org)
7
+ [![Ruff](https://img.shields.io/badge/lint-ruff-D7FF64?logo=ruff&logoColor=black)](https://docs.astral.sh/ruff)
8
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/qiyascc/fazercards-python-sdk/blob/main/LICENSE)
9
+
10
+ Typed Python client for the FazerCards reseller API. Game top-ups, gift cards, Telegram Stars and Premium, Steam wallet, orders and signed webhooks. Sync and async, with retry rules built so an order is never placed twice.
11
+
12
+ ```bash
13
+ pip install fazercards-python-sdk
14
+ ```
15
+
16
+ ## Top up a game
17
+
18
+ ```python
19
+ from fazercards_sdk import FazerCards
20
+
21
+ with FazerCards("API_KEY") as fc:
22
+ check = fc.topups.validate("free_fire", "123456789")
23
+ if check.valid:
24
+ order = fc.topups.order("free_fire_cis", "110_diamonds", "123456789", idempotency_key="ORDER-1042")
25
+ ```
26
+
27
+ `order.status` moves from `created` to `processing` to `completed`, or to `failed` / `refund` with `order.fail_reason`. Follow it by webhook, or with `fc.orders.wait(order.id)`.
28
+
29
+ ## Sell a gift card
30
+
31
+ ```python
32
+ order = fc.giftcards.order("playstation_tr", "250_try", quantity=1, idempotency_key="ORDER-1043")
33
+ order.codes
34
+ ```
35
+
36
+ FazerCards can answer a gift card order with `completed` and an empty `cards` list, then fill the list a moment later. The client reads the order again before it returns (`code_checks`, default 3). If the codes still have not arrived, `order.awaiting_codes` is `True`: keep the order open and read it later, never show the customer an empty result.
37
+
38
+ ## Telegram and Steam
39
+
40
+ ```python
41
+ fc.telegram.stars_quote().price(500)
42
+ fc.telegram.buy_stars("@username", 500)
43
+ fc.telegram.buy_premium("username", months=12)
44
+
45
+ if fc.steam.check_login("steam_login").valid:
46
+ fc.steam.order("steam_login", "19.50", "USD", idempotency_key="ORDER-1044")
47
+ ```
48
+
49
+ ## Catalog
50
+
51
+ ```python
52
+ games = fc.topups.all_categories()
53
+ cards = fc.giftcards.all_categories()
54
+ offers = fc.topups.offers("free_fire_cis")
55
+ offers.get("110_diamonds").price
56
+ offers.fields
57
+ fc.giftcards.cards("playstation_tr").offers[0].stock
58
+ ```
59
+
60
+ Categories come in cursor pages of up to 500 (`fc.topups.categories(limit, cursor)`); `all_categories()` follows the cursor. Orders come in numbered pages of up to 100 (`fc.orders.list(page, limit)`, `fc.orders.recent(count)`).
61
+
62
+ ## Webhooks
63
+
64
+ ```python
65
+ event = fc.verify_webhook(request.body, headers=request.headers)
66
+ if event.needs_lookup:
67
+ order = fc.orders.get(event.order_id)
68
+ ```
69
+
70
+ The body is checked against `X-Webhook-Signature` (HMAC SHA256 of the raw body, `sha256=` prefix optional) in constant time. Set the address with `fc.account.set_webhook("https://shop.example/webhook/fazercards")`. See [examples/fastapi_webhook.py](https://github.com/qiyascc/fazercards-python-sdk/blob/main/examples/fastapi_webhook.py).
71
+
72
+ ## Safety
73
+
74
+ | Situation | What the client does |
75
+ | --- | --- |
76
+ | Connection refused, nothing sent | Retries, then `NotSentError` |
77
+ | Timeout, cut connection or 5xx on an order | No retry, `OutcomeUnknownError` with the `idempotency_key` |
78
+ | Same failure on a read | Retries with backoff, then `TransportError` |
79
+ | 429 | Waits `Retry-After` and retries, the request was not processed |
80
+ | `resend_keyed_orders=True` | An order with an Idempotency-Key is resent once with the same key |
81
+ | Telegram purchase | Takes no key on the API side, so it is never resent |
82
+ | Reused key that returns another item | `KeyCollisionError`, never counted as success |
83
+ | Gift card `completed` without codes | Read again, then `order.awaiting_codes` |
84
+ | Quantity, months, amount like `1.005` or `True` | `InvalidRequestError` before anything is sent |
85
+ | `base_url` over plain http | `ConfigurationError`, except localhost or `allow_insecure_http` |
86
+ | Webhook body over 256 KB | Rejected before hashing |
87
+
88
+ Every top-up, gift card and Steam order carries an `Idempotency-Key`. Pass your own order number, so a double click or a crash can not buy twice. With several shops on one account set `idempotency_prefix`. Prices are `Decimal` end to end. `fc.rate_limit` keeps the last `X-RateLimit-*` headers.
89
+
90
+ ## Endpoints
91
+
92
+ | Namespace | Methods |
93
+ | --- | --- |
94
+ | `topups` | `categories` `all_categories` `offers` `validation_games` `validate` `order` |
95
+ | `giftcards` | `categories` `all_categories` `cards` `order` |
96
+ | `orders` | `get` `list` `recent` `wait` |
97
+ | `telegram` | `stars_quote` `premium_quote` `buy_stars` `buy_premium` |
98
+ | `steam` | `rates` `check_login` `order` |
99
+ | `account` | `me` `balance` `webhook` `set_webhook` |
100
+ | client | `balance` `verify_webhook` `request` |
101
+
102
+ `AsyncFazerCards` has the same methods, awaited.
103
+
104
+ ## Errors
105
+
106
+ `FazerCardsError` is the base. API answers raise `APIError` subclasses by status: `BadRequestError` (400), `AuthenticationError` (401, 403), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `ServerError` (5xx). Each carries `status_code`, `message` and the raw `body`.
107
+
108
+ ## Config
109
+
110
+ Pass values in code, or set `FAZERCARDS_API_KEY`, `FAZERCARDS_WEBHOOK_SECRET`, `FAZERCARDS_BASE_URL`, `FAZERCARDS_IDEMPOTENCY_PREFIX`, `FAZERCARDS_TIMEOUT`, `FAZERCARDS_MAX_RETRIES`, or put them in `fazercards.toml` ([example](https://github.com/qiyascc/fazercards-python-sdk/blob/main/examples/fazercards.toml)). Every path is a setting (`FAZERCARDS_ENDPOINTS__BALANCE=...`). Messages live in `messages.yaml` and can be replaced with `messages.use(...)`.
111
+
112
+ ## Develop
113
+
114
+ ```bash
115
+ pip install -e ".[dev]" && pytest && ruff check . && mypy src
116
+ ```
117
+
118
+ ## Links
119
+
120
+ - Source code: [github.com/qiyascc/fazercards-python-sdk](https://github.com/qiyascc/fazercards-python-sdk)
121
+ - PyPI: [pypi.org/project/fazercards-python-sdk](https://pypi.org/project/fazercards-python-sdk/)
122
+ - Project page: [theqiyas.cc/projects/fazercards-sdk](https://theqiyas.cc/projects/fazercards-sdk/)
123
+ - Author: [Qiyas Sadıqov](https://theqiyas.cc), more projects at [theqiyas.cc](https://theqiyas.cc)
124
+
125
+ ## License
126
+
127
+ MIT
@@ -0,0 +1,30 @@
1
+ from fastapi import FastAPI, HTTPException, Request
2
+
3
+ from fazercards_sdk import AsyncFazerCards, SignatureError
4
+
5
+ app = FastAPI()
6
+ fazercards = AsyncFazerCards()
7
+
8
+
9
+ @app.post("/webhook/fazercards")
10
+ async def fazercards_webhook(request: Request) -> dict:
11
+ try:
12
+ event = fazercards.verify_webhook(await request.body(), headers=request.headers)
13
+ except SignatureError:
14
+ raise HTTPException(status_code=401) from None
15
+ order = await fazercards.orders.get(event.order_id) if event.needs_lookup else event.order
16
+ if order is None:
17
+ return {"message": "OK"}
18
+ if order.delivered and not order.awaiting_codes:
19
+ await deliver(order.id, order.codes)
20
+ elif order.failed:
21
+ await refund(order.id, order.fail_reason)
22
+ return {"message": "OK"}
23
+
24
+
25
+ async def deliver(order_id: str, codes: tuple[str, ...]) -> None:
26
+ raise NotImplementedError
27
+
28
+
29
+ async def refund(order_id: str, reason: str | None) -> None:
30
+ raise NotImplementedError
@@ -0,0 +1,5 @@
1
+ api_key = "your-api-key"
2
+ webhook_secret = "your-webhook-secret"
3
+ idempotency_prefix = "shop"
4
+ timeout = 30
5
+ max_retries = 2
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "fazercards-python-sdk"
7
+ dynamic = ["version"]
8
+ description = "Typed Python client for the FazerCards reseller API: game top-ups, gift cards, Telegram Stars and Premium, Steam wallet. Sync and async."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "qiyascc", email = "qiyasturk@gmail.com" }]
14
+ keywords = ["fazercards", "gift cards", "top-up", "reseller", "telegram stars", "steam", "sdk", "api"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Office/Business :: Financial",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = ["httpx>=0.24,<1", "goodconf>=7.1", "pydantic>=2.7", "PyYAML>=6"]
28
+
29
+ [project.optional-dependencies]
30
+ toml = ["goodconf[toml]>=7.1"]
31
+ yaml = ["goodconf[yaml]>=7.1"]
32
+ dev = ["fazercards-python-sdk[toml,yaml]", "pytest>=7", "pytest-asyncio>=0.21", "ruff>=0.4", "mypy>=1.8", "types-PyYAML"]
33
+
34
+ [project.urls]
35
+ Homepage = "https://theqiyas.cc/projects/fazercards-sdk/"
36
+ Source = "https://github.com/qiyascc/fazercards-python-sdk"
37
+ Author = "https://theqiyas.cc"
38
+ Issues = "https://github.com/qiyascc/fazercards-python-sdk/issues"
39
+ Changelog = "https://github.com/qiyascc/fazercards-python-sdk/blob/main/CHANGELOG.md"
40
+
41
+ [tool.hatch.version]
42
+ path = "src/fazercards_sdk/_version.py"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/fazercards_sdk"]
46
+
47
+ [tool.hatch.build.targets.sdist]
48
+ include = ["src/fazercards_sdk", "tests", "examples", "README.md", "CHANGELOG.md", "LICENSE"]
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
52
+ asyncio_mode = "auto"
53
+ asyncio_default_fixture_loop_scope = "function"
54
+
55
+ [tool.ruff]
56
+ line-length = 120
57
+ target-version = "py310"
58
+
59
+ [tool.ruff.lint]
60
+ select = ["E", "F", "I", "B", "UP"]
61
+ ignore = ["E731", "UP006", "UP007", "UP035", "UP045"]
62
+
63
+ [tool.mypy]
64
+ python_version = "3.10"
65
+ plugins = ["pydantic.mypy"]
66
+ warn_unused_ignores = true
@@ -0,0 +1,102 @@
1
+ from . import messages, webhooks
2
+ from ._version import __version__
3
+ from .client import AsyncFazerCards, FazerCards
4
+ from .config import Endpoints, Settings, load_settings
5
+ from .enums import PREMIUM_MONTHS, OrderKind, OrderStatus, SteamCurrency
6
+ from .errors import (
7
+ APIError,
8
+ AuthenticationError,
9
+ BadRequestError,
10
+ ConfigurationError,
11
+ ConflictError,
12
+ FazerCardsError,
13
+ InvalidRequestError,
14
+ KeyCollisionError,
15
+ NotFoundError,
16
+ NotSentError,
17
+ OutcomeUnknownError,
18
+ RateLimitError,
19
+ ServerError,
20
+ SignatureError,
21
+ TransportError,
22
+ WaitTimeoutError,
23
+ )
24
+ from .models import (
25
+ Account,
26
+ Balance,
27
+ Category,
28
+ CategoryPage,
29
+ InputField,
30
+ Offer,
31
+ Offers,
32
+ Order,
33
+ OrderPage,
34
+ PlayerCheck,
35
+ PremiumPlan,
36
+ PremiumQuote,
37
+ RateLimit,
38
+ StarsQuote,
39
+ StatusChange,
40
+ SteamLogin,
41
+ SteamRates,
42
+ ValidationGame,
43
+ Webhook,
44
+ WebhookEvent,
45
+ card_code,
46
+ )
47
+ from .webhooks import SIGNATURE_HEADER, parse_webhook, verify_webhook
48
+
49
+ __all__ = [
50
+ "__version__",
51
+ "FazerCards",
52
+ "AsyncFazerCards",
53
+ "Settings",
54
+ "Endpoints",
55
+ "load_settings",
56
+ "messages",
57
+ "webhooks",
58
+ "verify_webhook",
59
+ "parse_webhook",
60
+ "SIGNATURE_HEADER",
61
+ "OrderStatus",
62
+ "OrderKind",
63
+ "SteamCurrency",
64
+ "PREMIUM_MONTHS",
65
+ "Account",
66
+ "Balance",
67
+ "Category",
68
+ "CategoryPage",
69
+ "InputField",
70
+ "Offer",
71
+ "Offers",
72
+ "Order",
73
+ "OrderPage",
74
+ "PlayerCheck",
75
+ "PremiumPlan",
76
+ "PremiumQuote",
77
+ "RateLimit",
78
+ "StarsQuote",
79
+ "StatusChange",
80
+ "SteamLogin",
81
+ "SteamRates",
82
+ "ValidationGame",
83
+ "Webhook",
84
+ "WebhookEvent",
85
+ "card_code",
86
+ "FazerCardsError",
87
+ "ConfigurationError",
88
+ "InvalidRequestError",
89
+ "SignatureError",
90
+ "TransportError",
91
+ "NotSentError",
92
+ "OutcomeUnknownError",
93
+ "APIError",
94
+ "BadRequestError",
95
+ "AuthenticationError",
96
+ "NotFoundError",
97
+ "ConflictError",
98
+ "RateLimitError",
99
+ "ServerError",
100
+ "KeyCollisionError",
101
+ "WaitTimeoutError",
102
+ ]