coinspay 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
coinspay-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CoinsPay
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,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: coinspay
3
+ Version: 0.1.0
4
+ Summary: Official Python library for the CoinsPay crypto payments API
5
+ License: MIT
6
+ Project-URL: Homepage, https://coinspay.app
7
+ Project-URL: Documentation, https://coinspay.app/docs
8
+ Project-URL: Source, https://github.com/coinspayapp/coinspay-python
9
+ Project-URL: Issues, https://github.com/coinspayapp/coinspay-python/issues
10
+ Project-URL: Changelog, https://github.com/coinspayapp/coinspay-python/blob/main/CHANGELOG.md
11
+ Keywords: coinspay,crypto,payments,invoices,webhooks,usdt,bitcoin
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # CoinsPay for Python
22
+
23
+ Official library for the [CoinsPay](https://coinspay.app) crypto payments API: invoices
24
+ with a payment page, receiving addresses, payments, payouts to your wallets and webhooks.
25
+ Python 3.8 or newer, typed, no dependencies.
26
+ The API reference is at [coinspay.app/docs](https://coinspay.app/docs).
27
+
28
+ ```bash
29
+ pip install coinspay
30
+ ```
31
+
32
+ ## Quick start
33
+
34
+ Create an API key in the dashboard: shop → "API keys". A key with "Invoices only" access is
35
+ enough for a website; addresses, payments, balances and payouts need full access. Keep the
36
+ key on your server.
37
+
38
+ ```python
39
+ import os
40
+ import coinspay
41
+
42
+ client = coinspay.Client(os.environ["COINSPAY_KEY"], "https://coinspay.app")
43
+
44
+ invoice = client.invoices.create(
45
+ currency="USD",
46
+ amount="49.90",
47
+ reference="order-1042",
48
+ return_url="https://shop.example/orders/1042",
49
+ idempotency_key="order-1042",
50
+ )
51
+ # redirect the buyer to invoice.pay_url
52
+ ```
53
+
54
+ Amounts are decimal strings or `Decimal`, never floats. Parameters go by name, so a typo
55
+ raises `TypeError` instead of being silently ignored by the API. Pass a field this version
56
+ does not know yet in `extra_body` or `extra_query`. `None` sends `null`; a parameter you do
57
+ not pass is not sent.
58
+
59
+ ## Webhooks
60
+
61
+ ```python
62
+ from coinspay import SignatureVerificationError, construct_event, models
63
+
64
+ # Flask
65
+ @app.post("/coinspay/webhook")
66
+ def webhook():
67
+ try:
68
+ event = construct_event(request.get_data(), request.headers.get("x-cpay-signature"), os.environ["COINSPAY_WEBHOOK_SECRET"])
69
+ except SignatureVerificationError:
70
+ return "", 401
71
+ if isinstance(event, models.InvoiceEvent) and not event.test and event.type == "invoice.paid":
72
+ mark_order_paid(event.data.invoice.reference, event.data.invoice.id)
73
+ return "", 200
74
+ ```
75
+
76
+ In Django use `request.body`, in FastAPI `await request.body()`: the body exactly as
77
+ received, before `json.loads`.
78
+
79
+ - One event may arrive twice: drop duplicates by `event.id`.
80
+ - Rely on the state in the body, not on the arrival order.
81
+ - While you replace the secret, pass both: `[new_secret, old_secret]`.
82
+ - An event of a type this version does not know is an `UnknownEvent`.
83
+ - `signature_header(body, secret)` signs a body for tests of your handler.
84
+
85
+ ## Objects
86
+
87
+ Answers are frozen dataclasses with fields named as in the API (`invoice.pay_url`,
88
+ `invoice.items[0].price`); `to_dict()` gives the object exactly as the API sent it,
89
+ including fields this version does not know yet. Dates are ISO 8601 strings.
90
+
91
+ ## Lists
92
+
93
+ ```python
94
+ page = client.invoices.list(status="paid", limit=100) # page.data, page.next_cursor
95
+ for invoice in client.invoices.iterate(status="paid"):
96
+ ... # every paid invoice, page after page
97
+ ```
98
+
99
+ ## Errors
100
+
101
+ ```python
102
+ try:
103
+ client.balances.list()
104
+ except coinspay.PermissionDeniedError as error:
105
+ if error.code == "KEY_ACCESS_DENIED":
106
+ ... # an "Invoices only" key
107
+ except coinspay.RateLimitError as error:
108
+ print(error.retry_after)
109
+ except coinspay.APIConnectionError as error:
110
+ if not error.sent:
111
+ ... # never reached the API: safe to try again
112
+ except coinspay.APIError as error:
113
+ print(error.status, error.code, error.details)
114
+ ```
115
+
116
+ `APIError` subclasses by status: `InvalidRequestError` 400, `AuthenticationError` 401,
117
+ `PermissionDeniedError` 403, `NotFoundError` 404, `ConflictError` 409,
118
+ `UnprocessableError` 422, `RateLimitError` 429, `ServiceUnavailableError` 503. Without an
119
+ answer: `APIConnectionError` and `APITimeoutError`; a non-JSON answer:
120
+ `InvalidResponseError`. All extend `CoinsPayError`. Codes are constants of
121
+ `coinspay.ErrorCode`; `coinspay.ERROR_CODES` lists them by status.
122
+
123
+ ## Retries and idempotency
124
+
125
+ Failures that are safe to retry are retried twice with a short pause: network errors and
126
+ timeouts, 429, 502, 503 and 504 for reads, quotes, cancellations and creates with an
127
+ idempotency key. `addresses.create` has no idempotency key and is retried only when the
128
+ request surely did not reach the API.
129
+
130
+ `invoices.create` without `idempotency_key` gets a random key for its own retries; pass
131
+ your order number to make your own retries safe too. `payouts.create` requires a key.
132
+ A repeat with a used key returns the existing object in its current state, and
133
+ `client.last_response.replayed` is `True`. `last_response` belongs to the calling thread,
134
+ so one client can serve the threads of a web server; it is `None` after a failed request.
135
+
136
+ ## Client behaviour
137
+
138
+ - The API address must use `https`. Plain `http` is accepted for `localhost` and `127.0.0.1`, or with `allow_insecure_http=True` for a test stand.
139
+ - Each attempt times out after 30 seconds, and connecting after 10.
140
+ - Redirects are not followed, so the key is never sent to another host.
141
+ - IDs in the path are checked as UUIDs before the request is sent.
142
+ - The key never shows up in exceptions or `repr`.
143
+ - The transport can be replaced, for example to go through a proxy (see Options). The timeout applies to each socket operation.
144
+
145
+ ## Options
146
+
147
+ ```python
148
+ client = coinspay.Client(
149
+ key, "https://coinspay.app",
150
+ timeout=30.0, # seconds for each socket operation of an attempt
151
+ max_retries=2,
152
+ max_retry_delay=5.0, # a longer Retry-After fails at once
153
+ transport=coinspay.UrllibTransport(proxies={"https": "http://proxy:3128"}),
154
+ )
155
+ client.invoices.get(invoice_id, timeout=5, max_retries=0)
156
+ client.request("GET", "/something/new") # a method this version does not know yet
157
+ ```
158
+
159
+ Proxies from `HTTPS_PROXY` work by default.
160
+
161
+ ## Development
162
+
163
+ ```bash
164
+ PYTHONPATH=src:tests python -m unittest discover -s tests
165
+ pip install mypy && MYPYPATH=src mypy --strict src tests/typing_check.py
166
+ ```
167
+
168
+ The library supports Python 3.8 and newer, and mypy checks it against 3.8. Tests that need a real HTTP server are skipped unless `COINSPAY_MOCK_URL` points to one. `node scripts/mock-server.mjs` from [coinspay-node](https://github.com/coinspayapp/coinspay-node) starts it and prints its address.
169
+
170
+ Files that start with "Generated from the CoinsPay OpenAPI document" and the data in `tests/data` come from our generator and are shared by the libraries for Node.js, PHP, Python and Go. Please do not edit them by hand: if a model or a test vector looks wrong, open an issue.
171
+
172
+ ## License
173
+
174
+ MIT
@@ -0,0 +1,154 @@
1
+ # CoinsPay for Python
2
+
3
+ Official library for the [CoinsPay](https://coinspay.app) crypto payments API: invoices
4
+ with a payment page, receiving addresses, payments, payouts to your wallets and webhooks.
5
+ Python 3.8 or newer, typed, no dependencies.
6
+ The API reference is at [coinspay.app/docs](https://coinspay.app/docs).
7
+
8
+ ```bash
9
+ pip install coinspay
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ Create an API key in the dashboard: shop → "API keys". A key with "Invoices only" access is
15
+ enough for a website; addresses, payments, balances and payouts need full access. Keep the
16
+ key on your server.
17
+
18
+ ```python
19
+ import os
20
+ import coinspay
21
+
22
+ client = coinspay.Client(os.environ["COINSPAY_KEY"], "https://coinspay.app")
23
+
24
+ invoice = client.invoices.create(
25
+ currency="USD",
26
+ amount="49.90",
27
+ reference="order-1042",
28
+ return_url="https://shop.example/orders/1042",
29
+ idempotency_key="order-1042",
30
+ )
31
+ # redirect the buyer to invoice.pay_url
32
+ ```
33
+
34
+ Amounts are decimal strings or `Decimal`, never floats. Parameters go by name, so a typo
35
+ raises `TypeError` instead of being silently ignored by the API. Pass a field this version
36
+ does not know yet in `extra_body` or `extra_query`. `None` sends `null`; a parameter you do
37
+ not pass is not sent.
38
+
39
+ ## Webhooks
40
+
41
+ ```python
42
+ from coinspay import SignatureVerificationError, construct_event, models
43
+
44
+ # Flask
45
+ @app.post("/coinspay/webhook")
46
+ def webhook():
47
+ try:
48
+ event = construct_event(request.get_data(), request.headers.get("x-cpay-signature"), os.environ["COINSPAY_WEBHOOK_SECRET"])
49
+ except SignatureVerificationError:
50
+ return "", 401
51
+ if isinstance(event, models.InvoiceEvent) and not event.test and event.type == "invoice.paid":
52
+ mark_order_paid(event.data.invoice.reference, event.data.invoice.id)
53
+ return "", 200
54
+ ```
55
+
56
+ In Django use `request.body`, in FastAPI `await request.body()`: the body exactly as
57
+ received, before `json.loads`.
58
+
59
+ - One event may arrive twice: drop duplicates by `event.id`.
60
+ - Rely on the state in the body, not on the arrival order.
61
+ - While you replace the secret, pass both: `[new_secret, old_secret]`.
62
+ - An event of a type this version does not know is an `UnknownEvent`.
63
+ - `signature_header(body, secret)` signs a body for tests of your handler.
64
+
65
+ ## Objects
66
+
67
+ Answers are frozen dataclasses with fields named as in the API (`invoice.pay_url`,
68
+ `invoice.items[0].price`); `to_dict()` gives the object exactly as the API sent it,
69
+ including fields this version does not know yet. Dates are ISO 8601 strings.
70
+
71
+ ## Lists
72
+
73
+ ```python
74
+ page = client.invoices.list(status="paid", limit=100) # page.data, page.next_cursor
75
+ for invoice in client.invoices.iterate(status="paid"):
76
+ ... # every paid invoice, page after page
77
+ ```
78
+
79
+ ## Errors
80
+
81
+ ```python
82
+ try:
83
+ client.balances.list()
84
+ except coinspay.PermissionDeniedError as error:
85
+ if error.code == "KEY_ACCESS_DENIED":
86
+ ... # an "Invoices only" key
87
+ except coinspay.RateLimitError as error:
88
+ print(error.retry_after)
89
+ except coinspay.APIConnectionError as error:
90
+ if not error.sent:
91
+ ... # never reached the API: safe to try again
92
+ except coinspay.APIError as error:
93
+ print(error.status, error.code, error.details)
94
+ ```
95
+
96
+ `APIError` subclasses by status: `InvalidRequestError` 400, `AuthenticationError` 401,
97
+ `PermissionDeniedError` 403, `NotFoundError` 404, `ConflictError` 409,
98
+ `UnprocessableError` 422, `RateLimitError` 429, `ServiceUnavailableError` 503. Without an
99
+ answer: `APIConnectionError` and `APITimeoutError`; a non-JSON answer:
100
+ `InvalidResponseError`. All extend `CoinsPayError`. Codes are constants of
101
+ `coinspay.ErrorCode`; `coinspay.ERROR_CODES` lists them by status.
102
+
103
+ ## Retries and idempotency
104
+
105
+ Failures that are safe to retry are retried twice with a short pause: network errors and
106
+ timeouts, 429, 502, 503 and 504 for reads, quotes, cancellations and creates with an
107
+ idempotency key. `addresses.create` has no idempotency key and is retried only when the
108
+ request surely did not reach the API.
109
+
110
+ `invoices.create` without `idempotency_key` gets a random key for its own retries; pass
111
+ your order number to make your own retries safe too. `payouts.create` requires a key.
112
+ A repeat with a used key returns the existing object in its current state, and
113
+ `client.last_response.replayed` is `True`. `last_response` belongs to the calling thread,
114
+ so one client can serve the threads of a web server; it is `None` after a failed request.
115
+
116
+ ## Client behaviour
117
+
118
+ - The API address must use `https`. Plain `http` is accepted for `localhost` and `127.0.0.1`, or with `allow_insecure_http=True` for a test stand.
119
+ - Each attempt times out after 30 seconds, and connecting after 10.
120
+ - Redirects are not followed, so the key is never sent to another host.
121
+ - IDs in the path are checked as UUIDs before the request is sent.
122
+ - The key never shows up in exceptions or `repr`.
123
+ - The transport can be replaced, for example to go through a proxy (see Options). The timeout applies to each socket operation.
124
+
125
+ ## Options
126
+
127
+ ```python
128
+ client = coinspay.Client(
129
+ key, "https://coinspay.app",
130
+ timeout=30.0, # seconds for each socket operation of an attempt
131
+ max_retries=2,
132
+ max_retry_delay=5.0, # a longer Retry-After fails at once
133
+ transport=coinspay.UrllibTransport(proxies={"https": "http://proxy:3128"}),
134
+ )
135
+ client.invoices.get(invoice_id, timeout=5, max_retries=0)
136
+ client.request("GET", "/something/new") # a method this version does not know yet
137
+ ```
138
+
139
+ Proxies from `HTTPS_PROXY` work by default.
140
+
141
+ ## Development
142
+
143
+ ```bash
144
+ PYTHONPATH=src:tests python -m unittest discover -s tests
145
+ pip install mypy && MYPYPATH=src mypy --strict src tests/typing_check.py
146
+ ```
147
+
148
+ The library supports Python 3.8 and newer, and mypy checks it against 3.8. Tests that need a real HTTP server are skipped unless `COINSPAY_MOCK_URL` points to one. `node scripts/mock-server.mjs` from [coinspay-node](https://github.com/coinspayapp/coinspay-node) starts it and prints its address.
149
+
150
+ Files that start with "Generated from the CoinsPay OpenAPI document" and the data in `tests/data` come from our generator and are shared by the libraries for Node.js, PHP, Python and Go. Please do not edit them by hand: if a model or a test vector looks wrong, open an issue.
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "coinspay"
7
+ version = "0.1.0"
8
+ description = "Official Python library for the CoinsPay crypto payments API"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.8"
12
+ dependencies = []
13
+ keywords = ["coinspay", "crypto", "payments", "invoices", "webhooks", "usdt", "bitcoin"]
14
+ classifiers = [
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Typing :: Typed",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://coinspay.app"
23
+ Documentation = "https://coinspay.app/docs"
24
+ Source = "https://github.com/coinspayapp/coinspay-python"
25
+ Issues = "https://github.com/coinspayapp/coinspay-python/issues"
26
+ Changelog = "https://github.com/coinspayapp/coinspay-python/blob/main/CHANGELOG.md"
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
30
+
31
+ [tool.setuptools.package-data]
32
+ coinspay = ["py.typed"]
33
+
34
+ [tool.mypy]
35
+ python_version = "3.8"
36
+ strict = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,63 @@
1
+ """Official Python library for the CoinsPay crypto payments API."""
2
+
3
+ from . import models, webhooks
4
+ from ._client import Client
5
+ from ._errors import (
6
+ APIConnectionError,
7
+ APIError,
8
+ APITimeoutError,
9
+ AuthenticationError,
10
+ CoinsPayError,
11
+ ConflictError,
12
+ InvalidRequestError,
13
+ InvalidResponseError,
14
+ NotFoundError,
15
+ PermissionDeniedError,
16
+ RateLimitError,
17
+ ServiceUnavailableError,
18
+ SignatureVerificationError,
19
+ UnprocessableError,
20
+ )
21
+ from ._http import HTTPResponse, ResponseInfo, Transport, TransportError, UrllibTransport, new_idempotency_key
22
+ from ._model import NOT_GIVEN, Model, NotGiven
23
+ from ._operations import ERROR_CODES, ErrorCode
24
+ from ._resource import Page
25
+ from ._version import __version__
26
+ from .webhooks import UnknownEvent, construct_event, signature_header, verify_signature
27
+
28
+ __all__ = [
29
+ "APIConnectionError",
30
+ "APIError",
31
+ "APITimeoutError",
32
+ "AuthenticationError",
33
+ "Client",
34
+ "CoinsPayError",
35
+ "ConflictError",
36
+ "ERROR_CODES",
37
+ "ErrorCode",
38
+ "HTTPResponse",
39
+ "InvalidRequestError",
40
+ "InvalidResponseError",
41
+ "Model",
42
+ "NOT_GIVEN",
43
+ "NotFoundError",
44
+ "NotGiven",
45
+ "Page",
46
+ "PermissionDeniedError",
47
+ "RateLimitError",
48
+ "ResponseInfo",
49
+ "ServiceUnavailableError",
50
+ "SignatureVerificationError",
51
+ "Transport",
52
+ "TransportError",
53
+ "UnknownEvent",
54
+ "UnprocessableError",
55
+ "UrllibTransport",
56
+ "__version__",
57
+ "construct_event",
58
+ "models",
59
+ "new_idempotency_key",
60
+ "signature_header",
61
+ "verify_signature",
62
+ "webhooks",
63
+ ]