diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..b19bcc8 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,26 @@ +name: Lint Check +on: + workflow_call: + +jobs: + lint: + name: Run lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Sync dev deps + run: uv sync --group dev + + - name: Lint check + run: | + uv run ruff format --check + uv run ruff check . \ No newline at end of file diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..305f8bd --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,18 @@ +name: Main +on: + pull_request: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + id-token: write + +jobs: + lint: + uses: ./.github/workflows/lint.yaml + typecheck: + needs: lint + uses: ./.github/workflows/typecheck.yaml \ No newline at end of file diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..27de564 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,55 @@ +name: Publish to PyPI + +on: + push: + branches: + - main + secrets: + PYPI_API_TOKEN: + required: true + workflow_dispatch: + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: write + pull-requests: write + id-token: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + release-type: python + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Build package + run: uv build + + - name: Publish to PyPI + env: + UV_PUBLISH_USERNAME: __token__ + UV_PUBLISH_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + uv publish \ No newline at end of file diff --git a/.github/workflows/typecheck.yaml b/.github/workflows/typecheck.yaml new file mode 100644 index 0000000..252c8cf --- /dev/null +++ b/.github/workflows/typecheck.yaml @@ -0,0 +1,28 @@ +name: Type Check +on: + workflow_call: + +jobs: + typecheck: + name: Run type checking + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Sync dev deps + run: uv sync --group dev + + - name: Type check + run: uv run pyright + + - name: Type check + run: uv run mypy . + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcd6191 --- /dev/null +++ b/.gitignore @@ -0,0 +1,145 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# poetry +poetry.lock + +# pdm +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + + +# uv +.uv/ \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..c04b615 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..60df2b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Blind Pay, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..55e3f5a --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: lint-check lint-fix typecheck + +lint-check: + uv run ruff format --check + uv run ruff check . + +lint-fix: + uv run ruff check --fix . + uv run ruff format + +typecheck: + uv run pyright + uv run mypy . diff --git a/README.md b/README.md index f1d09b2..2e5bde1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,71 @@ -# Blindpay Python SDK +# BlindPay Python SDK The official Python SDK for Blindpay - Global payments infrastructure made simple. + +## Installation + +```bash +pip install blindpay +``` + +## Requirements + +- Python 3.12 or higher + +## Error Handling + +All API methods return a response dictionary with either `data` or `error`: + +```python + blindpay = BlindPay( + api_key="your_api_key_here", + instance_id="your_instance_id_here" + ) + + response = await blindpay.receivers.get("receiver-id") + + if response['error']: + print(f"Error: {response['error']['message']}") + return + + receiver = response['data'] + print(f"Receiver: {receiver}") +``` + +## Types + +The SDK includes comprehensive type definitions for all API resources and parameters. These can be imported from the main package: + +```python +from blindpay import ( + AccountClass, + BankAccountType, + Country, + Currency, + CurrencyType, + Network, + Rail, + StablecoinToken, + TransactionDocumentType, + TransactionStatus, + PaginationParams, + PaginationMetadata, + # ... and more +) +``` + +## Development + +This SDK uses: +- `uv` for package management +- `httpx` for async HTTP requests +- `pydantic` for data validation +- `typing_extensions` for typing + +## License + +MIT + +## Support + +For support, please contact gabriel@blindpay.com or visit [blindpay](https://blindpay.com) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..5ffdecb --- /dev/null +++ b/examples/README.md @@ -0,0 +1,134 @@ +# BlindPay Python SDK - Examples + +This directory contains example code demonstrating how to use the BlindPay Python SDK. + +## Prerequisites + +Before running these examples, you need: + +1. **API Credentials**: Get your API key and instance ID from the [BlindPay dashboard](https://app.blindpay.com) +2. **Python 3.12+**: Ensure you have Python 3.12 or higher installed +3. **Install the SDK**: + ```bash + pip install blindpay + ``` + +## Examples + +### 1. Get Available Rails (`examples/get_available_rails.py`) + +Demonstrates how to: +- Get list of available payment rails +- Get bank details required for specific rails +- Use both async and sync clients +- Simple error handling (always remember to handle errors properly) + +**Copy the code and run it:** +```bash +python3 your_file_name.py +``` + +## Quick Start + +### Async Usage (Recommended) + +```python +import asyncio +from blindpay import BlindPay + +async def main(): + # Initialize the client + blindpay = BlindPay( + api_key="your_api_key_here", + instance_id="your_instance_id_here", + ) + + # Get available rails + response = await blindpay.available.get_rails() + + if response["error"]: + print(f"Error: {response['error']['message']}") + return + + rails = response["data"] + for rail in rails: + print(f"{rail['label']}: {rail['value']}") + +asyncio.run(main()) +``` + +### Sync Usage + +```python +from blindpay import BlindPaySync + +# Initialize the sync client +blindpay = BlindPaySync( + api_key="your_api_key_here", + instance_id="your_instance_id_here", +) + +# Get available rails +response = blindpay.available.get_rails() + +if response["error"]: + print(f"Error: {response['error']['message']}") +else: + rails = response["data"] + for rail in rails: + print(f"{rail['label']}: {rail['value']}") +``` + +## Response Format + +All API methods return a response in this format: + +```python +# Success response +{ + "data": {...}, # Your data here + "error": None +} + +# Error response +{ + "data": None, + "error": { + "message": "Error description" + } +} +``` + +Always check for errors before accessing data: + +```python +response = await blindpay.available.get_rails() + +if response["error"]: + # Handle error + print(f"Error: {response['error']['message']}") +else: + # Use data + data = response["data"] +``` + +## Environment Variables + +For production use, store credentials as environment variables: + +```python +import os +from blindpay import BlindPay + +blindpay = BlindPay( + api_key=os.environ.get("BLINDPAY_API_KEY"), + instance_id=os.environ.get("BLINDPAY_INSTANCE_ID"), +) +``` + +## Need Help? + +- **Documentation**: [https://api.blindpay.com/reference](https://api.blindpay.com/reference) +- **Dashboard**: [https://app.blindpay.com](https://app.blindpay.com) +- **Support**: [gabriel@blindpay.com](mailto:gabriel@blindpay.com) + diff --git a/examples/get_available_rails.py b/examples/get_available_rails.py new file mode 100644 index 0000000..2217618 --- /dev/null +++ b/examples/get_available_rails.py @@ -0,0 +1,106 @@ +""" +Example: Get Available Rails + +This example demonstrates how to use the BlindPay SDK to fetch available +payment rails and their bank details +""" + +import asyncio + +from blindpay import BlindPay, BlindPaySync + + +async def async_example() -> None: + print("=== Async Example ===\n") + + blindpay = BlindPay( + api_key="your_api_key_here", + instance_id="your_instance_id_here", + ) + + # Get available rails + rails_response = await blindpay.available.get_rails() + + if rails_response["error"]: + print(f"Error: {rails_response['error']['message']}") + return + + rails = rails_response["data"] + if rails is None: + print("No rails data returned") + return + + print(f"Found {len(rails)} available rails:\n") + + for rail in rails: + print(f" • {rail['label']} ({rail['value']}) - {rail['country']}") + + # Get bank details for a specific rail (e.g., PIX) + print("\n--- Getting bank details for PIX ---\n") + bank_details_response = await blindpay.available.get_bank_details("pix") + + if bank_details_response["error"]: + print(f"Error: {bank_details_response['error']['message']}") + return + + bank_details = bank_details_response["data"] + if bank_details is None: + print("No bank details returned") + return + + print(f"PIX requires {len(bank_details)} fields:\n") + + for detail in bank_details: + required = "Required" if detail["required"] else "Optional" + print(f" • {detail['label']} ({detail['key']}) - {required}") + + +def sync_example() -> None: + print("\n\n=== Sync Example ===\n") + + blindpay = BlindPaySync( + api_key="your_api_key_here", + instance_id="your_instance_id_here", + ) + + # Get available rails + rails_response = blindpay.available.get_rails() + + if rails_response["error"]: + print(f"Error: {rails_response['error']['message']}") + return + + rails = rails_response["data"] + if rails is None: + print("No rails data returned") + return + + print(f"Found {len(rails)} available rails:\n") + + for rail in rails: + print(f" • {rail['label']} ({rail['value']}) - {rail['country']}") + + # Get bank details for ACH + print("\n--- Getting bank details for ACH ---\n") + bank_details_response = blindpay.available.get_bank_details("ach") + + if bank_details_response["error"]: + print(f"Error: {bank_details_response['error']['message']}") + return + + bank_details = bank_details_response["data"] + if bank_details is None: + print("No bank details returned") + return + + print(f"ACH requires {len(bank_details)} fields:\n") + + for detail in bank_details: + required = "Required" if detail["required"] else "Optional" + print(f" • {detail['label']} ({detail['key']}) - {required}") + + +if __name__ == "__main__": + asyncio.run(async_example()) + + sync_example() diff --git a/py.typed b/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3a9474c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,101 @@ +[project] +name = "blindpay" +version = "1.0.0" +description = "Official Python SDK for the Blindpay API — Global payments infrastructure" +readme = "README.md" +authors = [{ name = "Blindpay", email = "gabriel@blindpay.com" }] +license = { text = "MIT" } +keywords = ["blindpay", "payments", "api", "sdk", "global-payments", "payment-processing"] +requires-python = ">=3.12" +dependencies = [ + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.11, <5", +] +classifiers = [ + "Typing :: Typed", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "License :: OSI Approved :: MIT License", +] + +[project.urls] +Homepage = "https://github.com/blindpaylabs/blindpay-python" +Repository = "https://github.com/blindpaylabs/blindpay-python" +Documentation = "https://api.blindpay.com/reference" +"Bug Tracker" = "https://github.com/blindpaylabs/blindpay-python/issues" + +[build-system] +requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "src/blindpay/__init__.py" + +[tool.hatch.build] +include = ["src/*", "README.md", "LICENSE", "py.typed"] + +[tool.hatch.build.targets.wheel] +packages = ["src/blindpay"] + +[tool.hatch.build.targets.sdist] +include = ["/src", "/README.md", "/LICENSE", "/pyproject.toml", "/py.typed"] + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "README.md" + +[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] +pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' +replacement = '[\1](https://github.com/blindpaylabs/blindpay-python/tree/main/\g<2>)' + +[dependency-groups] +dev = [ + "ruff", + "pyright==1.1.399", + "mypy", +] + +[tool.ruff] +line-length = 120 +target-version = "py312" +output-format = "grouped" + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +select = ["I","B","E","F","ARG","T201","T203"] +ignore = ["B006"] +unfixable = ["T201","T203"] + +[tool.ruff.lint.isort] +combine-as-imports = true +known-first-party = ["blindpay"] + +[tool.ruff.lint.per-file-ignores] +"examples/*.py" = ["T201"] + +[tool.mypy] +pretty = true +show_error_codes = true +strict_equality = true +implicit_reexport = true +check_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +exclude = [ + "^examples/", +] diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..edd1b3f --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,17 @@ +{ + "include": ["src"], + "exclude": [".venv", ".git", "examples/**", "**/__pycache__"], + "typeCheckingMode": "strict", + "pythonVersion": "3.12", + "executionEnvironments": [ + { + "root": "src/blindpay/resources", + "reportArgumentType": "error", + "reportTypedDictNotRequiredAccess": "error", + "reportMissingTypeArgument": "error", + }, + { + "root": "src/blindpay", + } + ] +} \ No newline at end of file diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..f357f6a --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,17 @@ +{ + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "release-type": "python", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false, + "include-v-in-tag": true, + "package-name": "blindpay", + "extra-files": [ + "src/blindpay/__init__.py" + ] + } + } +} diff --git a/src/blindpay/__init__.py b/src/blindpay/__init__.py new file mode 100644 index 0000000..27e73bb --- /dev/null +++ b/src/blindpay/__init__.py @@ -0,0 +1,54 @@ +__version__ = "1.0.0" + +from ._internal.exceptions import BlindPayError +from .client import BlindPay, BlindPaySync +from .types import ( + AccountClass, + BankAccountType, + BlindpayApiResponse, + BlindpayErrorResponse, + BlindpaySuccessResponse, + Country, + Currency, + CurrencyType, + ErrorResponse, + Network, + PaginationMetadata, + PaginationParams, + Rail, + StablecoinToken, + TrackingComplete, + TrackingLiquidity, + TrackingPartnerFee, + TrackingPayment, + TrackingTransaction, + TransactionDocumentType, + TransactionStatus, +) + +__all__ = [ + "BlindPay", + "BlindPaySync", + "BlindPayError", + "AccountClass", + "BankAccountType", + "Country", + "Currency", + "CurrencyType", + "Network", + "Rail", + "StablecoinToken", + "TransactionDocumentType", + "TransactionStatus", + "BlindpayApiResponse", + "BlindpayErrorResponse", + "BlindpaySuccessResponse", + "ErrorResponse", + "PaginationMetadata", + "PaginationParams", + "TrackingComplete", + "TrackingLiquidity", + "TrackingPartnerFee", + "TrackingPayment", + "TrackingTransaction", +] diff --git a/src/blindpay/_internal/__init__.py b/src/blindpay/_internal/__init__.py new file mode 100644 index 0000000..10ba7bb --- /dev/null +++ b/src/blindpay/_internal/__init__.py @@ -0,0 +1,4 @@ +from .api_client import InternalApiClient +from .exceptions import BlindPayError + +__all__ = ["InternalApiClient", "BlindPayError"] diff --git a/src/blindpay/_internal/api_client.py b/src/blindpay/_internal/api_client.py new file mode 100644 index 0000000..12b3dfd --- /dev/null +++ b/src/blindpay/_internal/api_client.py @@ -0,0 +1,54 @@ +from typing import Any, Mapping, Optional, Protocol + +from ..types import BlindpayApiResponse + + +class InternalApiClient(Protocol): + """ + Internal API client interface that resources use to make HTTP requests. + This interface is not exposed to SDK users. + """ + + async def get(self, path: str) -> BlindpayApiResponse[Any]: + """Make a GET request""" + ... + + async def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]: + """Make a POST request""" + ... + + async def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]: + """Make a PUT request""" + ... + + async def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]: + """Make a PATCH request""" + ... + + async def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[Any]: + """Make a DELETE request""" + ... + + +class InternalApiClientSync(Protocol): + """Synchronous version of InternalApiClient""" + + def get(self, path: str) -> BlindpayApiResponse[Any]: + """Make a GET request""" + ... + + def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]: + """Make a POST request""" + ... + + def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]: + """Make a PUT request""" + ... + + def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]: + """Make a PATCH request""" + ... + + def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[Any]: + """Make a DELETE request""" + ... diff --git a/src/blindpay/_internal/exceptions.py b/src/blindpay/_internal/exceptions.py new file mode 100644 index 0000000..476af79 --- /dev/null +++ b/src/blindpay/_internal/exceptions.py @@ -0,0 +1,6 @@ +class BlindPayError(Exception): + """BlindPay SDK error""" + + def __init__(self, message: str): + super().__init__(message) + self.name = "BlindpayError" diff --git a/src/blindpay/client.py b/src/blindpay/client.py new file mode 100644 index 0000000..82192e8 --- /dev/null +++ b/src/blindpay/client.py @@ -0,0 +1,536 @@ +import base64 +import hashlib +import hmac +from functools import cached_property +from typing import TYPE_CHECKING, Any, Dict, Literal, Mapping, Optional, TypeVar + +import httpx + +from ._internal.exceptions import BlindPayError +from .types import BlindpayApiResponse + +if TYPE_CHECKING: + from blindpay.resources.api_keys.api_keys import ApiKeysResource, ApiKeysResourceSync + from blindpay.resources.available.available import AvailableResource, AvailableResourceSync + from blindpay.resources.bank_accounts.bank_accounts import BankAccountsResource, BankAccountsResourceSync + from blindpay.resources.instances.instances import InstancesResource, InstancesResourceSync + from blindpay.resources.partner_fees.partner_fees import PartnerFeesResource, PartnerFeesResourceSync + from blindpay.resources.payins.payins import PayinsResource, PayinsResourceSync + from blindpay.resources.payins.quotes import PayinQuotesResource, PayinQuotesResourceSync + from blindpay.resources.payouts.payouts import PayoutsResource, PayoutsResourceSync + from blindpay.resources.quotes.quotes import QuotesResource, QuotesResourceSync + from blindpay.resources.receivers.receivers import ReceiversResource, ReceiversResourceSync + from blindpay.resources.virtual_accounts.virtual_accounts import ( + VirtualAccountsResource, + VirtualAccountsResourceSync, + ) + from blindpay.resources.wallets.blockchain import BlockchainWalletsResource, BlockchainWalletsResourceSync + from blindpay.resources.wallets.offramp import OfframpWalletsResource, OfframpWalletsResourceSync + from blindpay.resources.webhooks.webhooks import WebhookEndpointsResource, WebhookEndpointsResourceSync + +__version__ = "1.0.0" + +T = TypeVar("T") + + +class ApiClientImpl: + def __init__(self, base_url: str, headers: Dict[str, str]): + self.base_url = base_url + self.headers = headers + + async def get(self, path: str) -> BlindpayApiResponse[T]: + return await self._request("GET", path) + + async def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]: + return await self._request("POST", path, body) + + async def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]: + return await self._request("PUT", path, body) + + async def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]: + return await self._request("PATCH", path, body) + + async def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[T]: + return await self._request("DELETE", path, body) + + async def _request( + self, + method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"], + path: str, + body: Optional[Mapping[str, Any]] = None, + ) -> BlindpayApiResponse[T]: + url = f"{self.base_url}{path}" + + try: + async with httpx.AsyncClient() as client: + if body is not None: + response = await client.request(method=method, url=url, headers=self.headers, json=body) + else: + response = await client.request(method=method, url=url, headers=self.headers) + + if response.status_code >= 400: + try: + error_data = response.json() + error_message = error_data.get("message", "Unknown error") + except Exception: + error_message = "Unknown error" + + return {"data": None, "error": {"message": error_message}} + + data = response.json() + return {"data": data, "error": None} + + except Exception as e: + error_message = str(e) if str(e) else "Unknown error" + return {"data": None, "error": {"message": error_message}} + + +class ApiClientImplSync: + def __init__(self, base_url: str, headers: Dict[str, str]): + self.base_url = base_url + self.headers = headers + self.client = httpx.Client(base_url=base_url, headers=headers) + + def get(self, path: str) -> BlindpayApiResponse[T]: + return self._request("GET", path) + + def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]: + return self._request("POST", path, body) + + def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]: + return self._request("PUT", path, body) + + def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]: + return self._request("PATCH", path, body) + + def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[T]: + return self._request("DELETE", path, body) + + def _request( + self, + method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"], + path: str, + body: Optional[Mapping[str, Any]] = None, + ) -> BlindpayApiResponse[T]: + try: + if body is not None: + response = self.client.request(method=method, url=path, json=body) + else: + response = self.client.request(method=method, url=path) + + if response.status_code >= 400: + try: + error_data = response.json() + error_message = error_data.get("message", "Unknown error") + except Exception: + error_message = "Unknown error" + + return {"data": None, "error": {"message": error_message}} + + data = response.json() + return {"data": data, "error": None} + + except Exception as e: + error_message = str(e) if str(e) else "Unknown error" + return {"data": None, "error": {"message": error_message}} + + def close(self) -> None: + self.client.close() + + +class _InstancesNamespace: + def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def _base(self) -> "InstancesResource": + from blindpay.resources.instances.instances import create_instances_resource + + return create_instances_resource(self._instance_id, self._api) + + @cached_property + def api_keys(self) -> "ApiKeysResource": + from blindpay.resources.api_keys.api_keys import create_api_keys_resource + + return create_api_keys_resource(self._instance_id, self._api) + + @cached_property + def webhook_endpoints(self) -> "WebhookEndpointsResource": + from blindpay.resources.webhooks.webhooks import create_webhook_endpoints_resource + + return create_webhook_endpoints_resource(self._instance_id, self._api) + + def __getattr__(self, name: str) -> Any: + return getattr(self._base, name) + + +class _PayinsNamespace: + def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def _base(self) -> "PayinsResource": + from blindpay.resources.payins.payins import create_payins_resource + + return create_payins_resource(self._instance_id, self._api) + + @cached_property + def quotes(self) -> "PayinQuotesResource": + from blindpay.resources.payins.quotes import create_payin_quotes_resource + + return create_payin_quotes_resource(self._instance_id, self._api) + + def __getattr__(self, name: str) -> Any: + return getattr(self._base, name) + + +class _ReceiversNamespace: + def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def _base(self) -> "ReceiversResource": + from blindpay.resources.receivers.receivers import create_receivers_resource + + return create_receivers_resource(self._instance_id, self._api) + + @cached_property + def bank_accounts(self) -> "BankAccountsResource": + from blindpay.resources.bank_accounts.bank_accounts import create_bank_accounts_resource + + return create_bank_accounts_resource(self._instance_id, self._api) + + def __getattr__(self, name: str) -> Any: + return getattr(self._base, name) + + +class _WalletsNamespace: + def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def blockchain(self) -> "BlockchainWalletsResource": + from blindpay.resources.wallets.blockchain import create_blockchain_wallets_resource + + return create_blockchain_wallets_resource(self._instance_id, self._api) + + @cached_property + def offramp(self) -> "OfframpWalletsResource": + from blindpay.resources.wallets.offramp import create_offramp_wallets_resource + + return create_offramp_wallets_resource(self._instance_id, self._api) + + +class BlindPay: + _api_key: str + _instance_id: str + _base_url: str + + def __init__(self, *, api_key: str, instance_id: str): + if not api_key: + raise BlindPayError("Api key not provided, get your api key on blindpay dashboard") + + if not instance_id: + raise BlindPayError("Instance id not provided, get your instance id on blindpay dashboard") + + self._api_key = api_key + self._instance_id = instance_id + self._base_url = "https://api.blindpay.com/v1" + + self._headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "User-Agent": f"blindpay-python/{__version__}", + "Authorization": f"Bearer {self._api_key}", + } + + self._api = ApiClientImpl(self._base_url, self._headers) + + @cached_property + def available(self) -> "AvailableResource": + from blindpay.resources.available import create_available_resource + + return create_available_resource(self._api) + + @cached_property + def instances(self) -> _InstancesNamespace: + return _InstancesNamespace(self._instance_id, self._api) + + @cached_property + def partner_fees(self) -> "PartnerFeesResource": + from blindpay.resources.partner_fees import create_partner_fees_resource + + return create_partner_fees_resource(self._instance_id, self._api) + + @cached_property + def payins(self) -> _PayinsNamespace: + return _PayinsNamespace(self._instance_id, self._api) + + @cached_property + def quotes(self) -> "QuotesResource": + from blindpay.resources.quotes import create_quotes_resource + + return create_quotes_resource(self._instance_id, self._api) + + @cached_property + def payouts(self) -> "PayoutsResource": + from blindpay.resources.payouts import create_payouts_resource + + return create_payouts_resource(self._instance_id, self._api) + + @cached_property + def receivers(self) -> _ReceiversNamespace: + return _ReceiversNamespace(self._instance_id, self._api) + + @cached_property + def virtual_accounts(self) -> "VirtualAccountsResource": + from blindpay.resources.virtual_accounts import create_virtual_accounts_resource + + return create_virtual_accounts_resource(self._instance_id, self._api) + + @cached_property + def wallets(self) -> _WalletsNamespace: + return _WalletsNamespace(self._instance_id, self._api) + + def verify_webhook_signature( + self, *, secret: str, id: str, timestamp: str, payload: str, svix_signature: str + ) -> bool: + """ + Verifies the BlindPay webhook signature + + Args: + secret: The webhook secret from BlindPay dashboard + id: The value of the `svix-id` header + timestamp: The value of the `svix-timestamp` header + payload: The raw request body + svix_signature: The value of the `svix-signature` header + + Returns: + True if the signature is valid, False otherwise + """ + signed_content = f"{id}.{timestamp}.{payload}" + + secret_parts = secret.split("_") + if len(secret_parts) < 2: + return False + + try: + secret_bytes = base64.b64decode(secret_parts[1]) + except Exception: + return False + + expected_signature = base64.b64encode( + hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest() + ).decode() + + return len(svix_signature) == len(expected_signature) and hmac.compare_digest( + svix_signature, expected_signature + ) + + +class _InstancesNamespaceSync: + def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def _base(self) -> "InstancesResourceSync": + from blindpay.resources.instances.instances import create_instances_resource_sync + + return create_instances_resource_sync(self._instance_id, self._api) + + @cached_property + def api_keys(self) -> "ApiKeysResourceSync": + from blindpay.resources.api_keys.api_keys import create_api_keys_resource_sync + + return create_api_keys_resource_sync(self._instance_id, self._api) + + @cached_property + def webhook_endpoints(self) -> "WebhookEndpointsResourceSync": + from blindpay.resources.webhooks.webhooks import create_webhook_endpoints_resource_sync + + return create_webhook_endpoints_resource_sync(self._instance_id, self._api) + + def __getattr__(self, name: str) -> Any: + return getattr(self._base, name) + + +class _PayinsNamespaceSync: + def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def _base(self) -> "PayinsResourceSync": + from blindpay.resources.payins.payins import create_payins_resource_sync + + return create_payins_resource_sync(self._instance_id, self._api) + + @cached_property + def quotes(self) -> "PayinQuotesResourceSync": + from blindpay.resources.payins.quotes import create_payin_quotes_resource_sync + + return create_payin_quotes_resource_sync(self._instance_id, self._api) + + def __getattr__(self, name: str) -> Any: + return getattr(self._base, name) + + +class _ReceiversNamespaceSync: + def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def _base(self) -> "ReceiversResourceSync": + from blindpay.resources.receivers.receivers import create_receivers_resource_sync + + return create_receivers_resource_sync(self._instance_id, self._api) + + @cached_property + def bank_accounts(self) -> "BankAccountsResourceSync": + from blindpay.resources.bank_accounts.bank_accounts import create_bank_accounts_resource_sync + + return create_bank_accounts_resource_sync(self._instance_id, self._api) + + def __getattr__(self, name: str) -> Any: + return getattr(self._base, name) + + +class _WalletsNamespaceSync: + def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None: + self._instance_id = instance_id + self._api = api_client + + @cached_property + def blockchain(self) -> "BlockchainWalletsResourceSync": + from blindpay.resources.wallets.blockchain import create_blockchain_wallets_resource_sync + + return create_blockchain_wallets_resource_sync(self._instance_id, self._api) + + @cached_property + def offramp(self) -> "OfframpWalletsResourceSync": + from blindpay.resources.wallets.offramp import create_offramp_wallets_resource_sync + + return create_offramp_wallets_resource_sync(self._instance_id, self._api) + + +class BlindPaySync: + _api_key: str + _instance_id: str + _base_url: str + + def __init__(self, *, api_key: str, instance_id: str): + if not api_key: + raise BlindPayError("Api key not provided, get your api key on blindpay dashboard") + + if not instance_id: + raise BlindPayError("Instance id not provided, get your instance id on blindpay dashboard") + + self._api_key = api_key + self._instance_id = instance_id + self._base_url = "https://api.blindpay.com/v1" + + self._headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "User-Agent": f"blindpay-python/{__version__}", + "Authorization": f"Bearer {self._api_key}", + } + + self._api = ApiClientImplSync(self._base_url, self._headers) + + @cached_property + def available(self) -> "AvailableResourceSync": + from blindpay.resources.available import create_available_resource_sync + + return create_available_resource_sync(self._api) + + @cached_property + def instances(self) -> _InstancesNamespaceSync: + return _InstancesNamespaceSync(self._instance_id, self._api) + + @cached_property + def partner_fees(self) -> "PartnerFeesResourceSync": + from blindpay.resources.partner_fees import create_partner_fees_resource_sync + + return create_partner_fees_resource_sync(self._instance_id, self._api) + + @cached_property + def payins(self) -> _PayinsNamespaceSync: + return _PayinsNamespaceSync(self._instance_id, self._api) + + @cached_property + def quotes(self) -> "QuotesResourceSync": + from blindpay.resources.quotes import create_quotes_resource_sync + + return create_quotes_resource_sync(self._instance_id, self._api) + + @cached_property + def payouts(self) -> "PayoutsResourceSync": + from blindpay.resources.payouts import create_payouts_resource_sync + + return create_payouts_resource_sync(self._instance_id, self._api) + + @cached_property + def receivers(self) -> _ReceiversNamespaceSync: + return _ReceiversNamespaceSync(self._instance_id, self._api) + + @cached_property + def virtual_accounts(self) -> "VirtualAccountsResourceSync": + from blindpay.resources.virtual_accounts import create_virtual_accounts_resource_sync + + return create_virtual_accounts_resource_sync(self._instance_id, self._api) + + @cached_property + def wallets(self) -> _WalletsNamespaceSync: + return _WalletsNamespaceSync(self._instance_id, self._api) + + def verify_webhook_signature( + self, *, secret: str, id: str, timestamp: str, payload: str, svix_signature: str + ) -> bool: + """ + Verifies the BlindPay webhook signature + + Args: + secret: The webhook secret from BlindPay dashboard + id: The value of the `svix-id` header + timestamp: The value of the `svix-timestamp` header + payload: The raw request body + svix_signature: The value of the `svix-signature` header + + Returns: + True if the signature is valid, False otherwise + """ + signed_content = f"{id}.{timestamp}.{payload}" + + secret_parts = secret.split("_") + if len(secret_parts) < 2: + return False + + try: + secret_bytes = base64.b64decode(secret_parts[1]) + except Exception: + return False + + expected_signature = base64.b64encode( + hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest() + ).decode() + + return len(svix_signature) == len(expected_signature) and hmac.compare_digest( + svix_signature, expected_signature + ) + + def close(self) -> None: + if hasattr(self, "_api"): + self._api.close() + + def __enter__(self) -> "BlindPaySync": + return self + + def __exit__(self, exc_type: Optional[type], exc_val: Optional[BaseException], exc_tb: Optional[Any]) -> None: + self.close() diff --git a/src/blindpay/resources/__init__.py b/src/blindpay/resources/__init__.py new file mode 100644 index 0000000..4c70aba --- /dev/null +++ b/src/blindpay/resources/__init__.py @@ -0,0 +1,29 @@ +from .api_keys import create_api_keys_resource +from .available import create_available_resource +from .bank_accounts import create_bank_accounts_resource +from .instances import create_instances_resource +from .partner_fees import create_partner_fees_resource +from .payins import create_payin_quotes_resource, create_payins_resource +from .payouts import create_payouts_resource +from .quotes import create_quotes_resource +from .receivers import create_receivers_resource +from .virtual_accounts import create_virtual_accounts_resource +from .wallets import create_blockchain_wallets_resource, create_offramp_wallets_resource +from .webhooks import create_webhook_endpoints_resource + +__all__ = [ + "create_api_keys_resource", + "create_available_resource", + "create_bank_accounts_resource", + "create_instances_resource", + "create_partner_fees_resource", + "create_payins_resource", + "create_payin_quotes_resource", + "create_payouts_resource", + "create_quotes_resource", + "create_receivers_resource", + "create_virtual_accounts_resource", + "create_blockchain_wallets_resource", + "create_offramp_wallets_resource", + "create_webhook_endpoints_resource", +] diff --git a/src/blindpay/resources/api_keys/__init__.py b/src/blindpay/resources/api_keys/__init__.py new file mode 100644 index 0000000..f265df2 --- /dev/null +++ b/src/blindpay/resources/api_keys/__init__.py @@ -0,0 +1,21 @@ +from .api_keys import ( + ApiKeysResource, + ApiKeysResourceSync, + CreateApiKeyInput, + CreateApiKeyResponse, + GetApiKeyResponse, + ListApiKeysResponse, + create_api_keys_resource, + create_api_keys_resource_sync, +) + +__all__ = [ + "create_api_keys_resource", + "create_api_keys_resource_sync", + "ApiKeysResource", + "ApiKeysResourceSync", + "CreateApiKeyInput", + "CreateApiKeyResponse", + "GetApiKeyResponse", + "ListApiKeysResponse", +] diff --git a/src/blindpay/resources/api_keys/api_keys.py b/src/blindpay/resources/api_keys/api_keys.py new file mode 100644 index 0000000..cfb8eed --- /dev/null +++ b/src/blindpay/resources/api_keys/api_keys.py @@ -0,0 +1,82 @@ +from typing import List, Optional + +from typing_extensions import Literal, TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse + +Permission = Literal["full_access"] + + +class ApiKey(TypedDict): + id: str + name: str + permission: Permission + token: str + ip_whitelist: Optional[List[str]] + unkey_id: str + last_used_at: Optional[str] + instance_id: str + created_at: str + updated_at: str + + +ListApiKeysResponse = List[ApiKey] + + +class CreateApiKeyInput(TypedDict): + name: str + permission: Literal["full_access"] + ip_whitelist: Optional[List[str]] + + +class CreateApiKeyResponse(TypedDict): + id: str + token: str + + +GetApiKeyResponse = ApiKey + + +class ApiKeysResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self) -> BlindpayApiResponse[ListApiKeysResponse]: + return await self._client.get(f"/instances/{self._instance_id}/api-keys") + + async def create(self, data: CreateApiKeyInput) -> BlindpayApiResponse[CreateApiKeyResponse]: + return await self._client.post(f"/instances/{self._instance_id}/api-keys", data) + + async def get(self, id: str) -> BlindpayApiResponse[GetApiKeyResponse]: + return await self._client.get(f"/instances/{self._instance_id}/api-keys/{id}") + + async def delete(self, id: str) -> BlindpayApiResponse[None]: + return await self._client.delete(f"/instances/{self._instance_id}/api-keys/{id}") + + +class ApiKeysResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self) -> BlindpayApiResponse[ListApiKeysResponse]: + return self._client.get(f"/instances/{self._instance_id}/api-keys") + + def create(self, data: CreateApiKeyInput) -> BlindpayApiResponse[CreateApiKeyResponse]: + return self._client.post(f"/instances/{self._instance_id}/api-keys", data) + + def get(self, id: str) -> BlindpayApiResponse[GetApiKeyResponse]: + return self._client.get(f"/instances/{self._instance_id}/api-keys/{id}") + + def delete(self, id: str) -> BlindpayApiResponse[None]: + return self._client.delete(f"/instances/{self._instance_id}/api-keys/{id}") + + +def create_api_keys_resource(instance_id: str, client: InternalApiClient) -> ApiKeysResource: + return ApiKeysResource(instance_id, client) + + +def create_api_keys_resource_sync(instance_id: str, client: InternalApiClientSync) -> ApiKeysResourceSync: + return ApiKeysResourceSync(instance_id, client) diff --git a/src/blindpay/resources/available/__init__.py b/src/blindpay/resources/available/__init__.py new file mode 100644 index 0000000..e1be861 --- /dev/null +++ b/src/blindpay/resources/available/__init__.py @@ -0,0 +1,25 @@ +from .available import ( + AvailableResource, + AvailableResourceSync, + BankDetail, + BankDetailItem, + BankDetailKey, + GetBankDetailsResponse, + GetRailsResponse, + RailInfo, + create_available_resource, + create_available_resource_sync, +) + +__all__ = [ + "create_available_resource", + "create_available_resource_sync", + "AvailableResource", + "AvailableResourceSync", + "BankDetailKey", + "BankDetailItem", + "BankDetail", + "RailInfo", + "GetBankDetailsResponse", + "GetRailsResponse", +] diff --git a/src/blindpay/resources/available/available.py b/src/blindpay/resources/available/available.py new file mode 100644 index 0000000..f1ff47b --- /dev/null +++ b/src/blindpay/resources/available/available.py @@ -0,0 +1,113 @@ +from typing import List, Literal, Optional + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse, Rail + +BankDetailKey = Literal[ + "type", + "name", + "pix_key", + "beneficiary_name", + "routing_number", + "account_number", + "account_type", + "account_class", + "address_line_1", + "address_line_2", + "city", + "state_province_region", + "country", + "postal_code", + "checkbook_account_id", + "checkbook_user_key", + "spei_protocol", + "spei_institution_code", + "spei_clabe", + "transfers_type", + "transfers_account", + "ach_cop_beneficiary_first_name", + "ach_cop_beneficiary_last_name", + "ach_cop_document_id", + "ach_cop_document_type", + "ach_cop_email", + "ach_cop_bank_code", + "ach_cop_bank_account", + "swift_code_bic", + "swift_account_holder_name", + "swift_account_number_iban", + "swift_beneficiary_address_line_1", + "swift_beneficiary_address_line_2", + "swift_beneficiary_country", + "swift_beneficiary_city", + "swift_beneficiary_state_province_region", + "swift_beneficiary_postal_code", + "swift_bank_name", + "swift_bank_address_line_1", + "swift_bank_address_line_2", + "swift_bank_country", + "swift_bank_city", + "swift_bank_state_province_region", + "swift_bank_postal_code", + "swift_intermediary_bank_swift_code_bic", + "swift_intermediary_bank_account_number_iban", + "swift_intermediary_bank_name", + "swift_intermediary_bank_country", +] + + +class BankDetailItem(TypedDict): + label: str + value: str + is_active: Optional[bool] + + +class BankDetail(TypedDict): + label: str + regex: str + key: BankDetailKey + items: Optional[List[BankDetailItem]] + required: bool + + +class RailInfo(TypedDict): + label: str + value: Rail + country: str + + +GetBankDetailsResponse = List[BankDetail] +GetRailsResponse = List[RailInfo] + + +class AvailableResource: + def __init__(self, client: InternalApiClient): + self._client = client + + async def get_bank_details(self, rail: Rail) -> BlindpayApiResponse[GetBankDetailsResponse]: + return await self._client.get(f"/available/bank-details?rail={rail}") + + async def get_rails(self) -> BlindpayApiResponse[GetRailsResponse]: + return await self._client.get("/available/rails") + + +class AvailableResourceSync: + """Synchronous version of AvailableResource""" + + def __init__(self, client: InternalApiClientSync): + self._client = client + + def get_bank_details(self, rail: Rail) -> BlindpayApiResponse[GetBankDetailsResponse]: + return self._client.get(f"/available/bank-details?rail={rail}") + + def get_rails(self) -> BlindpayApiResponse[GetRailsResponse]: + return self._client.get("/available/rails") + + +def create_available_resource(client: InternalApiClient) -> AvailableResource: + return AvailableResource(client) + + +def create_available_resource_sync(client: InternalApiClientSync) -> AvailableResourceSync: + return AvailableResourceSync(client) diff --git a/src/blindpay/resources/bank_accounts/__init__.py b/src/blindpay/resources/bank_accounts/__init__.py new file mode 100644 index 0000000..d2d1b2b --- /dev/null +++ b/src/blindpay/resources/bank_accounts/__init__.py @@ -0,0 +1,69 @@ +from .bank_accounts import ( + AchCopBitsoType, + AchCopDocument, + AchType, + ArgentinaTransfers, + BankAccountsResource, + BankAccountsResourceSync, + CreateAchInput, + CreateAchResponse, + CreateArgentinaTransfersInput, + CreateArgentinaTransfersResponse, + CreateColombiaAchInput, + CreateColombiaAchResponse, + CreateInternationalSwiftInput, + CreateInternationalSwiftResponse, + CreatePixInput, + CreatePixResponse, + CreateRtpInput, + CreateRtpResponse, + CreateSpeiInput, + CreateSpeiResponse, + CreateWireInput, + CreateWireResponse, + GetBankAccountResponse, + ListBankAccountsResponse, + OfframpNetwork, + PixType, + RtpType, + SpeiBitsoType, + TransfersBitsoType, + WireType, + create_bank_accounts_resource, + create_bank_accounts_resource_sync, +) + +__all__ = [ + "create_bank_accounts_resource", + "create_bank_accounts_resource_sync", + "BankAccountsResource", + "BankAccountsResourceSync", + "CreatePixInput", + "CreateArgentinaTransfersInput", + "CreateSpeiInput", + "CreateColombiaAchInput", + "CreateAchInput", + "CreateWireInput", + "CreateInternationalSwiftInput", + "CreateRtpInput", + "AchCopBitsoType", + "AchCopDocument", + "AchType", + "ArgentinaTransfers", + "OfframpNetwork", + "PixType", + "TransfersBitsoType", + "SpeiBitsoType", + "WireType", + "RtpType", + "GetBankAccountResponse", + "ListBankAccountsResponse", + "CreateAchResponse", + "CreateArgentinaTransfersResponse", + "CreateColombiaAchResponse", + "CreateInternationalSwiftResponse", + "CreatePixResponse", + "CreateRtpResponse", + "CreateSpeiResponse", + "CreateWireResponse", +] diff --git a/src/blindpay/resources/bank_accounts/bank_accounts.py b/src/blindpay/resources/bank_accounts/bank_accounts.py new file mode 100644 index 0000000..d9d6507 --- /dev/null +++ b/src/blindpay/resources/bank_accounts/bank_accounts.py @@ -0,0 +1,473 @@ +from typing import List, Literal, Optional + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import AccountClass, BankAccountType, BlindpayApiResponse, Country, Rail, SpeiProtocol + +ArgentinaTransfers = Literal["CVU", "CBU", "ALIAS"] +AchCopDocument = Literal["CC", "CE", "NIT", "PASS", "PEP"] +OfframpNetwork = Literal["tron"] +PixType = Literal["pix"] +TransfersBitsoType = Literal["transfers_bitso"] +SpeiBitsoType = Literal["spei_bitso"] +AchCopBitsoType = Literal["ach_cop_bitso"] +AchType = Literal["ach"] +WireType = Literal["wire"] +InternationalSwiftType = Literal["international_swift"] +RtpType = Literal["rtp"] + + +class OfframpWallet(TypedDict): + address: str + id: str + network: OfframpNetwork + external_id: str + + +class BankAccount(TypedDict): + id: str + type: Rail + name: str + pix_key: Optional[str] + beneficiary_name: Optional[str] + routing_number: Optional[str] + account_number: Optional[str] + account_type: Optional[BankAccountType] + account_class: Optional[AccountClass] + address_line_1: Optional[str] + address_line_2: Optional[str] + city: Optional[str] + state_province_region: Optional[str] + country: Optional[Country] + postal_code: Optional[str] + spei_protocol: Optional[str] + spei_institution_code: Optional[str] + spei_clabe: Optional[str] + transfers_type: Optional[ArgentinaTransfers] + transfers_account: Optional[str] + ach_cop_beneficiary_first_name: Optional[str] + ach_cop_beneficiary_last_name: Optional[str] + ach_cop_document_id: Optional[str] + ach_cop_document_type: Optional[AchCopDocument] + ach_cop_email: Optional[str] + ach_cop_bank_code: Optional[str] + ach_cop_bank_account: Optional[str] + swift_code_bic: Optional[str] + swift_account_holder_name: Optional[str] + swift_account_number_iban: Optional[str] + swift_beneficiary_address_line_1: Optional[str] + swift_beneficiary_address_line_2: Optional[str] + swift_beneficiary_country: Optional[Country] + swift_beneficiary_city: Optional[str] + swift_beneficiary_state_province_region: Optional[str] + swift_beneficiary_postal_code: Optional[str] + swift_bank_name: Optional[str] + swift_bank_address_line_1: Optional[str] + swift_bank_address_line_2: Optional[str] + swift_bank_country: Optional[Country] + swift_bank_city: Optional[str] + swift_bank_state_province_region: Optional[str] + swift_bank_postal_code: Optional[str] + swift_intermediary_bank_swift_code_bic: Optional[str] + swift_intermediary_bank_account_number_iban: Optional[str] + swift_intermediary_bank_name: Optional[str] + swift_intermediary_bank_country: Optional[Country] + tron_wallet_hash: Optional[str] + offramp_wallets: Optional[List[OfframpWallet]] + created_at: str + + +class ListBankAccountsResponse(TypedDict): + data: List[BankAccount] + + +class GetBankAccountResponse(TypedDict): + id: str + receiver_id: str + account_holder_name: str + account_number: str + routing_number: str + account_type: BankAccountType + bank_name: str + swift_code: Optional[str] + iban: Optional[str] + is_primary: bool + created_at: str + updated_at: str + + +class CreatePixInput(TypedDict): + receiver_id: str + name: str + pix_key: str + + +class CreatePixResponse(TypedDict): + id: str + type: PixType + name: str + pix_key: str + created_at: str + + +class CreateArgentinaTransfersInput(TypedDict): + receiver_id: str + name: str + beneficiary_name: str + transfers_account: str + transfers_type: ArgentinaTransfers + + +class CreateArgentinaTransfersResponse(TypedDict): + id: str + type: TransfersBitsoType + name: str + beneficiary_name: str + transfers_type: ArgentinaTransfers + transfers_account: str + created_at: str + + +class CreateSpeiInput(TypedDict): + receiver_id: str + beneficiary_name: str + name: str + spei_clabe: str + spei_institution_code: str + spei_protocol: SpeiProtocol + + +class CreateSpeiResponse(TypedDict): + id: str + type: SpeiBitsoType + name: str + beneficiary_name: str + spei_protocol: SpeiProtocol + spei_institution_code: str + spei_clabe: str + created_at: str + + +class CreateColombiaAchInput(TypedDict): + receiver_id: str + name: str + account_type: BankAccountType + ach_cop_beneficiary_first_name: str + ach_cop_beneficiary_last_name: str + ach_cop_document_id: str + ach_cop_document_type: AchCopDocument + ach_cop_email: str + ach_cop_bank_code: str + ach_cop_bank_account: str + + +class CreateColombiaAchResponse(TypedDict): + id: str + type: AchCopBitsoType + name: str + account_type: BankAccountType + ach_cop_beneficiary_first_name: str + ach_cop_beneficiary_last_name: str + ach_cop_document_id: str + ach_cop_document_type: AchCopDocument + ach_cop_email: str + ach_cop_bank_code: str + ach_cop_bank_account: str + created_at: str + + +class CreateAchInput(TypedDict): + receiver_id: str + name: str + account_class: AccountClass + account_number: str + account_type: BankAccountType + beneficiary_name: str + routing_number: str + + +class CreateAchResponse(TypedDict): + id: str + type: AchType + name: str + beneficiary_name: str + routing_number: str + account_number: str + account_type: BankAccountType + account_class: AccountClass + address_line_1: Optional[str] + address_line_2: Optional[str] + city: Optional[str] + state_province_region: Optional[str] + country: Optional[Country] + postal_code: Optional[str] + ach_cop_beneficiary_first_name: Optional[str] + ach_cop_beneficiary_last_name: Optional[str] + ach_cop_document_id: Optional[str] + ach_cop_document_type: Optional[AchCopDocument] + ach_cop_email: Optional[str] + ach_cop_bank_code: Optional[str] + ach_cop_bank_account: Optional[str] + created_at: str + + +class CreateWireInput(TypedDict): + receiver_id: str + name: str + account_number: str + beneficiary_name: str + routing_number: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + + +class CreateWireResponse(TypedDict): + id: str + type: WireType + name: str + beneficiary_name: str + routing_number: str + account_number: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + created_at: str + + +class CreateInternationalSwiftInput(TypedDict): + receiver_id: str + name: str + swift_account_holder_name: str + swift_account_number_iban: str + swift_bank_address_line_1: str + swift_bank_address_line_2: Optional[str] + swift_bank_city: str + swift_bank_country: Country + swift_bank_name: str + swift_bank_postal_code: str + swift_bank_state_province_region: str + swift_beneficiary_address_line_1: str + swift_beneficiary_address_line_2: Optional[str] + swift_beneficiary_city: str + swift_beneficiary_country: Country + swift_beneficiary_postal_code: str + swift_beneficiary_state_province_region: str + swift_code_bic: str + swift_intermediary_bank_account_number_iban: Optional[str] + swift_intermediary_bank_country: Optional[Country] + swift_intermediary_bank_name: Optional[str] + swift_intermediary_bank_swift_code_bic: Optional[str] + + +class CreateInternationalSwiftResponse(TypedDict): + id: str + type: InternationalSwiftType + name: str + beneficiary_name: Optional[str] + address_line_1: Optional[str] + address_line_2: Optional[str] + city: Optional[str] + state_province_region: Optional[str] + country: Optional[Country] + postal_code: Optional[str] + swift_code_bic: str + swift_account_holder_name: str + swift_account_number_iban: str + swift_beneficiary_address_line_1: str + swift_beneficiary_address_line_2: Optional[str] + swift_beneficiary_country: Country + swift_beneficiary_city: str + swift_beneficiary_state_province_region: str + swift_beneficiary_postal_code: str + swift_bank_name: str + swift_bank_address_line_1: str + swift_bank_address_line_2: Optional[str] + swift_bank_country: Country + swift_bank_city: str + swift_bank_state_province_region: str + swift_bank_postal_code: str + swift_intermediary_bank_swift_code_bic: Optional[str] + swift_intermediary_bank_account_number_iban: Optional[str] + swift_intermediary_bank_name: Optional[str] + swift_intermediary_bank_country: Optional[Country] + created_at: str + + +class CreateRtpInput(TypedDict): + receiver_id: str + name: str + beneficiary_name: str + routing_number: str + account_number: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + + +class CreateRtpResponse(TypedDict): + id: str + type: RtpType + name: str + beneficiary_name: str + routing_number: str + account_number: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + created_at: str + + +class BankAccountsResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self, receiver_id: str) -> BlindpayApiResponse[ListBankAccountsResponse]: + return await self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts") + + async def get(self, receiver_id: str, id: str) -> BlindpayApiResponse[GetBankAccountResponse]: + return await self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{id}") + + async def delete(self, receiver_id: str, id: str) -> BlindpayApiResponse[None]: + return await self._client.delete(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{id}") + + async def create_pix(self, data: CreatePixInput) -> BlindpayApiResponse[CreatePixResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "pix" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + async def create_argentina_transfers( + self, data: CreateArgentinaTransfersInput + ) -> BlindpayApiResponse[CreateArgentinaTransfersResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "transfers_bitso" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + async def create_spei(self, data: CreateSpeiInput) -> BlindpayApiResponse[CreateSpeiResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "spei_bitso" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + async def create_colombia_ach(self, data: CreateColombiaAchInput) -> BlindpayApiResponse[CreateColombiaAchResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "ach_cop_bitso" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + async def create_ach(self, data: CreateAchInput) -> BlindpayApiResponse[CreateAchResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "ach" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + async def create_wire(self, data: CreateWireInput) -> BlindpayApiResponse[CreateWireResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "wire" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + async def create_international_swift( + self, data: CreateInternationalSwiftInput + ) -> BlindpayApiResponse[CreateInternationalSwiftResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "international_swift" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + async def create_rtp(self, data: CreateRtpInput) -> BlindpayApiResponse[CreateRtpResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "rtp" + return await self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + +class BankAccountsResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self, receiver_id: str) -> BlindpayApiResponse[ListBankAccountsResponse]: + return self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts") + + def get(self, receiver_id: str, id: str) -> BlindpayApiResponse[GetBankAccountResponse]: + return self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{id}") + + def delete(self, receiver_id: str, id: str) -> BlindpayApiResponse[None]: + return self._client.delete(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{id}") + + def create_pix(self, data: CreatePixInput) -> BlindpayApiResponse[CreatePixResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "pix" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + def create_argentina_transfers( + self, data: CreateArgentinaTransfersInput + ) -> BlindpayApiResponse[CreateArgentinaTransfersResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "transfers_bitso" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + def create_spei(self, data: CreateSpeiInput) -> BlindpayApiResponse[CreateSpeiResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "spei_bitso" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + def create_colombia_ach(self, data: CreateColombiaAchInput) -> BlindpayApiResponse[CreateColombiaAchResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "ach_cop_bitso" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + def create_ach(self, data: CreateAchInput) -> BlindpayApiResponse[CreateAchResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "ach" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + def create_wire(self, data: CreateWireInput) -> BlindpayApiResponse[CreateWireResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "wire" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + def create_international_swift( + self, data: CreateInternationalSwiftInput + ) -> BlindpayApiResponse[CreateInternationalSwiftResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "international_swift" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + def create_rtp(self, data: CreateRtpInput) -> BlindpayApiResponse[CreateRtpResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["type"] = "rtp" + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts", payload) + + +def create_bank_accounts_resource(instance_id: str, client: InternalApiClient) -> BankAccountsResource: + return BankAccountsResource(instance_id, client) + + +def create_bank_accounts_resource_sync(instance_id: str, client: InternalApiClientSync) -> BankAccountsResourceSync: + return BankAccountsResourceSync(instance_id, client) diff --git a/src/blindpay/resources/instances/__init__.py b/src/blindpay/resources/instances/__init__.py new file mode 100644 index 0000000..4818152 --- /dev/null +++ b/src/blindpay/resources/instances/__init__.py @@ -0,0 +1,23 @@ +from .instances import ( + GetInstanceMembersResponse, + InstanceMember, + InstanceMemberRole, + InstancesResource, + InstancesResourceSync, + UpdateInstanceInput, + UpdateInstanceMemberRoleInput, + create_instances_resource, + create_instances_resource_sync, +) + +__all__ = [ + "create_instances_resource", + "create_instances_resource_sync", + "InstancesResource", + "InstancesResourceSync", + "UpdateInstanceInput", + "UpdateInstanceMemberRoleInput", + "InstanceMemberRole", + "GetInstanceMembersResponse", + "InstanceMember", +] diff --git a/src/blindpay/resources/instances/instances.py b/src/blindpay/resources/instances/instances.py new file mode 100644 index 0000000..06d2952 --- /dev/null +++ b/src/blindpay/resources/instances/instances.py @@ -0,0 +1,82 @@ +from typing import List, Literal, Optional + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse + +InstanceMemberRole = Literal["owner", "admin", "finance", "checker", "operations", "developer", "viewer"] + + +class InstanceMember(TypedDict): + id: str + email: str + first_name: str + middle_name: str + last_name: str + image_url: str + created_at: str + role: InstanceMemberRole + + +GetInstanceMembersResponse = List[InstanceMember] + + +class UpdateInstanceInput(TypedDict): + name: str + receiver_invite_redirect_url: Optional[str] + + +class UpdateInstanceMemberRoleInput(TypedDict): + member_id: str + role: InstanceMemberRole + + +class InstancesResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def get_members(self) -> BlindpayApiResponse[GetInstanceMembersResponse]: + return await self._client.get(f"/instances/{self._instance_id}/members") + + async def update(self, data: UpdateInstanceInput) -> BlindpayApiResponse[None]: + return await self._client.put(f"/instances/{self._instance_id}", data) + + async def delete(self) -> BlindpayApiResponse[None]: + return await self._client.delete(f"/instances/{self._instance_id}") + + async def delete_member(self, member_id: str) -> BlindpayApiResponse[None]: + return await self._client.delete(f"/instances/{self._instance_id}/members/{member_id}") + + async def update_member_role(self, member_id: str, role: InstanceMemberRole) -> BlindpayApiResponse[None]: + return await self._client.put(f"/instances/{self._instance_id}/members/{member_id}", {"role": role}) + + +class InstancesResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def get_members(self) -> BlindpayApiResponse[GetInstanceMembersResponse]: + return self._client.get(f"/instances/{self._instance_id}/members") + + def update(self, data: UpdateInstanceInput) -> BlindpayApiResponse[None]: + return self._client.put(f"/instances/{self._instance_id}", data) + + def delete(self) -> BlindpayApiResponse[None]: + return self._client.delete(f"/instances/{self._instance_id}") + + def delete_member(self, member_id: str) -> BlindpayApiResponse[None]: + return self._client.delete(f"/instances/{self._instance_id}/members/{member_id}") + + def update_member_role(self, member_id: str, role: InstanceMemberRole) -> BlindpayApiResponse[None]: + return self._client.put(f"/instances/{self._instance_id}/members/{member_id}", {"role": role}) + + +def create_instances_resource(instance_id: str, client: InternalApiClient) -> InstancesResource: + return InstancesResource(instance_id, client) + + +def create_instances_resource_sync(instance_id: str, client: InternalApiClientSync) -> InstancesResourceSync: + return InstancesResourceSync(instance_id, client) diff --git a/src/blindpay/resources/partner_fees/__init__.py b/src/blindpay/resources/partner_fees/__init__.py new file mode 100644 index 0000000..900e7ea --- /dev/null +++ b/src/blindpay/resources/partner_fees/__init__.py @@ -0,0 +1,21 @@ +from .partner_fees import ( + CreatePartnerFeeInput, + CreatePartnerFeeResponse, + GetPartnerFeeResponse, + ListPartnerFeesResponse, + PartnerFeesResource, + PartnerFeesResourceSync, + create_partner_fees_resource, + create_partner_fees_resource_sync, +) + +__all__ = [ + "create_partner_fees_resource", + "create_partner_fees_resource_sync", + "PartnerFeesResource", + "PartnerFeesResourceSync", + "CreatePartnerFeeInput", + "CreatePartnerFeeResponse", + "GetPartnerFeeResponse", + "ListPartnerFeesResponse", +] diff --git a/src/blindpay/resources/partner_fees/partner_fees.py b/src/blindpay/resources/partner_fees/partner_fees.py new file mode 100644 index 0000000..34c6a75 --- /dev/null +++ b/src/blindpay/resources/partner_fees/partner_fees.py @@ -0,0 +1,100 @@ +from typing import List, Optional + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse + + +class PartnerFee(TypedDict): + id: str + instance_id: str + name: str + payout_percentage_fee: float + payout_flat_fee: float + payin_percentage_fee: float + payin_flat_fee: float + evm_wallet_address: str + stellar_wallet_address: str + + +ListPartnerFeesResponse = List[PartnerFee] + + +class CreatePartnerFeeInput(TypedDict): + virtual_account_set: Optional[bool] + evm_wallet_address: str + name: str + payin_flat_fee: float + payin_percentage_fee: float + payout_flat_fee: float + payout_percentage_fee: float + stellar_wallet_address: Optional[str] + + +class CreatePartnerFeeResponse(TypedDict): + id: str + instance_id: str + name: str + payout_percentage_fee: float + payout_flat_fee: float + payin_percentage_fee: float + payin_flat_fee: float + evm_wallet_address: Optional[str] + stellar_wallet_address: Optional[str] + + +class GetPartnerFeeResponse(TypedDict): + id: str + instance_id: str + evm_wallet_address: str + name: str + payin_flat_fee: float + payin_percentage_fee: float + payout_flat_fee: float + payout_percentage_fee: float + stellar_wallet_address: Optional[str] + + +class PartnerFeesResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self) -> BlindpayApiResponse[ListPartnerFeesResponse]: + return await self._client.get(f"/instances/{self._instance_id}/partner-fees") + + async def create(self, data: CreatePartnerFeeInput) -> BlindpayApiResponse[CreatePartnerFeeResponse]: + return await self._client.post(f"/instances/{self._instance_id}/partner-fees", data) + + async def get(self, id: str) -> BlindpayApiResponse[GetPartnerFeeResponse]: + return await self._client.get(f"/instances/{self._instance_id}/partner-fees/{id}") + + async def delete(self, id: str) -> BlindpayApiResponse[None]: + return await self._client.delete(f"/instances/{self._instance_id}/partner-fees/{id}") + + +class PartnerFeesResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self) -> BlindpayApiResponse[ListPartnerFeesResponse]: + return self._client.get(f"/instances/{self._instance_id}/partner-fees") + + def create(self, data: CreatePartnerFeeInput) -> BlindpayApiResponse[CreatePartnerFeeResponse]: + return self._client.post(f"/instances/{self._instance_id}/partner-fees", data) + + def get(self, id: str) -> BlindpayApiResponse[GetPartnerFeeResponse]: + return self._client.get(f"/instances/{self._instance_id}/partner-fees/{id}") + + def delete(self, id: str) -> BlindpayApiResponse[None]: + return self._client.delete(f"/instances/{self._instance_id}/partner-fees/{id}") + + +def create_partner_fees_resource(instance_id: str, client: InternalApiClient) -> PartnerFeesResource: + return PartnerFeesResource(instance_id, client) + + +def create_partner_fees_resource_sync(instance_id: str, client: InternalApiClientSync) -> PartnerFeesResourceSync: + return PartnerFeesResourceSync(instance_id, client) diff --git a/src/blindpay/resources/payins/__init__.py b/src/blindpay/resources/payins/__init__.py new file mode 100644 index 0000000..f394bf6 --- /dev/null +++ b/src/blindpay/resources/payins/__init__.py @@ -0,0 +1,69 @@ +from .payins import ( + AchDetails, + BankDetails, + BeneficiaryDetails, + CreateEvmPayinResponse, + CreatePayinInput, + ExportPayinsInput, + ExportPayinsResponse, + GetPayinTrackingTransaction, + GetPayinTrackResponse, + ListPayinsInput, + ListPayinsResponse, + Payin, + PayinsResource, + PayinsResourceSync, + ReceivingBankDetails, + RtpDetails, + WireDetails, + create_payins_resource, + create_payins_resource_sync, +) +from .quotes import ( + CreatePayinQuoteInput, + CreatePayinQuoteResponse, + GetPayinFxRateInput, + GetPayinFxRateResponse, + PayerRules, + PayinQuotesResource, + PayinQuotesResourceSync, + PaymentMethod, + create_payin_quotes_resource, + create_payin_quotes_resource_sync, +) + +__all__ = [ + "create_payins_resource", + "create_payins_resource_sync", + "PayinsResource", + "PayinsResourceSync", + "Payin", + "AchDetails", + "WireDetails", + "RtpDetails", + "BeneficiaryDetails", + "ExportPayinsResponse", + "ListPayinsInput", + "ListPayinsResponse", + "CreatePayinInput", + "ExportPayinsInput", + "CreateEvmPayinResponse", + "create_payin_quotes_resource", + "create_payin_quotes_resource_sync", + "PayinQuotesResource", + "PayinQuotesResourceSync", + "CreatePayinQuoteInput", + "CreatePayinQuoteResponse", + "GetPayinFxRateInput", + "GetPayinFxRateResponse", + "BankDetails", + "GetPayinTrackingTransaction", + "GetPayinTrackResponse", + "ReceivingBankDetails", + "RtpDetails", + "WireDetails", + "AchDetails", + "BeneficiaryDetails", + "PaymentMethod", + "PayerRules", +] diff --git a/src/blindpay/resources/payins/payins.py b/src/blindpay/resources/payins/payins.py new file mode 100644 index 0000000..3f4e949 --- /dev/null +++ b/src/blindpay/resources/payins/payins.py @@ -0,0 +1,246 @@ +from typing import List, Optional, TypedDict +from urllib.parse import urlencode + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import ( + BlindpayApiResponse, + Network, + PaginationMetadata, + PaginationParams, + StablecoinToken, + TrackingComplete, + TrackingPartnerFee, + TrackingPayment, + TrackingTransaction, + TransactionStatus, +) + + +class AchDetails(TypedDict): + routing_number: str + account_number: str + + +class WireDetails(TypedDict): + routing_number: str + account_number: str + + +class RtpDetails(TypedDict): + routing_number: str + account_number: str + + +class BeneficiaryDetails(TypedDict): + name: str + address_line_1: str + address_line_2: Optional[str] + + +class ReceivingBankDetails(TypedDict): + name: str + address_line_1: str + address_line_2: Optional[str] + + +class BankDetails(TypedDict): + routing_number: str + account_number: str + account_type: str + swift_bic_code: str + ach: AchDetails + wire: WireDetails + rtp: RtpDetails + beneficiary: BeneficiaryDetails + receiving_bank: ReceivingBankDetails + + +class Payin(TypedDict): + receiver_id: str + id: str + pix_code: Optional[str] + memo_code: Optional[str] + clabe: Optional[str] + status: TransactionStatus + payin_quote_id: str + instance_id: str + tracking_transaction: Optional[TrackingTransaction] + tracking_payment: Optional[TrackingPayment] + tracking_complete: Optional[TrackingComplete] + tracking_partner_fee: Optional[TrackingPartnerFee] + created_at: str + updated_at: str + image_url: Optional[str] + first_name: Optional[str] + last_name: Optional[str] + legal_name: Optional[str] + type: str + payment_method: str + sender_amount: float + receiver_amount: float + token: StablecoinToken + partner_fee_amount: float + total_fee_amount: float + commercial_quotation: float + blindpay_quotation: float + currency: str + billing_fee: float + name: str + address: str + network: Network + blindpay_bank_details: BankDetails + + +class ListPayinsInput(PaginationParams): + status: Optional[TransactionStatus] + receiver_id: Optional[str] + + +class ListPayinsResponse(TypedDict): + data: List[Payin] + pagination: PaginationMetadata + + +class CreatePayinInput(TypedDict): + quote_id: str + sender_address: str + receiver_address: str + amount: float + token: StablecoinToken + network: Network + description: Optional[str] + + +class GetPayinTrackingTransaction(TypedDict): + step: str + status: str + external_id: str + completed_at: str + sender_name: str + sender_tax_id: str + sender_bank_code: str + sender_account_number: str + trace_number: str + transaction_reference: str + description: str + + +class GetPayinTrackResponse(TypedDict): + receiver_id: str + id: str + pix_code: str + memo_code: str + clabe: str + status: str + payin_quote_id: str + instance_id: str + tracking_transaction: TrackingTransaction + tracking_payment: TrackingPayment + tracking_complete: TrackingComplete + tracking_partner_fee: TrackingPartnerFee + created_at: str + updated_at: str + image_url: str + first_name: str + last_name: str + legal_name: str + type: str + payment_method: str + sender_amount: float + receiver_amount: float + token: StablecoinToken + partner_fee_amount: float + total_fee_amount: float + commercial_quotation: float + blindpay_quotation: float + currency: str + billing_fee: float + name: str + address: str + network: Network + blindpay_bank_details: BankDetails + + +class ExportPayinsInput(TypedDict): + limit: Optional[str] + offset: Optional[str] + status: TransactionStatus + + +ExportPayinsResponse = List[Payin] + + +class CreateEvmPayinResponse(TypedDict): + id: str + status: TransactionStatus + pix_code: Optional[str] + memo_code: Optional[str] + clabe: Optional[str] + tracking_complete: Optional[TrackingComplete] + tracking_payment: Optional[TrackingPayment] + tracking_transaction: Optional[TrackingTransaction] + tracking_partner_fee: Optional[TrackingPartnerFee] + blindpay_bank_details: BankDetails + receiver_id: str + receiver_amount: float + + +class PayinsResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self, params: Optional[ListPayinsInput] = None) -> BlindpayApiResponse[ListPayinsResponse]: + query_string = "" + if params: + filtered_params = {k: v for k, v in params.items() if v is not None} + if filtered_params: + query_string = f"?{urlencode(filtered_params)}" + return await self._client.get(f"/instances/{self._instance_id}/payins{query_string}") + + async def get(self, payin_id: str) -> BlindpayApiResponse[Payin]: + return await self._client.get(f"/instances/{self._instance_id}/payins/{payin_id}") + + async def export(self, params: ExportPayinsInput) -> BlindpayApiResponse[ExportPayinsResponse]: + filtered_params = {k: v for k, v in params.items() if v is not None} + query_string = f"?{urlencode(filtered_params)}" if filtered_params else "" + return await self._client.get(f"/instances/{self._instance_id}/export/payins{query_string}") + + async def create_evm(self, payin_quote_id: str) -> BlindpayApiResponse[CreateEvmPayinResponse]: + return await self._client.post(f"/instances/{self._instance_id}/payins/evm", {"payin_quote_id": payin_quote_id}) + + +class PayinsResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self, params: Optional[ListPayinsInput] = None) -> BlindpayApiResponse[ListPayinsResponse]: + query_string = "" + if params: + filtered_params = {k: v for k, v in params.items() if v is not None} + if filtered_params: + query_string = f"?{urlencode(filtered_params)}" + return self._client.get(f"/instances/{self._instance_id}/payins{query_string}") + + def get(self, payin_id: str) -> BlindpayApiResponse[Payin]: + return self._client.get(f"/instances/{self._instance_id}/payins/{payin_id}") + + def get_track(self, payin_id: str) -> BlindpayApiResponse[GetPayinTrackResponse]: + return self._client.get(f"/e/payins/{payin_id}") + + def export(self, params: ExportPayinsInput) -> BlindpayApiResponse[ExportPayinsResponse]: + filtered_params = {k: v for k, v in params.items() if v is not None} + query_string = f"?{urlencode(filtered_params)}" if filtered_params else "" + return self._client.get(f"/instances/{self._instance_id}/export/payins{query_string}") + + def create_evm(self, payin_quote_id: str) -> BlindpayApiResponse[CreateEvmPayinResponse]: + return self._client.post(f"/instances/{self._instance_id}/payins/evm", {"payin_quote_id": payin_quote_id}) + + +def create_payins_resource(instance_id: str, client: InternalApiClient) -> PayinsResource: + return PayinsResource(instance_id, client) + + +def create_payins_resource_sync(instance_id: str, client: InternalApiClientSync) -> PayinsResourceSync: + return PayinsResourceSync(instance_id, client) diff --git a/src/blindpay/resources/payins/quotes.py b/src/blindpay/resources/payins/quotes.py new file mode 100644 index 0000000..3b93565 --- /dev/null +++ b/src/blindpay/resources/payins/quotes.py @@ -0,0 +1,100 @@ +from typing import List, Literal, Optional + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import ( + BlindpayApiResponse, + Currency, + CurrencyType, + StablecoinToken, +) + +PaymentMethod = Literal["ach", "wire", "pix", "spei"] + + +class PayerRules(TypedDict, total=False): + pix_allowed_tax_ids: List[str] + + +class CreatePayinQuoteInput(TypedDict): + blockchain_wallet_id: str + currency_type: CurrencyType + payment_method: PaymentMethod + request_amount: float + token: StablecoinToken + cover_fees: bool + partner_fee_id: Optional[str] + payer_rules: PayerRules + + +class CreatePayinQuoteResponse(TypedDict): + id: str + expires_at: int + commercial_quotation: float + blindpay_quotation: float + receiver_amount: float + sender_amount: float + partner_fee_amount: Optional[float] + flat_fee: Optional[float] + + +class GetPayinFxRateInput(TypedDict): + currency_type: CurrencyType + from_currency: Currency # 'from' is a reserved keyword in Python + to: Currency + request_amount: float + + +class GetPayinFxRateResponse(TypedDict): + commercial_quotation: float + blindpay_quotation: float + result_amount: float + instance_flat_fee: float + instance_percentage_fee: float + + +class PayinQuotesResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def create(self, data: CreatePayinQuoteInput) -> BlindpayApiResponse[CreatePayinQuoteResponse]: + return await self._client.post(f"/instances/{self._instance_id}/payin-quotes", data) + + async def get_fx_rate(self, data: GetPayinFxRateInput) -> BlindpayApiResponse[GetPayinFxRateResponse]: + # Convert 'from_currency' back to 'from' for API + payload = { + "currency_type": data["currency_type"], + "from": data["from_currency"], + "to": data["to"], + "request_amount": data["request_amount"], + } + return await self._client.post(f"/instances/{self._instance_id}/payin-quotes/fx", payload) + + +class PayinQuotesResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def create(self, data: CreatePayinQuoteInput) -> BlindpayApiResponse[CreatePayinQuoteResponse]: + return self._client.post(f"/instances/{self._instance_id}/payin-quotes", data) + + def get_fx_rate(self, data: GetPayinFxRateInput) -> BlindpayApiResponse[GetPayinFxRateResponse]: + # Convert 'from_currency' back to 'from' for API + payload = { + "currency_type": data["currency_type"], + "from": data["from_currency"], + "to": data["to"], + "request_amount": data["request_amount"], + } + return self._client.post(f"/instances/{self._instance_id}/payin-quotes/fx", payload) + + +def create_payin_quotes_resource(instance_id: str, client: InternalApiClient) -> PayinQuotesResource: + return PayinQuotesResource(instance_id, client) + + +def create_payin_quotes_resource_sync(instance_id: str, client: InternalApiClientSync) -> PayinQuotesResourceSync: + return PayinQuotesResourceSync(instance_id, client) diff --git a/src/blindpay/resources/payouts/__init__.py b/src/blindpay/resources/payouts/__init__.py new file mode 100644 index 0000000..85905a2 --- /dev/null +++ b/src/blindpay/resources/payouts/__init__.py @@ -0,0 +1,35 @@ +from .payouts import ( + AuthorizeStellarTokenInput, + AuthorizeStellarTokenResponse, + CreateEvmPayoutInput, + CreateEvmPayoutResponse, + CreateStellarPayoutInput, + CreateStellarPayoutResponse, + ExportPayoutsInput, + ExportPayoutsResponse, + ListPayoutsInput, + ListPayoutsResponse, + Payout, + PayoutsResource, + PayoutsResourceSync, + create_payouts_resource, + create_payouts_resource_sync, +) + +__all__ = [ + "create_payouts_resource", + "create_payouts_resource_sync", + "PayoutsResource", + "PayoutsResourceSync", + "Payout", + "ListPayoutsInput", + "ListPayoutsResponse", + "ExportPayoutsInput", + "ExportPayoutsResponse", + "AuthorizeStellarTokenInput", + "AuthorizeStellarTokenResponse", + "CreateStellarPayoutInput", + "CreateStellarPayoutResponse", + "CreateEvmPayoutInput", + "CreateEvmPayoutResponse", +] diff --git a/src/blindpay/resources/payouts/payouts.py b/src/blindpay/resources/payouts/payouts.py new file mode 100644 index 0000000..a3e165b --- /dev/null +++ b/src/blindpay/resources/payouts/payouts.py @@ -0,0 +1,241 @@ +from typing import List, Literal, Optional +from urllib.parse import urlencode + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import ( + AccountClass, + BankAccountType, + BlindpayApiResponse, + Country, + Currency, + Network, + PaginationMetadata, + PaginationParams, + Rail, + SpeiProtocol, + StablecoinToken, + TrackingComplete, + TrackingLiquidity, + TrackingPartnerFee, + TrackingPayment, + TrackingTransaction, + TransactionDocumentType, + TransactionStatus, +) + +ArgentinaTransfers = Literal["CVU", "CBU", "ALIAS"] + + +class Payout(TypedDict): + receiver_id: str + id: str + status: TransactionStatus + sender_wallet_address: str + signed_transaction: str + quote_id: str + instance_id: str + tracking_transaction: TrackingTransaction + tracking_payment: TrackingPayment + tracking_liquidity: TrackingLiquidity + tracking_complete: TrackingComplete + tracking_partner_fee: TrackingPartnerFee + created_at: str + updated_at: str + image_url: str + first_name: str + last_name: str + legal_name: str + network: Network + token: StablecoinToken + description: str + sender_amount: float + receiver_amount: float + partner_fee_amount: float + commercial_quotation: float + blindpay_quotation: float + total_fee_amount: float + receiver_local_amount: float + currency: Currency # Excluding "USDT" | "USDB" + transaction_document_file: str + transaction_document_type: TransactionDocumentType + transaction_document_id: str + name: str + type: Rail + pix_key: Optional[str] + account_number: Optional[str] + routing_number: Optional[str] + country: Optional[Country] + account_class: Optional[AccountClass] + address_line_1: Optional[str] + address_line_2: Optional[str] + city: Optional[str] + state_province_region: Optional[str] + postal_code: Optional[str] + account_type: Optional[BankAccountType] + ach_cop_beneficiary_first_name: Optional[str] + ach_cop_bank_account: Optional[str] + ach_cop_bank_code: Optional[str] + ach_cop_beneficiary_last_name: Optional[str] + ach_cop_document_id: Optional[str] + ach_cop_document_type: Optional[str] + ach_cop_email: Optional[str] + beneficiary_name: Optional[str] + spei_clabe: Optional[str] + spei_protocol: Optional[SpeiProtocol] + spei_institution_code: Optional[str] + swift_beneficiary_country: Optional[Country] + swift_code_bic: Optional[str] + swift_account_holder_name: Optional[str] + swift_account_number_iban: Optional[str] + transfers_account: Optional[str] + transfers_type: ArgentinaTransfers + has_virtual_account: bool + + +class ListPayoutsInput(PaginationParams, total=False): + receiver_id: str + + +class ListPayoutsResponse(TypedDict): + data: List[Payout] + pagination: PaginationMetadata + + +class ExportPayoutsInput(TypedDict, total=False): + limit: str + offset: str + + +ExportPayoutsResponse = List[Payout] + + +class AuthorizeStellarTokenInput(TypedDict): + quote_id: str + sender_wallet_address: str + + +class AuthorizeStellarTokenResponse(TypedDict): + transaction_hash: str + + +class CreateStellarPayoutInput(TypedDict): + quote_id: str + sender_wallet_address: str + signed_transaction: Optional[str] + + +class CreateStellarPayoutResponse(TypedDict): + id: str + status: TransactionStatus + sender_wallet_address: str + tracking_complete: Optional[TrackingComplete] + tracking_payment: Optional[TrackingPayment] + tracking_transaction: Optional[TrackingTransaction] + tracking_partner_fee: Optional[TrackingPartnerFee] + tracking_liquidity: Optional[TrackingLiquidity] + receiver_id: str + + +class CreateEvmPayoutInput(TypedDict): + quote_id: str + sender_wallet_address: str + + +class CreateEvmPayoutResponse(TypedDict): + id: str + status: TransactionStatus + sender_wallet_address: str + tracking_complete: Optional[TrackingComplete] + tracking_payment: Optional[TrackingPayment] + tracking_transaction: Optional[TrackingTransaction] + tracking_partner_fee: Optional[TrackingPartnerFee] + tracking_liquidity: Optional[TrackingLiquidity] + receiver_id: str + + +class PayoutsResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self, params: Optional[ListPayoutsInput] = None) -> BlindpayApiResponse[ListPayoutsResponse]: + query_string = "" + if params: + filtered_params = {k: v for k, v in params.items() if v is not None} + if filtered_params: + query_string = f"?{urlencode(filtered_params)}" + return await self._client.get(f"/instances/{self._instance_id}/payouts{query_string}") + + async def export(self, params: Optional[ExportPayoutsInput] = None) -> BlindpayApiResponse[ExportPayoutsResponse]: + query_string = "" + if params: + filtered_params = {k: v for k, v in params.items() if v is not None} + if filtered_params: + query_string = f"?{urlencode(filtered_params)}" + return await self._client.get(f"/instances/{self._instance_id}/export/payouts{query_string}") + + async def get(self, payout_id: str) -> BlindpayApiResponse[Payout]: + return await self._client.get(f"/instances/{self._instance_id}/payouts/{payout_id}") + + async def get_track(self, payout_id: str) -> BlindpayApiResponse[Payout]: + return await self._client.get(f"/e/payouts/{payout_id}") + + async def authorize_stellar_token( + self, data: AuthorizeStellarTokenInput + ) -> BlindpayApiResponse[AuthorizeStellarTokenResponse]: + return await self._client.post(f"/instances/{self._instance_id}/payouts/stellar/authorize", data) + + async def create_stellar(self, data: CreateStellarPayoutInput) -> BlindpayApiResponse[CreateStellarPayoutResponse]: + return await self._client.post(f"/instances/{self._instance_id}/payouts/stellar", data) + + async def create_evm(self, data: CreateEvmPayoutInput) -> BlindpayApiResponse[CreateEvmPayoutResponse]: + return await self._client.post(f"/instances/{self._instance_id}/payouts/evm", data) + + +class PayoutsResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self, params: Optional[ListPayoutsInput] = None) -> BlindpayApiResponse[ListPayoutsResponse]: + query_string = "" + if params: + filtered_params = {k: v for k, v in params.items() if v is not None} + if filtered_params: + query_string = f"?{urlencode(filtered_params)}" + return self._client.get(f"/instances/{self._instance_id}/payouts{query_string}") + + def export(self, params: Optional[ExportPayoutsInput] = None) -> BlindpayApiResponse[ExportPayoutsResponse]: + query_string = "" + if params: + filtered_params = {k: v for k, v in params.items() if v is not None} + if filtered_params: + query_string = f"?{urlencode(filtered_params)}" + return self._client.get(f"/instances/{self._instance_id}/export/payouts{query_string}") + + def get(self, payout_id: str) -> BlindpayApiResponse[Payout]: + return self._client.get(f"/instances/{self._instance_id}/payouts/{payout_id}") + + def get_track(self, payout_id: str) -> BlindpayApiResponse[Payout]: + return self._client.get(f"/e/payouts/{payout_id}") + + def authorize_stellar_token( + self, data: AuthorizeStellarTokenInput + ) -> BlindpayApiResponse[AuthorizeStellarTokenResponse]: + return self._client.post(f"/instances/{self._instance_id}/payouts/stellar/authorize", data) + + def create_stellar(self, data: CreateStellarPayoutInput) -> BlindpayApiResponse[CreateStellarPayoutResponse]: + return self._client.post(f"/instances/{self._instance_id}/payouts/stellar", data) + + def create_evm(self, data: CreateEvmPayoutInput) -> BlindpayApiResponse[CreateEvmPayoutResponse]: + return self._client.post(f"/instances/{self._instance_id}/payouts/evm", data) + + +def create_payouts_resource(instance_id: str, client: InternalApiClient) -> PayoutsResource: + return PayoutsResource(instance_id, client) + + +def create_payouts_resource_sync(instance_id: str, client: InternalApiClientSync) -> PayoutsResourceSync: + return PayoutsResourceSync(instance_id, client) diff --git a/src/blindpay/resources/quotes/__init__.py b/src/blindpay/resources/quotes/__init__.py new file mode 100644 index 0000000..cd64c74 --- /dev/null +++ b/src/blindpay/resources/quotes/__init__.py @@ -0,0 +1,25 @@ +from .quotes import ( + Contract, + ContractNetwork, + CreateQuoteInput, + CreateQuoteResponse, + GetFxRateInput, + GetFxRateResponse, + QuotesResource, + QuotesResourceSync, + create_quotes_resource, + create_quotes_resource_sync, +) + +__all__ = [ + "create_quotes_resource", + "create_quotes_resource_sync", + "QuotesResource", + "QuotesResourceSync", + "CreateQuoteInput", + "CreateQuoteResponse", + "GetFxRateInput", + "GetFxRateResponse", + "ContractNetwork", + "Contract", +] diff --git a/src/blindpay/resources/quotes/quotes.py b/src/blindpay/resources/quotes/quotes.py new file mode 100644 index 0000000..b1f362e --- /dev/null +++ b/src/blindpay/resources/quotes/quotes.py @@ -0,0 +1,114 @@ +from typing import Any, Dict, List, Optional + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import ( + BlindpayApiResponse, + Currency, + CurrencyType, + Network, + StablecoinToken, + TransactionDocumentType, +) + + +class ContractNetwork(TypedDict): + name: str + chainId: int + + +class Contract(TypedDict): + abi: List[Dict[str, Any]] + address: str + functionName: str + blindpayContractAddress: str + amount: str + network: ContractNetwork + + +class CreateQuoteInput(TypedDict): + bank_account_id: str + currency_type: CurrencyType + network: Optional[Network] + request_amount: float + token: Optional[StablecoinToken] + cover_fees: Optional[bool] + description: Optional[str] + partner_fee_id: Optional[str] + transaction_document_file: Optional[str] + transaction_document_id: Optional[str] + transaction_document_type: TransactionDocumentType + + +class CreateQuoteResponse(TypedDict): + id: str + expires_at: int + commercial_quotation: float + blindpay_quotation: float + receiver_amount: float + sender_amount: float + partner_fee_amount: float + flat_fee: float + contract: Contract + receiver_local_amount: float + description: str + + +class GetFxRateInput(TypedDict): + currency_type: CurrencyType + from_currency: Currency # 'from' is reserved in Python + to: Currency + request_amount: float + + +class GetFxRateResponse(TypedDict): + commercial_quotation: float + blindpay_quotation: float + result_amount: float + instance_flat_fee: float + instance_percentage_fee: float + + +class QuotesResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def create(self, data: CreateQuoteInput) -> BlindpayApiResponse[CreateQuoteResponse]: + return await self._client.post(f"/instances/{self._instance_id}/quotes", data) + + async def get_fx_rate(self, data: GetFxRateInput) -> BlindpayApiResponse[GetFxRateResponse]: + payload = { + "currency_type": data["currency_type"], + "from": data["from_currency"], + "to": data["to"], + "request_amount": data["request_amount"], + } + return await self._client.post(f"/instances/{self._instance_id}/quotes/fx", payload) + + +class QuotesResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def create(self, data: CreateQuoteInput) -> BlindpayApiResponse[CreateQuoteResponse]: + return self._client.post(f"/instances/{self._instance_id}/quotes", data) + + def get_fx_rate(self, data: GetFxRateInput) -> BlindpayApiResponse[GetFxRateResponse]: + payload = { + "currency_type": data["currency_type"], + "from": data["from_currency"], + "to": data["to"], + "request_amount": data["request_amount"], + } + return self._client.post(f"/instances/{self._instance_id}/quotes/fx", payload) + + +def create_quotes_resource(instance_id: str, client: InternalApiClient) -> QuotesResource: + return QuotesResource(instance_id, client) + + +def create_quotes_resource_sync(instance_id: str, client: InternalApiClientSync) -> QuotesResourceSync: + return QuotesResourceSync(instance_id, client) diff --git a/src/blindpay/resources/receivers/__init__.py b/src/blindpay/resources/receivers/__init__.py new file mode 100644 index 0000000..6ca5eeb --- /dev/null +++ b/src/blindpay/resources/receivers/__init__.py @@ -0,0 +1,66 @@ +from .receivers import ( + BusinessType, + BusinessWithStandardKYB, + CreateBusinessWithStandardKYBInput, + CreateBusinessWithStandardKYBResponse, + CreateIndividualWithEnhancedKYCInput, + CreateIndividualWithEnhancedKYCResponse, + CreateIndividualWithStandardKYCInput, + CreateIndividualWithStandardKYCResponse, + EnhancedKycType, + GetReceiverLimitsResponse, + GetReceiverResponse, + IdentificationDocument, + IndividualType, + IndividualWithEnhancedKYC, + IndividualWithStandardKYC, + KycType, + KycWarning, + ListReceiversResponse, + Owner, + OwnerRole, + OwnerUpdate, + ProofOfAddressDocType, + PurposeOfTransactions, + ReceiversResource, + ReceiversResourceSync, + SourceOfFundsDocType, + TransactionLimit, + UpdateReceiverInput, + create_receivers_resource, + create_receivers_resource_sync, +) + +__all__ = [ + "create_receivers_resource", + "create_receivers_resource_sync", + "ReceiversResource", + "ReceiversResourceSync", + "IndividualWithStandardKYC", + "IndividualWithEnhancedKYC", + "BusinessWithStandardKYB", + "CreateIndividualWithStandardKYCInput", + "CreateIndividualWithEnhancedKYCInput", + "CreateBusinessWithStandardKYBInput", + "UpdateReceiverInput", + "GetReceiverLimitsResponse", + "KycType", + "BusinessType", + "IndividualType", + "EnhancedKycType", + "ProofOfAddressDocType", + "PurposeOfTransactions", + "SourceOfFundsDocType", + "IdentificationDocument", + "OwnerRole", + "KycWarning", + "TransactionLimit", + "Owner", + "CreateBusinessWithStandardKYBResponse", + "CreateIndividualWithEnhancedKYCResponse", + "CreateIndividualWithStandardKYCResponse", + "ListReceiversResponse", + "GetReceiverResponse", + "OwnerUpdate", + "UpdateReceiverInput", +] diff --git a/src/blindpay/resources/receivers/receivers.py b/src/blindpay/resources/receivers/receivers.py new file mode 100644 index 0000000..4335379 --- /dev/null +++ b/src/blindpay/resources/receivers/receivers.py @@ -0,0 +1,452 @@ +from typing import List, Optional, Union + +from typing_extensions import Literal, TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import ( + BlindpayApiResponse, + Country, +) + +IndividualType = Literal["individual"] +BusinessType = Literal["business"] +StandardKycType = Literal["standard"] +EnhancedKycType = Literal["enhanced"] +KycType = Literal["light", "standard", "enhanced"] + +ProofOfAddressDocType = Literal[ + "UTILITY_BILL", "BANK_STATEMENT", "RENTAL_AGREEMENT", "TAX_DOCUMENT", "GOVERNMENT_CORRESPONDENCE" +] + +PurposeOfTransactions = Literal[ + "business_transactions", + "charitable_donations", + "investment_purposes", + "payments_to_friends_or_family_abroad", + "personal_or_living_expenses", + "protect_wealth", + "purchase_good_and_services", + "receive_payment_for_freelancing", + "receive_salary", + "other", +] + +SourceOfFundsDocType = Literal[ + "business_income", + "gambling_proceeds", + "gifts", + "government_benefits", + "inheritance", + "investment_loans", + "pension_retirement", + "salary", + "sale_of_assets_real_estate", + "savings", + "esops", + "investment_proceeds", + "someone_else_funds", +] + +IdentificationDocument = Literal["PASSPORT", "ID_CARD", "DRIVERS"] + +OwnerRole = Literal["beneficial_controlling", "beneficial_owner", "controlling_person"] + + +class KycWarning(TypedDict): + code: Optional[str] + message: Optional[str] + resolution_status: Optional[str] + warning_id: Optional[str] + + +class TransactionLimit(TypedDict): + per_transaction: float + daily: float + monthly: float + + +class Owner(TypedDict): + id: str + instance_id: str + receiver_id: str + role: OwnerRole + first_name: str + last_name: str + date_of_birth: str + tax_id: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + id_doc_country: Country + id_doc_type: IdentificationDocument + id_doc_front_file: str + id_doc_back_file: Optional[str] + proof_of_address_doc_type: ProofOfAddressDocType + proof_of_address_doc_file: str + + +class IndividualWithStandardKYC(TypedDict): + id: str + type: IndividualType + kyc_type: StandardKycType + kyc_status: str + kyc_warnings: Optional[List[KycWarning]] + email: str + tax_id: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + ip_address: Optional[str] + image_url: Optional[str] + phone_number: str + proof_of_address_doc_type: ProofOfAddressDocType + proof_of_address_doc_file: str + first_name: str + last_name: str + date_of_birth: str + id_doc_country: Country + id_doc_type: IdentificationDocument + id_doc_front_file: str + id_doc_back_file: str + aiprise_validation_key: str + instance_id: str + tos_id: Optional[str] + created_at: str + updated_at: str + limit: TransactionLimit + + +class IndividualWithEnhancedKYC(TypedDict): + id: str + type: IndividualType + kyc_type: EnhancedKycType + kyc_status: str + kyc_warnings: Optional[List[KycWarning]] + email: str + tax_id: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + ip_address: Optional[str] + image_url: Optional[str] + phone_number: Optional[str] + proof_of_address_doc_type: ProofOfAddressDocType + proof_of_address_doc_file: str + first_name: str + last_name: str + date_of_birth: str + id_doc_country: Country + id_doc_type: IdentificationDocument + id_doc_front_file: str + id_doc_back_file: Optional[str] + aiprise_validation_key: str + instance_id: str + source_of_funds_doc_type: str + source_of_funds_doc_file: str + individual_holding_doc_front_file: str + purpose_of_transactions: PurposeOfTransactions + purpose_of_transactions_explanation: Optional[str] + tos_id: Optional[str] + created_at: str + updated_at: str + limit: TransactionLimit + + +class BusinessWithStandardKYB(TypedDict): + id: str + type: BusinessType + kyc_type: StandardKycType + kyc_status: str + kyc_warnings: Optional[List[KycWarning]] + email: str + tax_id: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + ip_address: Optional[str] + image_url: Optional[str] + phone_number: Optional[str] + proof_of_address_doc_type: ProofOfAddressDocType + proof_of_address_doc_file: str + legal_name: str + alternate_name: Optional[str] + formation_date: str + website: Optional[str] + owners: List[Owner] + incorporation_doc_file: str + proof_of_ownership_doc_file: str + external_id: Optional[str] + instance_id: str + tos_id: Optional[str] + aiprise_validation_key: str + created_at: str + updated_at: str + limit: TransactionLimit + + +class CreateIndividualWithStandardKYCInput(TypedDict): + external_id: Optional[str] + address_line_1: str + address_line_2: Optional[str] + city: str + country: Country + date_of_birth: str + email: str + first_name: str + phone_number: Optional[str] + id_doc_country: Country + id_doc_front_file: str + id_doc_type: IdentificationDocument + id_doc_back_file: Optional[str] + last_name: str + postal_code: str + proof_of_address_doc_file: str + proof_of_address_doc_type: ProofOfAddressDocType + state_province_region: str + tax_id: str + tos_id: str + + +class CreateIndividualWithStandardKYCResponse(TypedDict): + id: str + + +class CreateIndividualWithEnhancedKYCInput(TypedDict): + external_id: Optional[str] + address_line_1: str + address_line_2: Optional[str] + city: str + country: Country + date_of_birth: str + email: str + first_name: str + id_doc_country: Country + id_doc_front_file: str + id_doc_type: IdentificationDocument + id_doc_back_file: Optional[str] + individual_holding_doc_front_file: str + last_name: str + postal_code: str + phone_number: Optional[str] + proof_of_address_doc_file: str + proof_of_address_doc_type: ProofOfAddressDocType + purpose_of_transactions: PurposeOfTransactions + source_of_funds_doc_file: str + source_of_funds_doc_type: SourceOfFundsDocType + purpose_of_transactions_explanation: Optional[str] + state_province_region: str + tax_id: str + tos_id: str + + +class CreateIndividualWithEnhancedKYCResponse(TypedDict): + id: str + + +class CreateBusinessWithStandardKYBInput(TypedDict): + external_id: Optional[str] + address_line_1: str + address_line_2: Optional[str] + alternate_name: str + city: str + country: Country + email: str + formation_date: str + incorporation_doc_file: str + legal_name: str + owners: List[Owner] + postal_code: str + proof_of_address_doc_file: str + proof_of_address_doc_type: ProofOfAddressDocType + proof_of_ownership_doc_file: str + state_province_region: str + tax_id: str + tos_id: str + website: Optional[str] + + +class CreateBusinessWithStandardKYBResponse(TypedDict): + id: str + + +ListReceiversResponse = List[Union[IndividualWithStandardKYC, IndividualWithEnhancedKYC, BusinessWithStandardKYB]] + +GetReceiverResponse = Union[IndividualWithStandardKYC, IndividualWithEnhancedKYC, BusinessWithStandardKYB] + + +class OwnerUpdate(TypedDict): + id: str + first_name: str + last_name: str + role: OwnerRole + date_of_birth: str + tax_id: str + address_line_1: str + address_line_2: Optional[str] + city: str + state_province_region: str + country: Country + postal_code: str + id_doc_country: Country + id_doc_type: IdentificationDocument + id_doc_front_file: str + id_doc_back_file: Optional[str] + + +class UpdateReceiverInput(TypedDict): + receiver_id: str + email: Optional[str] + tax_id: Optional[str] + address_line_1: Optional[str] + address_line_2: Optional[str] + city: Optional[str] + state_province_region: Optional[str] + country: Optional[Country] + postal_code: Optional[str] + ip_address: Optional[str] + image_url: Optional[str] + phone_number: Optional[str] + proof_of_address_doc_type: Optional[ProofOfAddressDocType] + proof_of_address_doc_file: Optional[str] + first_name: Optional[str] + last_name: Optional[str] + date_of_birth: Optional[str] + id_doc_country: Optional[Country] + id_doc_type: Optional[IdentificationDocument] + id_doc_front_file: Optional[str] + id_doc_back_file: Optional[str] + legal_name: Optional[str] + alternate_name: Optional[str] + formation_date: Optional[str] + website: Optional[str] + owners: Optional[List[OwnerUpdate]] + incorporation_doc_file: Optional[str] + proof_of_ownership_doc_file: Optional[str] + source_of_funds_doc_type: Optional[SourceOfFundsDocType] + source_of_funds_doc_file: Optional[str] + individual_holding_doc_front_file: Optional[str] + purpose_of_transactions: Optional[PurposeOfTransactions] + purpose_of_transactions_explanation: Optional[str] + external_id: Optional[str] + tos_id: Optional[str] + + +class PayinLimit(TypedDict): + daily: float + monthly: float + + +class PayoutLimit(TypedDict): + daily: float + monthly: float + + +class Limits(TypedDict): + payin: PayinLimit + payout: PayoutLimit + + +class GetReceiverLimitsResponse(TypedDict): + limits: Limits + + +class ReceiversResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self) -> BlindpayApiResponse[ListReceiversResponse]: + return await self._client.get(f"/instances/{self._instance_id}/receivers") + + async def create_individual_with_standard_kyc( + self, data: CreateIndividualWithStandardKYCInput + ) -> BlindpayApiResponse[CreateIndividualWithStandardKYCResponse]: + payload = {"kyc_type": "standard", "type": "individual", **data} + return await self._client.post(f"/instances/{self._instance_id}/receivers", payload) + + async def create_individual_with_enhanced_kyc( + self, data: CreateIndividualWithEnhancedKYCInput + ) -> BlindpayApiResponse[CreateIndividualWithEnhancedKYCResponse]: + payload = {"kyc_type": "enhanced", "type": "individual", **data} + return await self._client.post(f"/instances/{self._instance_id}/receivers", payload) + + async def create_business_with_standard_kyb( + self, data: CreateBusinessWithStandardKYBInput + ) -> BlindpayApiResponse[CreateBusinessWithStandardKYBResponse]: + payload = {"kyc_type": "standard", "type": "business", **data} + return await self._client.post(f"/instances/{self._instance_id}/receivers", payload) + + async def get(self, receiver_id: str) -> BlindpayApiResponse[GetReceiverResponse]: + return await self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}") + + async def update(self, data: UpdateReceiverInput) -> BlindpayApiResponse[None]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + return await self._client.patch(f"/instances/{self._instance_id}/receivers/{receiver_id}", payload) + + async def delete(self, receiver_id: str) -> BlindpayApiResponse[None]: + return await self._client.delete(f"/instances/{self._instance_id}/receivers/{receiver_id}") + + async def get_limits(self, receiver_id: str) -> BlindpayApiResponse[GetReceiverLimitsResponse]: + return await self._client.get(f"/instances/{self._instance_id}/limits/receivers/{receiver_id}") + + +class ReceiversResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self) -> BlindpayApiResponse[ListReceiversResponse]: + return self._client.get(f"/instances/{self._instance_id}/receivers") + + def create_individual_with_standard_kyc( + self, data: CreateIndividualWithStandardKYCInput + ) -> BlindpayApiResponse[CreateIndividualWithStandardKYCResponse]: + payload = {"kyc_type": "standard", "type": "individual", **data} + return self._client.post(f"/instances/{self._instance_id}/receivers", payload) + + def create_individual_with_enhanced_kyc( + self, data: CreateIndividualWithEnhancedKYCInput + ) -> BlindpayApiResponse[CreateIndividualWithEnhancedKYCResponse]: + payload = {"kyc_type": "enhanced", "type": "individual", **data} + return self._client.post(f"/instances/{self._instance_id}/receivers", payload) + + def create_business_with_standard_kyb( + self, data: CreateBusinessWithStandardKYBInput + ) -> BlindpayApiResponse[CreateBusinessWithStandardKYBResponse]: + payload = {"kyc_type": "standard", "type": "business", **data} + return self._client.post(f"/instances/{self._instance_id}/receivers", payload) + + def get(self, receiver_id: str) -> BlindpayApiResponse[GetReceiverResponse]: + return self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}") + + def update(self, data: UpdateReceiverInput) -> BlindpayApiResponse[None]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + return self._client.patch(f"/instances/{self._instance_id}/receivers/{receiver_id}", payload) + + def delete(self, receiver_id: str) -> BlindpayApiResponse[None]: + return self._client.delete(f"/instances/{self._instance_id}/receivers/{receiver_id}") + + def get_limits(self, receiver_id: str) -> BlindpayApiResponse[GetReceiverLimitsResponse]: + return self._client.get(f"/instances/{self._instance_id}/limits/receivers/{receiver_id}") + + +def create_receivers_resource(instance_id: str, client: InternalApiClient) -> ReceiversResource: + return ReceiversResource(instance_id, client) + + +def create_receivers_resource_sync(instance_id: str, client: InternalApiClientSync) -> ReceiversResourceSync: + return ReceiversResourceSync(instance_id, client) diff --git a/src/blindpay/resources/virtual_accounts/__init__.py b/src/blindpay/resources/virtual_accounts/__init__.py new file mode 100644 index 0000000..dc42220 --- /dev/null +++ b/src/blindpay/resources/virtual_accounts/__init__.py @@ -0,0 +1,31 @@ +from .virtual_accounts import ( + BankAccountInfo, + BeneficiaryInfo, + CreateVirtualAccountInput, + CreateVirtualAccountResponse, + GetVirtualAccountResponse, + ReceivingBankInfo, + UpdateVirtualAccountInput, + USBankDetails, + VirtualAccount, + VirtualAccountsResource, + VirtualAccountsResourceSync, + create_virtual_accounts_resource, + create_virtual_accounts_resource_sync, +) + +__all__ = [ + "create_virtual_accounts_resource", + "create_virtual_accounts_resource_sync", + "VirtualAccountsResource", + "VirtualAccountsResourceSync", + "VirtualAccount", + "CreateVirtualAccountInput", + "UpdateVirtualAccountInput", + "CreateVirtualAccountResponse", + "GetVirtualAccountResponse", + "USBankDetails", + "BeneficiaryInfo", + "ReceivingBankInfo", + "BankAccountInfo", +] diff --git a/src/blindpay/resources/virtual_accounts/virtual_accounts.py b/src/blindpay/resources/virtual_accounts/virtual_accounts.py new file mode 100644 index 0000000..e752feb --- /dev/null +++ b/src/blindpay/resources/virtual_accounts/virtual_accounts.py @@ -0,0 +1,106 @@ +from typing_extensions import Optional, TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse, StablecoinToken + + +class BankAccountInfo(TypedDict): + routing_number: str + account_number: str + + +class BeneficiaryInfo(TypedDict): + name: str + address_line_1: str + address_line_2: Optional[str] + + +class ReceivingBankInfo(TypedDict): + name: str + address_line_1: str + address_line_2: Optional[str] + + +class USBankDetails(TypedDict): + ach: BankAccountInfo + wire: BankAccountInfo + rtp: BankAccountInfo + swift_bic_code: str + account_type: str + beneficiary: BeneficiaryInfo + receiving_bank: ReceivingBankInfo + + +class VirtualAccount(TypedDict): + id: str + us: USBankDetails + token: StablecoinToken + blockchain_wallet_id: str + + +class CreateVirtualAccountInput(TypedDict): + receiver_id: str + blockchain_wallet_id: str + token: StablecoinToken + + +CreateVirtualAccountResponse = VirtualAccount +GetVirtualAccountResponse = VirtualAccount + + +class UpdateVirtualAccountInput(TypedDict): + receiver_id: str + blockchain_wallet_id: str + token: StablecoinToken + + +class VirtualAccountsResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def update(self, data: UpdateVirtualAccountInput) -> BlindpayApiResponse[None]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + return await self._client.put( + f"/instances/{self._instance_id}/receivers/{receiver_id}/virtual-accounts", payload + ) + + async def create(self, data: CreateVirtualAccountInput) -> BlindpayApiResponse[CreateVirtualAccountResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + return await self._client.post( + f"/instances/{self._instance_id}/receivers/{receiver_id}/virtual-accounts", payload + ) + + async def get(self, receiver_id: str) -> BlindpayApiResponse[GetVirtualAccountResponse]: + return await self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/virtual-accounts") + + +class VirtualAccountsResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def update(self, data: UpdateVirtualAccountInput) -> BlindpayApiResponse[None]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + return self._client.put(f"/instances/{self._instance_id}/receivers/{receiver_id}/virtual-accounts", payload) + + def create(self, data: CreateVirtualAccountInput) -> BlindpayApiResponse[CreateVirtualAccountResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/virtual-accounts", payload) + + def get(self, receiver_id: str) -> BlindpayApiResponse[GetVirtualAccountResponse]: + return self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/virtual-accounts") + + +def create_virtual_accounts_resource(instance_id: str, client: InternalApiClient) -> VirtualAccountsResource: + return VirtualAccountsResource(instance_id, client) + + +def create_virtual_accounts_resource_sync( + instance_id: str, client: InternalApiClientSync +) -> VirtualAccountsResourceSync: + return VirtualAccountsResourceSync(instance_id, client) diff --git a/src/blindpay/resources/wallets/__init__.py b/src/blindpay/resources/wallets/__init__.py new file mode 100644 index 0000000..e77e55f --- /dev/null +++ b/src/blindpay/resources/wallets/__init__.py @@ -0,0 +1,63 @@ +from .blockchain import ( + BlockchainWallet, + BlockchainWalletsResource, + BlockchainWalletsResourceSync, + CreateAssetTrustlineResponse, + CreateBlockchainWalletResponse, + CreateBlockchainWalletWithAddressInput, + CreateBlockchainWalletWithHashInput, + DeleteBlockchainWalletInput, + GetBlockchainWalletInput, + GetBlockchainWalletMessageResponse, + GetBlockchainWalletResponse, + ListBlockchainWalletsResponse, + MintUsdbStellarInput, + create_blockchain_wallets_resource, + create_blockchain_wallets_resource_sync, +) +from .offramp import ( + CreateOfframpWalletInput, + CreateOfframpWalletResponse, + GetOfframpWalletInput, + GetOfframpWalletResponse, + ListOfframpWalletsInput, + ListOfframpWalletsResponse, + OfframpWallet, + OfframpWalletsResource, + OfframpWalletsResourceSync, + create_offramp_wallets_resource, + create_offramp_wallets_resource_sync, +) + +__all__ = [ + "create_blockchain_wallets_resource", + "create_blockchain_wallets_resource_sync", + "BlockchainWalletsResource", + "BlockchainWalletsResourceSync", + "BlockchainWallet", + "CreateBlockchainWalletWithAddressInput", + "CreateBlockchainWalletWithHashInput", + "GetBlockchainWalletInput", + "DeleteBlockchainWalletInput", + "MintUsdbStellarInput", + "create_offramp_wallets_resource", + "create_offramp_wallets_resource_sync", + "OfframpWalletsResource", + "OfframpWalletsResourceSync", + "OfframpWallet", + "ListOfframpWalletsInput", + "CreateOfframpWalletInput", + "GetOfframpWalletInput", + "CreateOfframpWalletResponse", + "GetBlockchainWalletMessageResponse", + "GetBlockchainWalletResponse", + "ListBlockchainWalletsResponse", + "CreateBlockchainWalletResponse", + "CreateBlockchainWalletWithAddressInput", + "CreateBlockchainWalletWithHashInput", + "GetBlockchainWalletInput", + "DeleteBlockchainWalletInput", + "CreateAssetTrustlineResponse", + "ListOfframpWalletsResponse", + "GetOfframpWalletResponse", +] diff --git a/src/blindpay/resources/wallets/blockchain.py b/src/blindpay/resources/wallets/blockchain.py new file mode 100644 index 0000000..64da7e7 --- /dev/null +++ b/src/blindpay/resources/wallets/blockchain.py @@ -0,0 +1,167 @@ +from typing import List, Optional + +from typing_extensions import TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse, Network + + +class GetBlockchainWalletMessageResponse(TypedDict): + message: str + + +class BlockchainWallet(TypedDict): + id: str + name: str + network: Network + address: Optional[str] + signature_tx_hash: Optional[str] + is_account_abstraction: bool + receiver_id: str + + +ListBlockchainWalletsResponse = List[BlockchainWallet] +GetBlockchainWalletResponse = BlockchainWallet +CreateBlockchainWalletResponse = BlockchainWallet + + +class CreateBlockchainWalletWithAddressInput(TypedDict): + receiver_id: str + name: str + network: Network + address: str + + +class CreateBlockchainWalletWithHashInput(TypedDict): + receiver_id: str + name: str + network: Network + signature_tx_hash: str + + +class GetBlockchainWalletInput(TypedDict): + receiver_id: str + id: str + + +class DeleteBlockchainWalletInput(TypedDict): + receiver_id: str + id: str + + +class CreateAssetTrustlineResponse(TypedDict): + xdr: str + + +class MintUsdbStellarInput(TypedDict): + address: str + amount: str + signedXdr: str + + +class BlockchainWalletsResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self, receiver_id: str) -> BlindpayApiResponse[ListBlockchainWalletsResponse]: + return await self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets") + + async def create_with_address( + self, data: CreateBlockchainWalletWithAddressInput + ) -> BlindpayApiResponse[CreateBlockchainWalletResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["is_account_abstraction"] = True + return await self._client.post( + f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets", payload + ) + + async def create_with_hash( + self, data: CreateBlockchainWalletWithHashInput + ) -> BlindpayApiResponse[CreateBlockchainWalletResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["is_account_abstraction"] = False + return await self._client.post( + f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets", payload + ) + + async def get_wallet_message(self, receiver_id: str) -> BlindpayApiResponse[GetBlockchainWalletMessageResponse]: + return await self._client.get( + f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets/sign-message" + ) + + async def get(self, data: GetBlockchainWalletInput) -> BlindpayApiResponse[GetBlockchainWalletResponse]: + receiver_id = data["receiver_id"] + id = data["id"] + return await self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets/{id}") + + async def delete(self, data: DeleteBlockchainWalletInput) -> BlindpayApiResponse[None]: + receiver_id = data["receiver_id"] + id = data["id"] + return await self._client.delete( + f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets/{id}" + ) + + async def create_asset_trustline(self, address: str) -> BlindpayApiResponse[CreateAssetTrustlineResponse]: + return await self._client.post(f"/instances/{self._instance_id}/create-asset-trustline", {"address": address}) + + async def mint_usdb_stellar(self, data: MintUsdbStellarInput) -> BlindpayApiResponse[None]: + return await self._client.post(f"/instances/{self._instance_id}/mint-usdb-stellar", data) + + +class BlockchainWalletsResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self, receiver_id: str) -> BlindpayApiResponse[ListBlockchainWalletsResponse]: + return self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets") + + def create_with_address( + self, data: CreateBlockchainWalletWithAddressInput + ) -> BlindpayApiResponse[CreateBlockchainWalletResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["is_account_abstraction"] = True + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets", payload) + + def create_with_hash( + self, data: CreateBlockchainWalletWithHashInput + ) -> BlindpayApiResponse[CreateBlockchainWalletResponse]: + receiver_id = data["receiver_id"] + payload = {k: v for k, v in data.items() if k != "receiver_id"} + payload["is_account_abstraction"] = False + return self._client.post(f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets", payload) + + def get_wallet_message(self, receiver_id: str) -> BlindpayApiResponse[GetBlockchainWalletMessageResponse]: + return self._client.get( + f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets/sign-message" + ) + + def get(self, data: GetBlockchainWalletInput) -> BlindpayApiResponse[GetBlockchainWalletResponse]: + receiver_id = data["receiver_id"] + id = data["id"] + return self._client.get(f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets/{id}") + + def delete(self, data: DeleteBlockchainWalletInput) -> BlindpayApiResponse[None]: + receiver_id = data["receiver_id"] + id = data["id"] + return self._client.delete(f"/instances/{self._instance_id}/receivers/{receiver_id}/blockchain-wallets/{id}") + + def create_asset_trustline(self, address: str) -> BlindpayApiResponse[CreateAssetTrustlineResponse]: + return self._client.post(f"/instances/{self._instance_id}/create-asset-trustline", {"address": address}) + + def mint_usdb_stellar(self, data: MintUsdbStellarInput) -> BlindpayApiResponse[None]: + return self._client.post(f"/instances/{self._instance_id}/mint-usdb-stellar", data) + + +def create_blockchain_wallets_resource(instance_id: str, client: InternalApiClient) -> BlockchainWalletsResource: + return BlockchainWalletsResource(instance_id, client) + + +def create_blockchain_wallets_resource_sync( + instance_id: str, client: InternalApiClientSync +) -> BlockchainWalletsResourceSync: + return BlockchainWalletsResourceSync(instance_id, client) diff --git a/src/blindpay/resources/wallets/offramp.py b/src/blindpay/resources/wallets/offramp.py new file mode 100644 index 0000000..5b9d9c6 --- /dev/null +++ b/src/blindpay/resources/wallets/offramp.py @@ -0,0 +1,117 @@ +from typing import List + +from typing_extensions import Literal, TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse + +TronNetwork = Literal["tron"] + + +class OfframpWallet(TypedDict): + id: str + external_id: str + instance_id: str + receiver_id: str + bank_account_id: str + network: TronNetwork + address: str + created_at: str + updated_at: str + + +ListOfframpWalletsResponse = List[OfframpWallet] +GetOfframpWalletResponse = OfframpWallet + + +class ListOfframpWalletsInput(TypedDict): + receiver_id: str + bank_account_id: str + + +class CreateOfframpWalletInput(TypedDict): + receiver_id: str + bank_account_id: str + external_id: str + network: TronNetwork + + +class CreateOfframpWalletResponse(TypedDict): + id: str + external_id: str + network: TronNetwork + address: str + + +class GetOfframpWalletInput(TypedDict): + receiver_id: str + bank_account_id: str + id: str + + +class OfframpWalletsResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self, data: ListOfframpWalletsInput) -> BlindpayApiResponse[ListOfframpWalletsResponse]: + receiver_id = data["receiver_id"] + bank_account_id = data["bank_account_id"] + return await self._client.get( + f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{bank_account_id}/offramp-wallets" + ) + + async def create(self, data: CreateOfframpWalletInput) -> BlindpayApiResponse[CreateOfframpWalletResponse]: + receiver_id = data["receiver_id"] + bank_account_id = data["bank_account_id"] + payload = {k: v for k, v in data.items() if k not in ["receiver_id", "bank_account_id"]} + return await self._client.post( + f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{bank_account_id}/offramp-wallets", + payload, + ) + + async def get(self, data: GetOfframpWalletInput) -> BlindpayApiResponse[GetOfframpWalletResponse]: + receiver_id = data["receiver_id"] + bank_account_id = data["bank_account_id"] + id = data["id"] + return await self._client.get( + f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{bank_account_id}/offramp-wallets/{id}" + ) + + +class OfframpWalletsResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self, data: ListOfframpWalletsInput) -> BlindpayApiResponse[ListOfframpWalletsResponse]: + receiver_id = data["receiver_id"] + bank_account_id = data["bank_account_id"] + return self._client.get( + f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{bank_account_id}/offramp-wallets" + ) + + def create(self, data: CreateOfframpWalletInput) -> BlindpayApiResponse[CreateOfframpWalletResponse]: + receiver_id = data["receiver_id"] + bank_account_id = data["bank_account_id"] + payload = {k: v for k, v in data.items() if k not in ["receiver_id", "bank_account_id"]} + return self._client.post( + f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{bank_account_id}/offramp-wallets", + payload, + ) + + def get(self, data: GetOfframpWalletInput) -> BlindpayApiResponse[GetOfframpWalletResponse]: + receiver_id = data["receiver_id"] + bank_account_id = data["bank_account_id"] + id = data["id"] + return self._client.get( + f"/instances/{self._instance_id}/receivers/{receiver_id}/bank-accounts/{bank_account_id}/offramp-wallets/{id}" + ) + + +def create_offramp_wallets_resource(instance_id: str, client: InternalApiClient) -> OfframpWalletsResource: + return OfframpWalletsResource(instance_id, client) + + +def create_offramp_wallets_resource_sync(instance_id: str, client: InternalApiClientSync) -> OfframpWalletsResourceSync: + return OfframpWalletsResourceSync(instance_id, client) diff --git a/src/blindpay/resources/webhooks/__init__.py b/src/blindpay/resources/webhooks/__init__.py new file mode 100644 index 0000000..e78a8b5 --- /dev/null +++ b/src/blindpay/resources/webhooks/__init__.py @@ -0,0 +1,27 @@ +from .webhooks import ( + CreateWebhookEndpointInput, + CreateWebhookEndpointResponse, + GetPortalAccessUrlResponse, + GetWebhookEndpointSecretResponse, + ListWebhookEndpointsResponse, + WebhookEndpoint, + WebhookEndpointsResource, + WebhookEndpointsResourceSync, + WebhookEvents, + create_webhook_endpoints_resource, + create_webhook_endpoints_resource_sync, +) + +__all__ = [ + "create_webhook_endpoints_resource", + "create_webhook_endpoints_resource_sync", + "WebhookEndpointsResource", + "WebhookEndpointsResourceSync", + "WebhookEvents", + "CreateWebhookEndpointInput", + "CreateWebhookEndpointResponse", + "GetWebhookEndpointSecretResponse", + "GetPortalAccessUrlResponse", + "WebhookEndpoint", + "ListWebhookEndpointsResponse", +] diff --git a/src/blindpay/resources/webhooks/webhooks.py b/src/blindpay/resources/webhooks/webhooks.py new file mode 100644 index 0000000..269dea5 --- /dev/null +++ b/src/blindpay/resources/webhooks/webhooks.py @@ -0,0 +1,103 @@ +from typing import List + +from typing_extensions import Literal, TypedDict + +from ..._internal.api_client import InternalApiClient, InternalApiClientSync +from ...types import BlindpayApiResponse + +WebhookEvents = Literal[ + "receiver.new", + "receiver.update", + "bankAccount.new", + "payout.new", + "payout.update", + "payout.complete", + "payout.partnerFee", + "blockchainWallet.new", + "payin.new", + "payin.update", + "payin.complete", + "payin.partnerFee", +] + + +class CreateWebhookEndpointInput(TypedDict): + url: str + events: List[WebhookEvents] + + +class CreateWebhookEndpointResponse(TypedDict): + id: str + + +class WebhookEndpoint(TypedDict): + id: str + url: str + events: List[WebhookEvents] + last_event_at: str + instance_id: str + created_at: str + updated_at: str + + +ListWebhookEndpointsResponse = List[WebhookEndpoint] + + +class GetWebhookEndpointSecretResponse(TypedDict): + key: str + + +class GetPortalAccessUrlResponse(TypedDict): + url: str + + +class WebhookEndpointsResource: + def __init__(self, instance_id: str, client: InternalApiClient): + self._instance_id = instance_id + self._client = client + + async def list(self) -> BlindpayApiResponse[ListWebhookEndpointsResponse]: + return await self._client.get(f"/instances/{self._instance_id}/webhook-endpoints") + + async def create(self, data: CreateWebhookEndpointInput) -> BlindpayApiResponse[CreateWebhookEndpointResponse]: + return await self._client.post(f"/instances/{self._instance_id}/webhook-endpoints", data) + + async def delete(self, id: str) -> BlindpayApiResponse[None]: + return await self._client.delete(f"/instances/{self._instance_id}/webhook-endpoints/{id}") + + async def get_secret(self, id: str) -> BlindpayApiResponse[GetWebhookEndpointSecretResponse]: + return await self._client.get(f"/instances/{self._instance_id}/webhook-endpoints/{id}/secret") + + async def get_portal_access_url(self) -> BlindpayApiResponse[GetPortalAccessUrlResponse]: + return await self._client.get(f"/instances/{self._instance_id}/webhook-endpoints/portal-access") + + +class WebhookEndpointsResourceSync: + def __init__(self, instance_id: str, client: InternalApiClientSync): + self._instance_id = instance_id + self._client = client + + def list(self) -> BlindpayApiResponse[ListWebhookEndpointsResponse]: + return self._client.get(f"/instances/{self._instance_id}/webhook-endpoints") + + def create(self, data: CreateWebhookEndpointInput) -> BlindpayApiResponse[CreateWebhookEndpointResponse]: + return self._client.post(f"/instances/{self._instance_id}/webhook-endpoints", data) + + def delete(self, id: str) -> BlindpayApiResponse[None]: + return self._client.delete(f"/instances/{self._instance_id}/webhook-endpoints/{id}") + + def get_secret(self, id: str) -> BlindpayApiResponse[GetWebhookEndpointSecretResponse]: + return self._client.get(f"/instances/{self._instance_id}/webhook-endpoints/{id}/secret") + + def get_portal_access_url(self) -> BlindpayApiResponse[GetPortalAccessUrlResponse]: + return self._client.get(f"/instances/{self._instance_id}/webhook-endpoints/portal-access") + + +def create_webhook_endpoints_resource(instance_id: str, client: InternalApiClient) -> WebhookEndpointsResource: + return WebhookEndpointsResource(instance_id, client) + + +def create_webhook_endpoints_resource_sync( + instance_id: str, client: InternalApiClientSync +) -> WebhookEndpointsResourceSync: + return WebhookEndpointsResourceSync(instance_id, client) diff --git a/src/blindpay/types.py b/src/blindpay/types.py new file mode 100644 index 0000000..6cf0034 --- /dev/null +++ b/src/blindpay/types.py @@ -0,0 +1,361 @@ +from typing import Generic, Literal, TypeVar, Union + +from typing_extensions import TypedDict + +T = TypeVar("T") + + +class ErrorResponse(TypedDict): + message: str + + +class BlindpayErrorResponse(TypedDict): + data: None + error: ErrorResponse + + +class BlindpaySuccessResponse(TypedDict, Generic[T]): + data: T + error: None + + +BlindpayApiResponse = Union[BlindpayErrorResponse, BlindpaySuccessResponse[T]] + +CurrencyType = Literal["sender", "receiver"] + +Network = Literal[ + "base", + "sepolia", + "arbitrum_sepolia", + "base_sepolia", + "arbitrum", + "polygon", + "polygon_amoy", + "ethereum", + "stellar", + "stellar_testnet", + "tron", +] + +StablecoinToken = Literal["USDC", "USDT", "USDB"] + +TransactionDocumentType = Literal[ + "invoice", "purchase_order", "delivery_slip", "contract", "customs_declaration", "bill_of_lading", "others" +] + +BankAccountType = Literal["checking", "savings"] + +Currency = Literal["USDC", "USDT", "USDB", "BRL", "USD", "MXN", "COP", "ARS"] + +Rail = Literal["wire", "ach", "pix", "spei_bitso", "transfers_bitso", "ach_cop_bitso", "international_swift", "rtp"] + +AccountClass = Literal["individual", "business"] + +TransactionStatus = Literal["refunded", "processing", "completed", "failed", "on_hold"] + +SpeiProtocol = Literal["clabe", "debitcard", "phonenum"] + +Country = Literal[ + "AF", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BQ", + "BA", + "BW", + "BV", + "BR", + "IO", + "BN", + "BG", + "BF", + "BI", + "CV", + "KH", + "CM", + "CA", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CD", + "CG", + "CK", + "CR", + "HR", + "CU", + "CW", + "CY", + "CZ", + "CI", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "VA", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KP", + "KR", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "MK", + "RO", + "RU", + "RW", + "RE", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "SS", + "ES", + "LK", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UG", + "UA", + "AE", + "GB", + "UM", + "US", + "UY", + "UZ", + "VU", + "VE", + "VN", + "VG", + "VI", + "WF", + "EH", + "YE", + "ZM", + "ZW", + "AX", +] + +PaginationLimit = Literal["10", "50", "100", "200", "1000"] +PaginationOffset = Literal["0", "10", "50", "100", "200", "1000"] + + +class PaginationParams(TypedDict, total=False): + limit: PaginationLimit + offset: PaginationOffset + starting_after: str + ending_before: str + + +class PaginationMetadata(TypedDict): + has_more: bool + next_page: int + prev_page: int + + +class TrackingTransaction(TypedDict): + step: str + status: str + transaction_hash: str + completed_at: str + + +class TrackingPayment(TypedDict): + step: str + provider_name: str + provider_transaction_id: str + provider_status: str + estimated_time_of_arrival: str + completed_at: str + + +class TrackingLiquidity(TypedDict): + step: str + provider_transaction_id: str + provider_status: str + estimated_time_of_arrival: str + completed_at: str + + +class TrackingComplete(TypedDict): + step: str + status: str + transaction_hash: str + completed_at: str + + +class TrackingPartnerFee(TypedDict): + step: str + transaction_hash: str + completed_at: str diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..a33bd08 --- /dev/null +++ b/uv.lock @@ -0,0 +1,297 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "blindpay" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "pyright" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.23.0,<1" }, + { name = "pydantic", specifier = ">=1.9.0,<3" }, + { name = "typing-extensions", specifier = ">=4.11,<5" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy" }, + { name = "pyright", specifier = "==1.1.399" }, + { name = "ruff" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/54/ecab642b3bed45f7d5f59b38443dcb36ef50f85af192e6ece103dbfe9587/pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423", size = 788494, upload-time = "2025-10-04T10:40:41.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/1f/73c53fcbfb0b5a78f91176df41945ca466e71e9d9d836e5c522abda39ee7/pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a", size = 444823, upload-time = "2025-10-04T10:40:39.055Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.399" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/9d/d91d5f6d26b2db95476fefc772e2b9a16d54c6bd0ea6bb5c1b6d635ab8b4/pyright-1.1.399.tar.gz", hash = "sha256:439035d707a36c3d1b443aec980bc37053fbda88158eded24b8eedcf1c7b7a1b", size = 3856954, upload-time = "2025-04-10T04:40:25.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/b5/380380c9e7a534cb1783c70c3e8ac6d1193c599650a55838d0557586796e/pyright-1.1.399-py3-none-any.whl", hash = "sha256:55f9a875ddf23c9698f24208c764465ffdfd38be6265f7faf9a176e1dc549f3b", size = 5592584, upload-time = "2025-04-10T04:40:23.502Z" }, +] + +[[package]] +name = "ruff" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/8e/f9f9ca747fea8e3ac954e3690d4698c9737c23b51731d02df999c150b1c9/ruff-0.13.3.tar.gz", hash = "sha256:5b0ba0db740eefdfbcce4299f49e9eaefc643d4d007749d77d047c2bab19908e", size = 5438533, upload-time = "2025-10-02T19:29:31.582Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/33/8f7163553481466a92656d35dea9331095122bb84cf98210bef597dd2ecd/ruff-0.13.3-py3-none-linux_armv6l.whl", hash = "sha256:311860a4c5e19189c89d035638f500c1e191d283d0cc2f1600c8c80d6dcd430c", size = 12484040, upload-time = "2025-10-02T19:28:49.199Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b5/4a21a4922e5dd6845e91896b0d9ef493574cbe061ef7d00a73c61db531af/ruff-0.13.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2bdad6512fb666b40fcadb65e33add2b040fc18a24997d2e47fee7d66f7fcae2", size = 13122975, upload-time = "2025-10-02T19:28:52.446Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/15649af836d88c9f154e5be87e64ae7d2b1baa5a3ef317cb0c8fafcd882d/ruff-0.13.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fc6fa4637284708d6ed4e5e970d52fc3b76a557d7b4e85a53013d9d201d93286", size = 12346621, upload-time = "2025-10-02T19:28:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/bcbccb8141305f9a6d3f72549dd82d1134299177cc7eaf832599700f95a7/ruff-0.13.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c9e6469864f94a98f412f20ea143d547e4c652f45e44f369d7b74ee78185838", size = 12574408, upload-time = "2025-10-02T19:28:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/ce/19/0f3681c941cdcfa2d110ce4515624c07a964dc315d3100d889fcad3bfc9e/ruff-0.13.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5bf62b705f319476c78891e0e97e965b21db468b3c999086de8ffb0d40fd2822", size = 12285330, upload-time = "2025-10-02T19:28:58.79Z" }, + { url = "https://files.pythonhosted.org/packages/10/f8/387976bf00d126b907bbd7725219257feea58650e6b055b29b224d8cb731/ruff-0.13.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cc1abed87ce40cb07ee0667ce99dbc766c9f519eabfd948ed87295d8737c60", size = 13980815, upload-time = "2025-10-02T19:29:01.577Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a6/7c8ec09d62d5a406e2b17d159e4817b63c945a8b9188a771193b7e1cc0b5/ruff-0.13.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4fb75e7c402d504f7a9a259e0442b96403fa4a7310ffe3588d11d7e170d2b1e3", size = 14987733, upload-time = "2025-10-02T19:29:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/97/e5/f403a60a12258e0fd0c2195341cfa170726f254c788673495d86ab5a9a9d/ruff-0.13.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17b951f9d9afb39330b2bdd2dd144ce1c1335881c277837ac1b50bfd99985ed3", size = 14439848, upload-time = "2025-10-02T19:29:06.684Z" }, + { url = "https://files.pythonhosted.org/packages/39/49/3de381343e89364c2334c9f3268b0349dc734fc18b2d99a302d0935c8345/ruff-0.13.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6052f8088728898e0a449f0dde8fafc7ed47e4d878168b211977e3e7e854f662", size = 13421890, upload-time = "2025-10-02T19:29:08.767Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b5/c0feca27d45ae74185a6bacc399f5d8920ab82df2d732a17213fb86a2c4c/ruff-0.13.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc742c50f4ba72ce2a3be362bd359aef7d0d302bf7637a6f942eaa763bd292af", size = 13444870, upload-time = "2025-10-02T19:29:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/50/a1/b655298a1f3fda4fdc7340c3f671a4b260b009068fbeb3e4e151e9e3e1bf/ruff-0.13.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8e5640349493b378431637019366bbd73c927e515c9c1babfea3e932f5e68e1d", size = 13691599, upload-time = "2025-10-02T19:29:13.353Z" }, + { url = "https://files.pythonhosted.org/packages/32/b0/a8705065b2dafae007bcae21354e6e2e832e03eb077bb6c8e523c2becb92/ruff-0.13.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b139f638a80eae7073c691a5dd8d581e0ba319540be97c343d60fb12949c8d0", size = 12421893, upload-time = "2025-10-02T19:29:15.668Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/cbe7082588d025cddbb2f23e6dfef08b1a2ef6d6f8328584ad3015b5cebd/ruff-0.13.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6b547def0a40054825de7cfa341039ebdfa51f3d4bfa6a0772940ed351d2746c", size = 12267220, upload-time = "2025-10-02T19:29:17.583Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/4086f9c43f85e0755996d09bdcb334b6fee9b1eabdf34e7d8b877fadf964/ruff-0.13.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9cc48a3564423915c93573f1981d57d101e617839bef38504f85f3677b3a0a3e", size = 13177818, upload-time = "2025-10-02T19:29:19.943Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/7b5db7e39947d9dc1c5f9f17b838ad6e680527d45288eeb568e860467010/ruff-0.13.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1a993b17ec03719c502881cb2d5f91771e8742f2ca6de740034433a97c561989", size = 13618715, upload-time = "2025-10-02T19:29:22.527Z" }, + { url = "https://files.pythonhosted.org/packages/28/d3/bb25ee567ce2f61ac52430cf99f446b0e6d49bdfa4188699ad005fdd16aa/ruff-0.13.3-py3-none-win32.whl", hash = "sha256:f14e0d1fe6460f07814d03c6e32e815bff411505178a1f539a38f6097d3e8ee3", size = 12334488, upload-time = "2025-10-02T19:29:24.782Z" }, + { url = "https://files.pythonhosted.org/packages/cf/49/12f5955818a1139eed288753479ba9d996f6ea0b101784bb1fe6977ec128/ruff-0.13.3-py3-none-win_amd64.whl", hash = "sha256:621e2e5812b691d4f244638d693e640f188bacbb9bc793ddd46837cea0503dd2", size = 13455262, upload-time = "2025-10-02T19:29:26.882Z" }, + { url = "https://files.pythonhosted.org/packages/fe/72/7b83242b26627a00e3af70d0394d68f8f02750d642567af12983031777fc/ruff-0.13.3-py3-none-win_arm64.whl", hash = "sha256:9e9e9d699841eaf4c2c798fa783df2fabc680b72059a02ca0ed81c460bc58330", size = 12538484, upload-time = "2025-10-02T19:29:28.951Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +]