diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index dd939620..ff261bad 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} USER vscode -RUN curl -sSf https://rye-up.com/get | RYE_VERSION="0.24.0" RYE_INSTALL_OPTION="--yes" bash +RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH -RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc +RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbeb30b1..c17fdc16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,6 +24,9 @@ } } } + }, + "features": { + "ghcr.io/devcontainers/features/node:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65ca1794..b5b79a83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,41 +1,123 @@ name: CI on: push: - branches: - - main + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' pull_request: - branches: - - main + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: + timeout-minutes: 10 name: lint - runs-on: ubuntu-latest - if: github.repository == 'lithic-com/lithic-python' - + runs-on: ${{ github.repository == 'stainless-sdks/lithic-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 - name: Install Rye run: | - curl -sSf https://rye-up.com/get | bash + curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: 0.24.0 - RYE_INSTALL_OPTION: "--yes" + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' - name: Install dependencies + run: rye sync --all-features + + - name: Run lints + run: ./scripts/lint + + build: + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + runs-on: ${{ github.repository == 'stainless-sdks/lithic-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + steps: + - uses: actions/checkout@v4 + + - name: Install Rye run: | - rye sync --all-features + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run build + run: rye build + + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/lithic-python' + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: github.repository == 'stainless-sdks/lithic-python' + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + + test: + timeout-minutes: 10 + name: test + runs-on: ${{ github.repository == 'stainless-sdks/lithic-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + steps: + - uses: actions/checkout@v4 - - name: Run ruff + - name: Install Rye run: | - rye run check:ruff + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' - - name: Run type checking + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Run tests + run: ./scripts/test + + examples: + timeout-minutes: 10 + name: examples + runs-on: ${{ github.repository == 'stainless-sdks/lithic-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.repository == 'lithic-com/lithic-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) + + steps: + - uses: actions/checkout@v4 + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + - name: Install dependencies run: | - rye run typecheck + rye sync --all-features - - name: Ensure importable + - env: + LITHIC_API_KEY: ${{ secrets.LITHIC_API_KEY }} run: | - rye run python -c 'import lithic' + rye run python ./examples/transactions.py diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 6a3f9363..b7160d6a 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,15 +14,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rye run: | - curl -sSf https://rye-up.com/get | bash + curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: 0.24.0 - RYE_INSTALL_OPTION: "--yes" + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' - name: Publish to PyPI run: | diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 20d60569..82eba48b 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -1,6 +1,8 @@ name: Release Doctor on: pull_request: + branches: + - main workflow_dispatch: jobs: @@ -10,7 +12,7 @@ jobs: if: github.repository == 'lithic-com/lithic-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check release environment run: | diff --git a/.gitignore b/.gitignore index a4b2f8c0..95ceb189 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.vscode +.prism.log _dev __pycache__ @@ -12,3 +12,4 @@ dist .env .envrc codegen.log +Brewfile.lock.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1b5dc400..820b5c1a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.39.0" + ".": "0.112.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 8bdf2ea0..51bc2543 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1,4 @@ -configured_endpoints: 112 +configured_endpoints: 173 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-4fd8048b287f409ad2b91f7d0f0b7fc13cc9bc4ccc7859666f21203bab3d2f01.yml +openapi_spec_hash: a554c54d96a7604a770b6a8b1df46395 +config_hash: df0af4ff639b8a6923a6244d2247910c diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5b010307 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.importFormat": "relative", +} diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000..492ca37b --- /dev/null +++ b/Brewfile @@ -0,0 +1,2 @@ +brew "rye" + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45814ef0..d68a8975 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,13 @@ ### With Rye -We use [Rye](https://rye-up.com/) to manage dependencies so we highly recommend [installing it](https://rye-up.com/guide/installation/) as it will automatically provision a Python environment with the expected Python version. +We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run: -After installing Rye, you'll just have to run this command: +```sh +$ ./scripts/bootstrap +``` + +Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run: ```sh $ rye sync --all-features @@ -13,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix @@ -31,25 +34,25 @@ $ pip install -r requirements-dev.lock ## Modifying/Adding code -Most of the SDK is generated code, and any modified code will be overridden on the next generation. The -`src/lithic/lib/` and `examples/` directories are exceptions and will never be overridden. +Most of the SDK is generated code. Modifications to code will be persisted between generations, but may +result in merge conflicts between manual patches and changes from the generator. The generator will never +modify the contents of the `src/lithic/lib/` and `examples/` directories. ## Adding and running examples -All files in the `examples/` directory are not modified by the Stainless generator and can be freely edited or -added to. +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. -```bash +```py # add an example to examples/.py #!/usr/bin/env -S rye run python … ``` -``` -chmod +x examples/.py +```sh +$ chmod +x examples/.py # run the example against your api -./examples/.py +$ ./examples/.py ``` ## Using the repository from source @@ -58,8 +61,8 @@ If you’d like to use the repository from source, you can either install from g To install via git: -```bash -pip install git+ssh://git@github.com:lithic-com/lithic-python.git +```sh +$ pip install git+ssh://git@github.com/lithic-com/lithic-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -68,29 +71,29 @@ Building this package will create two files in the `dist/` directory, a `.tar.gz To create a distributable version of the library, all you have to do is run this command: -```bash -rye build +```sh +$ rye build # or -python -m build +$ python -m build ``` Then to install: ```sh -pip install ./path-to-wheel-file.whl +$ pip install ./path-to-wheel-file.whl ``` ## Running tests -Most tests will require you to [setup a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. -```bash +```sh # you will need npm installed -npx prism path/to/your/openapi.yml +$ npx prism mock path/to/your/openapi.yml ``` -```bash -rye run pytest +```sh +$ ./scripts/test ``` ## Linting and formatting @@ -100,14 +103,14 @@ This repository uses [ruff](https://github.com/astral-sh/ruff) and To lint: -```bash -rye run lint +```sh +$ ./scripts/lint ``` To format and fix all ruff issues automatically: -```bash -rye run format +```sh +$ ./scripts/format ``` ## Publishing and releases @@ -117,9 +120,9 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/lithic-com/lithic-python/actions/workflows/publish-pypi.yml). This will require a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/lithic-com/lithic-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. ### Publish manually -If you need to manually release a package, you can run the `bin/publish-pypi` script with an `PYPI_TOKEN` set on +If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on the environment. diff --git a/LICENSE b/LICENSE index bca930a9..06eee64e 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 Lithic + Copyright 2025 Lithic Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 1b6bf7a5..eb167082 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ # Lithic Python API library -[![PyPI version](https://img.shields.io/pypi/v/lithic.svg)](https://pypi.org/project/lithic/) + +[![PyPI version](https://img.shields.io/pypi/v/lithic.svg?label=pypi%20(stable))](https://pypi.org/project/lithic/) -The Lithic Python library provides convenient access to the Lithic REST API from any Python 3.7+ +The Lithic Python library provides convenient access to the Lithic REST API from any Python 3.9+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). ## Documentation -The REST API documentation can be found [on docs.lithic.com](https://docs.lithic.com). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). The full API of this library can be found in [api.md](api.md). ## Installation ```sh +# install from PyPI pip install lithic ``` @@ -25,8 +27,7 @@ import os from lithic import Lithic client = Lithic( - # This is the default and can be omitted - api_key=os.environ.get("LITHIC_API_KEY"), + api_key=os.environ.get("LITHIC_API_KEY"), # This is the default and can be omitted # defaults to "production". environment="sandbox", ) @@ -52,8 +53,7 @@ import asyncio from lithic import AsyncLithic client = AsyncLithic( - # This is the default and can be omitted - api_key=os.environ.get("LITHIC_API_KEY"), + api_key=os.environ.get("LITHIC_API_KEY"), # This is the default and can be omitted # defaults to "production". environment="sandbox", ) @@ -71,12 +71,46 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from PyPI +pip install lithic[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from lithic import DefaultAioHttpClient +from lithic import AsyncLithic + + +async def main() -> None: + async with AsyncLithic( + api_key=os.environ.get("LITHIC_API_KEY"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + card = await client.cards.create( + type="SINGLE_USE", + ) + print(card.token) + + +asyncio.run(main()) +``` + ## Using types -Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev), which provide helper methods for things like: +Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: -- Serializing back into JSON, `model.model_dump_json(indent=2, exclude_unset=True)` -- Converting to a dictionary, `model.model_dump(exclude_unset=True)` +- Serializing back into JSON, `model.to_json()` +- Converting to a dictionary, `model.to_dict()` Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. @@ -87,7 +121,7 @@ List methods in the Lithic API are paginated. This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually: ```python -import lithic +from lithic import Lithic client = Lithic() @@ -103,7 +137,7 @@ Or, asynchronously: ```python import asyncio -import lithic +from lithic import AsyncLithic client = AsyncLithic() @@ -153,32 +187,17 @@ from lithic import Lithic client = Lithic() card = client.cards.create( - type="VIRTUAL", + type="PHYSICAL", + shipping_address={ + "address1": "123", + "city": "NEW YORK", + "country": "USA", + "first_name": "Johnny", + "last_name": "Appleseed", + "postal_code": "10001", + "state": "NY", + }, ) -print(card.product_id) -``` - -## Webhook Verification - -We provide helper methods for verifying that a webhook request came from Lithic, and not a malicious third party. - -You can use `lithic.webhooks.verify_signature(body: string, headers, secret?) -> None` or `lithic.webhooks.unwrap(body: string, headers, secret?) -> Payload`, -both of which will raise an error if the signature is invalid. - -Note that the "body" parameter must be the raw JSON string sent from the server (do not parse it first). -The `.unwrap()` method can parse this JSON for you into a `Payload` object. - -For example, in [FastAPI](https://fastapi.tiangolo.com/): - -```py -@app.post('/my-webhook-handler') -async def handler(request: Request): - body = await request.body() - secret = os.environ['LITHIC_WEBHOOK_SECRET'] # env var used by default; explicit here. - payload = client.webhooks.unwrap(body, request.headers, secret) - print(payload) - - return {'ok': True} ``` ## Handling errors @@ -198,7 +217,7 @@ client = Lithic() try: client.cards.create( - type="an_incorrect_type", + type="MERCHANT_LOCKED", ) except lithic.APIConnectionError as e: print("The server could not be reached") @@ -211,7 +230,7 @@ except lithic.APIStatusError as e: print(e.response) ``` -Error codes are as followed: +Error codes are as follows: | Status Code | Error Type | | ----------- | -------------------------- | @@ -250,7 +269,7 @@ client.with_options(max_retries=5).cards.list( ### Timeouts By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from lithic import Lithic @@ -267,7 +286,7 @@ client = Lithic( ) # Override per-request: -client.with_options(timeout=5 * 1000).cards.list( +client.with_options(timeout=5.0).cards.list( page_size=10, ) ``` @@ -296,12 +315,14 @@ client = Lithic( We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. -You can enable logging by setting the environment variable `LITHIC_LOG` to `debug`. +You can enable logging by setting the environment variable `LITHIC_LOG` to `info`. ```shell -$ export LITHIC_LOG=debug +$ export LITHIC_LOG=info ``` +Or to `debug` for more verbose logging. + ### How to tell whether `None` means `null` or missing In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`: @@ -331,7 +352,7 @@ card = response.parse() # get the object that `cards.create()` would have retur print(card.token) ``` -These methods return an [`LegacyAPIResponse`](https://github.com/lithic-com/lithic-python/tree/main/src/lithic/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. +These methods return a [`LegacyAPIResponse`](https://github.com/lithic-com/lithic-python/tree/main/src/lithic/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. For the sync client this will mostly be the same with the exception of `content` & `text` will be methods instead of properties. In the @@ -360,44 +381,109 @@ with client.cards.with_streaming_response.create( The context manager is required so that the response will reliably be closed. +### Making custom/undocumented requests + +This library is typed for convenient access to the documented API. + +If you need to access undocumented endpoints, params, or response properties, the library can still be used. + +#### Undocumented endpoints + +To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other +http verbs. Options on the client will be respected (such as retries) when making this request. + +```py +import httpx + +response = client.post( + "/foo", + cast_to=httpx.Response, + body={"my_param": True}, +) + +print(response.headers.get("x-foo")) +``` + +#### Undocumented request params + +If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request +options. + +#### Undocumented response properties + +To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You +can also get all the extra fields on the Pydantic model as a dict with +[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra). + ### Configuring the HTTP client You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: -- Support for proxies -- Custom transports -- Additional [advanced](https://www.python-httpx.org/advanced/#client-instances) functionality +- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) +- Custom [transports](https://www.python-httpx.org/advanced/transports/) +- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality ```python import httpx -from lithic import Lithic +from lithic import Lithic, DefaultHttpxClient client = Lithic( # Or use the `LITHIC_BASE_URL` env var base_url="http://my.test.server.example.com:8083", - http_client=httpx.Client( - proxies="http://my.test.proxy.example.com", + http_client=DefaultHttpxClient( + proxy="http://my.test.proxy.example.com", transport=httpx.HTTPTransport(local_address="0.0.0.0"), ), ) ``` +You can also customize the client on a per-request basis by using `with_options()`: + +```python +client.with_options(http_client=DefaultHttpxClient(...)) +``` + ### Managing HTTP resources By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. +```py +from lithic import Lithic + +with Lithic() as client: + # make requests here + ... + +# HTTP client is now closed +``` + ## Versioning This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_. +2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 3. Changes that we do not expect to impact the vast majority of users in practice. We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. We are keen for your feedback; please open an [issue](https://www.github.com/lithic-com/lithic-python/issues) with questions, bugs, or suggestions. +### Determining the installed version + +If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version. + +You can determine the version that is being used at runtime with: + +```py +import lithic +print(lithic.__version__) +``` + ## Requirements -Python 3.7 or higher. +Python 3.9 or higher. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..d586eacc --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting Security Issues + +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. + +To report a security issue, please contact the Stainless team at security@stainless.com. + +## Responsible Disclosure + +We appreciate the efforts of security researchers and individuals who help us maintain the security of +SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible +disclosure practices by allowing us a reasonable amount of time to investigate and address the issue +before making any information public. + +## Reporting Non-SDK Related Security Issues + +If you encounter security issues that are not directly related to SDKs but pertain to the services +or products provided by Lithic, please follow the respective company's security reporting guidelines. + +### Lithic Terms and Policies + +Please contact sdk-feedback@lithic.com for any questions or concerns regarding the security of our services. + +--- + +Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/api.md b/api.md index 21359f56..ebac625f 100644 --- a/api.md +++ b/api.md @@ -1,7 +1,17 @@ # Shared Types ```python -from lithic.types import Address, Carrier, ShippingAddress +from lithic.types import ( + AccountFinancialAccountType, + Address, + Carrier, + Currency, + Document, + FinancialEvent, + InstanceFinancialAccountType, + Merchant, + ShippingAddress, +) ``` # Lithic @@ -14,29 +24,22 @@ from lithic.types import APIStatus Methods: -- client.api_status() -> APIStatus +- client.api_status() -> APIStatus # Accounts Types: ```python -from lithic.types import Account, AccountSpendLimits, BusinessAccount +from lithic.types import Account, AccountSpendLimits ``` Methods: -- client.accounts.retrieve(account_token) -> Account -- client.accounts.update(account_token, \*\*params) -> Account -- client.accounts.list(\*\*params) -> SyncCursorPage[Account] -- client.accounts.retrieve_spend_limits(account_token) -> AccountSpendLimits - -## CreditConfigurations - -Methods: - -- client.accounts.credit_configurations.retrieve(account_token) -> BusinessAccount -- client.accounts.credit_configurations.update(account_token, \*\*params) -> BusinessAccount +- client.accounts.retrieve(account_token) -> Account +- client.accounts.update(account_token, \*\*params) -> Account +- client.accounts.list(\*\*params) -> SyncCursorPage[Account] +- client.accounts.retrieve_spend_limits(account_token) -> AccountSpendLimits # AccountHolders @@ -45,43 +48,83 @@ Types: ```python from lithic.types import ( AccountHolder, - AccountHolderDocument, + AddressUpdate, KYB, + KYBBusinessEntity, KYC, KYCExempt, + RequiredDocument, AccountHolderCreateResponse, AccountHolderUpdateResponse, AccountHolderListDocumentsResponse, + AccountHolderSimulateEnrollmentReviewResponse, ) ``` Methods: -- client.account_holders.create(\*\*params) -> AccountHolderCreateResponse -- client.account_holders.retrieve(account_holder_token) -> AccountHolder -- client.account_holders.update(account_holder_token, \*\*params) -> AccountHolderUpdateResponse -- client.account_holders.list(\*\*params) -> SyncSinglePage[AccountHolder] -- client.account_holders.list_documents(account_holder_token) -> AccountHolderListDocumentsResponse -- client.account_holders.resubmit(account_holder_token, \*\*params) -> AccountHolder -- client.account_holders.retrieve_document(document_token, \*, account_holder_token) -> AccountHolderDocument -- client.account_holders.upload_document(account_holder_token, \*\*params) -> AccountHolderDocument +- client.account_holders.create(\*\*params) -> AccountHolderCreateResponse +- client.account_holders.retrieve(account_holder_token) -> AccountHolder +- client.account_holders.update(account_holder_token, \*\*params) -> AccountHolderUpdateResponse +- client.account_holders.list(\*\*params) -> SyncSinglePage[AccountHolder] +- client.account_holders.list_documents(account_holder_token) -> AccountHolderListDocumentsResponse +- client.account_holders.retrieve_document(document_token, \*, account_holder_token) -> Document +- client.account_holders.simulate_enrollment_document_review(\*\*params) -> Document +- client.account_holders.simulate_enrollment_review(\*\*params) -> AccountHolderSimulateEnrollmentReviewResponse +- client.account_holders.upload_document(account_holder_token, \*\*params) -> Document # AuthRules +## V2 + +Types: + +```python +from lithic.types.auth_rules import ( + AuthRule, + AuthRuleCondition, + Conditional3DSActionParameters, + ConditionalACHActionParameters, + ConditionalAttribute, + ConditionalAuthorizationActionParameters, + ConditionalBlockParameters, + ConditionalOperation, + ConditionalTokenizationActionParameters, + ConditionalValue, + EventStream, + MerchantLockParameters, + RuleStats, + VelocityLimitParams, + VelocityLimitPeriod, + V2RetrieveFeaturesResponse, + V2RetrieveReportResponse, +) +``` + +Methods: + +- client.auth_rules.v2.create(\*\*params) -> AuthRule +- client.auth_rules.v2.retrieve(auth_rule_token) -> AuthRule +- client.auth_rules.v2.update(auth_rule_token, \*\*params) -> AuthRule +- client.auth_rules.v2.list(\*\*params) -> SyncCursorPage[AuthRule] +- client.auth_rules.v2.delete(auth_rule_token) -> None +- client.auth_rules.v2.draft(auth_rule_token, \*\*params) -> AuthRule +- client.auth_rules.v2.promote(auth_rule_token) -> AuthRule +- client.auth_rules.v2.retrieve_features(auth_rule_token, \*\*params) -> V2RetrieveFeaturesResponse +- client.auth_rules.v2.retrieve_report(auth_rule_token, \*\*params) -> V2RetrieveReportResponse + +### Backtests + Types: ```python -from lithic.types import AuthRule, AuthRuleRetrieveResponse, AuthRuleRemoveResponse +from lithic.types.auth_rules.v2 import BacktestResults, BacktestCreateResponse ``` Methods: -- client.auth_rules.create(\*\*params) -> AuthRule -- client.auth_rules.retrieve(auth_rule_token) -> AuthRuleRetrieveResponse -- client.auth_rules.update(auth_rule_token, \*\*params) -> AuthRule -- client.auth_rules.list(\*\*params) -> SyncCursorPage[AuthRule] -- client.auth_rules.apply(auth_rule_token, \*\*params) -> AuthRule -- client.auth_rules.remove(\*\*params) -> AuthRuleRemoveResponse +- client.auth_rules.v2.backtests.create(auth_rule_token, \*\*params) -> BacktestCreateResponse +- client.auth_rules.v2.backtests.retrieve(auth_rule_backtest_token, \*, auth_rule_token) -> BacktestResults # AuthStreamEnrollment @@ -93,8 +136,8 @@ from lithic.types import AuthStreamSecret Methods: -- client.auth_stream_enrollment.retrieve_secret() -> AuthStreamSecret -- client.auth_stream_enrollment.rotate_secret() -> None +- client.auth_stream_enrollment.retrieve_secret() -> AuthStreamSecret +- client.auth_stream_enrollment.rotate_secret() -> None # TokenizationDecisioning @@ -106,22 +149,36 @@ from lithic.types import TokenizationSecret, TokenizationDecisioningRotateSecret Methods: -- client.tokenization_decisioning.retrieve_secret() -> TokenizationSecret -- client.tokenization_decisioning.rotate_secret() -> TokenizationDecisioningRotateSecretResponse +- client.tokenization_decisioning.retrieve_secret() -> TokenizationSecret +- client.tokenization_decisioning.rotate_secret() -> TokenizationDecisioningRotateSecretResponse # Tokenizations Types: ```python -from lithic.types import Tokenization, TokenizationRetrieveResponse, TokenizationSimulateResponse +from lithic.types import ( + Device, + DigitalWalletTokenMetadata, + Tokenization, + TokenizationDeclineReason, + TokenizationRuleResult, + TokenizationTfaReason, + WalletDecisioningInfo, +) ``` Methods: -- client.tokenizations.retrieve(tokenization_token) -> TokenizationRetrieveResponse -- client.tokenizations.list(\*\*params) -> SyncCursorPage[Tokenization] -- client.tokenizations.simulate(\*\*params) -> TokenizationSimulateResponse +- client.tokenizations.retrieve(tokenization_token) -> Tokenization +- client.tokenizations.list(\*\*params) -> SyncCursorPage[Tokenization] +- client.tokenizations.activate(tokenization_token) -> None +- client.tokenizations.deactivate(tokenization_token) -> None +- client.tokenizations.pause(tokenization_token) -> None +- client.tokenizations.resend_activation_code(tokenization_token, \*\*params) -> None +- client.tokenizations.simulate(\*\*params) -> Tokenization +- client.tokenizations.unpause(tokenization_token) -> None +- client.tokenizations.update_digital_card_art(tokenization_token, \*\*params) -> Tokenization # Cards @@ -131,52 +188,57 @@ Types: from lithic.types import ( Card, CardSpendLimits, - EmbedRequestParams, + NonPCICard, + ProvisionResponse, SpendLimitDuration, CardEmbedResponse, CardProvisionResponse, + CardWebProvisionResponse, ) ``` Methods: -- client.cards.create(\*\*params) -> Card -- client.cards.retrieve(card_token) -> Card -- client.cards.update(card_token, \*\*params) -> Card -- client.cards.list(\*\*params) -> SyncCursorPage[Card] -- client.cards.embed(\*\*params) -> str -- client.cards.provision(card_token, \*\*params) -> CardProvisionResponse -- client.cards.reissue(card_token, \*\*params) -> Card -- client.cards.renew(card_token, \*\*params) -> Card -- client.cards.retrieve_spend_limits(card_token) -> CardSpendLimits -- client.cards.search_by_pan(\*\*params) -> Card -- client.cards.get_embed_html(\*args) -> str -- client.cards.get_embed_url(\*args) -> URL - -## AggregateBalances - -Types: +- client.cards.create(\*\*params) -> Card +- client.cards.retrieve(card_token) -> Card +- client.cards.update(card_token, \*\*params) -> Card +- client.cards.list(\*\*params) -> SyncCursorPage[NonPCICard] +- client.cards.convert_physical(card_token, \*\*params) -> Card +- client.cards.embed(\*\*params) -> str +- client.cards.provision(card_token, \*\*params) -> CardProvisionResponse +- client.cards.reissue(card_token, \*\*params) -> Card +- client.cards.renew(card_token, \*\*params) -> Card +- client.cards.retrieve_spend_limits(card_token) -> CardSpendLimits +- client.cards.search_by_pan(\*\*params) -> Card +- client.cards.web_provision(card_token, \*\*params) -> CardWebProvisionResponse -```python -from lithic.types.cards import AggregateBalanceListResponse -``` +## Balances Methods: -- client.cards.aggregate_balances.list(\*\*params) -> SyncSinglePage[AggregateBalanceListResponse] +- client.cards.balances.list(card_token, \*\*params) -> SyncSinglePage[FinancialAccountBalance] -## Balances +## FinancialTransactions Methods: -- client.cards.balances.list(card_token, \*\*params) -> SyncSinglePage[Balance] +- client.cards.financial_transactions.retrieve(financial_transaction_token, \*, card_token) -> FinancialTransaction +- client.cards.financial_transactions.list(card_token, \*\*params) -> SyncSinglePage[FinancialTransaction] -## FinancialTransactions +# CardBulkOrders + +Types: + +```python +from lithic.types import CardBulkOrder +``` Methods: -- client.cards.financial_transactions.retrieve(financial_transaction_token, \*, card_token) -> FinancialTransaction -- client.cards.financial_transactions.list(card_token, \*\*params) -> SyncSinglePage[FinancialTransaction] +- client.card_bulk_orders.create(\*\*params) -> CardBulkOrder +- client.card_bulk_orders.retrieve(bulk_order_token) -> CardBulkOrder +- client.card_bulk_orders.update(bulk_order_token, \*\*params) -> CardBulkOrder +- client.card_bulk_orders.list(\*\*params) -> SyncCursorPage[CardBulkOrder] # Balances @@ -188,40 +250,40 @@ from lithic.types import Balance Methods: -- client.balances.list(\*\*params) -> SyncSinglePage[Balance] +- client.balances.list(\*\*params) -> SyncSinglePage[Balance] -# AggregateBalances +# Disputes Types: ```python -from lithic.types import AggregateBalance +from lithic.types import Dispute, DisputeEvidence ``` Methods: -- client.aggregate_balances.list(\*\*params) -> SyncSinglePage[AggregateBalance] +- client.disputes.create(\*\*params) -> Dispute +- client.disputes.retrieve(dispute_token) -> Dispute +- client.disputes.update(dispute_token, \*\*params) -> Dispute +- client.disputes.list(\*\*params) -> SyncCursorPage[Dispute] +- client.disputes.delete(dispute_token) -> Dispute +- client.disputes.delete_evidence(evidence_token, \*, dispute_token) -> DisputeEvidence +- client.disputes.initiate_evidence_upload(dispute_token, \*\*params) -> DisputeEvidence +- client.disputes.list_evidences(dispute_token, \*\*params) -> SyncCursorPage[DisputeEvidence] +- client.disputes.retrieve_evidence(evidence_token, \*, dispute_token) -> DisputeEvidence -# Disputes +# DisputesV2 Types: ```python -from lithic.types import Dispute, DisputeEvidence +from lithic.types import DisputeV2 ``` Methods: -- client.disputes.create(\*\*params) -> Dispute -- client.disputes.retrieve(dispute_token) -> Dispute -- client.disputes.update(dispute_token, \*\*params) -> Dispute -- client.disputes.list(\*\*params) -> SyncCursorPage[Dispute] -- client.disputes.delete(dispute_token) -> Dispute -- client.disputes.delete_evidence(evidence_token, \*, dispute_token) -> DisputeEvidence -- client.disputes.initiate_evidence_upload(dispute_token, \*\*params) -> DisputeEvidence -- client.disputes.list_evidences(dispute_token, \*\*params) -> SyncCursorPage[DisputeEvidence] -- client.disputes.retrieve_evidence(evidence_token, \*, dispute_token) -> DisputeEvidence -- client.disputes.upload_evidence(\*args) -> None +- client.disputes_v2.retrieve(dispute_token) -> DisputeV2 +- client.disputes_v2.list(\*\*params) -> SyncCursorPage[DisputeV2] # Events @@ -233,10 +295,9 @@ from lithic.types import Event, EventSubscription, MessageAttempt Methods: -- client.events.retrieve(event_token) -> Event -- client.events.list(\*\*params) -> SyncCursorPage[Event] -- client.events.list_attempts(event_token, \*\*params) -> SyncCursorPage[MessageAttempt] -- client.events.resend(\*args) -> None +- client.events.retrieve(event_token) -> Event +- client.events.list(\*\*params) -> SyncCursorPage[Event] +- client.events.list_attempts(event_token, \*\*params) -> SyncCursorPage[MessageAttempt] ## Subscriptions @@ -248,70 +309,110 @@ from lithic.types.events import SubscriptionRetrieveSecretResponse Methods: -- client.events.subscriptions.create(\*\*params) -> EventSubscription -- client.events.subscriptions.retrieve(event_subscription_token) -> EventSubscription -- client.events.subscriptions.update(event_subscription_token, \*\*params) -> EventSubscription -- client.events.subscriptions.list(\*\*params) -> SyncCursorPage[EventSubscription] -- client.events.subscriptions.delete(event_subscription_token) -> None -- client.events.subscriptions.list_attempts(event_subscription_token, \*\*params) -> SyncCursorPage[MessageAttempt] -- client.events.subscriptions.recover(event_subscription_token, \*\*params) -> None -- client.events.subscriptions.replay_missing(event_subscription_token, \*\*params) -> None -- client.events.subscriptions.retrieve_secret(event_subscription_token) -> SubscriptionRetrieveSecretResponse -- client.events.subscriptions.rotate_secret(event_subscription_token) -> None -- client.events.subscriptions.send_simulated_example(event_subscription_token, \*\*params) -> None +- client.events.subscriptions.create(\*\*params) -> EventSubscription +- client.events.subscriptions.retrieve(event_subscription_token) -> EventSubscription +- client.events.subscriptions.update(event_subscription_token, \*\*params) -> EventSubscription +- client.events.subscriptions.list(\*\*params) -> SyncCursorPage[EventSubscription] +- client.events.subscriptions.delete(event_subscription_token) -> None +- client.events.subscriptions.list_attempts(event_subscription_token, \*\*params) -> SyncCursorPage[MessageAttempt] +- client.events.subscriptions.recover(event_subscription_token, \*\*params) -> None +- client.events.subscriptions.replay_missing(event_subscription_token, \*\*params) -> None +- client.events.subscriptions.retrieve_secret(event_subscription_token) -> SubscriptionRetrieveSecretResponse +- client.events.subscriptions.rotate_secret(event_subscription_token) -> None +- client.events.subscriptions.send_simulated_example(event_subscription_token, \*\*params) -> None + +## EventSubscriptions + +Methods: + +- client.events.event_subscriptions.resend(event_subscription_token, \*, event_token) -> None # FinancialAccounts Types: ```python -from lithic.types import FinancialAccount, FinancialTransaction +from lithic.types import ( + CategoryDetails, + FinancialAccount, + FinancialAccountBalance, + FinancialTransaction, + StatementTotals, +) ``` Methods: -- client.financial_accounts.create(\*\*params) -> FinancialAccount -- client.financial_accounts.retrieve(financial_account_token) -> FinancialAccount -- client.financial_accounts.update(financial_account_token, \*\*params) -> FinancialAccount -- client.financial_accounts.list(\*\*params) -> SyncSinglePage[FinancialAccount] +- client.financial_accounts.create(\*\*params) -> FinancialAccount +- client.financial_accounts.retrieve(financial_account_token) -> FinancialAccount +- client.financial_accounts.update(financial_account_token, \*\*params) -> FinancialAccount +- client.financial_accounts.list(\*\*params) -> SyncSinglePage[FinancialAccount] +- client.financial_accounts.register_account_number(financial_account_token, \*\*params) -> None +- client.financial_accounts.update_status(financial_account_token, \*\*params) -> FinancialAccount ## Balances Methods: -- client.financial_accounts.balances.list(financial_account_token, \*\*params) -> SyncSinglePage[Balance] +- client.financial_accounts.balances.list(financial_account_token, \*\*params) -> SyncSinglePage[FinancialAccountBalance] ## FinancialTransactions Methods: -- client.financial_accounts.financial_transactions.retrieve(financial_transaction_token, \*, financial_account_token) -> FinancialTransaction -- client.financial_accounts.financial_transactions.list(financial_account_token, \*\*params) -> SyncSinglePage[FinancialTransaction] +- client.financial_accounts.financial_transactions.retrieve(financial_transaction_token, \*, financial_account_token) -> FinancialTransaction +- client.financial_accounts.financial_transactions.list(financial_account_token, \*\*params) -> SyncSinglePage[FinancialTransaction] + +## CreditConfiguration + +Types: + +```python +from lithic.types.financial_accounts import FinancialAccountCreditConfig +``` + +Methods: + +- client.financial_accounts.credit_configuration.retrieve(financial_account_token) -> FinancialAccountCreditConfig +- client.financial_accounts.credit_configuration.update(financial_account_token, \*\*params) -> FinancialAccountCreditConfig ## Statements Types: ```python -from lithic.types.financial_accounts import Statement +from lithic.types.financial_accounts import Statement, Statements ``` Methods: -- client.financial_accounts.statements.retrieve(statement_token, \*, financial_account_token) -> Statement -- client.financial_accounts.statements.list(financial_account_token, \*\*params) -> SyncCursorPage[Statement] +- client.financial_accounts.statements.retrieve(statement_token, \*, financial_account_token) -> Statement +- client.financial_accounts.statements.list(financial_account_token, \*\*params) -> SyncCursorPage[Statement] ### LineItems Types: ```python -from lithic.types.financial_accounts.statements import LineItemListResponse +from lithic.types.financial_accounts.statements import StatementLineItems +``` + +Methods: + +- client.financial_accounts.statements.line_items.list(statement_token, \*, financial_account_token, \*\*params) -> SyncCursorPage[Data] + +## LoanTapes + +Types: + +```python +from lithic.types.financial_accounts import CategoryBalances, LoanTape ``` Methods: -- client.financial_accounts.statements.line_items.list(statement_token, \*, financial_account_token, \*\*params) -> SyncCursorPage[LineItemListResponse] +- client.financial_accounts.loan_tapes.retrieve(loan_tape_token, \*, financial_account_token) -> LoanTape +- client.financial_accounts.loan_tapes.list(financial_account_token, \*\*params) -> SyncCursorPage[LoanTape] # Transactions @@ -319,11 +420,14 @@ Types: ```python from lithic.types import ( + CardholderAuthentication, + TokenInfo, Transaction, TransactionSimulateAuthorizationResponse, TransactionSimulateAuthorizationAdviceResponse, TransactionSimulateClearingResponse, TransactionSimulateCreditAuthorizationResponse, + TransactionSimulateCreditAuthorizationAdviceResponse, TransactionSimulateReturnResponse, TransactionSimulateReturnReversalResponse, TransactionSimulateVoidResponse, @@ -332,36 +436,57 @@ from lithic.types import ( Methods: -- client.transactions.retrieve(transaction_token) -> Transaction -- client.transactions.list(\*\*params) -> SyncCursorPage[Transaction] -- client.transactions.simulate_authorization(\*\*params) -> TransactionSimulateAuthorizationResponse -- client.transactions.simulate_authorization_advice(\*\*params) -> TransactionSimulateAuthorizationAdviceResponse -- client.transactions.simulate_clearing(\*\*params) -> TransactionSimulateClearingResponse -- client.transactions.simulate_credit_authorization(\*\*params) -> TransactionSimulateCreditAuthorizationResponse -- client.transactions.simulate_return(\*\*params) -> TransactionSimulateReturnResponse -- client.transactions.simulate_return_reversal(\*\*params) -> TransactionSimulateReturnReversalResponse -- client.transactions.simulate_void(\*\*params) -> TransactionSimulateVoidResponse +- client.transactions.retrieve(transaction_token) -> Transaction +- client.transactions.list(\*\*params) -> SyncCursorPage[Transaction] +- client.transactions.expire_authorization(transaction_token) -> None +- client.transactions.simulate_authorization(\*\*params) -> TransactionSimulateAuthorizationResponse +- client.transactions.simulate_authorization_advice(\*\*params) -> TransactionSimulateAuthorizationAdviceResponse +- client.transactions.simulate_clearing(\*\*params) -> TransactionSimulateClearingResponse +- client.transactions.simulate_credit_authorization(\*\*params) -> TransactionSimulateCreditAuthorizationResponse +- client.transactions.simulate_credit_authorization_advice(\*\*params) -> TransactionSimulateCreditAuthorizationAdviceResponse +- client.transactions.simulate_return(\*\*params) -> TransactionSimulateReturnResponse +- client.transactions.simulate_return_reversal(\*\*params) -> TransactionSimulateReturnReversalResponse +- client.transactions.simulate_void(\*\*params) -> TransactionSimulateVoidResponse -# ResponderEndpoints +## EnhancedCommercialData Types: ```python -from lithic.types import ResponderEndpointStatus, ResponderEndpointCreateResponse +from lithic.types.transactions import EnhancedCommercialDataRetrieveResponse ``` Methods: -- client.responder_endpoints.create(\*\*params) -> ResponderEndpointCreateResponse -- client.responder_endpoints.delete(\*\*params) -> None -- client.responder_endpoints.check_status(\*\*params) -> ResponderEndpointStatus +- client.transactions.enhanced_commercial_data.retrieve(transaction_token) -> EnhancedCommercialDataRetrieveResponse -# Webhooks +## Events + +### EnhancedCommercialData + +Types: + +```python +from lithic.types.transactions.events import EnhancedData +``` Methods: -- client.webhooks.unwrap(\*args) -> object -- client.webhooks.verify_signature(\*args) -> None +- client.transactions.events.enhanced_commercial_data.retrieve(event_token) -> EnhancedData + +# ResponderEndpoints + +Types: + +```python +from lithic.types import ResponderEndpointStatus, ResponderEndpointCreateResponse +``` + +Methods: + +- client.responder_endpoints.create(\*\*params) -> ResponderEndpointCreateResponse +- client.responder_endpoints.delete(\*\*params) -> None +- client.responder_endpoints.check_status(\*\*params) -> ResponderEndpointStatus # ExternalBankAccounts @@ -369,6 +494,7 @@ Types: ```python from lithic.types import ( + ExternalBankAccount, ExternalBankAccountAddress, OwnerType, VerificationMethod, @@ -382,11 +508,13 @@ from lithic.types import ( Methods: -- client.external_bank_accounts.create(\*\*params) -> ExternalBankAccountCreateResponse -- client.external_bank_accounts.retrieve(external_bank_account_token) -> ExternalBankAccountRetrieveResponse -- client.external_bank_accounts.update(external_bank_account_token, \*\*params) -> ExternalBankAccountUpdateResponse -- client.external_bank_accounts.list(\*\*params) -> SyncCursorPage[ExternalBankAccountListResponse] -- client.external_bank_accounts.retry_micro_deposits(external_bank_account_token) -> ExternalBankAccountRetryMicroDepositsResponse +- client.external_bank_accounts.create(\*\*params) -> ExternalBankAccountCreateResponse +- client.external_bank_accounts.retrieve(external_bank_account_token) -> ExternalBankAccountRetrieveResponse +- client.external_bank_accounts.update(external_bank_account_token, \*\*params) -> ExternalBankAccountUpdateResponse +- client.external_bank_accounts.list(\*\*params) -> SyncCursorPage[ExternalBankAccountListResponse] +- client.external_bank_accounts.retry_micro_deposits(external_bank_account_token, \*\*params) -> ExternalBankAccountRetryMicroDepositsResponse +- client.external_bank_accounts.retry_prenote(external_bank_account_token, \*\*params) -> ExternalBankAccount +- client.external_bank_accounts.unpause(external_bank_account_token) -> ExternalBankAccount ## MicroDeposits @@ -398,7 +526,7 @@ from lithic.types.external_bank_accounts import MicroDepositCreateResponse Methods: -- client.external_bank_accounts.micro_deposits.create(external_bank_account_token, \*\*params) -> MicroDepositCreateResponse +- client.external_bank_accounts.micro_deposits.create(external_bank_account_token, \*\*params) -> MicroDepositCreateResponse # Payments @@ -409,6 +537,8 @@ from lithic.types import ( Payment, PaymentCreateResponse, PaymentRetryResponse, + PaymentSimulateActionResponse, + PaymentSimulateReceiptResponse, PaymentSimulateReleaseResponse, PaymentSimulateReturnResponse, ) @@ -416,90 +546,306 @@ from lithic.types import ( Methods: -- client.payments.create(\*\*params) -> PaymentCreateResponse -- client.payments.retrieve(payment_token) -> Payment -- client.payments.list(\*\*params) -> SyncCursorPage[Payment] -- client.payments.retry(payment_token) -> PaymentRetryResponse -- client.payments.simulate_release(\*\*params) -> PaymentSimulateReleaseResponse -- client.payments.simulate_return(\*\*params) -> PaymentSimulateReturnResponse +- client.payments.create(\*\*params) -> PaymentCreateResponse +- client.payments.retrieve(payment_token) -> Payment +- client.payments.list(\*\*params) -> SyncCursorPage[Payment] +- client.payments.retry(payment_token) -> PaymentRetryResponse +- client.payments.return\_(payment_token, \*\*params) -> Payment +- client.payments.simulate_action(payment_token, \*\*params) -> PaymentSimulateActionResponse +- client.payments.simulate_receipt(\*\*params) -> PaymentSimulateReceiptResponse +- client.payments.simulate_release(\*\*params) -> PaymentSimulateReleaseResponse +- client.payments.simulate_return(\*\*params) -> PaymentSimulateReturnResponse # ThreeDS +Types: + +```python +from lithic.types import ThreeDSAuthentication +``` + ## Authentication Types: ```python -from lithic.types.three_ds import AuthenticationRetrieveResponse, AuthenticationSimulateResponse +from lithic.types.three_ds import AuthenticationSimulateResponse ``` Methods: -- client.three_ds.authentication.retrieve(three_ds_authentication_token) -> AuthenticationRetrieveResponse -- client.three_ds.authentication.simulate(\*\*params) -> AuthenticationSimulateResponse +- client.three_ds.authentication.retrieve(three_ds_authentication_token) -> ThreeDSAuthentication +- client.three_ds.authentication.simulate(\*\*params) -> AuthenticationSimulateResponse +- client.three_ds.authentication.simulate_otp_entry(\*\*params) -> None ## Decisioning Types: ```python -from lithic.types.three_ds import DecisioningRetrieveSecretResponse +from lithic.types.three_ds import ( + ChallengeResponse, + ChallengeResult, + DecisioningRetrieveSecretResponse, +) ``` Methods: -- client.three_ds.decisioning.retrieve_secret() -> DecisioningRetrieveSecretResponse -- client.three_ds.decisioning.rotate_secret() -> None +- client.three_ds.decisioning.challenge_response(\*\*params) -> None +- client.three_ds.decisioning.retrieve_secret() -> DecisioningRetrieveSecretResponse +- client.three_ds.decisioning.rotate_secret() -> None # Reports Types: ```python -from lithic.types import SettlementDetail, SettlementReport, SettlementSummaryDetails +from lithic.types import NetworkTotal, SettlementDetail, SettlementReport, SettlementSummaryDetails ``` ## Settlement Methods: -- client.reports.settlement.list_details(report_date, \*\*params) -> SyncCursorPage[SettlementDetail] -- client.reports.settlement.summary(report_date) -> SettlementReport +- client.reports.settlement.list_details(report_date, \*\*params) -> SyncCursorPage[SettlementDetail] +- client.reports.settlement.summary(report_date) -> SettlementReport + +### NetworkTotals -# CardProduct +Methods: + +- client.reports.settlement.network_totals.retrieve(token) -> NetworkTotal +- client.reports.settlement.network_totals.list(\*\*params) -> SyncCursorPage[NetworkTotal] + +# CardPrograms Types: ```python -from lithic.types import CardProductCreditDetailResponse +from lithic.types import CardProgram ``` Methods: -- client.card_product.credit_detail() -> CardProductCreditDetailResponse +- client.card_programs.retrieve(card_program_token) -> CardProgram +- client.card_programs.list(\*\*params) -> SyncCursorPage[CardProgram] -# CardPrograms +# DigitalCardArt Types: ```python -from lithic.types import CardProgram +from lithic.types import DigitalCardArt ``` Methods: -- client.card_programs.retrieve(card_program_token) -> CardProgram -- client.card_programs.list(\*\*params) -> SyncCursorPage[CardProgram] +- client.digital_card_art.retrieve(digital_card_art_token) -> DigitalCardArt +- client.digital_card_art.list(\*\*params) -> SyncCursorPage[DigitalCardArt] -# DigitalCardArtResource +# BookTransfers Types: ```python -from lithic.types import DigitalCardArt +from lithic.types import BookTransferResponse +``` + +Methods: + +- client.book_transfers.create(\*\*params) -> BookTransferResponse +- client.book_transfers.retrieve(book_transfer_token) -> BookTransferResponse +- client.book_transfers.list(\*\*params) -> SyncCursorPage[BookTransferResponse] +- client.book_transfers.reverse(book_transfer_token, \*\*params) -> BookTransferResponse + +# CreditProducts + +## ExtendedCredit + +Types: + +```python +from lithic.types.credit_products import ExtendedCredit +``` + +Methods: + +- client.credit_products.extended_credit.retrieve(credit_product_token) -> ExtendedCredit + +## PrimeRates + +Types: + +```python +from lithic.types.credit_products import PrimeRateRetrieveResponse +``` + +Methods: + +- client.credit_products.prime_rates.create(credit_product_token, \*\*params) -> None +- client.credit_products.prime_rates.retrieve(credit_product_token, \*\*params) -> PrimeRateRetrieveResponse + +# ExternalPayments + +Types: + +```python +from lithic.types import ExternalPayment +``` + +Methods: + +- client.external_payments.create(\*\*params) -> ExternalPayment +- client.external_payments.retrieve(external_payment_token) -> ExternalPayment +- client.external_payments.list(\*\*params) -> SyncCursorPage[ExternalPayment] +- client.external_payments.cancel(external_payment_token, \*\*params) -> ExternalPayment +- client.external_payments.release(external_payment_token, \*\*params) -> ExternalPayment +- client.external_payments.reverse(external_payment_token, \*\*params) -> ExternalPayment +- client.external_payments.settle(external_payment_token, \*\*params) -> ExternalPayment + +# ManagementOperations + +Types: + +```python +from lithic.types import ExternalResource, ExternalResourceType, ManagementOperationTransaction +``` + +Methods: + +- client.management_operations.create(\*\*params) -> ManagementOperationTransaction +- client.management_operations.retrieve(management_operation_token) -> ManagementOperationTransaction +- client.management_operations.list(\*\*params) -> SyncCursorPage[ManagementOperationTransaction] +- client.management_operations.reverse(management_operation_token, \*\*params) -> ManagementOperationTransaction + +# InternalTransaction + +Types: + +```python +from lithic.types import InternalTransaction +``` + +# FundingEvents + +Types: + +```python +from lithic.types import FundingEvent, FundingEventRetrieveDetailsResponse +``` + +Methods: + +- client.funding_events.retrieve(funding_event_token) -> FundingEvent +- client.funding_events.list(\*\*params) -> SyncCursorPage[FundingEvent] +- client.funding_events.retrieve_details(funding_event_token) -> FundingEventRetrieveDetailsResponse + +# Fraud + +## Transactions + +Types: + +```python +from lithic.types.fraud import TransactionRetrieveResponse, TransactionReportResponse ``` Methods: -- client.digital_card_art.retrieve(digital_card_art_token) -> DigitalCardArt -- client.digital_card_art.list(\*\*params) -> SyncCursorPage[DigitalCardArt] +- client.fraud.transactions.retrieve(transaction_token) -> TransactionRetrieveResponse +- client.fraud.transactions.report(transaction_token, \*\*params) -> TransactionReportResponse + +# NetworkPrograms + +Types: + +```python +from lithic.types import NetworkProgram +``` + +Methods: + +- client.network_programs.retrieve(network_program_token) -> NetworkProgram +- client.network_programs.list(\*\*params) -> SyncSinglePage[NetworkProgram] + +# AccountActivity + +Types: + +```python +from lithic.types import ( + WirePartyDetails, + AccountActivityListResponse, + AccountActivityRetrieveTransactionResponse, +) +``` + +Methods: + +- client.account_activity.list(\*\*params) -> SyncCursorPage[AccountActivityListResponse] +- client.account_activity.retrieve_transaction(transaction_token) -> AccountActivityRetrieveTransactionResponse + +# Webhooks + +Types: + +```python +from lithic.types import ( + AccountHolderCreatedWebhookEvent, + AccountHolderUpdatedWebhookEvent, + AccountHolderVerificationWebhookEvent, + AccountHolderDocumentUpdatedWebhookEvent, + AsaRequestWebhookEvent, + TokenizationDecisioningRequestWebhookEvent, + AuthRulesBacktestReportCreatedWebhookEvent, + BalanceUpdatedWebhookEvent, + BookTransferTransactionCreatedWebhookEvent, + BookTransferTransactionUpdatedWebhookEvent, + CardCreatedWebhookEvent, + CardConvertedWebhookEvent, + CardRenewedWebhookEvent, + CardReissuedWebhookEvent, + CardShippedWebhookEvent, + CardTransactionUpdatedWebhookEvent, + CardTransactionEnhancedDataCreatedWebhookEvent, + CardTransactionEnhancedDataUpdatedWebhookEvent, + DigitalWalletTokenizationApprovalRequestWebhookEvent, + DigitalWalletTokenizationResultWebhookEvent, + DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent, + DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent, + DigitalWalletTokenizationUpdatedWebhookEvent, + DisputeUpdatedWebhookEvent, + DisputeEvidenceUploadFailedWebhookEvent, + ExternalBankAccountCreatedWebhookEvent, + ExternalBankAccountUpdatedWebhookEvent, + ExternalPaymentCreatedWebhookEvent, + ExternalPaymentUpdatedWebhookEvent, + FinancialAccountCreatedWebhookEvent, + FinancialAccountUpdatedWebhookEvent, + FundingEventCreatedWebhookEvent, + LoanTapeCreatedWebhookEvent, + LoanTapeUpdatedWebhookEvent, + ManagementOperationCreatedWebhookEvent, + ManagementOperationUpdatedWebhookEvent, + InternalTransactionCreatedWebhookEvent, + InternalTransactionUpdatedWebhookEvent, + NetworkTotalCreatedWebhookEvent, + NetworkTotalUpdatedWebhookEvent, + PaymentTransactionCreatedWebhookEvent, + PaymentTransactionUpdatedWebhookEvent, + SettlementReportUpdatedWebhookEvent, + StatementsCreatedWebhookEvent, + ThreeDSAuthenticationCreatedWebhookEvent, + ThreeDSAuthenticationUpdatedWebhookEvent, + ThreeDSAuthenticationChallengeWebhookEvent, + TokenizationApprovalRequestWebhookEvent, + TokenizationResultWebhookEvent, + TokenizationTwoFactorAuthenticationCodeWebhookEvent, + TokenizationTwoFactorAuthenticationCodeSentWebhookEvent, + TokenizationUpdatedWebhookEvent, + DisputeTransactionCreatedWebhookEvent, + DisputeTransactionUpdatedWebhookEvent, + ParsedWebhookEvent, +) +``` diff --git a/bin/check-env-state.py b/bin/check-env-state.py deleted file mode 100644 index e1b8b6cb..00000000 --- a/bin/check-env-state.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Script that exits 1 if the current environment is not -in sync with the `requirements-dev.lock` file. -""" - -from pathlib import Path - -import importlib_metadata - - -def should_run_sync() -> bool: - dev_lock = Path(__file__).parent.parent.joinpath("requirements-dev.lock") - - for line in dev_lock.read_text().splitlines(): - if not line or line.startswith("#") or line.startswith("-e"): - continue - - dep, lock_version = line.split("==") - - try: - version = importlib_metadata.version(dep) - - if lock_version != version: - print(f"mismatch for {dep} current={version} lock={lock_version}") - return True - except Exception: - print(f"could not import {dep}") - return True - - return False - - -def main() -> None: - if should_run_sync(): - exit(1) - else: - exit(0) - - -if __name__ == "__main__": - main() diff --git a/bin/check-release-environment b/bin/check-release-environment index 075f33e3..b845b0f4 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The LITHIC_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi lenErrors=${#errors[@]} diff --git a/bin/check-test-server b/bin/check-test-server deleted file mode 100755 index a6fa3495..00000000 --- a/bin/check-test-server +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if is_overriding_api_base_url ; then - # If someone is running the tests against the live API, we can trust they know - # what they're doing and exit early. - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - - exit 0 -elif prism_is_running ; then - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo - - exit 0 -else - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "${YELLOW}To fix:${NC}" - echo - echo -e "1. Install Prism (requires Node 16+):" - echo - echo -e " With npm:" - echo -e " \$ ${YELLOW}npm install -g @stoplight/prism-cli${NC}" - echo - echo -e " With yarn:" - echo -e " \$ ${YELLOW}yarn global add @stoplight/prism-cli${NC}" - echo - echo -e "2. Run the mock server" - echo - echo -e " To run the server, pass in the path of your OpenAPI" - echo -e " spec to the prism command:" - echo - echo -e " \$ ${YELLOW}prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -fi diff --git a/bin/test b/bin/test deleted file mode 100755 index 60ede7a8..00000000 --- a/bin/test +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -bin/check-test-server && rye run pytest "$@" diff --git a/examples/datetime_usage.py b/examples/datetime_usage.py deleted file mode 100644 index a01df811..00000000 --- a/examples/datetime_usage.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env -S poetry run python - -from datetime import datetime - -from lithic import Lithic - -client = Lithic(environment="sandbox") - -now = datetime.now() - -# datetime responses will always be instances of `datetime` -card = client.cards.create(type="VIRTUAL") -assert isinstance(card.created, datetime) -assert card.created.year == now.year -assert card.created.month == now.month -assert card.created.tzname() == "UTC" - -dt = datetime.fromisoformat("2022-07-25T21:34:45+00:00") - -# # both `datetime` instances or datetime strings can be passed as a request param -page = client.cards.list(begin=dt, page_size=1) -assert len(page.data) == 1 - -page = client.cards.list(begin=dt.isoformat(), page_size=1) -assert len(page.data) == 1 diff --git a/examples/hello_sailor.txt b/examples/hello_sailor.txt deleted file mode 100644 index cc034734..00000000 --- a/examples/hello_sailor.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, Sailor! diff --git a/examples/hello_world.txt b/examples/hello_world.txt deleted file mode 100644 index 8ab686ea..00000000 --- a/examples/hello_world.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, World! diff --git a/examples/upload_evidence.py b/examples/upload_evidence.py deleted file mode 100644 index 440a1666..00000000 --- a/examples/upload_evidence.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env -S poetry run python - -# -# Run with: LITHIC_API_KEY= poetry run python examples/upload_evidence.py -# - -from lithic import Lithic, file_from_path - -client = Lithic(environment="sandbox") - -transactions_page = client.transactions.list() -assert len(transactions_page.data) > 0, "No transactions found" - -transaction = transactions_page.data[0] -assert transaction.token, "Transaction must have a token" - -disputes_page = client.disputes.list() -dispute = disputes_page.data[0] -if not dispute: - dispute = client.disputes.create( - amount=42, - reason="ATM_CASH_MISDISPENSE", - transaction_token=transaction.token, - ) - -print(dispute) -assert dispute, "Could not find or create a dispute" - -my_file = file_from_path("hello_world.txt") - -upload = client.disputes.upload_evidence(dispute.token, my_file) -print(upload) - -print("Done!") diff --git a/integrations/pagination.py b/integrations/pagination.py deleted file mode 100755 index e874414f..00000000 --- a/integrations/pagination.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env -S rye run python integrations/pagination.py - -from __future__ import annotations - -import json - -from lithic import Lithic - -client = Lithic(environment="sandbox") - - -def main() -> None: - page = client.transactions.list() - assert len(page.data) > 0, "No transactions found" - - if not page.has_more or not page.has_next_page(): - raise RuntimeError(f"Expected multiple pages to be present, only got {len(page.data)} items") - - tokens: dict[str, int] = {} - - for transaction in page: - tokens[transaction.token] = tokens.get(transaction.token, 0) + 1 - - duplicates = {token: count for token, count in tokens.items() if count > 1} - if duplicates: - print(json.dumps(duplicates, indent=2)) # noqa: T201 - raise RuntimeError(f"Found {len(duplicates)} duplicate entries!") - - print("Success!") # noqa: T201 - - -main() diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index b5862c11..00000000 --- a/mypy.ini +++ /dev/null @@ -1,47 +0,0 @@ -[mypy] -pretty = True -show_error_codes = True - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -exclude = ^(src/lithic/_files\.py|_dev/.*\.py)$ - -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 - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = False -warn_redundant_casts = False - -disallow_any_generics = True -disallow_untyped_defs = True -disallow_untyped_calls = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -cache_fine_grained = True - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = func-returns-value - -# https://github.com/python/mypy/issues/12162 -[mypy.overrides] -module = "black.files.*" -ignore_errors = true -ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index 059f64fd..5636e62b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,32 +1,32 @@ [project] name = "lithic" -version = "0.39.0" +version = "0.112.0" description = "The official Python library for the lithic API" -readme = "README.md" +dynamic = ["readme"] license = "Apache-2.0" authors = [ { name = "Lithic", email = "sdk-feedback@lithic.com" }, ] -dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.7, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", - "cached-property; python_version < '3.8'", +dependencies = [ + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.10, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", ] -requires-python = ">= 3.7" + +requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", @@ -36,20 +36,20 @@ classifiers = [ "License :: OSI Approved :: Apache Software License" ] - - [project.urls] Homepage = "https://github.com/lithic-com/lithic-python" Repository = "https://github.com/lithic-com/lithic-python" - +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] +webhooks = ["standardwebhooks"] [tool.rye] managed = true # version pins are in requirements-dev.lock dev-dependencies = [ - "pyright", - "mypy", + "pyright==1.1.399", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", @@ -58,7 +58,8 @@ dev-dependencies = [ "nox", "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", - + "rich>=13.7.1", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -66,18 +67,21 @@ format = { chain = [ "format:ruff", "format:docs", "fix:ruff", + # run formatting again to fix any inconsistencies when imports are stripped + "format:ruff", ]} -"format:black" = "black ." -"format:docs" = "python bin/ruffen-docs.py README.md api.md" +"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" "format:ruff" = "ruff format" -"format:isort" = "isort ." "lint" = { chain = [ "check:ruff", "typecheck", + "check:importable", ]} -"check:ruff" = "ruff ." -"fix:ruff" = "ruff --fix ." +"check:ruff" = "ruff check ." +"fix:ruff" = "ruff check --fix ." + +"check:importable" = "python -c 'import lithic'" typecheck = { chain = [ "typecheck:pyright", @@ -88,7 +92,7 @@ typecheck = { chain = [ "typecheck:mypy" = "mypy ." [build-system] -requires = ["hatchling"] +requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] build-backend = "hatchling.build" [tool.hatch.build] @@ -99,15 +103,38 @@ include = [ [tool.hatch.build.targets.wheel] packages = ["src/lithic"] -[tool.black] -line-length = 120 -target-version = ["py37"] +[tool.hatch.build.targets.sdist] +# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc) +include = [ + "/*.toml", + "/*.json", + "/*.lock", + "/*.md", + "/mypy.ini", + "/noxfile.py", + "bin/*", + "examples/*", + "src/*", + "tests/*", +] + +[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]] +# replace relative links with absolute links +pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' +replacement = '[\1](https://github.com/lithic-com/lithic-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" filterwarnings = [ "error" ] @@ -117,23 +144,82 @@ filterwarnings = [ # there are a couple of flags that are still disabled by # default in strict mode as they are experimental and niche. typeCheckingMode = "strict" -pythonVersion = "3.7" +pythonVersion = "3.9" exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true +reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false +[tool.mypy] +pretty = true +show_error_codes = true + +# Exclude _files.py because mypy isn't smart enough to apply +# the correct type narrowing and as this is an internal module +# it's fine to just use Pyright. +# +# We also exclude our `tests` as mypy doesn't always infer +# types correctly and Pyright will still catch any type errors. +exclude = ['src/lithic/_files.py', '_dev/.*.py', 'tests/.*'] + +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 + +# Turn these options off as it could cause conflicts +# with the Pyright options. +warn_unused_ignores = false +warn_redundant_casts = false + +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +cache_fine_grained = true + +# By default, mypy reports an error if you assign a value to the result +# of a function call that doesn't return anything. We do this in our test +# cases: +# ``` +# result = ... +# assert result is None +# ``` +# Changing this codegen to make mypy happy would increase complexity +# and would not be worth it. +disable_error_code = "func-returns-value,overload-cannot-match" + +# https://github.com/python/mypy/issues/12162 +[[tool.mypy.overrides]] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true + + [tool.ruff] line-length = 120 output-format = "grouped" -target-version = "py37" +target-version = "py38" + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] select = [ # isort "I", @@ -141,6 +227,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -149,7 +237,9 @@ select = [ "T201", "T203", # misuse of typing.TYPE_CHECKING - "TCH004" + "TC004", + # import rules + "TID251", ] ignore = [ # mutable defaults @@ -160,10 +250,11 @@ unfixable = [ "T201", "T203", ] -ignore-init-module-imports = true -[tool.ruff.format] -docstring-code-format = true +extend-safe-fixes = ["FA102"] + +[tool.ruff.lint.flake8-tidy-imports.banned-api] +"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" [tool.ruff.lint.isort] length-sort = true @@ -172,7 +263,8 @@ combine-as-imports = true extra-standard-library = ["typing_extensions"] known-first-party = ["lithic", "tests"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "bin/**.py" = ["T201", "T203"] +"scripts/**.py" = ["T201", "T203"] "tests/**.py" = ["T201", "T203"] "examples/**.py" = ["T201", "T203"] diff --git a/requirements-dev.lock b/requirements-dev.lock index 6078baa3..83c0dadf 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -6,91 +6,157 @@ # features: [] # all-features: true # with-sources: false +# generate-hashes: false +# universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.2 + # via httpx-aiohttp + # via lithic +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.1.0 +anyio==4.12.0 # via httpx # via lithic -argcomplete==3.1.2 +argcomplete==3.6.3 # via nox -attrs==23.1.0 - # via pytest -certifi==2023.7.22 +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp + # via nox + # via standardwebhooks +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +certifi==2025.11.12 # via httpcore # via httpx -colorlog==6.7.0 +colorlog==6.10.1 + # via nox +dependency-groups==1.3.1 # via nox -dirty-equals==0.6.0 -distlib==0.3.7 +deprecated==1.3.1 + # via standardwebhooks +dirty-equals==0.11 +distlib==0.4.0 # via virtualenv -distro==1.8.0 +distro==1.9.0 # via lithic -exceptiongroup==1.1.3 +exceptiongroup==1.3.1 # via anyio -filelock==3.12.4 + # via pytest +execnet==2.1.2 + # via pytest-xdist +filelock==3.19.1 # via virtualenv -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx -httpx==0.25.2 +httpx==0.28.1 + # via httpx-aiohttp # via lithic # via respx -idna==3.4 + # via standardwebhooks +httpx-aiohttp==0.1.9 + # via lithic +humanize==4.13.0 + # via nox +idna==3.11 # via anyio # via httpx -importlib-metadata==7.0.0 -iniconfig==2.0.0 + # via yarl +importlib-metadata==8.7.0 +iniconfig==2.1.0 # via pytest -mypy==1.7.1 -mypy-extensions==1.0.0 +markdown-it-py==3.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +multidict==6.7.0 + # via aiohttp + # via yarl +mypy==1.17.0 +mypy-extensions==1.1.0 # via mypy -nodeenv==1.8.0 +nodeenv==1.9.1 # via pyright -nox==2023.4.22 -packaging==23.2 +nox==2025.11.12 +packaging==25.0 + # via dependency-groups # via nox # via pytest -platformdirs==3.11.0 +pathspec==0.12.1 + # via mypy +platformdirs==4.4.0 # via virtualenv -pluggy==1.3.0 - # via pytest -py==1.11.0 +pluggy==1.6.0 # via pytest -pydantic==2.4.2 +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via lithic -pydantic-core==2.10.1 +pydantic-core==2.41.5 # via pydantic -pyright==1.1.351 -pytest==7.1.1 +pygments==2.19.2 + # via pytest + # via rich +pyright==1.1.399 +pytest==8.4.2 # via pytest-asyncio -pytest-asyncio==0.21.1 -python-dateutil==2.8.2 + # via pytest-xdist +pytest-asyncio==1.2.0 +pytest-xdist==3.8.0 +python-dateutil==2.9.0.post0 + # via standardwebhooks # via time-machine -pytz==2023.3.post1 - # via dirty-equals -respx==0.20.2 -ruff==0.1.9 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 +respx==0.22.0 +rich==14.2.0 +ruff==0.14.7 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio - # via httpx +sniffio==1.3.1 # via lithic -time-machine==2.9.0 -tomli==2.0.1 +standardwebhooks==1.0.0 + # via lithic +time-machine==2.19.0 +tomli==2.3.0 + # via dependency-groups # via mypy + # via nox # via pytest -typing-extensions==4.8.0 +types-deprecated==1.3.1.20251101 + # via standardwebhooks +types-python-dateutil==2.9.0.20251115 + # via standardwebhooks +typing-extensions==4.15.0 + # via aiosignal + # via anyio + # via exceptiongroup # via lithic + # via multidict # via mypy # via pydantic # via pydantic-core -virtualenv==20.24.5 + # via pyright + # via pytest-asyncio + # via typing-inspection + # via virtualenv +typing-inspection==0.4.2 + # via pydantic +virtualenv==20.35.4 # via nox -zipp==3.17.0 +wrapt==2.0.1 + # via deprecated +yarl==1.22.0 + # via aiohttp +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 7fbcaa73..08c68c21 100644 --- a/requirements.lock +++ b/requirements.lock @@ -6,38 +6,87 @@ # features: [] # all-features: true # with-sources: false +# generate-hashes: false +# universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.2 + # via httpx-aiohttp + # via lithic +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.1.0 +anyio==4.12.0 # via httpx # via lithic -certifi==2023.7.22 +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp + # via standardwebhooks +certifi==2025.11.12 # via httpcore # via httpx -distro==1.8.0 +deprecated==1.3.1 + # via standardwebhooks +distro==1.9.0 # via lithic -exceptiongroup==1.1.3 +exceptiongroup==1.3.1 # via anyio -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx -httpx==0.25.2 +httpx==0.28.1 + # via httpx-aiohttp + # via lithic + # via standardwebhooks +httpx-aiohttp==0.1.9 # via lithic -idna==3.4 +idna==3.11 # via anyio # via httpx -pydantic==2.4.2 + # via yarl +multidict==6.7.0 + # via aiohttp + # via yarl +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via lithic -pydantic-core==2.10.1 +pydantic-core==2.41.5 # via pydantic -sniffio==1.3.0 - # via anyio - # via httpx +python-dateutil==2.9.0.post0 + # via standardwebhooks +six==1.17.0 + # via python-dateutil +sniffio==1.3.1 # via lithic -typing-extensions==4.8.0 +standardwebhooks==1.0.0 # via lithic +types-deprecated==1.3.1.20251101 + # via standardwebhooks +types-python-dateutil==2.9.0.20251115 + # via standardwebhooks +typing-extensions==4.15.0 + # via aiosignal + # via anyio + # via exceptiongroup + # via lithic + # via multidict # via pydantic # via pydantic-core + # via typing-inspection +typing-inspection==0.4.2 + # via pydantic +wrapt==2.0.1 + # via deprecated +yarl==1.22.0 + # via aiohttp diff --git a/scripts/bootstrap b/scripts/bootstrap new file mode 100755 index 00000000..b430fee3 --- /dev/null +++ b/scripts/bootstrap @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then + brew bundle check >/dev/null 2>&1 || { + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo + } +fi + +echo "==> Installing Python dependencies…" + +# experimental uv support makes installations significantly faster +rye config --set-bool behavior.use-uv=true + +rye sync --all-features diff --git a/scripts/format b/scripts/format new file mode 100755 index 00000000..667ec2d7 --- /dev/null +++ b/scripts/format @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running formatters" +rye run format diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 00000000..c997f8ca --- /dev/null +++ b/scripts/lint @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running lints" +rye run lint + +echo "==> Making sure it imports" +rye run python -c 'import lithic' diff --git a/scripts/mock b/scripts/mock new file mode 100755 index 00000000..0b28f6ea --- /dev/null +++ b/scripts/mock @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [[ -n "$1" && "$1" != '--'* ]]; then + URL="$1" + shift +else + URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" +fi + +# Check if the URL is empty +if [ -z "$URL" ]; then + echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" + exit 1 +fi + +echo "==> Starting mock server with URL ${URL}" + +# Run prism mock on the given spec +if [ "$1" == "--daemon" ]; then + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + + # Wait for server to come online + echo -n "Waiting for server" + while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + echo -n "." + sleep 0.1 + done + + if grep -q "✖ fatal" ".prism.log"; then + cat .prism.log + exit 1 + fi + + echo +else + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" +fi diff --git a/scripts/test b/scripts/test new file mode 100755 index 00000000..dbeda2d2 --- /dev/null +++ b/scripts/test @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +function prism_is_running() { + curl --silent "http://localhost:4010" >/dev/null 2>&1 +} + +kill_server_on_port() { + pids=$(lsof -t -i tcp:"$1" || echo "") + if [ "$pids" != "" ]; then + kill "$pids" + echo "Stopped $pids." + fi +} + +function is_overriding_api_base_url() { + [ -n "$TEST_API_BASE_URL" ] +} + +if ! is_overriding_api_base_url && ! prism_is_running ; then + # When we exit this script, make sure to kill the background mock server process + trap 'kill_server_on_port 4010' EXIT + + # Start the dev server + ./scripts/mock --daemon +fi + +if is_overriding_api_base_url ; then + echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" + echo +elif ! prism_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" + echo -e "running against your OpenAPI spec." + echo + echo -e "To run the server, pass in the path or url of your OpenAPI" + echo -e "spec to the prism command:" + echo + echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo + + exit 1 +else + echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo +fi + +export DEFER_PYDANTIC_BUILD=false + +echo "==> Running tests" +rye run pytest "$@" + +echo "==> Running Pydantic v1 tests" +rye run nox -s test-pydantic-v1 -- "$@" diff --git a/bin/ruffen-docs.py b/scripts/utils/ruffen-docs.py similarity index 97% rename from bin/ruffen-docs.py rename to scripts/utils/ruffen-docs.py index 37b3d94f..0cf2bd2f 100644 --- a/bin/ruffen-docs.py +++ b/scripts/utils/ruffen-docs.py @@ -47,7 +47,7 @@ def _md_match(match: Match[str]) -> str: with _collect_error(match): code = format_code_block(code) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" def _pycon_match(match: Match[str]) -> str: code = "" @@ -97,7 +97,7 @@ def finish_fragment() -> None: def _md_pycon_match(match: Match[str]) -> str: code = _pycon_match(match) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" src = MD_RE.sub(_md_match, src) src = MD_PYCON_RE.sub(_md_pycon_match, src) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..f0638157 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -exuo pipefail + +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: binary/octet-stream" \ + --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/lithic-python/$SHA/$FILENAME'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/lithic/__init__.py b/src/lithic/__init__.py index 8b7691d9..4617707e 100644 --- a/src/lithic/__init__.py +++ b/src/lithic/__init__.py @@ -1,7 +1,9 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import typing as _t from . import types -from ._types import NoneType, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import ( ENVIRONMENTS, @@ -18,6 +20,7 @@ from ._models import BaseModel from ._version import __title__, __version__ from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse +from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS from ._exceptions import ( APIError, LithicError, @@ -32,8 +35,10 @@ InternalServerError, PermissionDeniedError, UnprocessableEntityError, + APIWebhookValidationError, APIResponseValidationError, ) +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging __all__ = [ @@ -43,12 +48,18 @@ "NoneType", "Transport", "ProxiesTypes", + "NotGiven", + "NOT_GIVEN", + "not_given", + "Omit", + "omit", "LithicError", "APIError", "APIStatusError", "APITimeoutError", "APIConnectionError", "APIResponseValidationError", + "APIWebhookValidationError", "BadRequestError", "AuthenticationError", "PermissionDeniedError", @@ -68,8 +79,17 @@ "ENVIRONMENTS", "file_from_path", "BaseModel", + "DEFAULT_TIMEOUT", + "DEFAULT_MAX_RETRIES", + "DEFAULT_CONNECTION_LIMITS", + "DefaultHttpxClient", + "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + _setup_logging() # Update the __module__ attribute for exported symbols so that diff --git a/src/lithic/_base_client.py b/src/lithic/_base_client.py index 73bd2411..ce040646 100644 --- a/src/lithic/_base_client.py +++ b/src/lithic/_base_client.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import json import time import uuid @@ -8,7 +9,6 @@ import inspect import logging import platform -import warnings import email.utils from types import TracebackType from random import random @@ -29,21 +29,19 @@ cast, overload, ) -from functools import lru_cache from typing_extensions import Literal, override, get_origin import anyio import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -51,18 +49,17 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, + HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) -from ._utils import is_dict, is_list, is_given, is_mapping -from ._compat import model_copy, model_dump +from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping +from ._compat import PYDANTIC_V1, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -71,15 +68,15 @@ extract_response_type, ) from ._constants import ( - DEFAULT_LIMITS, DEFAULT_TIMEOUT, MAX_RETRY_DELAY, DEFAULT_MAX_RETRIES, INITIAL_RETRY_DELAY, RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER, + DEFAULT_CONNECTION_LIMITS, ) -from ._streaming import Stream, AsyncStream +from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder from ._exceptions import ( APIStatusError, APITimeoutError, @@ -102,7 +99,11 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT @@ -119,32 +120,48 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( self, *, url: URL, - ) -> None: - ... + ) -> None: ... @overload def __init__( self, *, params: Query, - ) -> None: - ... + ) -> None: ... + + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url + self.json = json self.params = params + @override + def __repr__(self) -> str: + if self.url: + return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" + return f"{self.__class__.__name__}(params={self.params})" + class BasePage(GenericModel, Generic[_T]): """ @@ -167,8 +184,7 @@ def has_next_page(self) -> bool: return False return self.next_page_info() is not None - def next_page_info(self) -> Optional[PageInfo]: - ... + def next_page_info(self) -> Optional[PageInfo]: ... def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body] ... @@ -192,6 +208,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") @@ -204,6 +233,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -289,6 +321,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -328,9 +363,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -343,9 +375,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -353,13 +382,16 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation self._idempotency_header = None + self._platform: Platform | None = None + + if max_retries is None: # pyright: ignore[reportUnnecessaryComparison] + raise TypeError( + "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `lithic.DEFAULT_MAX_RETRIES`" + ) def _enforce_trailing_slash(self, url: URL) -> URL: if url.raw_path.endswith(b"/"): @@ -397,14 +429,7 @@ def _make_status_error( ) -> _exceptions.APIStatusError: raise NotImplementedError() - def _remaining_retries( - self, - remaining_retries: Optional[int], - options: FinalRequestOptions, - ) -> int: - return remaining_retries if remaining_retries is not None else options.get_max_retries(self.max_retries) - - def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers: + def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers: custom_headers = options.headers or {} headers_dict = _merge_mappings(self.default_headers, custom_headers) self._validate_headers(headers_dict, custom_headers) @@ -413,8 +438,20 @@ def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers: headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - headers[idempotency_header] = options.idempotency_key or self._idempotency_key() + if idempotency_header and options.idempotency_key and idempotency_header not in headers: + headers[idempotency_header] = options.idempotency_key + + # Don't set these headers if they were already set or removed by the caller. We check + # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. + lower_custom_headers = [header.lower() for header in custom_headers] + if "x-stainless-retry-count" not in lower_custom_headers: + headers["x-stainless-retry-count"] = str(retries_taken) + if "x-stainless-read-timeout" not in lower_custom_headers: + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout + if isinstance(timeout, Timeout): + timeout = timeout.read + if timeout is not None: + headers["x-stainless-read-timeout"] = str(timeout) return headers @@ -431,9 +468,14 @@ def _prepare_url(self, url: str) -> URL: return merge_url + def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: + return SSEDecoder() + def _build_request( self, options: FinalRequestOptions, + *, + retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): log.debug("Request options: %s", model_dump(options, exclude_unset=True)) @@ -449,9 +491,10 @@ def _build_request( else: raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") - headers = self._build_headers(options) - params = _merge_mappings(self._custom_query, options.params) + headers = self._build_headers(options, retries_taken=retries_taken) + params = _merge_mappings(self.default_query, options.params) content_type = headers.get("Content-Type") + files = options.files # If the given Content-Type header is multipart/form-data then it # has to be removed so that httpx can generate the header with @@ -465,7 +508,7 @@ def _build_request( headers.pop("Content-Type") # As we are now sending multipart/form-data instead of application/json - # we need to tell httpx to use it, https://www.python-httpx.org/advanced/#multipart-file-encoding + # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding if json_data: if not is_dict(json_data): raise TypeError( @@ -473,19 +516,43 @@ def _build_request( ) kwargs["data"] = self._serialize_multipartform(json_data) + # httpx determines whether or not to send a "multipart/form-data" + # request based on the truthiness of the "files" argument. + # This gets around that issue by generating a dict value that + # evaluates to true. + # + # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186 + if not files: + files = cast(HttpxRequestFiles, ForceMultipartDict()) + + prepared_url = self._prepare_url(options.url) + if "_" in prepared_url.host: + # work around https://github.com/encode/httpx/discussions/2880 + kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + if isinstance(json_data, bytes): + kwargs["content"] = json_data + else: + kwargs["json"] = json_data if is_given(json_data) else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout, method=options.method, - url=self._prepare_url(options.url), + url=prepared_url, # the `Query` type that we use is incompatible with qs' # `Params` type as it needs to be typed as `Mapping[str, object]` # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data, - files=options.files, **kwargs, ) @@ -529,7 +596,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) if is_given(override_cast_to): options.headers = headers return cast(Type[ResponseT], override_cast_to) @@ -586,6 +653,12 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @property + def default_query(self) -> dict[str, object]: + return { + **self._custom_query, + } + def _validate_headers( self, headers: Headers, # noqa: ARG002 @@ -610,7 +683,10 @@ def base_url(self, url: URL | str) -> None: self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url)) def platform_headers(self) -> Dict[str, str]: - return platform_headers(self._version) + # the actual implementation is in a separate `lru_cache` decorated + # function because adding `lru_cache` to methods will leak memory + # https://github.com/python/cpython/issues/88476 + return platform_headers(self._version, platform=self._platform) def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None: """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified. @@ -659,7 +735,8 @@ def _calculate_retry_timeout( if retry_after is not None and 0 < retry_after <= 60: return retry_after - nb_retries = max_retries - remaining_retries + # Also cap retry count to 1000 to avoid any potential overflows with `pow` + nb_retries = min(max_retries - remaining_retries, 1000) # Apply exponential backoff, but not more than the max. sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) @@ -708,8 +785,31 @@ def _idempotency_key(self) -> str: return f"stainless-python-retry-{uuid.uuid4()}" -class SyncHttpxClientWrapper(httpx.Client): +class _DefaultHttpxClient(httpx.Client): + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +if TYPE_CHECKING: + DefaultHttpxClient = httpx.Client + """An alias to `httpx.Client` that provides the same defaults that this SDK + uses internally. + + This is useful because overriding the `http_client` with your own instance of + `httpx.Client` will result in httpx's defaults being used, not ours. + """ +else: + DefaultHttpxClient = _DefaultHttpxClient + + +class SyncHttpxClientWrapper(DefaultHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: self.close() except Exception: @@ -726,44 +826,12 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_LIMITS - - if transport is not None: - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -777,14 +845,16 @@ def __init__( else: timeout = DEFAULT_TIMEOUT + if http_client is not None and not isinstance(http_client, httpx.Client): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}" + ) + super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -794,10 +864,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, - limits=limits, - follow_redirects=True, ) def is_closed(self) -> bool: @@ -827,9 +893,9 @@ def __exit__( def _prepare_options( self, options: FinalRequestOptions, # noqa: ARG002 - ) -> None: + ) -> FinalRequestOptions: """Hook for mutating the given options""" - return None + return options def _prepare_request( self, @@ -847,177 +913,163 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], - ) -> _StreamT: - ... + ) -> _StreamT: ... @overload def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - ... + ) -> ResponseT | _StreamT: ... def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - remaining_retries=remaining_retries, - ) - - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - remaining_retries: int | None, - stream: bool, - stream_cls: type[_StreamT] | None, ) -> ResponseT | _StreamT: cast_to = self._maybe_override_cast_to(cast_to, options) - self._prepare_options(options) - retries = self._remaining_retries(remaining_retries, options) - request = self._build_request(options) - self._prepare_request(request) + # create a copy of the options we were given so that if the + # options are mutated later & we then retry, the retries are + # given the original options + input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if retries > 0: - return self._retry_request( - options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - if retries > 0: - return self._retry_request( - options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - log.debug( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - options, - cast_to, - retries, - err.response.headers, - stream=stream, - stream_cls=stream_cls, + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) + + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None + break + + assert response is not None, "could not resolve response (should never happen)" return self._process_response( cast_to=cast_to, options=options, response=response, stream=stream, stream_cls=stream_cls, + retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - remaining_retries: int, - response_headers: httpx.Headers | None, - *, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining = remaining_retries - 1 - if remaining == 1: + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: log.debug("1 retry left") else: - log.debug("%i retries left", remaining) + log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) - # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a - # different thread if necessary. time.sleep(timeout) - return self._request( - options=options, - cast_to=cast_to, - remaining_retries=remaining, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1026,6 +1078,7 @@ def _process_response( response: httpx.Response, stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, ) -> ResponseT: if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": return cast( @@ -1037,12 +1090,20 @@ def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ), ) origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1056,6 +1117,7 @@ def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ), ) @@ -1069,6 +1131,7 @@ def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ) if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): return cast(ResponseT, api_response) @@ -1101,8 +1164,7 @@ def get( cast_to: Type[ResponseT], options: RequestOptions = {}, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload def get( @@ -1113,8 +1175,7 @@ def get( options: RequestOptions = {}, stream: Literal[True], stream_cls: type[_StreamT], - ) -> _StreamT: - ... + ) -> _StreamT: ... @overload def get( @@ -1125,8 +1186,7 @@ def get( options: RequestOptions = {}, stream: bool, stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - ... + ) -> ResponseT | _StreamT: ... def get( self, @@ -1152,8 +1212,7 @@ def post( options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload def post( @@ -1166,8 +1225,7 @@ def post( files: RequestFiles | None = None, stream: Literal[True], stream_cls: type[_StreamT], - ) -> _StreamT: - ... + ) -> _StreamT: ... @overload def post( @@ -1180,8 +1238,7 @@ def post( files: RequestFiles | None = None, stream: bool, stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - ... + ) -> ResponseT | _StreamT: ... def post( self, @@ -1205,9 +1262,12 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1249,8 +1309,53 @@ def get_api_list( return self._request_api_list(model, page, opts) -class AsyncHttpxClientWrapper(httpx.AsyncClient): +class _DefaultAsyncHttpxClient(httpx.AsyncClient): + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + +if TYPE_CHECKING: + DefaultAsyncHttpxClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK + uses internally. + + This is useful because overriding the `http_client` with your own instance of + `httpx.AsyncClient` will result in httpx's defaults being used, not ours. + """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" +else: + DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient + + +class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: # TODO(someday): support non asyncio runtimes here asyncio.get_running_loop().create_task(self.aclose()) @@ -1269,43 +1374,11 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_LIMITS - - if transport is not None: - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -1319,14 +1392,16 @@ def __init__( else: timeout = DEFAULT_TIMEOUT + if http_client is not None and not isinstance(http_client, httpx.AsyncClient): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}" + ) + super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1336,10 +1411,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, - limits=limits, - follow_redirects=True, ) def is_closed(self) -> bool: @@ -1366,9 +1437,9 @@ async def __aexit__( async def _prepare_options( self, options: FinalRequestOptions, # noqa: ARG002 - ) -> None: + ) -> FinalRequestOptions: """Hook for mutating the given options""" - return None + return options async def _prepare_request( self, @@ -1388,9 +1459,7 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload async def request( @@ -1400,9 +1469,7 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, - ) -> _AsyncStreamT: - ... + ) -> _AsyncStreamT: ... @overload async def request( @@ -1412,9 +1479,7 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - ... + ) -> ResponseT | _AsyncStreamT: ... async def request( self, @@ -1423,138 +1488,137 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - remaining_retries=remaining_retries, - ) + if self._platform is None: + # `get_platform` can make blocking IO calls so we + # execute it earlier while we are in an async context + self._platform = await asyncify(get_platform)() - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - remaining_retries: int | None, - ) -> ResponseT | _AsyncStreamT: cast_to = self._maybe_override_cast_to(cast_to, options) - await self._prepare_options(options) - retries = self._remaining_retries(remaining_retries, options) - request = self._build_request(options) - await self._prepare_request(request) + # create a copy of the options we were given so that if the + # options are mutated later & we then retry, the retries are + # given the original options + input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if retries > 0: - return await self._retry_request( - options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - if retries > 0: - return await self._retry_request( - options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - log.debug( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - options, - cast_to, - retries, - err.response.headers, - stream=stream, - stream_cls=stream_cls, + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - await err.response.aread() + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue + + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() + + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return await self._process_response( cast_to=cast_to, options=options, response=response, stream=stream, stream_cls=stream_cls, + retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - remaining_retries: int, - response_headers: httpx.Headers | None, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining = remaining_retries - 1 - if remaining == 1: + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: log.debug("1 retry left") else: - log.debug("%i retries left", remaining) + log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) await anyio.sleep(timeout) - return await self._request( - options=options, - cast_to=cast_to, - remaining_retries=remaining, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, @@ -1563,6 +1627,7 @@ async def _process_response( response: httpx.Response, stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, ) -> ResponseT: if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": return cast( @@ -1574,12 +1639,20 @@ async def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ), ) origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") @@ -1593,6 +1666,7 @@ async def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ), ) @@ -1606,6 +1680,7 @@ async def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ) if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): return cast(ResponseT, api_response) @@ -1628,8 +1703,7 @@ async def get( cast_to: Type[ResponseT], options: RequestOptions = {}, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload async def get( @@ -1640,8 +1714,7 @@ async def get( options: RequestOptions = {}, stream: Literal[True], stream_cls: type[_AsyncStreamT], - ) -> _AsyncStreamT: - ... + ) -> _AsyncStreamT: ... @overload async def get( @@ -1652,8 +1725,7 @@ async def get( options: RequestOptions = {}, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: - ... + ) -> ResponseT | _AsyncStreamT: ... async def get( self, @@ -1677,8 +1749,7 @@ async def post( files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload async def post( @@ -1691,8 +1762,7 @@ async def post( options: RequestOptions = {}, stream: Literal[True], stream_cls: type[_AsyncStreamT], - ) -> _AsyncStreamT: - ... + ) -> _AsyncStreamT: ... @overload async def post( @@ -1705,8 +1775,7 @@ async def post( options: RequestOptions = {}, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: - ... + ) -> ResponseT | _AsyncStreamT: ... async def post( self, @@ -1730,9 +1799,12 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return await self.request(cast_to, opts) async def put( @@ -1781,8 +1853,8 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} @@ -1811,6 +1883,11 @@ def make_request_options( return options +class ForceMultipartDict(Dict[str, None]): + def __bool__(self) -> bool: + return True + + class OtherPlatform: def __init__(self, name: str) -> None: self.name = name @@ -1878,11 +1955,11 @@ def get_platform() -> Platform: @lru_cache(maxsize=None) -def platform_headers(version: str) -> Dict[str, str]: +def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]: return { "X-Stainless-Lang": "python", "X-Stainless-Package-Version": version, - "X-Stainless-OS": str(get_platform()), + "X-Stainless-OS": str(platform or get_platform()), "X-Stainless-Arch": str(get_architecture()), "X-Stainless-Runtime": get_python_runtime(), "X-Stainless-Runtime-Version": get_python_version(), @@ -1917,7 +1994,6 @@ def get_python_version() -> str: def get_architecture() -> Arch: try: - python_bitness, _ = platform.architecture() machine = platform.machine().lower() except Exception: return "unknown" @@ -1933,7 +2009,7 @@ def get_architecture() -> Arch: return "x64" # TODO: untested - if python_bitness == "32bit": + if sys.maxsize <= 2**32: return "x32" if machine: diff --git a/src/lithic/_client.py b/src/lithic/_client.py index 3c0fda1a..baca51df 100644 --- a/src/lithic/_client.py +++ b/src/lithic/_client.py @@ -1,18 +1,16 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import os -from typing import Any, Dict, Union, Mapping, cast +from typing import TYPE_CHECKING, Any, Dict, Mapping, cast from typing_extensions import Self, Literal, override import httpx -from . import resources, _exceptions, _legacy_response +from . import _exceptions, _legacy_response from ._qs import Querystring -from .types import APIStatus from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -21,26 +19,85 @@ NotGiven, Transport, ProxiesTypes, - AsyncTransport, RequestOptions, + not_given, ) -from ._utils import ( - is_given, - get_async_library, -) +from ._utils import is_given, get_async_library +from ._compat import cached_property from ._version import __version__ from ._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import LithicError, APIStatusError from ._base_client import ( - DEFAULT_LIMITS, DEFAULT_MAX_RETRIES, SyncAPIClient, AsyncAPIClient, - SyncHttpxClientWrapper, - AsyncHttpxClientWrapper, make_request_options, ) +from .types.api_status import APIStatus + +if TYPE_CHECKING: + from .resources import ( + cards, + fraud, + events, + reports, + accounts, + balances, + disputes, + payments, + three_ds, + auth_rules, + disputes_v2, + transactions, + card_programs, + tokenizations, + book_transfers, + funding_events, + account_holders, + credit_products, + account_activity, + card_bulk_orders, + digital_card_art, + network_programs, + external_payments, + financial_accounts, + responder_endpoints, + management_operations, + auth_stream_enrollment, + external_bank_accounts, + tokenization_decisioning, + ) + from .resources.accounts import Accounts, AsyncAccounts + from .resources.balances import Balances, AsyncBalances + from .resources.disputes import Disputes, AsyncDisputes + from .resources.payments import Payments, AsyncPayments + from .resources.webhooks import Webhooks, AsyncWebhooks + from .resources.cards.cards import Cards, AsyncCards + from .resources.disputes_v2 import DisputesV2, AsyncDisputesV2 + from .resources.fraud.fraud import Fraud, AsyncFraud + from .resources.card_programs import CardPrograms, AsyncCardPrograms + from .resources.events.events import Events, AsyncEvents + from .resources.tokenizations import Tokenizations, AsyncTokenizations + from .resources.book_transfers import BookTransfers, AsyncBookTransfers + from .resources.funding_events import FundingEvents, AsyncFundingEvents + from .resources.account_holders import AccountHolders, AsyncAccountHolders + from .resources.reports.reports import Reports, AsyncReports + from .resources.account_activity import AccountActivity, AsyncAccountActivity + from .resources.card_bulk_orders import CardBulkOrders, AsyncCardBulkOrders + from .resources.digital_card_art import DigitalCardArtResource, AsyncDigitalCardArtResource + from .resources.network_programs import NetworkPrograms, AsyncNetworkPrograms + from .resources.external_payments import ExternalPayments, AsyncExternalPayments + from .resources.three_ds.three_ds import ThreeDS, AsyncThreeDS + from .resources.responder_endpoints import ResponderEndpoints, AsyncResponderEndpoints + from .resources.auth_rules.auth_rules import AuthRules, AsyncAuthRules + from .resources.management_operations import ManagementOperations, AsyncManagementOperations + from .resources.auth_stream_enrollment import AuthStreamEnrollment, AsyncAuthStreamEnrollment + from .resources.tokenization_decisioning import TokenizationDecisioning, AsyncTokenizationDecisioning + from .resources.transactions.transactions import Transactions, AsyncTransactions + from .resources.credit_products.credit_products import CreditProducts, AsyncCreditProducts + from .resources.financial_accounts.financial_accounts import FinancialAccounts, AsyncFinancialAccounts + from .resources.external_bank_accounts.external_bank_accounts import ExternalBankAccounts, AsyncExternalBankAccounts __all__ = [ "ENVIRONMENTS", @@ -48,7 +105,6 @@ "Transport", "ProxiesTypes", "RequestOptions", - "resources", "Lithic", "AsyncLithic", "Client", @@ -56,37 +112,12 @@ ] ENVIRONMENTS: Dict[str, str] = { - "production": "https://api.lithic.com/v1", - "sandbox": "https://sandbox.lithic.com/v1", + "production": "https://api.lithic.com", + "sandbox": "https://sandbox.lithic.com", } class Lithic(SyncAPIClient): - accounts: resources.Accounts - account_holders: resources.AccountHolders - auth_rules: resources.AuthRules - auth_stream_enrollment: resources.AuthStreamEnrollment - tokenization_decisioning: resources.TokenizationDecisioning - tokenizations: resources.Tokenizations - cards: resources.Cards - balances: resources.Balances - aggregate_balances: resources.AggregateBalances - disputes: resources.Disputes - events: resources.Events - financial_accounts: resources.FinancialAccounts - transactions: resources.Transactions - responder_endpoints: resources.ResponderEndpoints - webhooks: resources.Webhooks - external_bank_accounts: resources.ExternalBankAccounts - payments: resources.Payments - three_ds: resources.ThreeDS - reports: resources.Reports - card_product: resources.CardProduct - card_programs: resources.CardPrograms - digital_card_art: resources.DigitalCardArtResource - with_raw_response: LithicWithRawResponse - with_streaming_response: LithicWithStreamedResponse - # client options api_key: str webhook_secret: str | None @@ -98,20 +129,16 @@ def __init__( *, api_key: str | None = None, webhook_secret: str | None = None, - environment: Literal["production", "sandbox"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + environment: Literal["production", "sandbox"] | NotGiven = not_given, + base_url: str | httpx.URL | None | NotGiven = not_given, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - # Configure a custom httpx client. See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details. + # Configure a custom httpx client. + # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. + # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details. http_client: httpx.Client | None = None, - # See httpx documentation for [custom transports](https://www.python-httpx.org/advanced/#custom-transports) - transport: Transport | None = None, - # See httpx documentation for [proxies](https://www.python-httpx.org/advanced/#http-proxying) - proxies: ProxiesTypes | None = None, - # See httpx documentation for [limits](https://www.python-httpx.org/advanced/#pool-limit-configuration) - connection_pool_limits: httpx.Limits | None = None, # Enable or disable schema validation for data returned by the API. # When enabled an error APIResponseValidationError is raised # if the API responds with invalid data for the expected schema. @@ -122,7 +149,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous lithic client instance. + """Construct a new synchronous Lithic client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `api_key` from `LITHIC_API_KEY` @@ -172,38 +199,198 @@ def __init__( max_retries=max_retries, timeout=timeout, http_client=http_client, - transport=transport, - proxies=proxies, - limits=connection_pool_limits, custom_headers=default_headers, custom_query=default_query, _strict_response_validation=_strict_response_validation, ) - self.accounts = resources.Accounts(self) - self.account_holders = resources.AccountHolders(self) - self.auth_rules = resources.AuthRules(self) - self.auth_stream_enrollment = resources.AuthStreamEnrollment(self) - self.tokenization_decisioning = resources.TokenizationDecisioning(self) - self.tokenizations = resources.Tokenizations(self) - self.cards = resources.Cards(self) - self.balances = resources.Balances(self) - self.aggregate_balances = resources.AggregateBalances(self) - self.disputes = resources.Disputes(self) - self.events = resources.Events(self) - self.financial_accounts = resources.FinancialAccounts(self) - self.transactions = resources.Transactions(self) - self.responder_endpoints = resources.ResponderEndpoints(self) - self.webhooks = resources.Webhooks(self) - self.external_bank_accounts = resources.ExternalBankAccounts(self) - self.payments = resources.Payments(self) - self.three_ds = resources.ThreeDS(self) - self.reports = resources.Reports(self) - self.card_product = resources.CardProduct(self) - self.card_programs = resources.CardPrograms(self) - self.digital_card_art = resources.DigitalCardArtResource(self) - self.with_raw_response = LithicWithRawResponse(self) - self.with_streaming_response = LithicWithStreamedResponse(self) + @cached_property + def accounts(self) -> Accounts: + from .resources.accounts import Accounts + + return Accounts(self) + + @cached_property + def account_holders(self) -> AccountHolders: + from .resources.account_holders import AccountHolders + + return AccountHolders(self) + + @cached_property + def auth_rules(self) -> AuthRules: + from .resources.auth_rules import AuthRules + + return AuthRules(self) + + @cached_property + def auth_stream_enrollment(self) -> AuthStreamEnrollment: + from .resources.auth_stream_enrollment import AuthStreamEnrollment + + return AuthStreamEnrollment(self) + + @cached_property + def tokenization_decisioning(self) -> TokenizationDecisioning: + from .resources.tokenization_decisioning import TokenizationDecisioning + + return TokenizationDecisioning(self) + + @cached_property + def tokenizations(self) -> Tokenizations: + from .resources.tokenizations import Tokenizations + + return Tokenizations(self) + + @cached_property + def cards(self) -> Cards: + from .resources.cards import Cards + + return Cards(self) + + @cached_property + def card_bulk_orders(self) -> CardBulkOrders: + from .resources.card_bulk_orders import CardBulkOrders + + return CardBulkOrders(self) + + @cached_property + def balances(self) -> Balances: + from .resources.balances import Balances + + return Balances(self) + + @cached_property + def disputes(self) -> Disputes: + from .resources.disputes import Disputes + + return Disputes(self) + + @cached_property + def disputes_v2(self) -> DisputesV2: + from .resources.disputes_v2 import DisputesV2 + + return DisputesV2(self) + + @cached_property + def events(self) -> Events: + from .resources.events import Events + + return Events(self) + + @cached_property + def financial_accounts(self) -> FinancialAccounts: + from .resources.financial_accounts import FinancialAccounts + + return FinancialAccounts(self) + + @cached_property + def transactions(self) -> Transactions: + from .resources.transactions import Transactions + + return Transactions(self) + + @cached_property + def responder_endpoints(self) -> ResponderEndpoints: + from .resources.responder_endpoints import ResponderEndpoints + + return ResponderEndpoints(self) + + @cached_property + def external_bank_accounts(self) -> ExternalBankAccounts: + from .resources.external_bank_accounts import ExternalBankAccounts + + return ExternalBankAccounts(self) + + @cached_property + def payments(self) -> Payments: + from .resources.payments import Payments + + return Payments(self) + + @cached_property + def three_ds(self) -> ThreeDS: + from .resources.three_ds import ThreeDS + + return ThreeDS(self) + + @cached_property + def reports(self) -> Reports: + from .resources.reports import Reports + + return Reports(self) + + @cached_property + def card_programs(self) -> CardPrograms: + from .resources.card_programs import CardPrograms + + return CardPrograms(self) + + @cached_property + def digital_card_art(self) -> DigitalCardArtResource: + from .resources.digital_card_art import DigitalCardArtResource + + return DigitalCardArtResource(self) + + @cached_property + def book_transfers(self) -> BookTransfers: + from .resources.book_transfers import BookTransfers + + return BookTransfers(self) + + @cached_property + def credit_products(self) -> CreditProducts: + from .resources.credit_products import CreditProducts + + return CreditProducts(self) + + @cached_property + def external_payments(self) -> ExternalPayments: + from .resources.external_payments import ExternalPayments + + return ExternalPayments(self) + + @cached_property + def management_operations(self) -> ManagementOperations: + from .resources.management_operations import ManagementOperations + + return ManagementOperations(self) + + @cached_property + def funding_events(self) -> FundingEvents: + from .resources.funding_events import FundingEvents + + return FundingEvents(self) + + @cached_property + def fraud(self) -> Fraud: + from .resources.fraud import Fraud + + return Fraud(self) + + @cached_property + def network_programs(self) -> NetworkPrograms: + from .resources.network_programs import NetworkPrograms + + return NetworkPrograms(self) + + @cached_property + def account_activity(self) -> AccountActivity: + from .resources.account_activity import AccountActivity + + return AccountActivity(self) + + @cached_property + def webhooks(self) -> Webhooks: + from .resources.webhooks import Webhooks + + return Webhooks(self) + + @cached_property + def with_raw_response(self) -> LithicWithRawResponse: + return LithicWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LithicWithStreamedResponse: + return LithicWithStreamedResponse(self) @property @override @@ -233,10 +420,9 @@ def copy( webhook_secret: str | None = None, environment: Literal["production", "sandbox"] | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, - connection_pool_limits: httpx.Limits | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -264,24 +450,7 @@ def copy( elif set_default_query is not None: params = set_default_query - if connection_pool_limits is not None: - if http_client is not None: - raise ValueError("The 'http_client' argument is mutually exclusive with 'connection_pool_limits'") - - if not isinstance(self._client, SyncHttpxClientWrapper): - raise ValueError( - "A custom HTTP client has been set and is mutually exclusive with the 'connection_pool_limits' argument" - ) - - http_client = None - else: - if self._limits is not DEFAULT_LIMITS: - connection_pool_limits = self._limits - else: - connection_pool_limits = None - - http_client = http_client or self._client - + http_client = http_client or self._client return self.__class__( api_key=api_key or self.api_key, webhook_secret=webhook_secret or self.webhook_secret, @@ -289,7 +458,6 @@ def copy( environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, - connection_pool_limits=connection_pool_limits, max_retries=max_retries if is_given(max_retries) else self.max_retries, default_headers=headers, default_query=params, @@ -308,11 +476,11 @@ def api_status( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APIStatus: """Status of api""" return self.get( - "/status", + "/v1/status", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -354,31 +522,6 @@ def _make_status_error( class AsyncLithic(AsyncAPIClient): - accounts: resources.AsyncAccounts - account_holders: resources.AsyncAccountHolders - auth_rules: resources.AsyncAuthRules - auth_stream_enrollment: resources.AsyncAuthStreamEnrollment - tokenization_decisioning: resources.AsyncTokenizationDecisioning - tokenizations: resources.AsyncTokenizations - cards: resources.AsyncCards - balances: resources.AsyncBalances - aggregate_balances: resources.AsyncAggregateBalances - disputes: resources.AsyncDisputes - events: resources.AsyncEvents - financial_accounts: resources.AsyncFinancialAccounts - transactions: resources.AsyncTransactions - responder_endpoints: resources.AsyncResponderEndpoints - webhooks: resources.AsyncWebhooks - external_bank_accounts: resources.AsyncExternalBankAccounts - payments: resources.AsyncPayments - three_ds: resources.AsyncThreeDS - reports: resources.AsyncReports - card_product: resources.AsyncCardProduct - card_programs: resources.AsyncCardPrograms - digital_card_art: resources.AsyncDigitalCardArtResource - with_raw_response: AsyncLithicWithRawResponse - with_streaming_response: AsyncLithicWithStreamedResponse - # client options api_key: str webhook_secret: str | None @@ -390,20 +533,16 @@ def __init__( *, api_key: str | None = None, webhook_secret: str | None = None, - environment: Literal["production", "sandbox"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + environment: Literal["production", "sandbox"] | NotGiven = not_given, + base_url: str | httpx.URL | None | NotGiven = not_given, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, - # Configure a custom httpx client. See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details. + # Configure a custom httpx client. + # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. + # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details. http_client: httpx.AsyncClient | None = None, - # See httpx documentation for [custom transports](https://www.python-httpx.org/advanced/#custom-transports) - transport: AsyncTransport | None = None, - # See httpx documentation for [proxies](https://www.python-httpx.org/advanced/#http-proxying) - proxies: ProxiesTypes | None = None, - # See httpx documentation for [limits](https://www.python-httpx.org/advanced/#pool-limit-configuration) - connection_pool_limits: httpx.Limits | None = None, # Enable or disable schema validation for data returned by the API. # When enabled an error APIResponseValidationError is raised # if the API responds with invalid data for the expected schema. @@ -414,7 +553,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async lithic client instance. + """Construct a new async AsyncLithic client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `api_key` from `LITHIC_API_KEY` @@ -464,38 +603,198 @@ def __init__( max_retries=max_retries, timeout=timeout, http_client=http_client, - transport=transport, - proxies=proxies, - limits=connection_pool_limits, custom_headers=default_headers, custom_query=default_query, _strict_response_validation=_strict_response_validation, ) - self.accounts = resources.AsyncAccounts(self) - self.account_holders = resources.AsyncAccountHolders(self) - self.auth_rules = resources.AsyncAuthRules(self) - self.auth_stream_enrollment = resources.AsyncAuthStreamEnrollment(self) - self.tokenization_decisioning = resources.AsyncTokenizationDecisioning(self) - self.tokenizations = resources.AsyncTokenizations(self) - self.cards = resources.AsyncCards(self) - self.balances = resources.AsyncBalances(self) - self.aggregate_balances = resources.AsyncAggregateBalances(self) - self.disputes = resources.AsyncDisputes(self) - self.events = resources.AsyncEvents(self) - self.financial_accounts = resources.AsyncFinancialAccounts(self) - self.transactions = resources.AsyncTransactions(self) - self.responder_endpoints = resources.AsyncResponderEndpoints(self) - self.webhooks = resources.AsyncWebhooks(self) - self.external_bank_accounts = resources.AsyncExternalBankAccounts(self) - self.payments = resources.AsyncPayments(self) - self.three_ds = resources.AsyncThreeDS(self) - self.reports = resources.AsyncReports(self) - self.card_product = resources.AsyncCardProduct(self) - self.card_programs = resources.AsyncCardPrograms(self) - self.digital_card_art = resources.AsyncDigitalCardArtResource(self) - self.with_raw_response = AsyncLithicWithRawResponse(self) - self.with_streaming_response = AsyncLithicWithStreamedResponse(self) + @cached_property + def accounts(self) -> AsyncAccounts: + from .resources.accounts import AsyncAccounts + + return AsyncAccounts(self) + + @cached_property + def account_holders(self) -> AsyncAccountHolders: + from .resources.account_holders import AsyncAccountHolders + + return AsyncAccountHolders(self) + + @cached_property + def auth_rules(self) -> AsyncAuthRules: + from .resources.auth_rules import AsyncAuthRules + + return AsyncAuthRules(self) + + @cached_property + def auth_stream_enrollment(self) -> AsyncAuthStreamEnrollment: + from .resources.auth_stream_enrollment import AsyncAuthStreamEnrollment + + return AsyncAuthStreamEnrollment(self) + + @cached_property + def tokenization_decisioning(self) -> AsyncTokenizationDecisioning: + from .resources.tokenization_decisioning import AsyncTokenizationDecisioning + + return AsyncTokenizationDecisioning(self) + + @cached_property + def tokenizations(self) -> AsyncTokenizations: + from .resources.tokenizations import AsyncTokenizations + + return AsyncTokenizations(self) + + @cached_property + def cards(self) -> AsyncCards: + from .resources.cards import AsyncCards + + return AsyncCards(self) + + @cached_property + def card_bulk_orders(self) -> AsyncCardBulkOrders: + from .resources.card_bulk_orders import AsyncCardBulkOrders + + return AsyncCardBulkOrders(self) + + @cached_property + def balances(self) -> AsyncBalances: + from .resources.balances import AsyncBalances + + return AsyncBalances(self) + + @cached_property + def disputes(self) -> AsyncDisputes: + from .resources.disputes import AsyncDisputes + + return AsyncDisputes(self) + + @cached_property + def disputes_v2(self) -> AsyncDisputesV2: + from .resources.disputes_v2 import AsyncDisputesV2 + + return AsyncDisputesV2(self) + + @cached_property + def events(self) -> AsyncEvents: + from .resources.events import AsyncEvents + + return AsyncEvents(self) + + @cached_property + def financial_accounts(self) -> AsyncFinancialAccounts: + from .resources.financial_accounts import AsyncFinancialAccounts + + return AsyncFinancialAccounts(self) + + @cached_property + def transactions(self) -> AsyncTransactions: + from .resources.transactions import AsyncTransactions + + return AsyncTransactions(self) + + @cached_property + def responder_endpoints(self) -> AsyncResponderEndpoints: + from .resources.responder_endpoints import AsyncResponderEndpoints + + return AsyncResponderEndpoints(self) + + @cached_property + def external_bank_accounts(self) -> AsyncExternalBankAccounts: + from .resources.external_bank_accounts import AsyncExternalBankAccounts + + return AsyncExternalBankAccounts(self) + + @cached_property + def payments(self) -> AsyncPayments: + from .resources.payments import AsyncPayments + + return AsyncPayments(self) + + @cached_property + def three_ds(self) -> AsyncThreeDS: + from .resources.three_ds import AsyncThreeDS + + return AsyncThreeDS(self) + + @cached_property + def reports(self) -> AsyncReports: + from .resources.reports import AsyncReports + + return AsyncReports(self) + + @cached_property + def card_programs(self) -> AsyncCardPrograms: + from .resources.card_programs import AsyncCardPrograms + + return AsyncCardPrograms(self) + + @cached_property + def digital_card_art(self) -> AsyncDigitalCardArtResource: + from .resources.digital_card_art import AsyncDigitalCardArtResource + + return AsyncDigitalCardArtResource(self) + + @cached_property + def book_transfers(self) -> AsyncBookTransfers: + from .resources.book_transfers import AsyncBookTransfers + + return AsyncBookTransfers(self) + + @cached_property + def credit_products(self) -> AsyncCreditProducts: + from .resources.credit_products import AsyncCreditProducts + + return AsyncCreditProducts(self) + + @cached_property + def external_payments(self) -> AsyncExternalPayments: + from .resources.external_payments import AsyncExternalPayments + + return AsyncExternalPayments(self) + + @cached_property + def management_operations(self) -> AsyncManagementOperations: + from .resources.management_operations import AsyncManagementOperations + + return AsyncManagementOperations(self) + + @cached_property + def funding_events(self) -> AsyncFundingEvents: + from .resources.funding_events import AsyncFundingEvents + + return AsyncFundingEvents(self) + + @cached_property + def fraud(self) -> AsyncFraud: + from .resources.fraud import AsyncFraud + + return AsyncFraud(self) + + @cached_property + def network_programs(self) -> AsyncNetworkPrograms: + from .resources.network_programs import AsyncNetworkPrograms + + return AsyncNetworkPrograms(self) + + @cached_property + def account_activity(self) -> AsyncAccountActivity: + from .resources.account_activity import AsyncAccountActivity + + return AsyncAccountActivity(self) + + @cached_property + def webhooks(self) -> AsyncWebhooks: + from .resources.webhooks import AsyncWebhooks + + return AsyncWebhooks(self) + + @cached_property + def with_raw_response(self) -> AsyncLithicWithRawResponse: + return AsyncLithicWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLithicWithStreamedResponse: + return AsyncLithicWithStreamedResponse(self) @property @override @@ -525,10 +824,9 @@ def copy( webhook_secret: str | None = None, environment: Literal["production", "sandbox"] | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, - connection_pool_limits: httpx.Limits | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -556,24 +854,7 @@ def copy( elif set_default_query is not None: params = set_default_query - if connection_pool_limits is not None: - if http_client is not None: - raise ValueError("The 'http_client' argument is mutually exclusive with 'connection_pool_limits'") - - if not isinstance(self._client, AsyncHttpxClientWrapper): - raise ValueError( - "A custom HTTP client has been set and is mutually exclusive with the 'connection_pool_limits' argument" - ) - - http_client = None - else: - if self._limits is not DEFAULT_LIMITS: - connection_pool_limits = self._limits - else: - connection_pool_limits = None - - http_client = http_client or self._client - + http_client = http_client or self._client return self.__class__( api_key=api_key or self.api_key, webhook_secret=webhook_secret or self.webhook_secret, @@ -581,7 +862,6 @@ def copy( environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, - connection_pool_limits=connection_pool_limits, max_retries=max_retries if is_given(max_retries) else self.max_retries, default_headers=headers, default_query=params, @@ -600,11 +880,11 @@ async def api_status( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APIStatus: """Status of api""" return await self.get( - "/status", + "/v1/status", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -646,132 +926,744 @@ def _make_status_error( class LithicWithRawResponse: + _client: Lithic + def __init__(self, client: Lithic) -> None: - self.accounts = resources.AccountsWithRawResponse(client.accounts) - self.account_holders = resources.AccountHoldersWithRawResponse(client.account_holders) - self.auth_rules = resources.AuthRulesWithRawResponse(client.auth_rules) - self.auth_stream_enrollment = resources.AuthStreamEnrollmentWithRawResponse(client.auth_stream_enrollment) - self.tokenization_decisioning = resources.TokenizationDecisioningWithRawResponse( - client.tokenization_decisioning - ) - self.tokenizations = resources.TokenizationsWithRawResponse(client.tokenizations) - self.cards = resources.CardsWithRawResponse(client.cards) - self.balances = resources.BalancesWithRawResponse(client.balances) - self.aggregate_balances = resources.AggregateBalancesWithRawResponse(client.aggregate_balances) - self.disputes = resources.DisputesWithRawResponse(client.disputes) - self.events = resources.EventsWithRawResponse(client.events) - self.financial_accounts = resources.FinancialAccountsWithRawResponse(client.financial_accounts) - self.transactions = resources.TransactionsWithRawResponse(client.transactions) - self.responder_endpoints = resources.ResponderEndpointsWithRawResponse(client.responder_endpoints) - self.external_bank_accounts = resources.ExternalBankAccountsWithRawResponse(client.external_bank_accounts) - self.payments = resources.PaymentsWithRawResponse(client.payments) - self.three_ds = resources.ThreeDSWithRawResponse(client.three_ds) - self.reports = resources.ReportsWithRawResponse(client.reports) - self.card_product = resources.CardProductWithRawResponse(client.card_product) - self.card_programs = resources.CardProgramsWithRawResponse(client.card_programs) - self.digital_card_art = resources.DigitalCardArtResourceWithRawResponse(client.digital_card_art) + self._client = client self.api_status = _legacy_response.to_raw_response_wrapper( client.api_status, ) + @cached_property + def accounts(self) -> accounts.AccountsWithRawResponse: + from .resources.accounts import AccountsWithRawResponse + + return AccountsWithRawResponse(self._client.accounts) + + @cached_property + def account_holders(self) -> account_holders.AccountHoldersWithRawResponse: + from .resources.account_holders import AccountHoldersWithRawResponse + + return AccountHoldersWithRawResponse(self._client.account_holders) + + @cached_property + def auth_rules(self) -> auth_rules.AuthRulesWithRawResponse: + from .resources.auth_rules import AuthRulesWithRawResponse + + return AuthRulesWithRawResponse(self._client.auth_rules) + + @cached_property + def auth_stream_enrollment(self) -> auth_stream_enrollment.AuthStreamEnrollmentWithRawResponse: + from .resources.auth_stream_enrollment import AuthStreamEnrollmentWithRawResponse + + return AuthStreamEnrollmentWithRawResponse(self._client.auth_stream_enrollment) + + @cached_property + def tokenization_decisioning(self) -> tokenization_decisioning.TokenizationDecisioningWithRawResponse: + from .resources.tokenization_decisioning import TokenizationDecisioningWithRawResponse + + return TokenizationDecisioningWithRawResponse(self._client.tokenization_decisioning) + + @cached_property + def tokenizations(self) -> tokenizations.TokenizationsWithRawResponse: + from .resources.tokenizations import TokenizationsWithRawResponse + + return TokenizationsWithRawResponse(self._client.tokenizations) + + @cached_property + def cards(self) -> cards.CardsWithRawResponse: + from .resources.cards import CardsWithRawResponse + + return CardsWithRawResponse(self._client.cards) + + @cached_property + def card_bulk_orders(self) -> card_bulk_orders.CardBulkOrdersWithRawResponse: + from .resources.card_bulk_orders import CardBulkOrdersWithRawResponse + + return CardBulkOrdersWithRawResponse(self._client.card_bulk_orders) + + @cached_property + def balances(self) -> balances.BalancesWithRawResponse: + from .resources.balances import BalancesWithRawResponse + + return BalancesWithRawResponse(self._client.balances) + + @cached_property + def disputes(self) -> disputes.DisputesWithRawResponse: + from .resources.disputes import DisputesWithRawResponse + + return DisputesWithRawResponse(self._client.disputes) + + @cached_property + def disputes_v2(self) -> disputes_v2.DisputesV2WithRawResponse: + from .resources.disputes_v2 import DisputesV2WithRawResponse + + return DisputesV2WithRawResponse(self._client.disputes_v2) + + @cached_property + def events(self) -> events.EventsWithRawResponse: + from .resources.events import EventsWithRawResponse + + return EventsWithRawResponse(self._client.events) + + @cached_property + def financial_accounts(self) -> financial_accounts.FinancialAccountsWithRawResponse: + from .resources.financial_accounts import FinancialAccountsWithRawResponse + + return FinancialAccountsWithRawResponse(self._client.financial_accounts) + + @cached_property + def transactions(self) -> transactions.TransactionsWithRawResponse: + from .resources.transactions import TransactionsWithRawResponse + + return TransactionsWithRawResponse(self._client.transactions) + + @cached_property + def responder_endpoints(self) -> responder_endpoints.ResponderEndpointsWithRawResponse: + from .resources.responder_endpoints import ResponderEndpointsWithRawResponse + + return ResponderEndpointsWithRawResponse(self._client.responder_endpoints) + + @cached_property + def external_bank_accounts(self) -> external_bank_accounts.ExternalBankAccountsWithRawResponse: + from .resources.external_bank_accounts import ExternalBankAccountsWithRawResponse + + return ExternalBankAccountsWithRawResponse(self._client.external_bank_accounts) + + @cached_property + def payments(self) -> payments.PaymentsWithRawResponse: + from .resources.payments import PaymentsWithRawResponse + + return PaymentsWithRawResponse(self._client.payments) + + @cached_property + def three_ds(self) -> three_ds.ThreeDSWithRawResponse: + from .resources.three_ds import ThreeDSWithRawResponse + + return ThreeDSWithRawResponse(self._client.three_ds) + + @cached_property + def reports(self) -> reports.ReportsWithRawResponse: + from .resources.reports import ReportsWithRawResponse + + return ReportsWithRawResponse(self._client.reports) + + @cached_property + def card_programs(self) -> card_programs.CardProgramsWithRawResponse: + from .resources.card_programs import CardProgramsWithRawResponse + + return CardProgramsWithRawResponse(self._client.card_programs) + + @cached_property + def digital_card_art(self) -> digital_card_art.DigitalCardArtResourceWithRawResponse: + from .resources.digital_card_art import DigitalCardArtResourceWithRawResponse + + return DigitalCardArtResourceWithRawResponse(self._client.digital_card_art) + + @cached_property + def book_transfers(self) -> book_transfers.BookTransfersWithRawResponse: + from .resources.book_transfers import BookTransfersWithRawResponse + + return BookTransfersWithRawResponse(self._client.book_transfers) + + @cached_property + def credit_products(self) -> credit_products.CreditProductsWithRawResponse: + from .resources.credit_products import CreditProductsWithRawResponse + + return CreditProductsWithRawResponse(self._client.credit_products) + + @cached_property + def external_payments(self) -> external_payments.ExternalPaymentsWithRawResponse: + from .resources.external_payments import ExternalPaymentsWithRawResponse + + return ExternalPaymentsWithRawResponse(self._client.external_payments) + + @cached_property + def management_operations(self) -> management_operations.ManagementOperationsWithRawResponse: + from .resources.management_operations import ManagementOperationsWithRawResponse + + return ManagementOperationsWithRawResponse(self._client.management_operations) + + @cached_property + def funding_events(self) -> funding_events.FundingEventsWithRawResponse: + from .resources.funding_events import FundingEventsWithRawResponse + + return FundingEventsWithRawResponse(self._client.funding_events) + + @cached_property + def fraud(self) -> fraud.FraudWithRawResponse: + from .resources.fraud import FraudWithRawResponse + + return FraudWithRawResponse(self._client.fraud) + + @cached_property + def network_programs(self) -> network_programs.NetworkProgramsWithRawResponse: + from .resources.network_programs import NetworkProgramsWithRawResponse + + return NetworkProgramsWithRawResponse(self._client.network_programs) + + @cached_property + def account_activity(self) -> account_activity.AccountActivityWithRawResponse: + from .resources.account_activity import AccountActivityWithRawResponse + + return AccountActivityWithRawResponse(self._client.account_activity) + class AsyncLithicWithRawResponse: + _client: AsyncLithic + def __init__(self, client: AsyncLithic) -> None: - self.accounts = resources.AsyncAccountsWithRawResponse(client.accounts) - self.account_holders = resources.AsyncAccountHoldersWithRawResponse(client.account_holders) - self.auth_rules = resources.AsyncAuthRulesWithRawResponse(client.auth_rules) - self.auth_stream_enrollment = resources.AsyncAuthStreamEnrollmentWithRawResponse(client.auth_stream_enrollment) - self.tokenization_decisioning = resources.AsyncTokenizationDecisioningWithRawResponse( - client.tokenization_decisioning - ) - self.tokenizations = resources.AsyncTokenizationsWithRawResponse(client.tokenizations) - self.cards = resources.AsyncCardsWithRawResponse(client.cards) - self.balances = resources.AsyncBalancesWithRawResponse(client.balances) - self.aggregate_balances = resources.AsyncAggregateBalancesWithRawResponse(client.aggregate_balances) - self.disputes = resources.AsyncDisputesWithRawResponse(client.disputes) - self.events = resources.AsyncEventsWithRawResponse(client.events) - self.financial_accounts = resources.AsyncFinancialAccountsWithRawResponse(client.financial_accounts) - self.transactions = resources.AsyncTransactionsWithRawResponse(client.transactions) - self.responder_endpoints = resources.AsyncResponderEndpointsWithRawResponse(client.responder_endpoints) - self.external_bank_accounts = resources.AsyncExternalBankAccountsWithRawResponse(client.external_bank_accounts) - self.payments = resources.AsyncPaymentsWithRawResponse(client.payments) - self.three_ds = resources.AsyncThreeDSWithRawResponse(client.three_ds) - self.reports = resources.AsyncReportsWithRawResponse(client.reports) - self.card_product = resources.AsyncCardProductWithRawResponse(client.card_product) - self.card_programs = resources.AsyncCardProgramsWithRawResponse(client.card_programs) - self.digital_card_art = resources.AsyncDigitalCardArtResourceWithRawResponse(client.digital_card_art) + self._client = client self.api_status = _legacy_response.async_to_raw_response_wrapper( client.api_status, ) + @cached_property + def accounts(self) -> accounts.AsyncAccountsWithRawResponse: + from .resources.accounts import AsyncAccountsWithRawResponse + + return AsyncAccountsWithRawResponse(self._client.accounts) + + @cached_property + def account_holders(self) -> account_holders.AsyncAccountHoldersWithRawResponse: + from .resources.account_holders import AsyncAccountHoldersWithRawResponse + + return AsyncAccountHoldersWithRawResponse(self._client.account_holders) + + @cached_property + def auth_rules(self) -> auth_rules.AsyncAuthRulesWithRawResponse: + from .resources.auth_rules import AsyncAuthRulesWithRawResponse + + return AsyncAuthRulesWithRawResponse(self._client.auth_rules) + + @cached_property + def auth_stream_enrollment(self) -> auth_stream_enrollment.AsyncAuthStreamEnrollmentWithRawResponse: + from .resources.auth_stream_enrollment import AsyncAuthStreamEnrollmentWithRawResponse + + return AsyncAuthStreamEnrollmentWithRawResponse(self._client.auth_stream_enrollment) + + @cached_property + def tokenization_decisioning(self) -> tokenization_decisioning.AsyncTokenizationDecisioningWithRawResponse: + from .resources.tokenization_decisioning import AsyncTokenizationDecisioningWithRawResponse + + return AsyncTokenizationDecisioningWithRawResponse(self._client.tokenization_decisioning) + + @cached_property + def tokenizations(self) -> tokenizations.AsyncTokenizationsWithRawResponse: + from .resources.tokenizations import AsyncTokenizationsWithRawResponse + + return AsyncTokenizationsWithRawResponse(self._client.tokenizations) + + @cached_property + def cards(self) -> cards.AsyncCardsWithRawResponse: + from .resources.cards import AsyncCardsWithRawResponse + + return AsyncCardsWithRawResponse(self._client.cards) + + @cached_property + def card_bulk_orders(self) -> card_bulk_orders.AsyncCardBulkOrdersWithRawResponse: + from .resources.card_bulk_orders import AsyncCardBulkOrdersWithRawResponse + + return AsyncCardBulkOrdersWithRawResponse(self._client.card_bulk_orders) + + @cached_property + def balances(self) -> balances.AsyncBalancesWithRawResponse: + from .resources.balances import AsyncBalancesWithRawResponse + + return AsyncBalancesWithRawResponse(self._client.balances) + + @cached_property + def disputes(self) -> disputes.AsyncDisputesWithRawResponse: + from .resources.disputes import AsyncDisputesWithRawResponse + + return AsyncDisputesWithRawResponse(self._client.disputes) + + @cached_property + def disputes_v2(self) -> disputes_v2.AsyncDisputesV2WithRawResponse: + from .resources.disputes_v2 import AsyncDisputesV2WithRawResponse + + return AsyncDisputesV2WithRawResponse(self._client.disputes_v2) + + @cached_property + def events(self) -> events.AsyncEventsWithRawResponse: + from .resources.events import AsyncEventsWithRawResponse + + return AsyncEventsWithRawResponse(self._client.events) + + @cached_property + def financial_accounts(self) -> financial_accounts.AsyncFinancialAccountsWithRawResponse: + from .resources.financial_accounts import AsyncFinancialAccountsWithRawResponse + + return AsyncFinancialAccountsWithRawResponse(self._client.financial_accounts) + + @cached_property + def transactions(self) -> transactions.AsyncTransactionsWithRawResponse: + from .resources.transactions import AsyncTransactionsWithRawResponse + + return AsyncTransactionsWithRawResponse(self._client.transactions) + + @cached_property + def responder_endpoints(self) -> responder_endpoints.AsyncResponderEndpointsWithRawResponse: + from .resources.responder_endpoints import AsyncResponderEndpointsWithRawResponse + + return AsyncResponderEndpointsWithRawResponse(self._client.responder_endpoints) + + @cached_property + def external_bank_accounts(self) -> external_bank_accounts.AsyncExternalBankAccountsWithRawResponse: + from .resources.external_bank_accounts import AsyncExternalBankAccountsWithRawResponse + + return AsyncExternalBankAccountsWithRawResponse(self._client.external_bank_accounts) + + @cached_property + def payments(self) -> payments.AsyncPaymentsWithRawResponse: + from .resources.payments import AsyncPaymentsWithRawResponse + + return AsyncPaymentsWithRawResponse(self._client.payments) + + @cached_property + def three_ds(self) -> three_ds.AsyncThreeDSWithRawResponse: + from .resources.three_ds import AsyncThreeDSWithRawResponse + + return AsyncThreeDSWithRawResponse(self._client.three_ds) + + @cached_property + def reports(self) -> reports.AsyncReportsWithRawResponse: + from .resources.reports import AsyncReportsWithRawResponse + + return AsyncReportsWithRawResponse(self._client.reports) + + @cached_property + def card_programs(self) -> card_programs.AsyncCardProgramsWithRawResponse: + from .resources.card_programs import AsyncCardProgramsWithRawResponse + + return AsyncCardProgramsWithRawResponse(self._client.card_programs) + + @cached_property + def digital_card_art(self) -> digital_card_art.AsyncDigitalCardArtResourceWithRawResponse: + from .resources.digital_card_art import AsyncDigitalCardArtResourceWithRawResponse + + return AsyncDigitalCardArtResourceWithRawResponse(self._client.digital_card_art) + + @cached_property + def book_transfers(self) -> book_transfers.AsyncBookTransfersWithRawResponse: + from .resources.book_transfers import AsyncBookTransfersWithRawResponse + + return AsyncBookTransfersWithRawResponse(self._client.book_transfers) + + @cached_property + def credit_products(self) -> credit_products.AsyncCreditProductsWithRawResponse: + from .resources.credit_products import AsyncCreditProductsWithRawResponse + + return AsyncCreditProductsWithRawResponse(self._client.credit_products) + + @cached_property + def external_payments(self) -> external_payments.AsyncExternalPaymentsWithRawResponse: + from .resources.external_payments import AsyncExternalPaymentsWithRawResponse + + return AsyncExternalPaymentsWithRawResponse(self._client.external_payments) + + @cached_property + def management_operations(self) -> management_operations.AsyncManagementOperationsWithRawResponse: + from .resources.management_operations import AsyncManagementOperationsWithRawResponse + + return AsyncManagementOperationsWithRawResponse(self._client.management_operations) + + @cached_property + def funding_events(self) -> funding_events.AsyncFundingEventsWithRawResponse: + from .resources.funding_events import AsyncFundingEventsWithRawResponse + + return AsyncFundingEventsWithRawResponse(self._client.funding_events) + + @cached_property + def fraud(self) -> fraud.AsyncFraudWithRawResponse: + from .resources.fraud import AsyncFraudWithRawResponse + + return AsyncFraudWithRawResponse(self._client.fraud) + + @cached_property + def network_programs(self) -> network_programs.AsyncNetworkProgramsWithRawResponse: + from .resources.network_programs import AsyncNetworkProgramsWithRawResponse + + return AsyncNetworkProgramsWithRawResponse(self._client.network_programs) + + @cached_property + def account_activity(self) -> account_activity.AsyncAccountActivityWithRawResponse: + from .resources.account_activity import AsyncAccountActivityWithRawResponse + + return AsyncAccountActivityWithRawResponse(self._client.account_activity) + class LithicWithStreamedResponse: + _client: Lithic + def __init__(self, client: Lithic) -> None: - self.accounts = resources.AccountsWithStreamingResponse(client.accounts) - self.account_holders = resources.AccountHoldersWithStreamingResponse(client.account_holders) - self.auth_rules = resources.AuthRulesWithStreamingResponse(client.auth_rules) - self.auth_stream_enrollment = resources.AuthStreamEnrollmentWithStreamingResponse(client.auth_stream_enrollment) - self.tokenization_decisioning = resources.TokenizationDecisioningWithStreamingResponse( - client.tokenization_decisioning - ) - self.tokenizations = resources.TokenizationsWithStreamingResponse(client.tokenizations) - self.cards = resources.CardsWithStreamingResponse(client.cards) - self.balances = resources.BalancesWithStreamingResponse(client.balances) - self.aggregate_balances = resources.AggregateBalancesWithStreamingResponse(client.aggregate_balances) - self.disputes = resources.DisputesWithStreamingResponse(client.disputes) - self.events = resources.EventsWithStreamingResponse(client.events) - self.financial_accounts = resources.FinancialAccountsWithStreamingResponse(client.financial_accounts) - self.transactions = resources.TransactionsWithStreamingResponse(client.transactions) - self.responder_endpoints = resources.ResponderEndpointsWithStreamingResponse(client.responder_endpoints) - self.external_bank_accounts = resources.ExternalBankAccountsWithStreamingResponse(client.external_bank_accounts) - self.payments = resources.PaymentsWithStreamingResponse(client.payments) - self.three_ds = resources.ThreeDSWithStreamingResponse(client.three_ds) - self.reports = resources.ReportsWithStreamingResponse(client.reports) - self.card_product = resources.CardProductWithStreamingResponse(client.card_product) - self.card_programs = resources.CardProgramsWithStreamingResponse(client.card_programs) - self.digital_card_art = resources.DigitalCardArtResourceWithStreamingResponse(client.digital_card_art) + self._client = client self.api_status = to_streamed_response_wrapper( client.api_status, ) + @cached_property + def accounts(self) -> accounts.AccountsWithStreamingResponse: + from .resources.accounts import AccountsWithStreamingResponse + + return AccountsWithStreamingResponse(self._client.accounts) + + @cached_property + def account_holders(self) -> account_holders.AccountHoldersWithStreamingResponse: + from .resources.account_holders import AccountHoldersWithStreamingResponse + + return AccountHoldersWithStreamingResponse(self._client.account_holders) + + @cached_property + def auth_rules(self) -> auth_rules.AuthRulesWithStreamingResponse: + from .resources.auth_rules import AuthRulesWithStreamingResponse + + return AuthRulesWithStreamingResponse(self._client.auth_rules) + + @cached_property + def auth_stream_enrollment(self) -> auth_stream_enrollment.AuthStreamEnrollmentWithStreamingResponse: + from .resources.auth_stream_enrollment import AuthStreamEnrollmentWithStreamingResponse + + return AuthStreamEnrollmentWithStreamingResponse(self._client.auth_stream_enrollment) + + @cached_property + def tokenization_decisioning(self) -> tokenization_decisioning.TokenizationDecisioningWithStreamingResponse: + from .resources.tokenization_decisioning import TokenizationDecisioningWithStreamingResponse + + return TokenizationDecisioningWithStreamingResponse(self._client.tokenization_decisioning) + + @cached_property + def tokenizations(self) -> tokenizations.TokenizationsWithStreamingResponse: + from .resources.tokenizations import TokenizationsWithStreamingResponse + + return TokenizationsWithStreamingResponse(self._client.tokenizations) + + @cached_property + def cards(self) -> cards.CardsWithStreamingResponse: + from .resources.cards import CardsWithStreamingResponse + + return CardsWithStreamingResponse(self._client.cards) + + @cached_property + def card_bulk_orders(self) -> card_bulk_orders.CardBulkOrdersWithStreamingResponse: + from .resources.card_bulk_orders import CardBulkOrdersWithStreamingResponse + + return CardBulkOrdersWithStreamingResponse(self._client.card_bulk_orders) + + @cached_property + def balances(self) -> balances.BalancesWithStreamingResponse: + from .resources.balances import BalancesWithStreamingResponse + + return BalancesWithStreamingResponse(self._client.balances) + + @cached_property + def disputes(self) -> disputes.DisputesWithStreamingResponse: + from .resources.disputes import DisputesWithStreamingResponse + + return DisputesWithStreamingResponse(self._client.disputes) + + @cached_property + def disputes_v2(self) -> disputes_v2.DisputesV2WithStreamingResponse: + from .resources.disputes_v2 import DisputesV2WithStreamingResponse + + return DisputesV2WithStreamingResponse(self._client.disputes_v2) + + @cached_property + def events(self) -> events.EventsWithStreamingResponse: + from .resources.events import EventsWithStreamingResponse + + return EventsWithStreamingResponse(self._client.events) + + @cached_property + def financial_accounts(self) -> financial_accounts.FinancialAccountsWithStreamingResponse: + from .resources.financial_accounts import FinancialAccountsWithStreamingResponse + + return FinancialAccountsWithStreamingResponse(self._client.financial_accounts) + + @cached_property + def transactions(self) -> transactions.TransactionsWithStreamingResponse: + from .resources.transactions import TransactionsWithStreamingResponse + + return TransactionsWithStreamingResponse(self._client.transactions) + + @cached_property + def responder_endpoints(self) -> responder_endpoints.ResponderEndpointsWithStreamingResponse: + from .resources.responder_endpoints import ResponderEndpointsWithStreamingResponse + + return ResponderEndpointsWithStreamingResponse(self._client.responder_endpoints) + + @cached_property + def external_bank_accounts(self) -> external_bank_accounts.ExternalBankAccountsWithStreamingResponse: + from .resources.external_bank_accounts import ExternalBankAccountsWithStreamingResponse + + return ExternalBankAccountsWithStreamingResponse(self._client.external_bank_accounts) + + @cached_property + def payments(self) -> payments.PaymentsWithStreamingResponse: + from .resources.payments import PaymentsWithStreamingResponse + + return PaymentsWithStreamingResponse(self._client.payments) + + @cached_property + def three_ds(self) -> three_ds.ThreeDSWithStreamingResponse: + from .resources.three_ds import ThreeDSWithStreamingResponse + + return ThreeDSWithStreamingResponse(self._client.three_ds) + + @cached_property + def reports(self) -> reports.ReportsWithStreamingResponse: + from .resources.reports import ReportsWithStreamingResponse + + return ReportsWithStreamingResponse(self._client.reports) + + @cached_property + def card_programs(self) -> card_programs.CardProgramsWithStreamingResponse: + from .resources.card_programs import CardProgramsWithStreamingResponse + + return CardProgramsWithStreamingResponse(self._client.card_programs) + + @cached_property + def digital_card_art(self) -> digital_card_art.DigitalCardArtResourceWithStreamingResponse: + from .resources.digital_card_art import DigitalCardArtResourceWithStreamingResponse + + return DigitalCardArtResourceWithStreamingResponse(self._client.digital_card_art) + + @cached_property + def book_transfers(self) -> book_transfers.BookTransfersWithStreamingResponse: + from .resources.book_transfers import BookTransfersWithStreamingResponse + + return BookTransfersWithStreamingResponse(self._client.book_transfers) + + @cached_property + def credit_products(self) -> credit_products.CreditProductsWithStreamingResponse: + from .resources.credit_products import CreditProductsWithStreamingResponse + + return CreditProductsWithStreamingResponse(self._client.credit_products) + + @cached_property + def external_payments(self) -> external_payments.ExternalPaymentsWithStreamingResponse: + from .resources.external_payments import ExternalPaymentsWithStreamingResponse + + return ExternalPaymentsWithStreamingResponse(self._client.external_payments) + + @cached_property + def management_operations(self) -> management_operations.ManagementOperationsWithStreamingResponse: + from .resources.management_operations import ManagementOperationsWithStreamingResponse + + return ManagementOperationsWithStreamingResponse(self._client.management_operations) + + @cached_property + def funding_events(self) -> funding_events.FundingEventsWithStreamingResponse: + from .resources.funding_events import FundingEventsWithStreamingResponse + + return FundingEventsWithStreamingResponse(self._client.funding_events) + + @cached_property + def fraud(self) -> fraud.FraudWithStreamingResponse: + from .resources.fraud import FraudWithStreamingResponse + + return FraudWithStreamingResponse(self._client.fraud) + + @cached_property + def network_programs(self) -> network_programs.NetworkProgramsWithStreamingResponse: + from .resources.network_programs import NetworkProgramsWithStreamingResponse + + return NetworkProgramsWithStreamingResponse(self._client.network_programs) + + @cached_property + def account_activity(self) -> account_activity.AccountActivityWithStreamingResponse: + from .resources.account_activity import AccountActivityWithStreamingResponse + + return AccountActivityWithStreamingResponse(self._client.account_activity) + class AsyncLithicWithStreamedResponse: + _client: AsyncLithic + def __init__(self, client: AsyncLithic) -> None: - self.accounts = resources.AsyncAccountsWithStreamingResponse(client.accounts) - self.account_holders = resources.AsyncAccountHoldersWithStreamingResponse(client.account_holders) - self.auth_rules = resources.AsyncAuthRulesWithStreamingResponse(client.auth_rules) - self.auth_stream_enrollment = resources.AsyncAuthStreamEnrollmentWithStreamingResponse( - client.auth_stream_enrollment - ) - self.tokenization_decisioning = resources.AsyncTokenizationDecisioningWithStreamingResponse( - client.tokenization_decisioning - ) - self.tokenizations = resources.AsyncTokenizationsWithStreamingResponse(client.tokenizations) - self.cards = resources.AsyncCardsWithStreamingResponse(client.cards) - self.balances = resources.AsyncBalancesWithStreamingResponse(client.balances) - self.aggregate_balances = resources.AsyncAggregateBalancesWithStreamingResponse(client.aggregate_balances) - self.disputes = resources.AsyncDisputesWithStreamingResponse(client.disputes) - self.events = resources.AsyncEventsWithStreamingResponse(client.events) - self.financial_accounts = resources.AsyncFinancialAccountsWithStreamingResponse(client.financial_accounts) - self.transactions = resources.AsyncTransactionsWithStreamingResponse(client.transactions) - self.responder_endpoints = resources.AsyncResponderEndpointsWithStreamingResponse(client.responder_endpoints) - self.external_bank_accounts = resources.AsyncExternalBankAccountsWithStreamingResponse( - client.external_bank_accounts - ) - self.payments = resources.AsyncPaymentsWithStreamingResponse(client.payments) - self.three_ds = resources.AsyncThreeDSWithStreamingResponse(client.three_ds) - self.reports = resources.AsyncReportsWithStreamingResponse(client.reports) - self.card_product = resources.AsyncCardProductWithStreamingResponse(client.card_product) - self.card_programs = resources.AsyncCardProgramsWithStreamingResponse(client.card_programs) - self.digital_card_art = resources.AsyncDigitalCardArtResourceWithStreamingResponse(client.digital_card_art) + self._client = client self.api_status = async_to_streamed_response_wrapper( client.api_status, ) + @cached_property + def accounts(self) -> accounts.AsyncAccountsWithStreamingResponse: + from .resources.accounts import AsyncAccountsWithStreamingResponse + + return AsyncAccountsWithStreamingResponse(self._client.accounts) + + @cached_property + def account_holders(self) -> account_holders.AsyncAccountHoldersWithStreamingResponse: + from .resources.account_holders import AsyncAccountHoldersWithStreamingResponse + + return AsyncAccountHoldersWithStreamingResponse(self._client.account_holders) + + @cached_property + def auth_rules(self) -> auth_rules.AsyncAuthRulesWithStreamingResponse: + from .resources.auth_rules import AsyncAuthRulesWithStreamingResponse + + return AsyncAuthRulesWithStreamingResponse(self._client.auth_rules) + + @cached_property + def auth_stream_enrollment(self) -> auth_stream_enrollment.AsyncAuthStreamEnrollmentWithStreamingResponse: + from .resources.auth_stream_enrollment import AsyncAuthStreamEnrollmentWithStreamingResponse + + return AsyncAuthStreamEnrollmentWithStreamingResponse(self._client.auth_stream_enrollment) + + @cached_property + def tokenization_decisioning(self) -> tokenization_decisioning.AsyncTokenizationDecisioningWithStreamingResponse: + from .resources.tokenization_decisioning import AsyncTokenizationDecisioningWithStreamingResponse + + return AsyncTokenizationDecisioningWithStreamingResponse(self._client.tokenization_decisioning) + + @cached_property + def tokenizations(self) -> tokenizations.AsyncTokenizationsWithStreamingResponse: + from .resources.tokenizations import AsyncTokenizationsWithStreamingResponse + + return AsyncTokenizationsWithStreamingResponse(self._client.tokenizations) + + @cached_property + def cards(self) -> cards.AsyncCardsWithStreamingResponse: + from .resources.cards import AsyncCardsWithStreamingResponse + + return AsyncCardsWithStreamingResponse(self._client.cards) + + @cached_property + def card_bulk_orders(self) -> card_bulk_orders.AsyncCardBulkOrdersWithStreamingResponse: + from .resources.card_bulk_orders import AsyncCardBulkOrdersWithStreamingResponse + + return AsyncCardBulkOrdersWithStreamingResponse(self._client.card_bulk_orders) + + @cached_property + def balances(self) -> balances.AsyncBalancesWithStreamingResponse: + from .resources.balances import AsyncBalancesWithStreamingResponse + + return AsyncBalancesWithStreamingResponse(self._client.balances) + + @cached_property + def disputes(self) -> disputes.AsyncDisputesWithStreamingResponse: + from .resources.disputes import AsyncDisputesWithStreamingResponse + + return AsyncDisputesWithStreamingResponse(self._client.disputes) + + @cached_property + def disputes_v2(self) -> disputes_v2.AsyncDisputesV2WithStreamingResponse: + from .resources.disputes_v2 import AsyncDisputesV2WithStreamingResponse + + return AsyncDisputesV2WithStreamingResponse(self._client.disputes_v2) + + @cached_property + def events(self) -> events.AsyncEventsWithStreamingResponse: + from .resources.events import AsyncEventsWithStreamingResponse + + return AsyncEventsWithStreamingResponse(self._client.events) + + @cached_property + def financial_accounts(self) -> financial_accounts.AsyncFinancialAccountsWithStreamingResponse: + from .resources.financial_accounts import AsyncFinancialAccountsWithStreamingResponse + + return AsyncFinancialAccountsWithStreamingResponse(self._client.financial_accounts) + + @cached_property + def transactions(self) -> transactions.AsyncTransactionsWithStreamingResponse: + from .resources.transactions import AsyncTransactionsWithStreamingResponse + + return AsyncTransactionsWithStreamingResponse(self._client.transactions) + + @cached_property + def responder_endpoints(self) -> responder_endpoints.AsyncResponderEndpointsWithStreamingResponse: + from .resources.responder_endpoints import AsyncResponderEndpointsWithStreamingResponse + + return AsyncResponderEndpointsWithStreamingResponse(self._client.responder_endpoints) + + @cached_property + def external_bank_accounts(self) -> external_bank_accounts.AsyncExternalBankAccountsWithStreamingResponse: + from .resources.external_bank_accounts import AsyncExternalBankAccountsWithStreamingResponse + + return AsyncExternalBankAccountsWithStreamingResponse(self._client.external_bank_accounts) + + @cached_property + def payments(self) -> payments.AsyncPaymentsWithStreamingResponse: + from .resources.payments import AsyncPaymentsWithStreamingResponse + + return AsyncPaymentsWithStreamingResponse(self._client.payments) + + @cached_property + def three_ds(self) -> three_ds.AsyncThreeDSWithStreamingResponse: + from .resources.three_ds import AsyncThreeDSWithStreamingResponse + + return AsyncThreeDSWithStreamingResponse(self._client.three_ds) + + @cached_property + def reports(self) -> reports.AsyncReportsWithStreamingResponse: + from .resources.reports import AsyncReportsWithStreamingResponse + + return AsyncReportsWithStreamingResponse(self._client.reports) + + @cached_property + def card_programs(self) -> card_programs.AsyncCardProgramsWithStreamingResponse: + from .resources.card_programs import AsyncCardProgramsWithStreamingResponse + + return AsyncCardProgramsWithStreamingResponse(self._client.card_programs) + + @cached_property + def digital_card_art(self) -> digital_card_art.AsyncDigitalCardArtResourceWithStreamingResponse: + from .resources.digital_card_art import AsyncDigitalCardArtResourceWithStreamingResponse + + return AsyncDigitalCardArtResourceWithStreamingResponse(self._client.digital_card_art) + + @cached_property + def book_transfers(self) -> book_transfers.AsyncBookTransfersWithStreamingResponse: + from .resources.book_transfers import AsyncBookTransfersWithStreamingResponse + + return AsyncBookTransfersWithStreamingResponse(self._client.book_transfers) + + @cached_property + def credit_products(self) -> credit_products.AsyncCreditProductsWithStreamingResponse: + from .resources.credit_products import AsyncCreditProductsWithStreamingResponse + + return AsyncCreditProductsWithStreamingResponse(self._client.credit_products) + + @cached_property + def external_payments(self) -> external_payments.AsyncExternalPaymentsWithStreamingResponse: + from .resources.external_payments import AsyncExternalPaymentsWithStreamingResponse + + return AsyncExternalPaymentsWithStreamingResponse(self._client.external_payments) + + @cached_property + def management_operations(self) -> management_operations.AsyncManagementOperationsWithStreamingResponse: + from .resources.management_operations import AsyncManagementOperationsWithStreamingResponse + + return AsyncManagementOperationsWithStreamingResponse(self._client.management_operations) + + @cached_property + def funding_events(self) -> funding_events.AsyncFundingEventsWithStreamingResponse: + from .resources.funding_events import AsyncFundingEventsWithStreamingResponse + + return AsyncFundingEventsWithStreamingResponse(self._client.funding_events) + + @cached_property + def fraud(self) -> fraud.AsyncFraudWithStreamingResponse: + from .resources.fraud import AsyncFraudWithStreamingResponse + + return AsyncFraudWithStreamingResponse(self._client.fraud) + + @cached_property + def network_programs(self) -> network_programs.AsyncNetworkProgramsWithStreamingResponse: + from .resources.network_programs import AsyncNetworkProgramsWithStreamingResponse + + return AsyncNetworkProgramsWithStreamingResponse(self._client.network_programs) + + @cached_property + def account_activity(self) -> account_activity.AsyncAccountActivityWithStreamingResponse: + from .resources.account_activity import AsyncAccountActivityWithStreamingResponse + + return AsyncAccountActivityWithStreamingResponse(self._client.account_activity) + Client = Lithic diff --git a/src/lithic/_compat.py b/src/lithic/_compat.py index 74c7639b..bdef67f0 100644 --- a/src/lithic/_compat.py +++ b/src/lithic/_compat.py @@ -2,24 +2,23 @@ from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload from datetime import date, datetime -from typing_extensions import Self +from typing_extensions import Self, Literal import pydantic from pydantic.fields import FieldInfo -from ._types import StrBytesIntFloat +from ._types import IncEx, StrBytesIntFloat _T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) -# --------------- Pydantic v2 compatibility --------------- +# --------------- Pydantic v2, v3 compatibility --------------- # Pyright incorrectly reports some of our functions as overriding a method when they don't # pyright: reportIncompatibleMethodOverride=false -PYDANTIC_V2 = pydantic.VERSION.startswith("2.") +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") -# v1 re-exports if TYPE_CHECKING: def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 @@ -44,106 +43,116 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 ... else: - if PYDANTIC_V2: - from pydantic.v1.typing import ( + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( get_args as get_args, is_union as is_union, get_origin as get_origin, is_typeddict as is_typeddict, is_literal_type as is_literal_type, ) - from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime else: - from pydantic.typing import ( + from ._utils import ( get_args as get_args, is_union as is_union, get_origin as get_origin, + parse_date as parse_date, is_typeddict as is_typeddict, + parse_datetime as parse_datetime, is_literal_type as is_literal_type, ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime # refactored config if TYPE_CHECKING: from pydantic import ConfigDict as ConfigDict else: - if PYDANTIC_V2: - from pydantic import ConfigDict - else: + if PYDANTIC_V1: # TODO: provide an error message here? ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict # renamed methods / properties def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(value) - else: + if PYDANTIC_V1: return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V2: - return field.is_required() - return field.required # type: ignore + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() def field_get_default(field: FieldInfo) -> Any: value = field.get_default() - if PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None + if PYDANTIC_V1: return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None return value def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V2: - return field.annotation - return field.outer_type_ # type: ignore + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V2: - return model.model_config - return model.__config__ # type: ignore + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V2: - return model.model_fields - return model.__fields__ # type: ignore + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields -def model_copy(model: _ModelT) -> _ModelT: - if PYDANTIC_V2: - return model.model_copy() - return model.copy() # type: ignore +def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V2: - return model.model_dump_json(indent=indent) - return model.json(indent=indent) # type: ignore + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) def model_dump( model: pydantic.BaseModel, *, + exclude: IncEx | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, + warnings: bool = True, + mode: Literal["json", "python"] = "python", ) -> dict[str, Any]: - if PYDANTIC_V2: + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( + mode=mode, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, + # warnings are not supported in Pydantic v1 + warnings=True if PYDANTIC_V1 else warnings, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, ), @@ -151,30 +160,26 @@ def model_dump( def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(data) - return model.parse_obj(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) # generic models if TYPE_CHECKING: - class GenericModel(pydantic.BaseModel): - ... + class GenericModel(pydantic.BaseModel): ... else: - if PYDANTIC_V2: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: # there no longer needs to be a distinction in v2 but # we still have to create our own subclass to avoid # inconsistent MRO ordering errors - class GenericModel(pydantic.BaseModel): - ... - - else: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): - ... + class GenericModel(pydantic.BaseModel): ... # cached properties @@ -193,30 +198,22 @@ class typed_cached_property(Generic[_T]): func: Callable[[Any], _T] attrname: str | None - def __init__(self, func: Callable[[Any], _T]) -> None: - ... + def __init__(self, func: Callable[[Any], _T]) -> None: ... @overload - def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: - ... + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... @overload - def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: - ... + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ... def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self: raise NotImplementedError() - def __set_name__(self, owner: type[Any], name: str) -> None: - ... + def __set_name__(self, owner: type[Any], name: str) -> None: ... # __set__ is not defined at runtime, but @cached_property is designed to be settable - def __set__(self, instance: object, value: _T) -> None: - ... + def __set__(self, instance: object, value: _T) -> None: ... else: - try: - from functools import cached_property as cached_property - except ImportError: - from cached_property import cached_property as cached_property + from functools import cached_property as cached_property typed_cached_property = cached_property diff --git a/src/lithic/_constants.py b/src/lithic/_constants.py index bf15141a..6ddf2c71 100644 --- a/src/lithic/_constants.py +++ b/src/lithic/_constants.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import httpx @@ -6,9 +6,9 @@ OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 1 minute -DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0) +DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) DEFAULT_MAX_RETRIES = 2 -DEFAULT_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) +DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) INITIAL_RETRY_DELAY = 0.5 MAX_RETRY_DELAY = 8.0 diff --git a/src/lithic/_exceptions.py b/src/lithic/_exceptions.py index a9133f78..b1190bfd 100644 --- a/src/lithic/_exceptions.py +++ b/src/lithic/_exceptions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -54,6 +54,10 @@ def __init__(self, response: httpx.Response, body: object | None, *, message: st self.status_code = response.status_code +class APIWebhookValidationError(APIError): + pass + + class APIStatusError(APIError): """Raised when an API response has a status code of 4xx or 5xx.""" diff --git a/src/lithic/_files.py b/src/lithic/_files.py index b6e8af8b..cc14c14f 100644 --- a/src/lithic/_files.py +++ b/src/lithic/_files.py @@ -13,12 +13,17 @@ FileContent, RequestFiles, HttpxFileTypes, + Base64FileInput, HttpxFileContent, HttpxRequestFiles, ) from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: + return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + + def is_file_content(obj: object) -> TypeGuard[FileContent]: return ( isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) @@ -34,13 +39,11 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: @overload -def to_httpx_files(files: None) -> None: - ... +def to_httpx_files(files: None) -> None: ... @overload -def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: - ... +def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: @@ -66,25 +69,23 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], _read_file_content(file[1]), *file[2:]) + return (file[0], read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -def _read_file_content(file: FileContent) -> HttpxFileContent: +def read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return pathlib.Path(file).read_bytes() return file @overload -async def async_to_httpx_files(files: None) -> None: - ... +async def async_to_httpx_files(files: None) -> None: ... @overload -async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: - ... +async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: @@ -110,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], await _async_read_file_content(file[1]), *file[2:]) + return (file[0], await async_read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -async def _async_read_file_content(file: FileContent) -> HttpxFileContent: +async def async_read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return await anyio.Path(file).read_bytes() diff --git a/src/lithic/_legacy_response.py b/src/lithic/_legacy_response.py index a13df7ab..d3758f2b 100644 --- a/src/lithic/_legacy_response.py +++ b/src/lithic/_legacy_response.py @@ -5,7 +5,18 @@ import logging import datetime import functools -from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, Iterator, AsyncIterator, cast, overload +from typing import ( + TYPE_CHECKING, + Any, + Union, + Generic, + TypeVar, + Callable, + Iterator, + AsyncIterator, + cast, + overload, +) from typing_extensions import Awaitable, ParamSpec, override, deprecated, get_origin import anyio @@ -13,7 +24,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -53,6 +64,9 @@ class LegacyAPIResponse(Generic[R]): http_response: httpx.Response + retries_taken: int + """The number of retries made. If no retries happened this will be `0`""" + def __init__( self, *, @@ -62,6 +76,7 @@ def __init__( stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, options: FinalRequestOptions, + retries_taken: int = 0, ) -> None: self._cast_to = cast_to self._client = client @@ -70,14 +85,13 @@ def __init__( self._stream_cls = stream_cls self._options = options self.http_response = raw + self.retries_taken = retries_taken @overload - def parse(self, *, to: type[_T]) -> _T: - ... + def parse(self, *, to: type[_T]) -> _T: ... @overload - def parse(self) -> R: - ... + def parse(self) -> R: ... def parse(self, *, to: type[_T] | None = None) -> R | _T: """Returns the rich python representation of this response's data. @@ -107,6 +121,8 @@ class MyModel(BaseModel): - `list` - `Union` - `str` + - `int` + - `float` - `httpx.Response` """ cache_key = to if to is not None else self._cast_to @@ -172,6 +188,18 @@ def elapsed(self) -> datetime.timedelta: return self.http_response.elapsed def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + origin = get_origin(cast_to) or cast_to + if self._stream: if to: if not is_stream_class_type(to): @@ -206,13 +234,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), ), ) - cast_to = to if to is not None else self._cast_to if cast_to is NoneType: return cast(R, None) @@ -220,7 +247,14 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == str: return cast(R, response.text) - origin = get_origin(cast_to) or cast_to + if cast_to == int: + return cast(R, int(response.text)) + + if cast_to == float: + return cast(R, float(response.text)) + + if cast_to == bool: + return cast(R, response.text.lower() == "true") if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent): return cast(R, cast_to(response)) # type: ignore @@ -228,7 +262,9 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if origin == LegacyAPIResponse: raise RuntimeError("Unexpected state - cast_to is `APIResponse`") - if inspect.isclass(origin) and issubclass(origin, httpx.Response): + if inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) and issubclass(origin, httpx.Response): # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response # and pass that class to our request functions. We cannot change the variance to be either # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct @@ -238,7 +274,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError("Pydantic models must subclass our base model type, e.g. `from lithic import BaseModel`") if ( @@ -255,7 +297,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() @@ -307,7 +349,7 @@ def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, LegacyAPIRespon @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "true" kwargs["extra_headers"] = extra_headers @@ -324,7 +366,7 @@ def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P @functools.wraps(func) async def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "true" kwargs["extra_headers"] = extra_headers diff --git a/src/lithic/_models.py b/src/lithic/_models.py index 81089149..ca9500b2 100644 --- a/src/lithic/_models.py +++ b/src/lithic/_models.py @@ -1,22 +1,26 @@ from __future__ import annotations +import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +import weakref +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, Protocol, Required, + ParamSpec, TypedDict, + TypeGuard, final, override, runtime_checkable, ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( @@ -30,9 +34,24 @@ AnyMapping, HttpxRequestFiles, ) -from ._utils import is_list, is_given, is_mapping, parse_date, parse_datetime, strip_not_given +from ._utils import ( + PropertyInfo, + is_list, + is_given, + json_safe, + lru_cache, + is_mapping, + parse_date, + coerce_boolean, + parse_datetime, + strip_not_given, + extract_type_arg, + is_annotated_type, + is_type_alias_type, + strip_annotated_type, +) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -46,9 +65,15 @@ ) from ._constants import RAW_RESPONSE_HEADER +if TYPE_CHECKING: + from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema + __all__ = ["BaseModel", "GenericModel"] _T = TypeVar("_T") +_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel") + +P = ParamSpec("P") @runtime_checkable @@ -57,9 +82,7 @@ class _ConfigProtocol(Protocol): class BaseModel(pydantic.BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow") - else: + if PYDANTIC_V1: @property @override @@ -69,25 +92,102 @@ def model_fields_set(self) -> set[str]: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) + + def to_dict( + self, + *, + mode: Literal["json", "python"] = "python", + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> dict[str, object]: + """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + mode: + If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`. + If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)` + + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value from the output. + exclude_none: Whether to exclude fields that have a value of `None` from the output. + warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2. + """ + return self.model_dump( + mode=mode, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) + + def to_json( + self, + *, + indent: int | None = 2, + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> str: + """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation). + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2` + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that have the default value. + exclude_none: Whether to exclude fields that have a value of `None`. + warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2. + """ + return self.model_dump_json( + indent=indent, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) @override def __str__(self) -> str: # mypy complains about an invalid self arg - return f'{self.__repr_name__()}({self.__repr_str__(", ")})' # type: ignore[misc] + return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc] # Override the 'construct' method in a way that supports recursive parsing without validation. # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836. @classmethod @override - def construct( - cls: Type[ModelT], + def construct( # pyright: ignore[reportIncompatibleMethodOverride] + __cls: Type[ModelT], _fields_set: set[str] | None = None, **values: object, ) -> ModelT: - m = cls.__new__(cls) + m = __cls.__new__(__cls) fields_values: dict[str, object] = {} - config = get_model_config(cls) + config = get_model_config(__cls) populate_by_name = ( config.allow_population_by_field_name if isinstance(config, _ConfigProtocol) @@ -97,7 +197,7 @@ def construct( if _fields_set is None: _fields_set = set() - model_fields = get_model_fields(cls) + model_fields = get_model_fields(__cls) for name, field in model_fields.items(): key = field.alias if key is None or (key not in values and populate_by_name): @@ -109,28 +209,32 @@ def construct( else: fields_values[name] = field_get_default(field) + extra_field_type = _get_extra_fields_type(__cls) + _extra = {} for key, value in values.items(): if key not in model_fields: - if PYDANTIC_V2: - _extra[key] = value - else: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + + if PYDANTIC_V1: _fields_set.add(key) - fields_values[key] = value + fields_values[key] = parsed + else: + _extra[key] = parsed object.__setattr__(m, "__dict__", fields_values) - if PYDANTIC_V2: - # these properties are copied from Pydantic's `model_construct()` method - object.__setattr__(m, "__pydantic_private__", None) - object.__setattr__(m, "__pydantic_extra__", _extra) - object.__setattr__(m, "__pydantic_fields_set__", _fields_set) - else: + if PYDANTIC_V1: # init_private_attributes() does not exist in v2 m._init_private_attributes() # type: ignore # copied from Pydantic v1's `construct()` method object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) return m @@ -140,7 +244,7 @@ def construct( # although not in practice model_construct = construct - if not PYDANTIC_V2: + if PYDANTIC_V1: # we define aliases for some of the new pydantic v2 methods so # that we can just document these methods without having to specify # a specific pydantic version as some users may not know which @@ -151,14 +255,18 @@ def model_dump( self, *, mode: Literal["json", "python"] | str = "python", - include: IncEx = None, - exclude: IncEx = None, - by_alias: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool = True, + warnings: bool | Literal["none", "warn", "error"] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -166,48 +274,71 @@ def model_dump( Args: mode: The mode in which `to_python` should run. - If mode is 'json', the dictionary will only contain JSON serializable types. - If mode is 'python', the dictionary may contain any Python objects. - include: A list of fields to include in the output. - exclude: A list of fields to exclude from the output. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. by_alias: Whether to use the field's alias in the dictionary key if defined. - exclude_unset: Whether to exclude fields that are unset or None from the output. - exclude_defaults: Whether to exclude fields that are set to their default value from the output. - exclude_none: Whether to exclude fields that have a value of `None` from the output. - round_trip: Whether to enable serialization and deserialization round-trip support. - warnings: Whether to log warnings when invalid fields are encountered. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. Returns: A dictionary representation of the model. """ - if mode != "python": - raise ValueError("mode is only supported in Pydantic v2") + if mode not in {"json", "python"}: + raise ValueError("mode must be either 'json' or 'python'") if round_trip != False: raise ValueError("round_trip is only supported in Pydantic v2") if warnings != True: raise ValueError("warnings is only supported in Pydantic v2") - return super().dict( # pyright: ignore[reportDeprecated] + if context is not None: + raise ValueError("context is only supported in Pydantic v2") + if serialize_as_any != False: + raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") + dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, ) + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped + @override def model_dump_json( self, *, indent: int | None = None, - include: IncEx = None, - exclude: IncEx = None, - by_alias: bool = False, + ensure_ascii: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool = True, + warnings: bool | Literal["none", "warn", "error"] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, ) -> str: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json @@ -231,11 +362,21 @@ def model_dump_json( raise ValueError("round_trip is only supported in Pydantic v2") if warnings != True: raise ValueError("warnings is only supported in Pydantic v2") + if context is not None: + raise ValueError("context is only supported in Pydantic v2") + if serialize_as_any != False: + raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if ensure_ascii != False: + raise ValueError("ensure_ascii is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, @@ -246,20 +387,36 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) - if PYDANTIC_V2: - type_ = field.annotation - else: + if PYDANTIC_V1: type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) + + +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if PYDANTIC_V1: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None def is_basemodel(type_: type) -> bool: """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" - origin = get_origin(type_) or type_ if is_union(type_): for variant in get_args(type_): if is_basemodel(variant): @@ -267,15 +424,74 @@ def is_basemodel(type_: type) -> bool: return False + return is_basemodel_type(type_) + + +def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]: + origin = get_origin(type_) or type_ + if not inspect.isclass(origin): + return False return issubclass(origin, BaseModel) or issubclass(origin, GenericModel) -def construct_type(*, value: object, type_: type) -> object: +def build( + base_model_cls: Callable[P, _BaseModelT], + *args: P.args, + **kwargs: P.kwargs, +) -> _BaseModelT: + """Construct a BaseModel class without validation. + + This is useful for cases where you need to instantiate a `BaseModel` + from an API response as this provides type-safe params which isn't supported + by helpers like `construct_type()`. + + ```py + build(MyModel, my_field_a="foo", my_field_b=123) + ``` + """ + if args: + raise TypeError( + "Received positional arguments which are not supported; Keyword arguments must be used instead", + ) + + return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs)) + + +def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: + """Loose coercion to the expected type with construction of nested values. + + Note: the returned value from this function is not guaranteed to match the + given type. + """ + return cast(_T, construct_type(value=value, type_=type_)) + + +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. """ + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + + # we allow `object` as the input type because otherwise, passing things like + # `Literal['value']` will be reported as a type error by type checkers + type_ = cast("type[object]", type_) + if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] + type_ = type_.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if metadata is not None and len(metadata) > 0: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] + type_ = extract_type_arg(type_, 0) + else: + meta = tuple() + # we need to use the origin class for any types that are subscripted generics # e.g. Dict[str, object] origin = get_origin(type_) or type_ @@ -283,10 +499,32 @@ def construct_type(*, value: object, type_: type) -> object: if is_union(origin): try: - return validate_type(type_=cast("type[object]", type_), value=value) + return validate_type(type_=cast("type[object]", original_type or type_), value=value) except Exception: pass + # if the type is a discriminated union then we want to construct the right variant + # in the union, even if the data doesn't match exactly, otherwise we'd break code + # that relies on the constructed class types, e.g. + # + # class FooType: + # kind: Literal['foo'] + # value: str + # + # class BarType: + # kind: Literal['bar'] + # value: int + # + # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then + # we'd end up constructing `FooType` when it should be `BarType`. + discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta) + if discriminator and is_mapping(value): + variant_value = value.get(discriminator.field_alias_from or discriminator.field_name) + if variant_value and isinstance(variant_value, str): + variant_type = discriminator.mapping.get(variant_value) + if variant_type: + return construct_type(type_=variant_type, value=value) + # if the data is not valid, use the first variant that doesn't fail while deserializing for variant in args: try: @@ -303,7 +541,11 @@ def construct_type(*, value: object, type_: type) -> object: _, items_type = get_args(type_) # Dict[_, items_type] return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} - if not is_literal_type(type_) and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)): + if ( + not is_literal_type(type_) + and inspect.isclass(origin) + and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) + ): if is_list(value): return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] @@ -344,6 +586,136 @@ def construct_type(*, value: object, type_: type) -> object: return value +@runtime_checkable +class CachedDiscriminatorType(Protocol): + __discriminator__: DiscriminatorDetails + + +DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() + + +class DiscriminatorDetails: + field_name: str + """The name of the discriminator field in the variant class, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] + ``` + + Will result in field_name='type' + """ + + field_alias_from: str | None + """The name of the discriminator field in the API response, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] = Field(alias='type_from_api') + ``` + + Will result in field_alias_from='type_from_api' + """ + + mapping: dict[str, type] + """Mapping of discriminator value to variant type, e.g. + + {'foo': FooVariant, 'bar': BarVariant} + """ + + def __init__( + self, + *, + mapping: dict[str, type], + discriminator_field: str, + discriminator_alias: str | None, + ) -> None: + self.mapping = mapping + self.field_name = discriminator_field + self.field_alias_from = discriminator_alias + + +def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: + cached = DISCRIMINATOR_CACHE.get(union) + if cached is not None: + return cached + + discriminator_field_name: str | None = None + + for annotation in meta_annotations: + if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None: + discriminator_field_name = annotation.discriminator + break + + if not discriminator_field_name: + return None + + mapping: dict[str, type] = {} + discriminator_alias: str | None = None + + for variant in get_args(union): + variant = strip_annotated_type(variant) + if is_basemodel_type(variant): + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field_info.alias + + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): + if isinstance(entry, str): + mapping[entry] = variant + else: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field.get("serialization_alias") + + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: + if isinstance(entry, str): + mapping[entry] = variant + + if not mapping: + return None + + details = DiscriminatorDetails( + mapping=mapping, + discriminator_field=discriminator_field_name, + discriminator_alias=discriminator_alias, + ) + DISCRIMINATOR_CACHE.setdefault(union, details) + return details + + +def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: + schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + + if schema["type"] != "model": + return None + + schema = cast("ModelSchema", schema) + fields_schema = schema["schema"] + if fields_schema["type"] != "model-fields": + return None + + fields_schema = cast("ModelFieldsSchema", fields_schema) + field = fields_schema["fields"].get(field_name) + if not field: + return None + + return cast("ModelField", field) # pyright: ignore[reportUnnecessaryCast] + + def validate_type(*, type_: type[_T], value: object) -> _T: """Strict validation that the given value matches the expected type""" if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): @@ -352,7 +724,15 @@ def validate_type(*, type_: type[_T], value: object) -> _T: return cast(_T, _validate_non_model_type(type_=type_, value=value)) -# our use of subclasssing here causes weirdness for type checkers, +def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: + """Add a pydantic config for the given type. + + Note: this is a no-op on Pydantic v1. + """ + setattr(typ, "__pydantic_config__", config) # noqa: B010 + + +# our use of subclassing here causes weirdness for type checkers, # so we just pretend that we don't subclass if TYPE_CHECKING: GenericModel = BaseModel @@ -362,8 +742,15 @@ class GenericModel(BaseGenericModel, BaseModel): pass -if PYDANTIC_V2: - from pydantic import TypeAdapter +if not PYDANTIC_V1: + from pydantic import TypeAdapter as _TypeAdapter + + _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) + + if TYPE_CHECKING: + from pydantic import TypeAdapter + else: + TypeAdapter = _CachedTypeAdapter def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: return TypeAdapter(type_).validate_python(value) @@ -402,6 +789,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): idempotency_key: str json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -415,18 +803,19 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. json_data: Union[Body, None] = None extra_json: Union[AnyMapping, None] = None - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - else: + if PYDANTIC_V1: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) def get_max_retries(self, max_retries: int) -> int: if isinstance(self.max_retries, NotGiven): @@ -459,9 +848,9 @@ def construct( # type: ignore key: strip_not_given(value) for key, value in values.items() } - if PYDANTIC_V2: - return super().model_construct(_fields_set, **kwargs) - return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) if not TYPE_CHECKING: # type checkers incorrectly complain about this assignment diff --git a/src/lithic/_qs.py b/src/lithic/_qs.py index 274320ca..ada6fd3f 100644 --- a/src/lithic/_qs.py +++ b/src/lithic/_qs.py @@ -4,7 +4,7 @@ from urllib.parse import parse_qs, urlencode from typing_extensions import Literal, get_args -from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._types import NotGiven, not_given from ._utils import flatten _T = TypeVar("_T") @@ -41,8 +41,8 @@ def stringify( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> str: return urlencode( self.stringify_items( @@ -56,8 +56,8 @@ def stringify_items( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> list[tuple[str, str]]: opts = Options( qs=self, @@ -143,8 +143,8 @@ def __init__( self, qs: Querystring = _qs, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> None: self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/lithic/_resource.py b/src/lithic/_resource.py index f38e1085..62fc8293 100644 --- a/src/lithic/_resource.py +++ b/src/lithic/_resource.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/_response.py b/src/lithic/_response.py index 5e4db166..083b08df 100644 --- a/src/lithic/_response.py +++ b/src/lithic/_response.py @@ -25,7 +25,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_var_from_base +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -55,6 +55,9 @@ class BaseAPIResponse(Generic[R]): http_response: httpx.Response + retries_taken: int + """The number of retries made. If no retries happened this will be `0`""" + def __init__( self, *, @@ -64,6 +67,7 @@ def __init__( stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, options: FinalRequestOptions, + retries_taken: int = 0, ) -> None: self._cast_to = cast_to self._client = client @@ -72,6 +76,7 @@ def __init__( self._stream_cls = stream_cls self._options = options self.http_response = raw + self.retries_taken = retries_taken @property def headers(self) -> httpx.Headers: @@ -121,6 +126,18 @@ def __repr__(self) -> str: ) def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + origin = get_origin(cast_to) or cast_to + if self._is_sse_stream: if to: if not is_stream_class_type(to): @@ -155,13 +172,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), ), ) - cast_to = to if to is not None else self._cast_to if cast_to is NoneType: return cast(R, None) @@ -172,7 +188,14 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == bytes: return cast(R, response.content) - origin = get_origin(cast_to) or cast_to + if cast_to == int: + return cast(R, int(response.text)) + + if cast_to == float: + return cast(R, float(response.text)) + + if cast_to == bool: + return cast(R, response.text.lower() == "true") # handle the legacy binary response case if inspect.isclass(cast_to) and cast_to.__name__ == "HttpxBinaryResponseContent": @@ -191,7 +214,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError("Pydantic models must subclass our base model type, e.g. `from lithic import BaseModel`") if ( @@ -208,7 +237,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() @@ -244,12 +273,10 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: class APIResponse(BaseAPIResponse[R]): @overload - def parse(self, *, to: type[_T]) -> _T: - ... + def parse(self, *, to: type[_T]) -> _T: ... @overload - def parse(self) -> R: - ... + def parse(self) -> R: ... def parse(self, *, to: type[_T] | None = None) -> R | _T: """Returns the rich python representation of this response's data. @@ -277,6 +304,8 @@ class MyModel(BaseModel): - `list` - `Union` - `str` + - `int` + - `float` - `httpx.Response` """ cache_key = to if to is not None else self._cast_to @@ -346,12 +375,10 @@ def iter_lines(self) -> Iterator[str]: class AsyncAPIResponse(BaseAPIResponse[R]): @overload - async def parse(self, *, to: type[_T]) -> _T: - ... + async def parse(self, *, to: type[_T]) -> _T: ... @overload - async def parse(self) -> R: - ... + async def parse(self) -> R: ... async def parse(self, *, to: type[_T] | None = None) -> R | _T: """Returns the rich python representation of this response's data. @@ -626,7 +653,7 @@ def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseCo @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" kwargs["extra_headers"] = extra_headers @@ -647,7 +674,7 @@ def async_to_streamed_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" kwargs["extra_headers"] = extra_headers @@ -671,7 +698,7 @@ def to_custom_streamed_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls @@ -696,7 +723,7 @@ def async_to_custom_streamed_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls @@ -716,7 +743,7 @@ def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]] @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers @@ -733,7 +760,7 @@ def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P @functools.wraps(func) async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers @@ -755,7 +782,7 @@ def to_custom_raw_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls @@ -778,7 +805,7 @@ def async_to_custom_raw_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls diff --git a/src/lithic/_streaming.py b/src/lithic/_streaming.py index 2689d4d3..73afbbec 100644 --- a/src/lithic/_streaming.py +++ b/src/lithic/_streaming.py @@ -5,7 +5,7 @@ import inspect from types import TracebackType from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast -from typing_extensions import Self, TypeGuard, override, get_origin +from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -23,6 +23,8 @@ class Stream(Generic[_T]): response: httpx.Response + _decoder: SSEBytesDecoder + def __init__( self, *, @@ -33,7 +35,7 @@ def __init__( self.response = response self._cast_to = cast_to self._client = client - self._decoder = SSEDecoder() + self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() def __next__(self) -> _T: @@ -44,7 +46,7 @@ def __iter__(self) -> Iterator[_T]: yield item def _iter_events(self) -> Iterator[ServerSentEvent]: - yield from self._decoder.iter(self.response.iter_lines()) + yield from self._decoder.iter_bytes(self.response.iter_bytes()) def __stream__(self) -> Iterator[_T]: cast_to = cast(Any, self._cast_to) @@ -52,12 +54,12 @@ def __stream__(self) -> Iterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - for _sse in iterator: - ... + try: + for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() def __enter__(self) -> Self: return self @@ -84,6 +86,8 @@ class AsyncStream(Generic[_T]): response: httpx.Response + _decoder: SSEDecoder | SSEBytesDecoder + def __init__( self, *, @@ -94,7 +98,7 @@ def __init__( self.response = response self._cast_to = cast_to self._client = client - self._decoder = SSEDecoder() + self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() async def __anext__(self) -> _T: @@ -105,7 +109,7 @@ async def __aiter__(self) -> AsyncIterator[_T]: yield item async def _iter_events(self) -> AsyncIterator[ServerSentEvent]: - async for sse in self._decoder.aiter(self.response.aiter_lines()): + async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()): yield sse async def __stream__(self) -> AsyncIterator[_T]: @@ -114,12 +118,12 @@ async def __stream__(self) -> AsyncIterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - async for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - async for _sse in iterator: - ... + try: + async for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() async def __aenter__(self) -> Self: return self @@ -194,21 +198,49 @@ def __init__(self) -> None: self._last_event_id = None self._retry = None - def iter(self, iterator: Iterator[str]) -> Iterator[ServerSentEvent]: - """Given an iterator that yields lines, iterate over it & yield every event encountered""" - for line in iterator: - line = line.rstrip("\n") - sse = self.decode(line) - if sse is not None: - yield sse - - async def aiter(self, iterator: AsyncIterator[str]) -> AsyncIterator[ServerSentEvent]: - """Given an async iterator that yields lines, iterate over it & yield every event encountered""" - async for line in iterator: - line = line.rstrip("\n") - sse = self.decode(line) - if sse is not None: - yield sse + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + for chunk in self._iter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + async for chunk in self._aiter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + async for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data def decode(self, line: str) -> ServerSentEvent | None: # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 @@ -259,6 +291,17 @@ def decode(self, line: str) -> ServerSentEvent | None: return None +@runtime_checkable +class SSEBytesDecoder(Protocol): + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]: """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`""" origin = get_origin(typ) or typ diff --git a/src/lithic/_types.py b/src/lithic/_types.py index e6b86b65..a20a5cc9 100644 --- a/src/lithic/_types.py +++ b/src/lithic/_types.py @@ -13,10 +13,21 @@ Mapping, TypeVar, Callable, + Iterator, Optional, Sequence, ) -from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, +) import httpx import pydantic @@ -41,8 +52,10 @@ ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] ProxiesTypes = Union[str, Proxy, ProxiesDict] if TYPE_CHECKING: + Base64FileInput = Union[IO[bytes], PathLike[str]] FileContent = Union[IO[bytes], bytes, PathLike[str]] else: + Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. FileTypes = Union[ # file (or bytes) @@ -99,24 +112,27 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted class NotGiven: """ - A sentinel singleton class used to distinguish omitted keyword arguments - from those passed in with the value None (which may have different behavior). + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. For example: ```py - def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: - ... + def create(timeout: Timeout | None | NotGiven = not_given): ... - get(timeout=1) # 1s timeout - get(timeout=None) # No timeout - get() # Default timeout behavior, which may not be statically known at the method definition. + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior ``` """ @@ -128,13 +144,14 @@ def __repr__(self) -> str: return "NOT_GIVEN" -NotGivenOr = Union[_T, NotGiven] +not_given = NotGiven() +# for backwards compatibility: NOT_GIVEN = NotGiven() class Omit: - """In certain situations you need to be able to represent a case where a default value has - to be explicitly removed and `None` is not an appropriate substitute, for example: + """ + To explicitly omit something from being sent in a request, use `omit`. ```py # as the default `Content-Type` header is `application/json` that will be sent @@ -144,8 +161,8 @@ class Omit: # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' client.post(..., headers={"Content-Type": "multipart/form-data"}) - # instead you can remove the default `application/json` header by passing Omit - client.post(..., headers={"Content-Type": Omit()}) + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) ``` """ @@ -153,6 +170,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod @@ -161,16 +181,14 @@ def build( *, response: Response, data: object, - ) -> _T: - ... + ) -> _T: ... Headers = Mapping[str, Union[str, Omit]] class HeadersLikeProtocol(Protocol): - def get(self, __key: str) -> str | None: - ... + def get(self, __key: str) -> str | None: ... HeadersLike = Union[Headers, HeadersLikeProtocol] @@ -195,8 +213,8 @@ def get(self, __key: str) -> str | None: StrBytesIntFloat = Union[str, bytes, int, float] # Note: copied from Pydantic -# https://github.com/pydantic/pydantic/blob/32ea570bf96e84234d2992e1ddf40ab8a565925a/pydantic/main.py#L49 -IncEx: TypeAlias = "set[int] | set[str] | dict[int, Any] | dict[str, Any] | None" +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] PostParser = Callable[[Any], Any] @@ -218,3 +236,28 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/lithic/_utils/__init__.py b/src/lithic/_utils/__init__.py index b5790a87..dc64e29a 100644 --- a/src/lithic/_utils/__init__.py +++ b/src/lithic/_utils/__init__.py @@ -6,9 +6,10 @@ is_list as is_list, is_given as is_given, is_tuple as is_tuple, + json_safe as json_safe, + lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, - parse_date as parse_date, is_iterable as is_iterable, is_sequence as is_sequence, coerce_float as coerce_float, @@ -21,7 +22,6 @@ coerce_boolean as coerce_boolean, coerce_integer as coerce_integer, file_from_path as file_from_path, - parse_datetime as parse_datetime, strip_not_given as strip_not_given, deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, @@ -30,13 +30,22 @@ maybe_coerce_boolean as maybe_coerce_boolean, maybe_coerce_integer as maybe_coerce_integer, ) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, +) from ._typing import ( is_list_type as is_list_type, is_union_type as is_union_type, extract_type_arg as extract_type_arg, is_iterable_type as is_iterable_type, is_required_type as is_required_type, + is_sequence_type as is_sequence_type, is_annotated_type as is_annotated_type, + is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, extract_type_var_from_base as extract_type_var_from_base, ) @@ -44,5 +53,12 @@ from ._transform import ( PropertyInfo as PropertyInfo, transform as transform, + async_transform as async_transform, maybe_transform as maybe_transform, + async_maybe_transform as async_maybe_transform, +) +from ._reflection import ( + function_has_argument as function_has_argument, + assert_signatures_in_sync as assert_signatures_in_sync, ) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/lithic/_utils/_compat.py b/src/lithic/_utils/_compat.py new file mode 100644 index 00000000..dd703233 --- /dev/null +++ b/src/lithic/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/lithic/_utils/_datetime_parse.py b/src/lithic/_utils/_datetime_parse.py new file mode 100644 index 00000000..7cb9d9e6 --- /dev/null +++ b/src/lithic/_utils/_datetime_parse.py @@ -0,0 +1,136 @@ +""" +This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py +without the Pydantic v1 specific errors. +""" + +from __future__ import annotations + +import re +from typing import Dict, Union, Optional +from datetime import date, datetime, timezone, timedelta + +from .._types import StrBytesIntFloat + +date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" +time_expr = ( + r"(?P\d{1,2}):(?P\d{1,2})" + r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" + r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" +) + +date_re = re.compile(f"{date_expr}$") +datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") + + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) + + +def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None + + +def _from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: + if value == "Z": + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == "-": + offset = -offset + return timezone(timedelta(minutes=offset)) + else: + return None + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = _get_numeric(value, "datetime") + if number is not None: + return _from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + + match = datetime_re.match(value) + if match is None: + raise ValueError("invalid datetime format") + + kw = match.groupdict() + if kw["microsecond"]: + kw["microsecond"] = kw["microsecond"].ljust(6, "0") + + tzinfo = _parse_timezone(kw.pop("tzinfo")) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_["tzinfo"] = tzinfo + + return datetime(**kw_) # type: ignore + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = _get_numeric(value, "date") + if number is not None: + return _from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + match = date_re.match(value) + if match is None: + raise ValueError("invalid date format") + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise ValueError("invalid date format") from None diff --git a/src/lithic/_utils/_proxy.py b/src/lithic/_utils/_proxy.py index b9c12dc3..0f239a33 100644 --- a/src/lithic/_utils/_proxy.py +++ b/src/lithic/_utils/_proxy.py @@ -10,7 +10,7 @@ class LazyProxy(Generic[T], ABC): """Implements data methods to pretend that an instance is another instance. - This includes forwarding attribute access and othe methods. + This includes forwarding attribute access and other methods. """ # Note: we have to special case proxies that themselves return proxies @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ @@ -59,5 +62,4 @@ def __as_proxied__(self) -> T: return cast(T, self) @abstractmethod - def __load__(self) -> T: - ... + def __load__(self) -> T: ... diff --git a/src/lithic/_utils/_reflection.py b/src/lithic/_utils/_reflection.py new file mode 100644 index 00000000..89aa712a --- /dev/null +++ b/src/lithic/_utils/_reflection.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import inspect +from typing import Any, Callable + + +def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool: + """Returns whether or not the given function has a specific parameter""" + sig = inspect.signature(func) + return arg_name in sig.parameters + + +def assert_signatures_in_sync( + source_func: Callable[..., Any], + check_func: Callable[..., Any], + *, + exclude_params: set[str] = set(), +) -> None: + """Ensure that the signature of the second function matches the first.""" + + check_sig = inspect.signature(check_func) + source_sig = inspect.signature(source_func) + + errors: list[str] = [] + + for name, source_param in source_sig.parameters.items(): + if name in exclude_params: + continue + + custom_param = check_sig.parameters.get(name) + if not custom_param: + errors.append(f"the `{name}` param is missing") + continue + + if custom_param.annotation != source_param.annotation: + errors.append( + f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}" + ) + continue + + if errors: + raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors)) diff --git a/src/lithic/_utils/_resources_proxy.py b/src/lithic/_utils/_resources_proxy.py new file mode 100644 index 00000000..efa0c112 --- /dev/null +++ b/src/lithic/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `lithic.resources` module. + + This is used so that we can lazily import `lithic.resources` only when + needed *and* so that users can just import `lithic` and reference `lithic.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("lithic.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/lithic/_utils/_sync.py b/src/lithic/_utils/_sync.py index 595924e5..f6027c18 100644 --- a/src/lithic/_utils/_sync.py +++ b/src/lithic/_utils/_sync.py @@ -1,54 +1,49 @@ from __future__ import annotations +import asyncio import functools from typing import TypeVar, Callable, Awaitable from typing_extensions import ParamSpec import anyio +import sniffio import anyio.to_thread T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") -# copied from `asyncer`, https://github.com/tiangolo/asyncer -def asyncify( - function: Callable[T_ParamSpec, T_Retval], - *, - cancellable: bool = False, - limiter: anyio.CapacityLimiter | None = None, -) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: +async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs +) -> T_Retval: + if sniffio.current_async_library() == "asyncio": + return await asyncio.to_thread(func, *args, **kwargs) + + return await anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) + + +# inspired by `asyncer`, https://github.com/tiangolo/asyncer +def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments, and that when called, calls the original function - in a worker thread using `anyio.to_thread.run_sync()`. Internally, - `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports - keyword arguments additional to positional arguments and it adds better support for - autocompletion and inline errors for the arguments of the function called and the - return value. - - If the `cancellable` option is enabled and the task waiting for its completion is - cancelled, the thread will still run its course but its return value (or any raised - exception) will be ignored. + positional and keyword arguments. - Use it like this: + Usage: - ```Python - def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: - # Do work - return "Some result" + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result - result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b") - print(result) + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) ``` ## Arguments `function`: a blocking regular callable (e.g. a function) - `cancellable`: `True` to allow cancellation of the operation - `limiter`: capacity limiter to use to limit the total amount of threads running - (if omitted, the default limiter is used) ## Return @@ -58,7 +53,6 @@ def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: """ async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: - partial_f = functools.partial(function, *args, **kwargs) - return await anyio.to_thread.run_sync(partial_f, cancellable=cancellable, limiter=limiter) + return await to_thread(function, *args, **kwargs) return wrapper diff --git a/src/lithic/_utils/_transform.py b/src/lithic/_utils/_transform.py index 2cb7726c..52075492 100644 --- a/src/lithic/_utils/_transform.py +++ b/src/lithic/_utils/_transform.py @@ -1,26 +1,35 @@ from __future__ import annotations +import io +import base64 +import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints +import anyio import pydantic from ._utils import ( is_list, + is_given, + lru_cache, is_mapping, is_iterable, + is_sequence, ) +from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict from ._typing import ( is_list_type, is_union_type, extract_type_arg, is_iterable_type, is_required_type, + is_sequence_type, is_annotated_type, strip_annotated_type, ) -from .._compat import model_dump, is_typeddict _T = TypeVar("_T") @@ -29,7 +38,7 @@ # TODO: ensure works correctly with forward references in all cases -PropertyFormat = Literal["iso8601", "custom"] +PropertyFormat = Literal["iso8601", "base64", "custom"] class PropertyInfo: @@ -46,6 +55,7 @@ class MyParams(TypedDict): alias: str | None format: PropertyFormat | None format_template: str | None + discriminator: str | None def __init__( self, @@ -53,14 +63,16 @@ def __init__( alias: str | None = None, format: PropertyFormat | None = None, format_template: str | None = None, + discriminator: str | None = None, ) -> None: self.alias = alias self.format = format self.format_template = format_template + self.discriminator = discriminator @override def __repr__(self) -> str: - return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}')" + return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')" def maybe_transform( @@ -100,6 +112,7 @@ class Params(TypedDict, total=False): return cast(_T, transformed) +@lru_cache(maxsize=8096) def _get_annotated_type(type_: type) -> type | None: """If the given type is an `Annotated` type then it is returned, if not `None` is returned. @@ -118,7 +131,7 @@ def _get_annotated_type(type_: type) -> type | None: def _maybe_transform_key(key: str, type_: type) -> str: """Transform the given `data` based on the annotations provided in `type_`. - Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata. + Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata. """ annotated_type = _get_annotated_type(type_) if annotated_type is None: @@ -134,6 +147,10 @@ def _maybe_transform_key(key: str, type_: type) -> str: return key +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + def _transform_recursive( data: object, *, @@ -152,20 +169,43 @@ def _transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return _transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -178,13 +218,9 @@ def _transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True) - - return _transform_value(data, annotation) + return model_dump(data, exclude_unset=True, mode="json") - -def _transform_value(data: object, type_: type) -> object: - annotated_type = _get_annotated_type(type_) + annotated_type = _get_annotated_type(annotation) if annotated_type is None: return data @@ -205,6 +241,22 @@ def _format_data(data: object, format_: PropertyFormat, format_template: str | N if format_ == "custom" and format_template is not None: return data.strftime(format_template) + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = data.read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + return data @@ -215,6 +267,11 @@ def _transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -222,3 +279,179 @@ def _transform_typeddict( else: result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_) return result + + +async def async_maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `async_transform()` that allows `None` to be passed. + + See `async_transform()` for more details. + """ + if data is None: + return None + return await async_transform(data, expected_type) + + +async def async_transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +async def _async_transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + from .._compat import model_dump + + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type + if is_typeddict(stripped_type) and is_mapping(data): + return await _async_transform_typeddict(data, stripped_type) + + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + + if ( + # List[T] + (is_list_type(stripped_type) and is_list(data)) + # Iterable[T] + or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) + ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True, mode="json") + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return await _async_format_data(data, annotation.format, annotation.format_template) + + return data + + +async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = await anyio.Path(data).read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + + return data + + +async def _async_transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) + return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/lithic/_utils/_typing.py b/src/lithic/_utils/_typing.py index c036991f..193109f3 100644 --- a/src/lithic/_utils/_typing.py +++ b/src/lithic/_utils/_typing.py @@ -1,11 +1,21 @@ from __future__ import annotations +import sys +import typing +import typing_extensions from typing import Any, TypeVar, Iterable, cast from collections import abc as _c_abc -from typing_extensions import Required, Annotated, get_args, get_origin - +from typing_extensions import ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) + +from ._utils import lru_cache from .._types import InheritsGeneric -from .._compat import is_union as _is_union +from ._compat import is_union as _is_union def is_annotated_type(typ: type) -> bool: @@ -16,6 +26,11 @@ def is_list_type(typ: type) -> bool: return (get_origin(typ) or typ) == list +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + def is_iterable_type(typ: type) -> bool: """If the given type is `typing.Iterable[T]`""" origin = get_origin(typ) or typ @@ -36,7 +51,28 @@ def is_typevar(typ: type) -> bool: return type(typ) == TypeVar # type: ignore +_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) +if sys.version_info >= (3, 12): + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + + +def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: + """Return whether the provided argument is an instance of `TypeAliasType`. + + ```python + type Int = int + is_type_alias_type(Int) + # > True + Str = TypeAliasType("Str", str) + is_type_alias_type(Str) + # > True + ``` + """ + return isinstance(tp, _TYPE_ALIAS_TYPES) + + # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): return strip_annotated_type(cast(type, get_args(typ)[0])) @@ -79,7 +115,7 @@ class MyResponse(Foo[_T]): ``` """ cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] # we're given the class directly return extract_type_arg(typ, index) diff --git a/src/lithic/_utils/_utils.py b/src/lithic/_utils/_utils.py index 93c95517..eec7f4a1 100644 --- a/src/lithic/_utils/_utils.py +++ b/src/lithic/_utils/_utils.py @@ -16,12 +16,12 @@ overload, ) from pathlib import Path +from datetime import date, datetime from typing_extensions import TypeGuard import sniffio -from .._types import Headers, NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import parse_date as parse_date, parse_datetime as parse_datetime +from .._types import Omit, NotGiven, FileTypes, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -63,7 +63,7 @@ def _extract_items( try: key = path[index] except IndexError: - if isinstance(obj, NotGiven): + if not is_given(obj): # no value was provided - we can safely ignore return [] @@ -71,8 +71,16 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for entry in obj: + assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") + files.append((flattened_key + "[]", cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 @@ -118,14 +126,14 @@ def _extract_items( return [] -def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) # Type safe methods for narrowing types with TypeVars. # The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], # however this cause Pyright to rightfully report errors. As we know we don't -# care about the contained types we can safely use `object` in it's place. +# care about the contained types we can safely use `object` in its place. # # There are two separate functions defined, `is_*` and `is_*_t` for different use cases. # `is_*` is for when you're dealing with an unknown input @@ -211,20 +219,17 @@ def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]: Example usage: ```py @overload - def foo(*, a: str) -> str: - ... + def foo(*, a: str) -> str: ... @overload - def foo(*, b: bool) -> str: - ... + def foo(*, b: bool) -> str: ... # This enforces the same constraints that a static type checker would # i.e. that either a or b must be passed to the function @required_args(["a"], ["b"]) - def foo(*, a: str | None = None, b: bool | None = None) -> str: - ... + def foo(*, a: str | None = None, b: bool | None = None) -> str: ... ``` """ @@ -265,6 +270,8 @@ def wrapper(*args: object, **kwargs: object) -> object: ) msg = f"Missing required arguments; Expected either {variations} arguments to be given" else: + assert len(variants) > 0 + # TODO: this error message is not deterministic missing = list(set(variants[0]) - given_params) if len(missing) > 1: @@ -284,18 +291,15 @@ def wrapper(*args: object, **kwargs: object) -> object: @overload -def strip_not_given(obj: None) -> None: - ... +def strip_not_given(obj: None) -> None: ... @overload -def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: - ... +def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ... @overload -def strip_not_given(obj: object) -> object: - ... +def strip_not_given(obj: object) -> object: ... def strip_not_given(obj: object | None) -> object: @@ -367,13 +371,13 @@ def file_from_path(path: str) -> FileTypes: def get_required_header(headers: HeadersLike, header: str) -> str: lower_header = header.lower() - if isinstance(headers, Mapping): - headers = cast(Headers, headers) - for k, v in headers.items(): + if is_mapping_t(headers): + # mypy doesn't understand the type narrowing here + for k, v in headers.items(): # type: ignore if k.lower() == lower_header and isinstance(v, str): return v - """ to deal with the case where the header looks like Stainless-Event-Id """ + # to deal with the case where the header looks like Stainless-Event-Id intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize()) for normalized_header in [header, lower_header, header.upper(), intercaps_header]: @@ -389,3 +393,29 @@ def get_async_library() -> str: return sniffio.current_async_library() except Exception: return "false" + + +def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: + """A version of functools.lru_cache that retains the type signature + for the wrapped function arguments. + """ + wrapper = functools.lru_cache( # noqa: TID251 + maxsize=maxsize, + ) + return cast(Any, wrapper) # type: ignore[no-any-return] + + +def json_safe(data: object) -> object: + """Translates a mapping / sequence recursively in the same fashion + as `pydantic` v2's `model_dump(mode="json")`. + """ + if is_mapping(data): + return {json_safe(key): json_safe(value) for key, value in data.items()} + + if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)): + return [json_safe(item) for item in data] + + if isinstance(data, (datetime, date)): + return data.isoformat() + + return data diff --git a/src/lithic/_version.py b/src/lithic/_version.py index 4381d2f0..768e4d15 100644 --- a/src/lithic/_version.py +++ b/src/lithic/_version.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "lithic" -__version__ = "0.39.0" # x-release-please-version +__version__ = "0.112.0" # x-release-please-version diff --git a/src/lithic/pagination.py b/src/lithic/pagination.py index 31ab30d4..73624536 100644 --- a/src/lithic/pagination.py +++ b/src/lithic/pagination.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Any, List, Generic, TypeVar, Optional, cast from typing_extensions import Protocol, override, runtime_checkable @@ -26,6 +26,11 @@ def _get_page_items(self) -> List[_T]: return [] return data + @override + def has_next_page(self) -> bool: + has_more = self.has_more + return has_more and super().has_next_page() + @override def next_page_info(self) -> Optional[PageInfo]: is_forwards = not self._options.params.get("ending_before", False) @@ -61,6 +66,11 @@ def _get_page_items(self) -> List[_T]: return [] return data + @override + def has_next_page(self) -> bool: + has_more = self.has_more + return has_more and super().has_next_page() + @override def next_page_info(self) -> Optional[PageInfo]: is_forwards = not self._options.params.get("ending_before", False) @@ -96,6 +106,11 @@ def _get_page_items(self) -> List[_T]: return [] return data + @override + def has_next_page(self) -> bool: + has_more = self.has_more + return has_more and super().has_next_page() + @override def next_page_info(self) -> None: """ @@ -116,6 +131,11 @@ def _get_page_items(self) -> List[_T]: return [] return data + @override + def has_next_page(self) -> bool: + has_more = self.has_more + return has_more and super().has_next_page() + @override def next_page_info(self) -> None: """ diff --git a/src/lithic/resources/__init__.py b/src/lithic/resources/__init__.py index aabd710d..77245ab4 100644 --- a/src/lithic/resources/__init__.py +++ b/src/lithic/resources/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .cards import ( Cards, @@ -8,6 +8,14 @@ CardsWithStreamingResponse, AsyncCardsWithStreamingResponse, ) +from .fraud import ( + Fraud, + AsyncFraud, + FraudWithRawResponse, + AsyncFraudWithRawResponse, + FraudWithStreamingResponse, + AsyncFraudWithStreamingResponse, +) from .events import ( Events, AsyncEvents, @@ -73,13 +81,13 @@ AuthRulesWithStreamingResponse, AsyncAuthRulesWithStreamingResponse, ) -from .card_product import ( - CardProduct, - AsyncCardProduct, - CardProductWithRawResponse, - AsyncCardProductWithRawResponse, - CardProductWithStreamingResponse, - AsyncCardProductWithStreamingResponse, +from .disputes_v2 import ( + DisputesV2, + AsyncDisputesV2, + DisputesV2WithRawResponse, + AsyncDisputesV2WithRawResponse, + DisputesV2WithStreamingResponse, + AsyncDisputesV2WithStreamingResponse, ) from .transactions import ( Transactions, @@ -105,6 +113,22 @@ TokenizationsWithStreamingResponse, AsyncTokenizationsWithStreamingResponse, ) +from .book_transfers import ( + BookTransfers, + AsyncBookTransfers, + BookTransfersWithRawResponse, + AsyncBookTransfersWithRawResponse, + BookTransfersWithStreamingResponse, + AsyncBookTransfersWithStreamingResponse, +) +from .funding_events import ( + FundingEvents, + AsyncFundingEvents, + FundingEventsWithRawResponse, + AsyncFundingEventsWithRawResponse, + FundingEventsWithStreamingResponse, + AsyncFundingEventsWithStreamingResponse, +) from .account_holders import ( AccountHolders, AsyncAccountHolders, @@ -113,6 +137,30 @@ AccountHoldersWithStreamingResponse, AsyncAccountHoldersWithStreamingResponse, ) +from .credit_products import ( + CreditProducts, + AsyncCreditProducts, + CreditProductsWithRawResponse, + AsyncCreditProductsWithRawResponse, + CreditProductsWithStreamingResponse, + AsyncCreditProductsWithStreamingResponse, +) +from .account_activity import ( + AccountActivity, + AsyncAccountActivity, + AccountActivityWithRawResponse, + AsyncAccountActivityWithRawResponse, + AccountActivityWithStreamingResponse, + AsyncAccountActivityWithStreamingResponse, +) +from .card_bulk_orders import ( + CardBulkOrders, + AsyncCardBulkOrders, + CardBulkOrdersWithRawResponse, + AsyncCardBulkOrdersWithRawResponse, + CardBulkOrdersWithStreamingResponse, + AsyncCardBulkOrdersWithStreamingResponse, +) from .digital_card_art import ( DigitalCardArtResource, AsyncDigitalCardArtResource, @@ -121,13 +169,21 @@ DigitalCardArtResourceWithStreamingResponse, AsyncDigitalCardArtResourceWithStreamingResponse, ) -from .aggregate_balances import ( - AggregateBalances, - AsyncAggregateBalances, - AggregateBalancesWithRawResponse, - AsyncAggregateBalancesWithRawResponse, - AggregateBalancesWithStreamingResponse, - AsyncAggregateBalancesWithStreamingResponse, +from .network_programs import ( + NetworkPrograms, + AsyncNetworkPrograms, + NetworkProgramsWithRawResponse, + AsyncNetworkProgramsWithRawResponse, + NetworkProgramsWithStreamingResponse, + AsyncNetworkProgramsWithStreamingResponse, +) +from .external_payments import ( + ExternalPayments, + AsyncExternalPayments, + ExternalPaymentsWithRawResponse, + AsyncExternalPaymentsWithRawResponse, + ExternalPaymentsWithStreamingResponse, + AsyncExternalPaymentsWithStreamingResponse, ) from .financial_accounts import ( FinancialAccounts, @@ -145,6 +201,14 @@ ResponderEndpointsWithStreamingResponse, AsyncResponderEndpointsWithStreamingResponse, ) +from .management_operations import ( + ManagementOperations, + AsyncManagementOperations, + ManagementOperationsWithRawResponse, + AsyncManagementOperationsWithRawResponse, + ManagementOperationsWithStreamingResponse, + AsyncManagementOperationsWithStreamingResponse, +) from .auth_stream_enrollment import ( AuthStreamEnrollment, AsyncAuthStreamEnrollment, @@ -213,24 +277,30 @@ "AsyncCardsWithRawResponse", "CardsWithStreamingResponse", "AsyncCardsWithStreamingResponse", + "CardBulkOrders", + "AsyncCardBulkOrders", + "CardBulkOrdersWithRawResponse", + "AsyncCardBulkOrdersWithRawResponse", + "CardBulkOrdersWithStreamingResponse", + "AsyncCardBulkOrdersWithStreamingResponse", "Balances", "AsyncBalances", "BalancesWithRawResponse", "AsyncBalancesWithRawResponse", "BalancesWithStreamingResponse", "AsyncBalancesWithStreamingResponse", - "AggregateBalances", - "AsyncAggregateBalances", - "AggregateBalancesWithRawResponse", - "AsyncAggregateBalancesWithRawResponse", - "AggregateBalancesWithStreamingResponse", - "AsyncAggregateBalancesWithStreamingResponse", "Disputes", "AsyncDisputes", "DisputesWithRawResponse", "AsyncDisputesWithRawResponse", "DisputesWithStreamingResponse", "AsyncDisputesWithStreamingResponse", + "DisputesV2", + "AsyncDisputesV2", + "DisputesV2WithRawResponse", + "AsyncDisputesV2WithRawResponse", + "DisputesV2WithStreamingResponse", + "AsyncDisputesV2WithStreamingResponse", "Events", "AsyncEvents", "EventsWithRawResponse", @@ -255,8 +325,6 @@ "AsyncResponderEndpointsWithRawResponse", "ResponderEndpointsWithStreamingResponse", "AsyncResponderEndpointsWithStreamingResponse", - "Webhooks", - "AsyncWebhooks", "ExternalBankAccounts", "AsyncExternalBankAccounts", "ExternalBankAccountsWithRawResponse", @@ -281,12 +349,6 @@ "AsyncReportsWithRawResponse", "ReportsWithStreamingResponse", "AsyncReportsWithStreamingResponse", - "CardProduct", - "AsyncCardProduct", - "CardProductWithRawResponse", - "AsyncCardProductWithRawResponse", - "CardProductWithStreamingResponse", - "AsyncCardProductWithStreamingResponse", "CardPrograms", "AsyncCardPrograms", "CardProgramsWithRawResponse", @@ -299,4 +361,54 @@ "AsyncDigitalCardArtResourceWithRawResponse", "DigitalCardArtResourceWithStreamingResponse", "AsyncDigitalCardArtResourceWithStreamingResponse", + "BookTransfers", + "AsyncBookTransfers", + "BookTransfersWithRawResponse", + "AsyncBookTransfersWithRawResponse", + "BookTransfersWithStreamingResponse", + "AsyncBookTransfersWithStreamingResponse", + "CreditProducts", + "AsyncCreditProducts", + "CreditProductsWithRawResponse", + "AsyncCreditProductsWithRawResponse", + "CreditProductsWithStreamingResponse", + "AsyncCreditProductsWithStreamingResponse", + "ExternalPayments", + "AsyncExternalPayments", + "ExternalPaymentsWithRawResponse", + "AsyncExternalPaymentsWithRawResponse", + "ExternalPaymentsWithStreamingResponse", + "AsyncExternalPaymentsWithStreamingResponse", + "ManagementOperations", + "AsyncManagementOperations", + "ManagementOperationsWithRawResponse", + "AsyncManagementOperationsWithRawResponse", + "ManagementOperationsWithStreamingResponse", + "AsyncManagementOperationsWithStreamingResponse", + "FundingEvents", + "AsyncFundingEvents", + "FundingEventsWithRawResponse", + "AsyncFundingEventsWithRawResponse", + "FundingEventsWithStreamingResponse", + "AsyncFundingEventsWithStreamingResponse", + "Fraud", + "AsyncFraud", + "FraudWithRawResponse", + "AsyncFraudWithRawResponse", + "FraudWithStreamingResponse", + "AsyncFraudWithStreamingResponse", + "NetworkPrograms", + "AsyncNetworkPrograms", + "NetworkProgramsWithRawResponse", + "AsyncNetworkProgramsWithRawResponse", + "NetworkProgramsWithStreamingResponse", + "AsyncNetworkProgramsWithStreamingResponse", + "AccountActivity", + "AsyncAccountActivity", + "AccountActivityWithRawResponse", + "AsyncAccountActivityWithRawResponse", + "AccountActivityWithStreamingResponse", + "AsyncAccountActivityWithStreamingResponse", + "Webhooks", + "AsyncWebhooks", ] diff --git a/src/lithic/resources/account_activity.py b/src/lithic/resources/account_activity.py new file mode 100644 index 00000000..c9cd0223 --- /dev/null +++ b/src/lithic/resources/account_activity.py @@ -0,0 +1,411 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, Union, cast +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from .. import _legacy_response +from ..types import account_activity_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.account_activity_list_response import AccountActivityListResponse +from ..types.account_activity_retrieve_transaction_response import AccountActivityRetrieveTransactionResponse + +__all__ = ["AccountActivity", "AsyncAccountActivity"] + + +class AccountActivity(SyncAPIResource): + @cached_property + def with_raw_response(self) -> AccountActivityWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AccountActivityWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AccountActivityWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AccountActivityWithStreamingResponse(self) + + def list( + self, + *, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "ACH", + "BALANCE_OR_FUNDING", + "FEE", + "REWARD", + "ADJUSTMENT", + "DERECOGNITION", + "DISPUTE", + "CARD", + "EXTERNAL_ACH", + "EXTERNAL_CHECK", + "EXTERNAL_FEDNOW", + "EXTERNAL_RTP", + "EXTERNAL_TRANSFER", + "EXTERNAL_WIRE", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_FEE", + "MANAGEMENT_REWARD", + "MANAGEMENT_DISBURSEMENT", + "PROGRAM_FUNDING", + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "REVERSED", "SETTLED", "VOIDED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[AccountActivityListResponse]: + """ + Retrieve a list of transactions across all public accounts. + + Args: + account_token: Filter by account token + + begin: Date string in RFC 3339 format. Only entries created after the specified time + will be included. UTC time zone. + + business_account_token: Filter by business account token + + category: Filter by transaction category + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Filter by financial account token + + page_size: Page size (for pagination). + + result: Filter by transaction result + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Filter by transaction status + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/account_activity", + page=SyncCursorPage[AccountActivityListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "result": result, + "starting_after": starting_after, + "status": status, + }, + account_activity_list_params.AccountActivityListParams, + ), + ), + model=cast( + Any, AccountActivityListResponse + ), # Union types cannot be passed in as arguments in the type system + ) + + def retrieve_transaction( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountActivityRetrieveTransactionResponse: + """ + Retrieve a single transaction + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return cast( + AccountActivityRetrieveTransactionResponse, + self._get( + f"/v1/account_activity/{transaction_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, AccountActivityRetrieveTransactionResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + + +class AsyncAccountActivity(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAccountActivityWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncAccountActivityWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAccountActivityWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncAccountActivityWithStreamingResponse(self) + + def list( + self, + *, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "ACH", + "BALANCE_OR_FUNDING", + "FEE", + "REWARD", + "ADJUSTMENT", + "DERECOGNITION", + "DISPUTE", + "CARD", + "EXTERNAL_ACH", + "EXTERNAL_CHECK", + "EXTERNAL_FEDNOW", + "EXTERNAL_RTP", + "EXTERNAL_TRANSFER", + "EXTERNAL_WIRE", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_FEE", + "MANAGEMENT_REWARD", + "MANAGEMENT_DISBURSEMENT", + "PROGRAM_FUNDING", + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "REVERSED", "SETTLED", "VOIDED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AccountActivityListResponse, AsyncCursorPage[AccountActivityListResponse]]: + """ + Retrieve a list of transactions across all public accounts. + + Args: + account_token: Filter by account token + + begin: Date string in RFC 3339 format. Only entries created after the specified time + will be included. UTC time zone. + + business_account_token: Filter by business account token + + category: Filter by transaction category + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Filter by financial account token + + page_size: Page size (for pagination). + + result: Filter by transaction result + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Filter by transaction status + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/account_activity", + page=AsyncCursorPage[AccountActivityListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "result": result, + "starting_after": starting_after, + "status": status, + }, + account_activity_list_params.AccountActivityListParams, + ), + ), + model=cast( + Any, AccountActivityListResponse + ), # Union types cannot be passed in as arguments in the type system + ) + + async def retrieve_transaction( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountActivityRetrieveTransactionResponse: + """ + Retrieve a single transaction + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return cast( + AccountActivityRetrieveTransactionResponse, + await self._get( + f"/v1/account_activity/{transaction_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, AccountActivityRetrieveTransactionResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + + +class AccountActivityWithRawResponse: + def __init__(self, account_activity: AccountActivity) -> None: + self._account_activity = account_activity + + self.list = _legacy_response.to_raw_response_wrapper( + account_activity.list, + ) + self.retrieve_transaction = _legacy_response.to_raw_response_wrapper( + account_activity.retrieve_transaction, + ) + + +class AsyncAccountActivityWithRawResponse: + def __init__(self, account_activity: AsyncAccountActivity) -> None: + self._account_activity = account_activity + + self.list = _legacy_response.async_to_raw_response_wrapper( + account_activity.list, + ) + self.retrieve_transaction = _legacy_response.async_to_raw_response_wrapper( + account_activity.retrieve_transaction, + ) + + +class AccountActivityWithStreamingResponse: + def __init__(self, account_activity: AccountActivity) -> None: + self._account_activity = account_activity + + self.list = to_streamed_response_wrapper( + account_activity.list, + ) + self.retrieve_transaction = to_streamed_response_wrapper( + account_activity.retrieve_transaction, + ) + + +class AsyncAccountActivityWithStreamingResponse: + def __init__(self, account_activity: AsyncAccountActivity) -> None: + self._account_activity = account_activity + + self.list = async_to_streamed_response_wrapper( + account_activity.list, + ) + self.retrieve_transaction = async_to_streamed_response_wrapper( + account_activity.retrieve_transaction, + ) diff --git a/src/lithic/resources/account_holders.py b/src/lithic/resources/account_holders.py index 39981086..bd371f9b 100644 --- a/src/lithic/resources/account_holders.py +++ b/src/lithic/resources/account_holders.py @@ -1,36 +1,38 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing import Iterable, overload -from typing_extensions import Literal +from typing import Any, List, Union, Iterable, cast +from datetime import datetime +from typing_extensions import Literal, overload import httpx from .. import _legacy_response from ..types import ( - AccountHolder, - AccountHolderDocument, - AccountHolderCreateResponse, - AccountHolderUpdateResponse, - AccountHolderListDocumentsResponse, - shared_params, account_holder_list_params, account_holder_create_params, account_holder_update_params, - account_holder_resubmit_params, account_holder_upload_document_params, + account_holder_simulate_enrollment_review_params, + account_holder_simulate_enrollment_document_review_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import required_args, maybe_transform +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import is_given, required_args, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .._constants import DEFAULT_TIMEOUT from ..pagination import SyncSinglePage, AsyncSinglePage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.account_holder import AccountHolder +from ..types.shared.document import Document +from ..types.address_update_param import AddressUpdateParam +from ..types.shared_params.address import Address +from ..types.account_holder_create_response import AccountHolderCreateResponse +from ..types.account_holder_update_response import AccountHolderUpdateResponse +from ..types.account_holder_list_documents_response import AccountHolderListDocumentsResponse +from ..types.account_holder_simulate_enrollment_review_response import AccountHolderSimulateEnrollmentReviewResponse __all__ = ["AccountHolders", "AsyncAccountHolders"] @@ -38,58 +40,61 @@ class AccountHolders(SyncAPIResource): @cached_property def with_raw_response(self) -> AccountHoldersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AccountHoldersWithRawResponse(self) @cached_property def with_streaming_response(self) -> AccountHoldersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AccountHoldersWithStreamingResponse(self) @overload def create( self, *, - beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity], beneficial_owner_individuals: Iterable[account_holder_create_params.KYBBeneficialOwnerIndividual], business_entity: account_holder_create_params.KYBBusinessEntity, control_person: account_holder_create_params.KYBControlPerson, nature_of_business: str, tos_timestamp: str, workflow: Literal["KYB_BASIC", "KYB_BYO"], - external_id: str | NotGiven = NOT_GIVEN, - kyb_passed_timestamp: str | NotGiven = NOT_GIVEN, - website_url: str | NotGiven = NOT_GIVEN, + beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, + external_id: str | Omit = omit, + kyb_passed_timestamp: str | Omit = omit, + website_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: """ - Run an individual or business's information through the Customer Identification - Program (CIP) and return an `account_token` if the status is accepted or pending - (i.e., further action required). All calls to this endpoint will return an - immediate response - though in some cases, the response may indicate the - workflow is under review or further action will be needed to complete the - account creation process. This endpoint can only be used on accounts that are - part of the program that the calling API key manages. + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. Args: - beneficial_owner_entities: List of all entities with >25% ownership in the company. If no entity or - individual owns >25% of the company, and the largest shareholder is an entity, - please identify them in this field. See - [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) - (Section I) for more background. If no business owner is an entity, pass in an - empty list. However, either this parameter or `beneficial_owner_individuals` - must be populated. on entities that should be included. - - beneficial_owner_individuals: List of all individuals with >25% ownership in the company. If no entity or - individual owns >25% of the company, and the largest shareholder is an - individual, please identify them in this field. See + beneficial_owner_individuals: You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) - (Section I) for more background on individuals that should be included. If no - individual is an entity, pass in an empty list. However, either this parameter - or `beneficial_owner_entities` must be populated. + (Section I) for more background on individuals that should be included. business_entity: Information for business for which the account is being opened and KYB is being run. @@ -112,10 +117,12 @@ def create( workflow: Specifies the type of KYB workflow to run. + beneficial_owner_entities: Deprecated. + external_id: A user provided id that can be used to link an account holder with an external system - kyb_passed_timestamp: An RFC 3339 timestamp indicating when precomputed KYC was completed on the + kyb_passed_timestamp: An RFC 3339 timestamp indicating when precomputed KYB was completed on the business with a pass result. This field is required only if workflow type is `KYB_BYO`. @@ -132,30 +139,104 @@ def create( """ ... + @overload + def create( + self, + *, + business_entity: account_holder_create_params.KYBDelegatedBusinessEntity, + beneficial_owner_individuals: Iterable[account_holder_create_params.KYBDelegatedBeneficialOwnerIndividual] + | Omit = omit, + control_person: account_holder_create_params.KYBDelegatedControlPerson | Omit = omit, + external_id: str | Omit = omit, + nature_of_business: str | Omit = omit, + tos_timestamp: str | Omit = omit, + website_url: str | Omit = omit, + workflow: Literal["KYB_DELEGATED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderCreateResponse: + """ + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. + + Args: + business_entity: Information for business for which the account is being opened. + + beneficial_owner_individuals: You can submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + + control_person: An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + + external_id: A user provided id that can be used to link an account holder with an external + system + + nature_of_business: Short description of the company's line of business (i.e., what does the company + do?). + + tos_timestamp: An RFC 3339 timestamp indicating when the account holder accepted the applicable + legal agreements (e.g., cardholder terms) as agreed upon during API customer's + implementation with Lithic. + + website_url: Company website URL. + + workflow: Specifies the type of KYB workflow to run. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + @overload def create( self, *, individual: account_holder_create_params.KYCIndividual, tos_timestamp: str, - workflow: Literal["KYC_ADVANCED", "KYC_BASIC", "KYC_BYO"], - external_id: str | NotGiven = NOT_GIVEN, - kyc_passed_timestamp: str | NotGiven = NOT_GIVEN, + workflow: Literal["KYC_BASIC", "KYC_BYO"], + external_id: str | Omit = omit, + kyc_passed_timestamp: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: """ - Run an individual or business's information through the Customer Identification - Program (CIP) and return an `account_token` if the status is accepted or pending - (i.e., further action required). All calls to this endpoint will return an - immediate response - though in some cases, the response may indicate the - workflow is under review or further action will be needed to complete the - account creation process. This endpoint can only be used on accounts that are - part of the program that the calling API key manages. + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. Args: individual: Information on individual for whom the account is being opened and KYC is being @@ -189,32 +270,36 @@ def create( def create( self, *, + address: Address, email: str, first_name: str, kyc_exemption_type: Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"], last_name: str, phone_number: str, workflow: Literal["KYC_EXEMPT"], - address: shared_params.Address | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, - external_id: str | NotGiven = NOT_GIVEN, + business_account_token: str | Omit = omit, + external_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: """ - Run an individual or business's information through the Customer Identification - Program (CIP) and return an `account_token` if the status is accepted or pending - (i.e., further action required). All calls to this endpoint will return an - immediate response - though in some cases, the response may indicate the - workflow is under review or further action will be needed to complete the - account creation process. This endpoint can only be used on accounts that are - part of the program that the calling API key manages. + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. Args: + address: KYC Exempt user's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + email: The KYC Exempt user's email first_name: The KYC Exempt user's first name @@ -223,13 +308,10 @@ def create( last_name: The KYC Exempt user's last name - phone_number: The KYC Exempt user's phone number + phone_number: The KYC Exempt user's phone number, entered in E.164 format. workflow: Specifies the workflow type. This must be 'KYC_EXEMPT' - address: KYC Exempt user's current address - PO boxes, UPS drops, and FedEx drops are not - acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. - business_account_token: Only applicable for customers using the KYC-Exempt workflow to enroll authorized users of businesses. Pass the account_token of the enrolled business associated with the AUTHORIZED_USER in this field. @@ -249,7 +331,6 @@ def create( @required_args( [ - "beneficial_owner_entities", "beneficial_owner_individuals", "business_entity", "control_person", @@ -257,64 +338,73 @@ def create( "tos_timestamp", "workflow", ], + ["business_entity"], ["individual", "tos_timestamp", "workflow"], - ["email", "first_name", "kyc_exemption_type", "last_name", "phone_number", "workflow"], + ["address", "email", "first_name", "kyc_exemption_type", "last_name", "phone_number", "workflow"], ) def create( self, *, - beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] - | NotGiven = NOT_GIVEN, beneficial_owner_individuals: Iterable[account_holder_create_params.KYBBeneficialOwnerIndividual] - | NotGiven = NOT_GIVEN, - business_entity: account_holder_create_params.KYBBusinessEntity | NotGiven = NOT_GIVEN, - control_person: account_holder_create_params.KYBControlPerson | NotGiven = NOT_GIVEN, - nature_of_business: str | NotGiven = NOT_GIVEN, - tos_timestamp: str | NotGiven = NOT_GIVEN, + | Iterable[account_holder_create_params.KYBDelegatedBeneficialOwnerIndividual] + | Omit = omit, + business_entity: account_holder_create_params.KYBBusinessEntity + | account_holder_create_params.KYBDelegatedBusinessEntity + | Omit = omit, + control_person: account_holder_create_params.KYBControlPerson + | account_holder_create_params.KYBDelegatedControlPerson + | Omit = omit, + nature_of_business: str | Omit = omit, + tos_timestamp: str | Omit = omit, workflow: Literal["KYB_BASIC", "KYB_BYO"] - | Literal["KYC_ADVANCED", "KYC_BASIC", "KYC_BYO"] - | Literal["KYC_EXEMPT"], - external_id: str | NotGiven = NOT_GIVEN, - kyb_passed_timestamp: str | NotGiven = NOT_GIVEN, - website_url: str | NotGiven = NOT_GIVEN, - individual: account_holder_create_params.KYCIndividual | NotGiven = NOT_GIVEN, - kyc_passed_timestamp: str | NotGiven = NOT_GIVEN, - email: str | NotGiven = NOT_GIVEN, - first_name: str | NotGiven = NOT_GIVEN, - kyc_exemption_type: Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"] | NotGiven = NOT_GIVEN, - last_name: str | NotGiven = NOT_GIVEN, - phone_number: str | NotGiven = NOT_GIVEN, - address: shared_params.Address | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, + | Literal["KYB_DELEGATED"] + | Literal["KYC_BASIC", "KYC_BYO"] + | Literal["KYC_EXEMPT"] + | Omit = omit, + beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, + external_id: str | Omit = omit, + kyb_passed_timestamp: str | Omit = omit, + website_url: str | Omit = omit, + individual: account_holder_create_params.KYCIndividual | Omit = omit, + kyc_passed_timestamp: str | Omit = omit, + address: Address | Omit = omit, + email: str | Omit = omit, + first_name: str | Omit = omit, + kyc_exemption_type: Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"] | Omit = omit, + last_name: str | Omit = omit, + phone_number: str | Omit = omit, + business_account_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 300 return self._post( - "/account_holders", + "/v1/account_holders", body=maybe_transform( { - "beneficial_owner_entities": beneficial_owner_entities, "beneficial_owner_individuals": beneficial_owner_individuals, "business_entity": business_entity, "control_person": control_person, "nature_of_business": nature_of_business, "tos_timestamp": tos_timestamp, "workflow": workflow, + "beneficial_owner_entities": beneficial_owner_entities, "external_id": external_id, "kyb_passed_timestamp": kyb_passed_timestamp, "website_url": website_url, "individual": individual, "kyc_passed_timestamp": kyc_passed_timestamp, + "address": address, "email": email, "first_name": first_name, "kyc_exemption_type": kyc_exemption_type, "last_name": last_name, "phone_number": phone_number, - "address": address, "business_account_token": business_account_token, }, account_holder_create_params.AccountHolderCreateParams, @@ -334,7 +424,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolder: """ Get an Individual or Business Account Holder and/or their KYC or KYB evaluation @@ -354,42 +444,179 @@ def retrieve( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) return self._get( - f"/account_holders/{account_holder_token}", + f"/v1/account_holders/{account_holder_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=AccountHolder, ) + @overload def update( self, account_holder_token: str, *, - business_account_token: str | NotGiven = NOT_GIVEN, - email: str | NotGiven = NOT_GIVEN, - phone_number: str | NotGiven = NOT_GIVEN, + beneficial_owner_entities: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerEntity] + | Omit = omit, + beneficial_owner_individuals: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerIndividual] + | Omit = omit, + business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, + control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, + external_id: str | Omit = omit, + nature_of_business: str | Omit = omit, + website_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderUpdateResponse: """ - Update the information associated with a particular account holder. + Update the information associated with a particular account holder (including + business owners and control persons associated to a business account). If Lithic + is performing KYB or KYC and additional verification is required we will run the + individual's or business's updated information again and return whether the + status is accepted or pending (i.e., further action required). All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on existing accounts that are part of + the program that the calling API key manages. Args: - business_account_token: Only applicable for customers using the KYC-Exempt workflow to enroll authorized - users of businesses. Pass the account_token of the enrolled business associated - with the AUTHORIZED_USER in this field. + beneficial_owner_entities: Deprecated. + + beneficial_owner_individuals: You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + + business_entity: Information for business for which the account is being opened and KYB is being + run. + + control_person: An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + + external_id: A user provided id that can be used to link an account holder with an external + system + + nature_of_business: Short description of the company's line of business (i.e., what does the company + do?). + + website_url: Company website URL. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def update( + self, + account_holder_token: str, + *, + external_id: str | Omit = omit, + individual: account_holder_update_params.KYCPatchRequestIndividual | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderUpdateResponse: + """ + Update the information associated with a particular account holder (including + business owners and control persons associated to a business account). If Lithic + is performing KYB or KYC and additional verification is required we will run the + individual's or business's updated information again and return whether the + status is accepted or pending (i.e., further action required). All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on existing accounts that are part of + the program that the calling API key manages. + + Args: + external_id: A user provided id that can be used to link an account holder with an external + system + + individual: Information on the individual for whom the account is being opened and KYC is + being run. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def update( + self, + account_holder_token: str, + *, + address: AddressUpdateParam | Omit = omit, + business_account_token: str | Omit = omit, + email: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + legal_business_name: str | Omit = omit, + phone_number: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderUpdateResponse: + """ + Update the information associated with a particular account holder (including + business owners and control persons associated to a business account). If Lithic + is performing KYB or KYC and additional verification is required we will run the + individual's or business's updated information again and return whether the + status is accepted or pending (i.e., further action required). All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on existing accounts that are part of + the program that the calling API key manages. + + Args: + address: Allowed for: KYC-Exempt, BYO-KYC, BYO-KYB. - email: Account holder's email address. The primary purpose of this field is for - cardholder identification and verification during the digital wallet - tokenization process. + business_account_token: Allowed for: KYC-Exempt, BYO-KYC. The token of the business account to which the + account holder is associated. - phone_number: Account holder's phone number, entered in E.164 format. The primary purpose of - this field is for cardholder identification and verification during the digital - wallet tokenization process. + email: Allowed for all Account Holders. Account holder's email address. The primary + purpose of this field is for cardholder identification and verification during + the digital wallet tokenization process. + + first_name: Allowed for KYC-Exempt, BYO-KYC. Account holder's first name. + + last_name: Allowed for KYC-Exempt, BYO-KYC. Account holder's last name. + + legal_business_name: Allowed for BYO-KYB. Legal business name of the account holder. + + phone_number: Allowed for all Account Holders. Account holder's phone number, entered in E.164 + format. The primary purpose of this field is for cardholder identification and + verification during the digital wallet tokenization process. extra_headers: Send extra headers @@ -399,52 +626,126 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + def update( + self, + account_holder_token: str, + *, + beneficial_owner_entities: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerEntity] + | Omit = omit, + beneficial_owner_individuals: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerIndividual] + | Omit = omit, + business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, + control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, + external_id: str | Omit = omit, + nature_of_business: str | Omit = omit, + website_url: str | Omit = omit, + individual: account_holder_update_params.KYCPatchRequestIndividual | Omit = omit, + address: AddressUpdateParam | Omit = omit, + business_account_token: str | Omit = omit, + email: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + legal_business_name: str | Omit = omit, + phone_number: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderUpdateResponse: if not account_holder_token: raise ValueError( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) - return self._patch( - f"/account_holders/{account_holder_token}", - body=maybe_transform( - { - "business_account_token": business_account_token, - "email": email, - "phone_number": phone_number, - }, - account_holder_update_params.AccountHolderUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + return cast( + AccountHolderUpdateResponse, + self._patch( + f"/v1/account_holders/{account_holder_token}", + body=maybe_transform( + { + "beneficial_owner_entities": beneficial_owner_entities, + "beneficial_owner_individuals": beneficial_owner_individuals, + "business_entity": business_entity, + "control_person": control_person, + "external_id": external_id, + "nature_of_business": nature_of_business, + "website_url": website_url, + "individual": individual, + "address": address, + "business_account_token": business_account_token, + "email": email, + "first_name": first_name, + "last_name": last_name, + "legal_business_name": legal_business_name, + "phone_number": phone_number, + }, + account_holder_update_params.AccountHolderUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, AccountHolderUpdateResponse + ), # Union types cannot be passed in as arguments in the type system ), - cast_to=AccountHolderUpdateResponse, ) def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - external_id: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + email: str | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + external_id: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + legal_business_name: str | Omit = omit, + limit: int | Omit = omit, + phone_number: str | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncSinglePage[AccountHolder]: """ Get a list of individual or business account holders and their KYC or KYB evaluation status. Args: + begin: Date string in RFC 3339 format. Only entries created after the specified time + will be included. UTC time zone. + + email: Email address of the account holder. The query must be an exact match, case + insensitive. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. external_id: If applicable, represents the external_id associated with the account_holder. + first_name: (Individual Account Holders only) The first name of the account holder. The + query is case insensitive and supports partial matches. + + last_name: (Individual Account Holders only) The last name of the account holder. The query + is case insensitive and supports partial matches. + + legal_business_name: (Business Account Holders only) The legal business name of the account holder. + The query is case insensitive and supports partial matches. + limit: The number of account_holders to limit the response to. + phone_number: Phone number of the account holder. The query must be an exact match. + starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. @@ -457,7 +758,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/account_holders", + "/v1/account_holders", page=SyncSinglePage[AccountHolder], options=make_request_options( extra_headers=extra_headers, @@ -466,9 +767,16 @@ def list( timeout=timeout, query=maybe_transform( { + "begin": begin, + "email": email, + "end": end, "ending_before": ending_before, "external_id": external_id, + "first_name": first_name, + "last_name": last_name, + "legal_business_name": legal_business_name, "limit": limit, + "phone_number": phone_number, "starting_after": starting_after, }, account_holder_list_params.AccountHolderListParams, @@ -486,7 +794,7 @@ def list_documents( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderListDocumentsResponse: """ Retrieve the status of account holder document uploads, or retrieve the upload @@ -519,46 +827,42 @@ def list_documents( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) return self._get( - f"/account_holders/{account_holder_token}/documents", + f"/v1/account_holders/{account_holder_token}/documents", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=AccountHolderListDocumentsResponse, ) - def resubmit( + def retrieve_document( self, - account_holder_token: str, + document_token: str, *, - individual: account_holder_resubmit_params.Individual, - tos_timestamp: str, - workflow: Literal["KYC_ADVANCED"], + account_holder_token: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccountHolder: - """Resubmit a KYC submission. - - This endpoint should be used in cases where a KYC - submission returned a `PENDING_RESUBMIT` result, meaning one or more critical - KYC fields may have been mis-entered and the individual's identity has not yet - been successfully verified. This step must be completed in order to proceed with - the KYC evaluation. + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Document: + """ + Check the status of an account holder document upload, or retrieve the upload + URLs to process your image uploads. - Two resubmission attempts are permitted via this endpoint before a `REJECTED` - status is returned and the account creation process is ended. + Note that this is not equivalent to checking the status of the KYC evaluation + overall (a document may be successfully uploaded but not be sufficient for KYC + to pass). - Args: - individual: Information on individual for whom the account is being opened and KYC is being - re-run. + In the event your upload URLs have expired, calling this endpoint will refresh + them. Similarly, in the event a document upload has failed, you can use this + endpoint to get a new upload URL for the failed image upload. - tos_timestamp: An RFC 3339 timestamp indicating when the account holder accepted the applicable - legal agreements (e.g., cardholder terms) as agreed upon during API customer's - implementation with Lithic. + When a new account holder document upload is generated for a failed attempt, the + response will show an additional entry in the `required_document_uploads` array + in a `PENDING` state for the corresponding `image_type`. + Args: extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -571,51 +875,128 @@ def resubmit( raise ValueError( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) + if not document_token: + raise ValueError(f"Expected a non-empty value for `document_token` but received {document_token!r}") + return self._get( + f"/v1/account_holders/{account_holder_token}/documents/{document_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Document, + ) + + def simulate_enrollment_document_review( + self, + *, + document_upload_token: str, + status: Literal["UPLOADED", "ACCEPTED", "REJECTED", "PARTIAL_APPROVAL"], + accepted_entity_status_reasons: SequenceNotStr[str] | Omit = omit, + status_reason: Literal[ + "DOCUMENT_MISSING_REQUIRED_DATA", + "DOCUMENT_UPLOAD_TOO_BLURRY", + "FILE_SIZE_TOO_LARGE", + "INVALID_DOCUMENT_TYPE", + "INVALID_DOCUMENT_UPLOAD", + "INVALID_ENTITY", + "DOCUMENT_EXPIRED", + "DOCUMENT_ISSUED_GREATER_THAN_30_DAYS", + "DOCUMENT_TYPE_NOT_SUPPORTED", + "UNKNOWN_FAILURE_REASON", + "UNKNOWN_ERROR", + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Document: + """ + Simulates a review for an account holder document upload. + + Args: + document_upload_token: The account holder document upload which to perform the simulation upon. + + status: An account holder document's upload status for use within the simulation. + + accepted_entity_status_reasons: A list of status reasons associated with a KYB account holder in PENDING_REVIEW + + status_reason: Status reason that will be associated with the simulated account holder status. + Only required for a `REJECTED` status or `PARTIAL_APPROVAL` status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ return self._post( - f"/account_holders/{account_holder_token}/resubmit", + "/v1/simulate/account_holders/enrollment_document_review", body=maybe_transform( { - "individual": individual, - "tos_timestamp": tos_timestamp, - "workflow": workflow, + "document_upload_token": document_upload_token, + "status": status, + "accepted_entity_status_reasons": accepted_entity_status_reasons, + "status_reason": status_reason, }, - account_holder_resubmit_params.AccountHolderResubmitParams, + account_holder_simulate_enrollment_document_review_params.AccountHolderSimulateEnrollmentDocumentReviewParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AccountHolder, + cast_to=Document, ) - def retrieve_document( + def simulate_enrollment_review( self, - document_token: str, *, - account_holder_token: str, + account_holder_token: str | Omit = omit, + status: Literal["ACCEPTED", "REJECTED"] | Omit = omit, + status_reasons: List[ + Literal[ + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_DOB_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_BLOCKLIST_ALERT_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_ID_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_NAME_VERIFICATION_FAILURE", + ] + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccountHolderDocument: - """ - Check the status of an account holder document upload, or retrieve the upload - URLs to process your image uploads. + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderSimulateEnrollmentReviewResponse: + """Simulates an enrollment review for an account holder. - Note that this is not equivalent to checking the status of the KYC evaluation - overall (a document may be successfully uploaded but not be sufficient for KYC - to pass). + This endpoint is only + applicable for workflows that may required intervention such as `KYB_BASIC`. - In the event your upload URLs have expired, calling this endpoint will refresh - them. Similarly, in the event a document upload has failed, you can use this - endpoint to get a new upload URL for the failed image upload. + Args: + account_holder_token: The account holder which to perform the simulation upon. - When a new account holder document upload is generated for a failed attempt, the - response will show an additional entry in the `required_document_uploads` array - in a `PENDING` state for the corresponding `image_type`. + status: An account holder's status for use within the simulation. + + status_reasons: Status reason that will be associated with the simulated account holder status. + Only required for a `REJECTED` status. - Args: extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -624,32 +1005,55 @@ def retrieve_document( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_holder_token: - raise ValueError( - f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" - ) - if not document_token: - raise ValueError(f"Expected a non-empty value for `document_token` but received {document_token!r}") - return self._get( - f"/account_holders/{account_holder_token}/documents/{document_token}", + return self._post( + "/v1/simulate/account_holders/enrollment_review", + body=maybe_transform( + { + "account_holder_token": account_holder_token, + "status": status, + "status_reasons": status_reasons, + }, + account_holder_simulate_enrollment_review_params.AccountHolderSimulateEnrollmentReviewParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AccountHolderDocument, + cast_to=AccountHolderSimulateEnrollmentReviewResponse, ) def upload_document( self, account_holder_token: str, *, - document_type: Literal["commercial_license", "drivers_license", "passport", "passport_card", "visa"], + document_type: Literal[ + "EIN_LETTER", + "TAX_RETURN", + "OPERATING_AGREEMENT", + "CERTIFICATE_OF_FORMATION", + "DRIVERS_LICENSE", + "PASSPORT", + "PASSPORT_CARD", + "CERTIFICATE_OF_GOOD_STANDING", + "ARTICLES_OF_INCORPORATION", + "ARTICLES_OF_ORGANIZATION", + "BYLAWS", + "GOVERNMENT_BUSINESS_LICENSE", + "PARTNERSHIP_AGREEMENT", + "SS4_FORM", + "BANK_STATEMENT", + "UTILITY_BILL_STATEMENT", + "SSN_CARD", + "ITIN_LETTER", + "FINCEN_BOI_REPORT", + ], + entity_token: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccountHolderDocument: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Document: """ Use this endpoint to identify which type of supported government-issued documentation you will upload for further verification. It will return two URLs @@ -671,7 +1075,9 @@ def upload_document( verification. Args: - document_type: Type of the document to upload. + document_type: The type of document to upload + + entity_token: Globally unique identifier for the entity. extra_headers: Send extra headers @@ -686,76 +1092,162 @@ def upload_document( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) return self._post( - f"/account_holders/{account_holder_token}/documents", + f"/v1/account_holders/{account_holder_token}/documents", body=maybe_transform( - {"document_type": document_type}, + { + "document_type": document_type, + "entity_token": entity_token, + }, account_holder_upload_document_params.AccountHolderUploadDocumentParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AccountHolderDocument, + cast_to=Document, ) class AsyncAccountHolders(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncAccountHoldersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncAccountHoldersWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncAccountHoldersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncAccountHoldersWithStreamingResponse(self) @overload async def create( self, *, - beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity], - beneficial_owner_individuals: Iterable[account_holder_create_params.KYBBeneficialOwnerIndividual], - business_entity: account_holder_create_params.KYBBusinessEntity, - control_person: account_holder_create_params.KYBControlPerson, - nature_of_business: str, - tos_timestamp: str, - workflow: Literal["KYB_BASIC", "KYB_BYO"], - external_id: str | NotGiven = NOT_GIVEN, - kyb_passed_timestamp: str | NotGiven = NOT_GIVEN, - website_url: str | NotGiven = NOT_GIVEN, + beneficial_owner_individuals: Iterable[account_holder_create_params.KYBBeneficialOwnerIndividual], + business_entity: account_holder_create_params.KYBBusinessEntity, + control_person: account_holder_create_params.KYBControlPerson, + nature_of_business: str, + tos_timestamp: str, + workflow: Literal["KYB_BASIC", "KYB_BYO"], + beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, + external_id: str | Omit = omit, + kyb_passed_timestamp: str | Omit = omit, + website_url: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderCreateResponse: + """ + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. + + Args: + beneficial_owner_individuals: You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + + business_entity: Information for business for which the account is being opened and KYB is being + run. + + control_person: An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + + nature_of_business: Short description of the company's line of business (i.e., what does the company + do?). + + tos_timestamp: An RFC 3339 timestamp indicating when the account holder accepted the applicable + legal agreements (e.g., cardholder terms) as agreed upon during API customer's + implementation with Lithic. + + workflow: Specifies the type of KYB workflow to run. + + beneficial_owner_entities: Deprecated. + + external_id: A user provided id that can be used to link an account holder with an external + system + + kyb_passed_timestamp: An RFC 3339 timestamp indicating when precomputed KYB was completed on the + business with a pass result. + + This field is required only if workflow type is `KYB_BYO`. + + website_url: Company website URL. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + business_entity: account_holder_create_params.KYBDelegatedBusinessEntity, + beneficial_owner_individuals: Iterable[account_holder_create_params.KYBDelegatedBeneficialOwnerIndividual] + | Omit = omit, + control_person: account_holder_create_params.KYBDelegatedControlPerson | Omit = omit, + external_id: str | Omit = omit, + nature_of_business: str | Omit = omit, + tos_timestamp: str | Omit = omit, + website_url: str | Omit = omit, + workflow: Literal["KYB_DELEGATED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: """ - Run an individual or business's information through the Customer Identification - Program (CIP) and return an `account_token` if the status is accepted or pending - (i.e., further action required). All calls to this endpoint will return an - immediate response - though in some cases, the response may indicate the - workflow is under review or further action will be needed to complete the - account creation process. This endpoint can only be used on accounts that are - part of the program that the calling API key manages. + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. Args: - beneficial_owner_entities: List of all entities with >25% ownership in the company. If no entity or - individual owns >25% of the company, and the largest shareholder is an entity, - please identify them in this field. See - [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) - (Section I) for more background. If no business owner is an entity, pass in an - empty list. However, either this parameter or `beneficial_owner_individuals` - must be populated. on entities that should be included. + business_entity: Information for business for which the account is being opened. - beneficial_owner_individuals: List of all individuals with >25% ownership in the company. If no entity or - individual owns >25% of the company, and the largest shareholder is an - individual, please identify them in this field. See + beneficial_owner_individuals: You can submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) - (Section I) for more background on individuals that should be included. If no - individual is an entity, pass in an empty list. However, either this parameter - or `beneficial_owner_entities` must be populated. - - business_entity: Information for business for which the account is being opened and KYB is being - run. + (Section I) for more background on individuals that should be included. control_person: An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating @@ -766,6 +1258,9 @@ async def create( [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) (Section II) for more background. + external_id: A user provided id that can be used to link an account holder with an external + system + nature_of_business: Short description of the company's line of business (i.e., what does the company do?). @@ -773,18 +1268,10 @@ async def create( legal agreements (e.g., cardholder terms) as agreed upon during API customer's implementation with Lithic. - workflow: Specifies the type of KYB workflow to run. - - external_id: A user provided id that can be used to link an account holder with an external - system - - kyb_passed_timestamp: An RFC 3339 timestamp indicating when precomputed KYC was completed on the - business with a pass result. - - This field is required only if workflow type is `KYB_BYO`. - website_url: Company website URL. + workflow: Specifies the type of KYB workflow to run. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -801,24 +1288,25 @@ async def create( *, individual: account_holder_create_params.KYCIndividual, tos_timestamp: str, - workflow: Literal["KYC_ADVANCED", "KYC_BASIC", "KYC_BYO"], - external_id: str | NotGiven = NOT_GIVEN, - kyc_passed_timestamp: str | NotGiven = NOT_GIVEN, + workflow: Literal["KYC_BASIC", "KYC_BYO"], + external_id: str | Omit = omit, + kyc_passed_timestamp: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: """ - Run an individual or business's information through the Customer Identification - Program (CIP) and return an `account_token` if the status is accepted or pending - (i.e., further action required). All calls to this endpoint will return an - immediate response - though in some cases, the response may indicate the - workflow is under review or further action will be needed to complete the - account creation process. This endpoint can only be used on accounts that are - part of the program that the calling API key manages. + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. Args: individual: Information on individual for whom the account is being opened and KYC is being @@ -852,32 +1340,36 @@ async def create( async def create( self, *, + address: Address, email: str, first_name: str, kyc_exemption_type: Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"], last_name: str, phone_number: str, workflow: Literal["KYC_EXEMPT"], - address: shared_params.Address | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, - external_id: str | NotGiven = NOT_GIVEN, + business_account_token: str | Omit = omit, + external_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: """ - Run an individual or business's information through the Customer Identification - Program (CIP) and return an `account_token` if the status is accepted or pending - (i.e., further action required). All calls to this endpoint will return an - immediate response - though in some cases, the response may indicate the - workflow is under review or further action will be needed to complete the - account creation process. This endpoint can only be used on accounts that are - part of the program that the calling API key manages. + Create an account holder and initiate the appropriate onboarding workflow. + Account holders and accounts have a 1:1 relationship. When an account holder is + successfully created an associated account is also created. All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on accounts that are part of the program + that the calling API key manages. Args: + address: KYC Exempt user's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + email: The KYC Exempt user's email first_name: The KYC Exempt user's first name @@ -886,13 +1378,10 @@ async def create( last_name: The KYC Exempt user's last name - phone_number: The KYC Exempt user's phone number + phone_number: The KYC Exempt user's phone number, entered in E.164 format. workflow: Specifies the workflow type. This must be 'KYC_EXEMPT' - address: KYC Exempt user's current address - PO boxes, UPS drops, and FedEx drops are not - acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. - business_account_token: Only applicable for customers using the KYC-Exempt workflow to enroll authorized users of businesses. Pass the account_token of the enrolled business associated with the AUTHORIZED_USER in this field. @@ -912,7 +1401,6 @@ async def create( @required_args( [ - "beneficial_owner_entities", "beneficial_owner_individuals", "business_entity", "control_person", @@ -920,64 +1408,73 @@ async def create( "tos_timestamp", "workflow", ], + ["business_entity"], ["individual", "tos_timestamp", "workflow"], - ["email", "first_name", "kyc_exemption_type", "last_name", "phone_number", "workflow"], + ["address", "email", "first_name", "kyc_exemption_type", "last_name", "phone_number", "workflow"], ) async def create( self, *, - beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] - | NotGiven = NOT_GIVEN, beneficial_owner_individuals: Iterable[account_holder_create_params.KYBBeneficialOwnerIndividual] - | NotGiven = NOT_GIVEN, - business_entity: account_holder_create_params.KYBBusinessEntity | NotGiven = NOT_GIVEN, - control_person: account_holder_create_params.KYBControlPerson | NotGiven = NOT_GIVEN, - nature_of_business: str | NotGiven = NOT_GIVEN, - tos_timestamp: str | NotGiven = NOT_GIVEN, + | Iterable[account_holder_create_params.KYBDelegatedBeneficialOwnerIndividual] + | Omit = omit, + business_entity: account_holder_create_params.KYBBusinessEntity + | account_holder_create_params.KYBDelegatedBusinessEntity + | Omit = omit, + control_person: account_holder_create_params.KYBControlPerson + | account_holder_create_params.KYBDelegatedControlPerson + | Omit = omit, + nature_of_business: str | Omit = omit, + tos_timestamp: str | Omit = omit, workflow: Literal["KYB_BASIC", "KYB_BYO"] - | Literal["KYC_ADVANCED", "KYC_BASIC", "KYC_BYO"] - | Literal["KYC_EXEMPT"], - external_id: str | NotGiven = NOT_GIVEN, - kyb_passed_timestamp: str | NotGiven = NOT_GIVEN, - website_url: str | NotGiven = NOT_GIVEN, - individual: account_holder_create_params.KYCIndividual | NotGiven = NOT_GIVEN, - kyc_passed_timestamp: str | NotGiven = NOT_GIVEN, - email: str | NotGiven = NOT_GIVEN, - first_name: str | NotGiven = NOT_GIVEN, - kyc_exemption_type: Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"] | NotGiven = NOT_GIVEN, - last_name: str | NotGiven = NOT_GIVEN, - phone_number: str | NotGiven = NOT_GIVEN, - address: shared_params.Address | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, + | Literal["KYB_DELEGATED"] + | Literal["KYC_BASIC", "KYC_BYO"] + | Literal["KYC_EXEMPT"] + | Omit = omit, + beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, + external_id: str | Omit = omit, + kyb_passed_timestamp: str | Omit = omit, + website_url: str | Omit = omit, + individual: account_holder_create_params.KYCIndividual | Omit = omit, + kyc_passed_timestamp: str | Omit = omit, + address: Address | Omit = omit, + email: str | Omit = omit, + first_name: str | Omit = omit, + kyc_exemption_type: Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"] | Omit = omit, + last_name: str | Omit = omit, + phone_number: str | Omit = omit, + business_account_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = 300, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderCreateResponse: + if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: + timeout = 300 return await self._post( - "/account_holders", - body=maybe_transform( + "/v1/account_holders", + body=await async_maybe_transform( { - "beneficial_owner_entities": beneficial_owner_entities, "beneficial_owner_individuals": beneficial_owner_individuals, "business_entity": business_entity, "control_person": control_person, "nature_of_business": nature_of_business, "tos_timestamp": tos_timestamp, "workflow": workflow, + "beneficial_owner_entities": beneficial_owner_entities, "external_id": external_id, "kyb_passed_timestamp": kyb_passed_timestamp, "website_url": website_url, "individual": individual, "kyc_passed_timestamp": kyc_passed_timestamp, + "address": address, "email": email, "first_name": first_name, "kyc_exemption_type": kyc_exemption_type, "last_name": last_name, "phone_number": phone_number, - "address": address, "business_account_token": business_account_token, }, account_holder_create_params.AccountHolderCreateParams, @@ -997,7 +1494,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolder: """ Get an Individual or Business Account Holder and/or their KYC or KYB evaluation @@ -1017,42 +1514,179 @@ async def retrieve( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) return await self._get( - f"/account_holders/{account_holder_token}", + f"/v1/account_holders/{account_holder_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=AccountHolder, ) + @overload async def update( self, account_holder_token: str, *, - business_account_token: str | NotGiven = NOT_GIVEN, - email: str | NotGiven = NOT_GIVEN, - phone_number: str | NotGiven = NOT_GIVEN, + beneficial_owner_entities: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerEntity] + | Omit = omit, + beneficial_owner_individuals: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerIndividual] + | Omit = omit, + business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, + control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, + external_id: str | Omit = omit, + nature_of_business: str | Omit = omit, + website_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderUpdateResponse: """ - Update the information associated with a particular account holder. + Update the information associated with a particular account holder (including + business owners and control persons associated to a business account). If Lithic + is performing KYB or KYC and additional verification is required we will run the + individual's or business's updated information again and return whether the + status is accepted or pending (i.e., further action required). All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on existing accounts that are part of + the program that the calling API key manages. Args: - business_account_token: Only applicable for customers using the KYC-Exempt workflow to enroll authorized - users of businesses. Pass the account_token of the enrolled business associated - with the AUTHORIZED_USER in this field. + beneficial_owner_entities: Deprecated. + + beneficial_owner_individuals: You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + + business_entity: Information for business for which the account is being opened and KYB is being + run. + + control_person: An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + + external_id: A user provided id that can be used to link an account holder with an external + system + + nature_of_business: Short description of the company's line of business (i.e., what does the company + do?). + + website_url: Company website URL. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request - email: Account holder's email address. The primary purpose of this field is for - cardholder identification and verification during the digital wallet - tokenization process. + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def update( + self, + account_holder_token: str, + *, + external_id: str | Omit = omit, + individual: account_holder_update_params.KYCPatchRequestIndividual | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderUpdateResponse: + """ + Update the information associated with a particular account holder (including + business owners and control persons associated to a business account). If Lithic + is performing KYB or KYC and additional verification is required we will run the + individual's or business's updated information again and return whether the + status is accepted or pending (i.e., further action required). All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on existing accounts that are part of + the program that the calling API key manages. + + Args: + external_id: A user provided id that can be used to link an account holder with an external + system + + individual: Information on the individual for whom the account is being opened and KYC is + being run. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def update( + self, + account_holder_token: str, + *, + address: AddressUpdateParam | Omit = omit, + business_account_token: str | Omit = omit, + email: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + legal_business_name: str | Omit = omit, + phone_number: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderUpdateResponse: + """ + Update the information associated with a particular account holder (including + business owners and control persons associated to a business account). If Lithic + is performing KYB or KYC and additional verification is required we will run the + individual's or business's updated information again and return whether the + status is accepted or pending (i.e., further action required). All calls to this + endpoint will return a synchronous response. The response time will depend on + the workflow. In some cases, the response may indicate the workflow is under + review or further action will be needed to complete the account creation + process. This endpoint can only be used on existing accounts that are part of + the program that the calling API key manages. + + Args: + address: Allowed for: KYC-Exempt, BYO-KYC, BYO-KYB. - phone_number: Account holder's phone number, entered in E.164 format. The primary purpose of - this field is for cardholder identification and verification during the digital - wallet tokenization process. + business_account_token: Allowed for: KYC-Exempt, BYO-KYC. The token of the business account to which the + account holder is associated. + + email: Allowed for all Account Holders. Account holder's email address. The primary + purpose of this field is for cardholder identification and verification during + the digital wallet tokenization process. + + first_name: Allowed for KYC-Exempt, BYO-KYC. Account holder's first name. + + last_name: Allowed for KYC-Exempt, BYO-KYC. Account holder's last name. + + legal_business_name: Allowed for BYO-KYB. Legal business name of the account holder. + + phone_number: Allowed for all Account Holders. Account holder's phone number, entered in E.164 + format. The primary purpose of this field is for cardholder identification and + verification during the digital wallet tokenization process. extra_headers: Send extra headers @@ -1062,52 +1696,126 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + async def update( + self, + account_holder_token: str, + *, + beneficial_owner_entities: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerEntity] + | Omit = omit, + beneficial_owner_individuals: Iterable[account_holder_update_params.KYBPatchRequestBeneficialOwnerIndividual] + | Omit = omit, + business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, + control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, + external_id: str | Omit = omit, + nature_of_business: str | Omit = omit, + website_url: str | Omit = omit, + individual: account_holder_update_params.KYCPatchRequestIndividual | Omit = omit, + address: AddressUpdateParam | Omit = omit, + business_account_token: str | Omit = omit, + email: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + legal_business_name: str | Omit = omit, + phone_number: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderUpdateResponse: if not account_holder_token: raise ValueError( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) - return await self._patch( - f"/account_holders/{account_holder_token}", - body=maybe_transform( - { - "business_account_token": business_account_token, - "email": email, - "phone_number": phone_number, - }, - account_holder_update_params.AccountHolderUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + return cast( + AccountHolderUpdateResponse, + await self._patch( + f"/v1/account_holders/{account_holder_token}", + body=await async_maybe_transform( + { + "beneficial_owner_entities": beneficial_owner_entities, + "beneficial_owner_individuals": beneficial_owner_individuals, + "business_entity": business_entity, + "control_person": control_person, + "external_id": external_id, + "nature_of_business": nature_of_business, + "website_url": website_url, + "individual": individual, + "address": address, + "business_account_token": business_account_token, + "email": email, + "first_name": first_name, + "last_name": last_name, + "legal_business_name": legal_business_name, + "phone_number": phone_number, + }, + account_holder_update_params.AccountHolderUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, AccountHolderUpdateResponse + ), # Union types cannot be passed in as arguments in the type system ), - cast_to=AccountHolderUpdateResponse, ) def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - external_id: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + email: str | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + external_id: str | Omit = omit, + first_name: str | Omit = omit, + last_name: str | Omit = omit, + legal_business_name: str | Omit = omit, + limit: int | Omit = omit, + phone_number: str | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[AccountHolder, AsyncSinglePage[AccountHolder]]: """ Get a list of individual or business account holders and their KYC or KYB evaluation status. Args: + begin: Date string in RFC 3339 format. Only entries created after the specified time + will be included. UTC time zone. + + email: Email address of the account holder. The query must be an exact match, case + insensitive. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. external_id: If applicable, represents the external_id associated with the account_holder. + first_name: (Individual Account Holders only) The first name of the account holder. The + query is case insensitive and supports partial matches. + + last_name: (Individual Account Holders only) The last name of the account holder. The query + is case insensitive and supports partial matches. + + legal_business_name: (Business Account Holders only) The legal business name of the account holder. + The query is case insensitive and supports partial matches. + limit: The number of account_holders to limit the response to. + phone_number: Phone number of the account holder. The query must be an exact match. + starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. @@ -1120,7 +1828,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/account_holders", + "/v1/account_holders", page=AsyncSinglePage[AccountHolder], options=make_request_options( extra_headers=extra_headers, @@ -1129,9 +1837,16 @@ def list( timeout=timeout, query=maybe_transform( { + "begin": begin, + "email": email, + "end": end, "ending_before": ending_before, "external_id": external_id, + "first_name": first_name, + "last_name": last_name, + "legal_business_name": legal_business_name, "limit": limit, + "phone_number": phone_number, "starting_after": starting_after, }, account_holder_list_params.AccountHolderListParams, @@ -1149,7 +1864,7 @@ async def list_documents( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountHolderListDocumentsResponse: """ Retrieve the status of account holder document uploads, or retrieve the upload @@ -1182,46 +1897,42 @@ async def list_documents( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) return await self._get( - f"/account_holders/{account_holder_token}/documents", + f"/v1/account_holders/{account_holder_token}/documents", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=AccountHolderListDocumentsResponse, ) - async def resubmit( + async def retrieve_document( self, - account_holder_token: str, + document_token: str, *, - individual: account_holder_resubmit_params.Individual, - tos_timestamp: str, - workflow: Literal["KYC_ADVANCED"], + account_holder_token: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccountHolder: - """Resubmit a KYC submission. - - This endpoint should be used in cases where a KYC - submission returned a `PENDING_RESUBMIT` result, meaning one or more critical - KYC fields may have been mis-entered and the individual's identity has not yet - been successfully verified. This step must be completed in order to proceed with - the KYC evaluation. + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Document: + """ + Check the status of an account holder document upload, or retrieve the upload + URLs to process your image uploads. - Two resubmission attempts are permitted via this endpoint before a `REJECTED` - status is returned and the account creation process is ended. + Note that this is not equivalent to checking the status of the KYC evaluation + overall (a document may be successfully uploaded but not be sufficient for KYC + to pass). - Args: - individual: Information on individual for whom the account is being opened and KYC is being - re-run. + In the event your upload URLs have expired, calling this endpoint will refresh + them. Similarly, in the event a document upload has failed, you can use this + endpoint to get a new upload URL for the failed image upload. - tos_timestamp: An RFC 3339 timestamp indicating when the account holder accepted the applicable - legal agreements (e.g., cardholder terms) as agreed upon during API customer's - implementation with Lithic. + When a new account holder document upload is generated for a failed attempt, the + response will show an additional entry in the `required_document_uploads` array + in a `PENDING` state for the corresponding `image_type`. + Args: extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1234,51 +1945,128 @@ async def resubmit( raise ValueError( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) + if not document_token: + raise ValueError(f"Expected a non-empty value for `document_token` but received {document_token!r}") + return await self._get( + f"/v1/account_holders/{account_holder_token}/documents/{document_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Document, + ) + + async def simulate_enrollment_document_review( + self, + *, + document_upload_token: str, + status: Literal["UPLOADED", "ACCEPTED", "REJECTED", "PARTIAL_APPROVAL"], + accepted_entity_status_reasons: SequenceNotStr[str] | Omit = omit, + status_reason: Literal[ + "DOCUMENT_MISSING_REQUIRED_DATA", + "DOCUMENT_UPLOAD_TOO_BLURRY", + "FILE_SIZE_TOO_LARGE", + "INVALID_DOCUMENT_TYPE", + "INVALID_DOCUMENT_UPLOAD", + "INVALID_ENTITY", + "DOCUMENT_EXPIRED", + "DOCUMENT_ISSUED_GREATER_THAN_30_DAYS", + "DOCUMENT_TYPE_NOT_SUPPORTED", + "UNKNOWN_FAILURE_REASON", + "UNKNOWN_ERROR", + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Document: + """ + Simulates a review for an account holder document upload. + + Args: + document_upload_token: The account holder document upload which to perform the simulation upon. + + status: An account holder document's upload status for use within the simulation. + + accepted_entity_status_reasons: A list of status reasons associated with a KYB account holder in PENDING_REVIEW + + status_reason: Status reason that will be associated with the simulated account holder status. + Only required for a `REJECTED` status or `PARTIAL_APPROVAL` status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ return await self._post( - f"/account_holders/{account_holder_token}/resubmit", - body=maybe_transform( + "/v1/simulate/account_holders/enrollment_document_review", + body=await async_maybe_transform( { - "individual": individual, - "tos_timestamp": tos_timestamp, - "workflow": workflow, + "document_upload_token": document_upload_token, + "status": status, + "accepted_entity_status_reasons": accepted_entity_status_reasons, + "status_reason": status_reason, }, - account_holder_resubmit_params.AccountHolderResubmitParams, + account_holder_simulate_enrollment_document_review_params.AccountHolderSimulateEnrollmentDocumentReviewParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AccountHolder, + cast_to=Document, ) - async def retrieve_document( + async def simulate_enrollment_review( self, - document_token: str, *, - account_holder_token: str, + account_holder_token: str | Omit = omit, + status: Literal["ACCEPTED", "REJECTED"] | Omit = omit, + status_reasons: List[ + Literal[ + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_DOB_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_BLOCKLIST_ALERT_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_ID_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_NAME_VERIFICATION_FAILURE", + ] + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccountHolderDocument: - """ - Check the status of an account holder document upload, or retrieve the upload - URLs to process your image uploads. + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AccountHolderSimulateEnrollmentReviewResponse: + """Simulates an enrollment review for an account holder. - Note that this is not equivalent to checking the status of the KYC evaluation - overall (a document may be successfully uploaded but not be sufficient for KYC - to pass). + This endpoint is only + applicable for workflows that may required intervention such as `KYB_BASIC`. - In the event your upload URLs have expired, calling this endpoint will refresh - them. Similarly, in the event a document upload has failed, you can use this - endpoint to get a new upload URL for the failed image upload. + Args: + account_holder_token: The account holder which to perform the simulation upon. - When a new account holder document upload is generated for a failed attempt, the - response will show an additional entry in the `required_document_uploads` array - in a `PENDING` state for the corresponding `image_type`. + status: An account holder's status for use within the simulation. + + status_reasons: Status reason that will be associated with the simulated account holder status. + Only required for a `REJECTED` status. - Args: extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1287,32 +2075,55 @@ async def retrieve_document( timeout: Override the client-level default timeout for this request, in seconds """ - if not account_holder_token: - raise ValueError( - f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" - ) - if not document_token: - raise ValueError(f"Expected a non-empty value for `document_token` but received {document_token!r}") - return await self._get( - f"/account_holders/{account_holder_token}/documents/{document_token}", + return await self._post( + "/v1/simulate/account_holders/enrollment_review", + body=await async_maybe_transform( + { + "account_holder_token": account_holder_token, + "status": status, + "status_reasons": status_reasons, + }, + account_holder_simulate_enrollment_review_params.AccountHolderSimulateEnrollmentReviewParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AccountHolderDocument, + cast_to=AccountHolderSimulateEnrollmentReviewResponse, ) async def upload_document( self, account_holder_token: str, *, - document_type: Literal["commercial_license", "drivers_license", "passport", "passport_card", "visa"], + document_type: Literal[ + "EIN_LETTER", + "TAX_RETURN", + "OPERATING_AGREEMENT", + "CERTIFICATE_OF_FORMATION", + "DRIVERS_LICENSE", + "PASSPORT", + "PASSPORT_CARD", + "CERTIFICATE_OF_GOOD_STANDING", + "ARTICLES_OF_INCORPORATION", + "ARTICLES_OF_ORGANIZATION", + "BYLAWS", + "GOVERNMENT_BUSINESS_LICENSE", + "PARTNERSHIP_AGREEMENT", + "SS4_FORM", + "BANK_STATEMENT", + "UTILITY_BILL_STATEMENT", + "SSN_CARD", + "ITIN_LETTER", + "FINCEN_BOI_REPORT", + ], + entity_token: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AccountHolderDocument: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Document: """ Use this endpoint to identify which type of supported government-issued documentation you will upload for further verification. It will return two URLs @@ -1334,7 +2145,9 @@ async def upload_document( verification. Args: - document_type: Type of the document to upload. + document_type: The type of document to upload + + entity_token: Globally unique identifier for the entity. extra_headers: Send extra headers @@ -1349,15 +2162,18 @@ async def upload_document( f"Expected a non-empty value for `account_holder_token` but received {account_holder_token!r}" ) return await self._post( - f"/account_holders/{account_holder_token}/documents", - body=maybe_transform( - {"document_type": document_type}, + f"/v1/account_holders/{account_holder_token}/documents", + body=await async_maybe_transform( + { + "document_type": document_type, + "entity_token": entity_token, + }, account_holder_upload_document_params.AccountHolderUploadDocumentParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AccountHolderDocument, + cast_to=Document, ) @@ -1380,12 +2196,15 @@ def __init__(self, account_holders: AccountHolders) -> None: self.list_documents = _legacy_response.to_raw_response_wrapper( account_holders.list_documents, ) - self.resubmit = _legacy_response.to_raw_response_wrapper( - account_holders.resubmit, - ) self.retrieve_document = _legacy_response.to_raw_response_wrapper( account_holders.retrieve_document, ) + self.simulate_enrollment_document_review = _legacy_response.to_raw_response_wrapper( + account_holders.simulate_enrollment_document_review, + ) + self.simulate_enrollment_review = _legacy_response.to_raw_response_wrapper( + account_holders.simulate_enrollment_review, + ) self.upload_document = _legacy_response.to_raw_response_wrapper( account_holders.upload_document, ) @@ -1410,12 +2229,15 @@ def __init__(self, account_holders: AsyncAccountHolders) -> None: self.list_documents = _legacy_response.async_to_raw_response_wrapper( account_holders.list_documents, ) - self.resubmit = _legacy_response.async_to_raw_response_wrapper( - account_holders.resubmit, - ) self.retrieve_document = _legacy_response.async_to_raw_response_wrapper( account_holders.retrieve_document, ) + self.simulate_enrollment_document_review = _legacy_response.async_to_raw_response_wrapper( + account_holders.simulate_enrollment_document_review, + ) + self.simulate_enrollment_review = _legacy_response.async_to_raw_response_wrapper( + account_holders.simulate_enrollment_review, + ) self.upload_document = _legacy_response.async_to_raw_response_wrapper( account_holders.upload_document, ) @@ -1440,12 +2262,15 @@ def __init__(self, account_holders: AccountHolders) -> None: self.list_documents = to_streamed_response_wrapper( account_holders.list_documents, ) - self.resubmit = to_streamed_response_wrapper( - account_holders.resubmit, - ) self.retrieve_document = to_streamed_response_wrapper( account_holders.retrieve_document, ) + self.simulate_enrollment_document_review = to_streamed_response_wrapper( + account_holders.simulate_enrollment_document_review, + ) + self.simulate_enrollment_review = to_streamed_response_wrapper( + account_holders.simulate_enrollment_review, + ) self.upload_document = to_streamed_response_wrapper( account_holders.upload_document, ) @@ -1470,12 +2295,15 @@ def __init__(self, account_holders: AsyncAccountHolders) -> None: self.list_documents = async_to_streamed_response_wrapper( account_holders.list_documents, ) - self.resubmit = async_to_streamed_response_wrapper( - account_holders.resubmit, - ) self.retrieve_document = async_to_streamed_response_wrapper( account_holders.retrieve_document, ) + self.simulate_enrollment_document_review = async_to_streamed_response_wrapper( + account_holders.simulate_enrollment_document_review, + ) + self.simulate_enrollment_review = async_to_streamed_response_wrapper( + account_holders.simulate_enrollment_review, + ) self.upload_document = async_to_streamed_response_wrapper( account_holders.upload_document, ) diff --git a/src/lithic/resources/accounts/accounts.py b/src/lithic/resources/accounts.py similarity index 59% rename from src/lithic/resources/accounts/accounts.py rename to src/lithic/resources/accounts.py index 74547a0d..2fb57e59 100644 --- a/src/lithic/resources/accounts/accounts.py +++ b/src/lithic/resources/accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -8,41 +8,39 @@ import httpx -from ... import _legacy_response -from ...types import Account, AccountSpendLimits, account_list_params, account_update_params -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ...pagination import SyncCursorPage, AsyncCursorPage -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) -from .credit_configurations import ( - CreditConfigurations, - AsyncCreditConfigurations, - CreditConfigurationsWithRawResponse, - AsyncCreditConfigurationsWithRawResponse, - CreditConfigurationsWithStreamingResponse, - AsyncCreditConfigurationsWithStreamingResponse, -) +from .. import _legacy_response +from ..types import account_list_params, account_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.account import Account +from ..types.account_spend_limits import AccountSpendLimits __all__ = ["Accounts", "AsyncAccounts"] class Accounts(SyncAPIResource): - @cached_property - def credit_configurations(self) -> CreditConfigurations: - return CreditConfigurations(self._client) - @cached_property def with_raw_response(self) -> AccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AccountsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AccountsWithStreamingResponse(self) def retrieve( @@ -54,7 +52,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Account: """ Get account configuration such as spend limits. @@ -71,7 +69,7 @@ def retrieve( if not account_token: raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return self._get( - f"/accounts/{account_token}", + f"/v1/accounts/{account_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -82,45 +80,88 @@ def update( self, account_token: str, *, - daily_spend_limit: int | NotGiven = NOT_GIVEN, - lifetime_spend_limit: int | NotGiven = NOT_GIVEN, - monthly_spend_limit: int | NotGiven = NOT_GIVEN, - state: Literal["ACTIVE", "PAUSED"] | NotGiven = NOT_GIVEN, - verification_address: account_update_params.VerificationAddress | NotGiven = NOT_GIVEN, + comment: str | Omit = omit, + daily_spend_limit: int | Omit = omit, + lifetime_spend_limit: int | Omit = omit, + monthly_spend_limit: int | Omit = omit, + state: Literal["ACTIVE", "PAUSED", "CLOSED"] | Omit = omit, + substatus: Literal[ + "FRAUD_IDENTIFIED", + "SUSPICIOUS_ACTIVITY", + "RISK_VIOLATION", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "INTERNAL_REVIEW", + "OTHER", + ] + | Omit = omit, + verification_address: account_update_params.VerificationAddress | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Account: - """Update account configuration such as spend limits and verification address. + """Update account configuration such as state or spend limits. - Can - only be run on accounts that are part of the program managed by this API key. - - Accounts that are in the `PAUSED` state will not be able to transact or create - new cards. + Can only be run on + accounts that are part of the program managed by this API key. Accounts that are + in the `PAUSED` state will not be able to transact or create new cards. Args: - daily_spend_limit: Amount (in cents) for the account's daily spend limit. By default the daily - spend limit is set to $1,250. + comment: Additional context or information related to the account. + + daily_spend_limit: Amount (in cents) for the account's daily spend limit (e.g. 100000 would be a + $1,000 limit). By default the daily spend limit is set to $1,250. - lifetime_spend_limit: Amount (in cents) for the account's lifetime spend limit. Once this limit is - reached, no transactions will be accepted on any card created for this account - until the limit is updated. Note that a spend limit of 0 is effectively no - limit, and should only be used to reset or remove a prior limit. Only a limit of - 1 or above will result in declined transactions due to checks against the - account limit. This behavior differs from the daily spend limit and the monthly - spend limit. + lifetime_spend_limit: Amount (in cents) for the account's lifetime spend limit (e.g. 100000 would be a + $1,000 limit). Once this limit is reached, no transactions will be accepted on + any card created for this account until the limit is updated. Note that a spend + limit of 0 is effectively no limit, and should only be used to reset or remove a + prior limit. Only a limit of 1 or above will result in declined transactions due + to checks against the account limit. This behavior differs from the daily spend + limit and the monthly spend limit. - monthly_spend_limit: Amount (in cents) for the account's monthly spend limit. By default the monthly - spend limit is set to $5,000. + monthly_spend_limit: Amount (in cents) for the account's monthly spend limit (e.g. 100000 would be a + $1,000 limit). By default the monthly spend limit is set to $5,000. state: Account states. + substatus: + Account state substatus values: + + - `FRAUD_IDENTIFIED` - The account has been recognized as being created or used + with stolen or fabricated identity information, encompassing both true + identity theft and synthetic identities. + - `SUSPICIOUS_ACTIVITY` - The account has exhibited suspicious behavior, such as + unauthorized access or fraudulent transactions, necessitating further + investigation. + - `RISK_VIOLATION` - The account has been involved in deliberate misuse by the + legitimate account holder. Examples include disputing valid transactions + without cause, falsely claiming non-receipt of goods, or engaging in + intentional bust-out schemes to exploit account services. + - `END_USER_REQUEST` - The account holder has voluntarily requested the closure + of the account for personal reasons. This encompasses situations such as + bankruptcy, other financial considerations, or the account holder's death. + - `ISSUER_REQUEST` - The issuer has initiated the closure of the account due to + business strategy, risk management, inactivity, product changes, regulatory + concerns, or violations of terms and conditions. + - `NOT_ACTIVE` - The account has not had any transactions or payment activity + within a specified period. This status applies to accounts that are paused or + closed due to inactivity. + - `INTERNAL_REVIEW` - The account is temporarily paused pending further internal + review. In future implementations, this status may prevent clients from + activating the account via APIs until the review is completed. + - `OTHER` - The reason for the account's current status does not fall into any + of the above categories. A comment should be provided to specify the + particular reason. + verification_address: Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + transactions if enabled via Auth Rules. This field is deprecated as AVS checks + are no longer supported by Auth Rules. The field will be removed from the schema + in a future release. extra_headers: Send extra headers @@ -133,13 +174,15 @@ def update( if not account_token: raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return self._patch( - f"/accounts/{account_token}", + f"/v1/accounts/{account_token}", body=maybe_transform( { + "comment": comment, "daily_spend_limit": daily_spend_limit, "lifetime_spend_limit": lifetime_spend_limit, "monthly_spend_limit": monthly_spend_limit, "state": state, + "substatus": substatus, "verification_address": verification_address, }, account_update_params.AccountUpdateParams, @@ -153,17 +196,17 @@ def update( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Account]: """List account configurations. @@ -193,7 +236,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/accounts", + "/v1/accounts", page=SyncCursorPage[Account], options=make_request_options( extra_headers=extra_headers, @@ -223,7 +266,7 @@ def retrieve_spend_limits( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountSpendLimits: """ Get an Account's available spend limits, which is based on the spend limit @@ -244,7 +287,7 @@ def retrieve_spend_limits( if not account_token: raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return self._get( - f"/accounts/{account_token}/spend_limits", + f"/v1/accounts/{account_token}/spend_limits", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -253,16 +296,23 @@ def retrieve_spend_limits( class AsyncAccounts(AsyncAPIResource): - @cached_property - def credit_configurations(self) -> AsyncCreditConfigurations: - return AsyncCreditConfigurations(self._client) - @cached_property def with_raw_response(self) -> AsyncAccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncAccountsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncAccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncAccountsWithStreamingResponse(self) async def retrieve( @@ -274,7 +324,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Account: """ Get account configuration such as spend limits. @@ -291,7 +341,7 @@ async def retrieve( if not account_token: raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return await self._get( - f"/accounts/{account_token}", + f"/v1/accounts/{account_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -302,45 +352,88 @@ async def update( self, account_token: str, *, - daily_spend_limit: int | NotGiven = NOT_GIVEN, - lifetime_spend_limit: int | NotGiven = NOT_GIVEN, - monthly_spend_limit: int | NotGiven = NOT_GIVEN, - state: Literal["ACTIVE", "PAUSED"] | NotGiven = NOT_GIVEN, - verification_address: account_update_params.VerificationAddress | NotGiven = NOT_GIVEN, + comment: str | Omit = omit, + daily_spend_limit: int | Omit = omit, + lifetime_spend_limit: int | Omit = omit, + monthly_spend_limit: int | Omit = omit, + state: Literal["ACTIVE", "PAUSED", "CLOSED"] | Omit = omit, + substatus: Literal[ + "FRAUD_IDENTIFIED", + "SUSPICIOUS_ACTIVITY", + "RISK_VIOLATION", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "INTERNAL_REVIEW", + "OTHER", + ] + | Omit = omit, + verification_address: account_update_params.VerificationAddress | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Account: - """Update account configuration such as spend limits and verification address. - - Can - only be run on accounts that are part of the program managed by this API key. + """Update account configuration such as state or spend limits. - Accounts that are in the `PAUSED` state will not be able to transact or create - new cards. + Can only be run on + accounts that are part of the program managed by this API key. Accounts that are + in the `PAUSED` state will not be able to transact or create new cards. Args: - daily_spend_limit: Amount (in cents) for the account's daily spend limit. By default the daily - spend limit is set to $1,250. + comment: Additional context or information related to the account. - lifetime_spend_limit: Amount (in cents) for the account's lifetime spend limit. Once this limit is - reached, no transactions will be accepted on any card created for this account - until the limit is updated. Note that a spend limit of 0 is effectively no - limit, and should only be used to reset or remove a prior limit. Only a limit of - 1 or above will result in declined transactions due to checks against the - account limit. This behavior differs from the daily spend limit and the monthly - spend limit. + daily_spend_limit: Amount (in cents) for the account's daily spend limit (e.g. 100000 would be a + $1,000 limit). By default the daily spend limit is set to $1,250. - monthly_spend_limit: Amount (in cents) for the account's monthly spend limit. By default the monthly - spend limit is set to $5,000. + lifetime_spend_limit: Amount (in cents) for the account's lifetime spend limit (e.g. 100000 would be a + $1,000 limit). Once this limit is reached, no transactions will be accepted on + any card created for this account until the limit is updated. Note that a spend + limit of 0 is effectively no limit, and should only be used to reset or remove a + prior limit. Only a limit of 1 or above will result in declined transactions due + to checks against the account limit. This behavior differs from the daily spend + limit and the monthly spend limit. + + monthly_spend_limit: Amount (in cents) for the account's monthly spend limit (e.g. 100000 would be a + $1,000 limit). By default the monthly spend limit is set to $5,000. state: Account states. + substatus: + Account state substatus values: + + - `FRAUD_IDENTIFIED` - The account has been recognized as being created or used + with stolen or fabricated identity information, encompassing both true + identity theft and synthetic identities. + - `SUSPICIOUS_ACTIVITY` - The account has exhibited suspicious behavior, such as + unauthorized access or fraudulent transactions, necessitating further + investigation. + - `RISK_VIOLATION` - The account has been involved in deliberate misuse by the + legitimate account holder. Examples include disputing valid transactions + without cause, falsely claiming non-receipt of goods, or engaging in + intentional bust-out schemes to exploit account services. + - `END_USER_REQUEST` - The account holder has voluntarily requested the closure + of the account for personal reasons. This encompasses situations such as + bankruptcy, other financial considerations, or the account holder's death. + - `ISSUER_REQUEST` - The issuer has initiated the closure of the account due to + business strategy, risk management, inactivity, product changes, regulatory + concerns, or violations of terms and conditions. + - `NOT_ACTIVE` - The account has not had any transactions or payment activity + within a specified period. This status applies to accounts that are paused or + closed due to inactivity. + - `INTERNAL_REVIEW` - The account is temporarily paused pending further internal + review. In future implementations, this status may prevent clients from + activating the account via APIs until the review is completed. + - `OTHER` - The reason for the account's current status does not fall into any + of the above categories. A comment should be provided to specify the + particular reason. + verification_address: Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + transactions if enabled via Auth Rules. This field is deprecated as AVS checks + are no longer supported by Auth Rules. The field will be removed from the schema + in a future release. extra_headers: Send extra headers @@ -353,13 +446,15 @@ async def update( if not account_token: raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return await self._patch( - f"/accounts/{account_token}", - body=maybe_transform( + f"/v1/accounts/{account_token}", + body=await async_maybe_transform( { + "comment": comment, "daily_spend_limit": daily_spend_limit, "lifetime_spend_limit": lifetime_spend_limit, "monthly_spend_limit": monthly_spend_limit, "state": state, + "substatus": substatus, "verification_address": verification_address, }, account_update_params.AccountUpdateParams, @@ -373,17 +468,17 @@ async def update( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Account, AsyncCursorPage[Account]]: """List account configurations. @@ -413,7 +508,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/accounts", + "/v1/accounts", page=AsyncCursorPage[Account], options=make_request_options( extra_headers=extra_headers, @@ -443,7 +538,7 @@ async def retrieve_spend_limits( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AccountSpendLimits: """ Get an Account's available spend limits, which is based on the spend limit @@ -464,7 +559,7 @@ async def retrieve_spend_limits( if not account_token: raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return await self._get( - f"/accounts/{account_token}/spend_limits", + f"/v1/accounts/{account_token}/spend_limits", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -489,10 +584,6 @@ def __init__(self, accounts: Accounts) -> None: accounts.retrieve_spend_limits, ) - @cached_property - def credit_configurations(self) -> CreditConfigurationsWithRawResponse: - return CreditConfigurationsWithRawResponse(self._accounts.credit_configurations) - class AsyncAccountsWithRawResponse: def __init__(self, accounts: AsyncAccounts) -> None: @@ -511,10 +602,6 @@ def __init__(self, accounts: AsyncAccounts) -> None: accounts.retrieve_spend_limits, ) - @cached_property - def credit_configurations(self) -> AsyncCreditConfigurationsWithRawResponse: - return AsyncCreditConfigurationsWithRawResponse(self._accounts.credit_configurations) - class AccountsWithStreamingResponse: def __init__(self, accounts: Accounts) -> None: @@ -533,10 +620,6 @@ def __init__(self, accounts: Accounts) -> None: accounts.retrieve_spend_limits, ) - @cached_property - def credit_configurations(self) -> CreditConfigurationsWithStreamingResponse: - return CreditConfigurationsWithStreamingResponse(self._accounts.credit_configurations) - class AsyncAccountsWithStreamingResponse: def __init__(self, accounts: AsyncAccounts) -> None: @@ -554,7 +637,3 @@ def __init__(self, accounts: AsyncAccounts) -> None: self.retrieve_spend_limits = async_to_streamed_response_wrapper( accounts.retrieve_spend_limits, ) - - @cached_property - def credit_configurations(self) -> AsyncCreditConfigurationsWithStreamingResponse: - return AsyncCreditConfigurationsWithStreamingResponse(self._accounts.credit_configurations) diff --git a/src/lithic/resources/accounts/__init__.py b/src/lithic/resources/accounts/__init__.py deleted file mode 100644 index ab103384..00000000 --- a/src/lithic/resources/accounts/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from .accounts import ( - Accounts, - AsyncAccounts, - AccountsWithRawResponse, - AsyncAccountsWithRawResponse, - AccountsWithStreamingResponse, - AsyncAccountsWithStreamingResponse, -) -from .credit_configurations import ( - CreditConfigurations, - AsyncCreditConfigurations, - CreditConfigurationsWithRawResponse, - AsyncCreditConfigurationsWithRawResponse, - CreditConfigurationsWithStreamingResponse, - AsyncCreditConfigurationsWithStreamingResponse, -) - -__all__ = [ - "CreditConfigurations", - "AsyncCreditConfigurations", - "CreditConfigurationsWithRawResponse", - "AsyncCreditConfigurationsWithRawResponse", - "CreditConfigurationsWithStreamingResponse", - "AsyncCreditConfigurationsWithStreamingResponse", - "Accounts", - "AsyncAccounts", - "AccountsWithRawResponse", - "AsyncAccountsWithRawResponse", - "AccountsWithStreamingResponse", - "AsyncAccountsWithStreamingResponse", -] diff --git a/src/lithic/resources/accounts/credit_configurations.py b/src/lithic/resources/accounts/credit_configurations.py deleted file mode 100644 index 39e55d2a..00000000 --- a/src/lithic/resources/accounts/credit_configurations.py +++ /dev/null @@ -1,261 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import httpx - -from ... import _legacy_response -from ...types import BusinessAccount -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..._base_client import ( - make_request_options, -) -from ...types.accounts import credit_configuration_update_params - -__all__ = ["CreditConfigurations", "AsyncCreditConfigurations"] - - -class CreditConfigurations(SyncAPIResource): - @cached_property - def with_raw_response(self) -> CreditConfigurationsWithRawResponse: - return CreditConfigurationsWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> CreditConfigurationsWithStreamingResponse: - return CreditConfigurationsWithStreamingResponse(self) - - def retrieve( - self, - account_token: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> BusinessAccount: - """ - Get an Account's credit configuration - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not account_token: - raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") - return self._get( - f"/accounts/{account_token}/credit_configuration", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=BusinessAccount, - ) - - def update( - self, - account_token: str, - *, - billing_period: int | NotGiven = NOT_GIVEN, - credit_limit: int | NotGiven = NOT_GIVEN, - external_bank_account_token: str | NotGiven = NOT_GIVEN, - payment_period: int | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> BusinessAccount: - """ - Update a Business Accounts credit configuration - - Args: - billing_period: Number of days within the billing period - - credit_limit: Credit limit extended to the Business Account - - external_bank_account_token: The external bank account token to use for auto-collections - - payment_period: Number of days after the billing period ends that a payment is required - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not account_token: - raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") - return self._patch( - f"/accounts/{account_token}/credit_configuration", - body=maybe_transform( - { - "billing_period": billing_period, - "credit_limit": credit_limit, - "external_bank_account_token": external_bank_account_token, - "payment_period": payment_period, - }, - credit_configuration_update_params.CreditConfigurationUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=BusinessAccount, - ) - - -class AsyncCreditConfigurations(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncCreditConfigurationsWithRawResponse: - return AsyncCreditConfigurationsWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncCreditConfigurationsWithStreamingResponse: - return AsyncCreditConfigurationsWithStreamingResponse(self) - - async def retrieve( - self, - account_token: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> BusinessAccount: - """ - Get an Account's credit configuration - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not account_token: - raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") - return await self._get( - f"/accounts/{account_token}/credit_configuration", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=BusinessAccount, - ) - - async def update( - self, - account_token: str, - *, - billing_period: int | NotGiven = NOT_GIVEN, - credit_limit: int | NotGiven = NOT_GIVEN, - external_bank_account_token: str | NotGiven = NOT_GIVEN, - payment_period: int | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> BusinessAccount: - """ - Update a Business Accounts credit configuration - - Args: - billing_period: Number of days within the billing period - - credit_limit: Credit limit extended to the Business Account - - external_bank_account_token: The external bank account token to use for auto-collections - - payment_period: Number of days after the billing period ends that a payment is required - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not account_token: - raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") - return await self._patch( - f"/accounts/{account_token}/credit_configuration", - body=maybe_transform( - { - "billing_period": billing_period, - "credit_limit": credit_limit, - "external_bank_account_token": external_bank_account_token, - "payment_period": payment_period, - }, - credit_configuration_update_params.CreditConfigurationUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=BusinessAccount, - ) - - -class CreditConfigurationsWithRawResponse: - def __init__(self, credit_configurations: CreditConfigurations) -> None: - self._credit_configurations = credit_configurations - - self.retrieve = _legacy_response.to_raw_response_wrapper( - credit_configurations.retrieve, - ) - self.update = _legacy_response.to_raw_response_wrapper( - credit_configurations.update, - ) - - -class AsyncCreditConfigurationsWithRawResponse: - def __init__(self, credit_configurations: AsyncCreditConfigurations) -> None: - self._credit_configurations = credit_configurations - - self.retrieve = _legacy_response.async_to_raw_response_wrapper( - credit_configurations.retrieve, - ) - self.update = _legacy_response.async_to_raw_response_wrapper( - credit_configurations.update, - ) - - -class CreditConfigurationsWithStreamingResponse: - def __init__(self, credit_configurations: CreditConfigurations) -> None: - self._credit_configurations = credit_configurations - - self.retrieve = to_streamed_response_wrapper( - credit_configurations.retrieve, - ) - self.update = to_streamed_response_wrapper( - credit_configurations.update, - ) - - -class AsyncCreditConfigurationsWithStreamingResponse: - def __init__(self, credit_configurations: AsyncCreditConfigurations) -> None: - self._credit_configurations = credit_configurations - - self.retrieve = async_to_streamed_response_wrapper( - credit_configurations.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - credit_configurations.update, - ) diff --git a/src/lithic/resources/aggregate_balances.py b/src/lithic/resources/aggregate_balances.py deleted file mode 100644 index 502542ca..00000000 --- a/src/lithic/resources/aggregate_balances.py +++ /dev/null @@ -1,162 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing_extensions import Literal - -import httpx - -from .. import _legacy_response -from ..types import AggregateBalance, aggregate_balance_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..pagination import SyncSinglePage, AsyncSinglePage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) - -__all__ = ["AggregateBalances", "AsyncAggregateBalances"] - - -class AggregateBalances(SyncAPIResource): - @cached_property - def with_raw_response(self) -> AggregateBalancesWithRawResponse: - return AggregateBalancesWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AggregateBalancesWithStreamingResponse: - return AggregateBalancesWithStreamingResponse(self) - - def list( - self, - *, - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncSinglePage[AggregateBalance]: - """ - Get the aggregated balance across all end-user accounts by financial account - type - - Args: - financial_account_type: Get the aggregate balance for a given Financial Account type. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get_api_list( - "/aggregate_balances", - page=SyncSinglePage[AggregateBalance], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"financial_account_type": financial_account_type}, - aggregate_balance_list_params.AggregateBalanceListParams, - ), - ), - model=AggregateBalance, - ) - - -class AsyncAggregateBalances(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncAggregateBalancesWithRawResponse: - return AsyncAggregateBalancesWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncAggregateBalancesWithStreamingResponse: - return AsyncAggregateBalancesWithStreamingResponse(self) - - def list( - self, - *, - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[AggregateBalance, AsyncSinglePage[AggregateBalance]]: - """ - Get the aggregated balance across all end-user accounts by financial account - type - - Args: - financial_account_type: Get the aggregate balance for a given Financial Account type. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get_api_list( - "/aggregate_balances", - page=AsyncSinglePage[AggregateBalance], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - {"financial_account_type": financial_account_type}, - aggregate_balance_list_params.AggregateBalanceListParams, - ), - ), - model=AggregateBalance, - ) - - -class AggregateBalancesWithRawResponse: - def __init__(self, aggregate_balances: AggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = _legacy_response.to_raw_response_wrapper( - aggregate_balances.list, - ) - - -class AsyncAggregateBalancesWithRawResponse: - def __init__(self, aggregate_balances: AsyncAggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = _legacy_response.async_to_raw_response_wrapper( - aggregate_balances.list, - ) - - -class AggregateBalancesWithStreamingResponse: - def __init__(self, aggregate_balances: AggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = to_streamed_response_wrapper( - aggregate_balances.list, - ) - - -class AsyncAggregateBalancesWithStreamingResponse: - def __init__(self, aggregate_balances: AsyncAggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = async_to_streamed_response_wrapper( - aggregate_balances.list, - ) diff --git a/src/lithic/resources/auth_rules.py b/src/lithic/resources/auth_rules.py deleted file mode 100644 index 159f0a03..00000000 --- a/src/lithic/resources/auth_rules.py +++ /dev/null @@ -1,795 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import List - -import httpx - -from .. import _legacy_response -from ..types import ( - AuthRule, - AuthRuleRemoveResponse, - AuthRuleRetrieveResponse, - auth_rule_list_params, - auth_rule_apply_params, - auth_rule_create_params, - auth_rule_remove_params, - auth_rule_update_params, -) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) - -__all__ = ["AuthRules", "AsyncAuthRules"] - - -class AuthRules(SyncAPIResource): - @cached_property - def with_raw_response(self) -> AuthRulesWithRawResponse: - return AuthRulesWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AuthRulesWithStreamingResponse: - return AuthRulesWithStreamingResponse(self) - - def create( - self, - *, - account_tokens: List[str] | NotGiven = NOT_GIVEN, - allowed_countries: List[str] | NotGiven = NOT_GIVEN, - allowed_mcc: List[str] | NotGiven = NOT_GIVEN, - blocked_countries: List[str] | NotGiven = NOT_GIVEN, - blocked_mcc: List[str] | NotGiven = NOT_GIVEN, - card_tokens: List[str] | NotGiven = NOT_GIVEN, - program_level: bool | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRule: - """ - Creates an authorization rule (Auth Rule) and applies it at the program, - account, or card level. - - Args: - account_tokens: Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - - allowed_countries: Countries in which the Auth Rule permits transactions. Note that Lithic - maintains a list of countries in which all transactions are blocked; "allowing" - those countries in an Auth Rule does not override the Lithic-wide restrictions. - - allowed_mcc: Merchant category codes for which the Auth Rule permits transactions. - - blocked_countries: Countries in which the Auth Rule automatically declines transactions. - - blocked_mcc: Merchant category codes for which the Auth Rule automatically declines - transactions. - - card_tokens: Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - - program_level: Boolean indicating whether the Auth Rule is applied at the program level. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/auth_rules", - body=maybe_transform( - { - "account_tokens": account_tokens, - "allowed_countries": allowed_countries, - "allowed_mcc": allowed_mcc, - "blocked_countries": blocked_countries, - "blocked_mcc": blocked_mcc, - "card_tokens": card_tokens, - "program_level": program_level, - }, - auth_rule_create_params.AuthRuleCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRule, - ) - - def retrieve( - self, - auth_rule_token: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRuleRetrieveResponse: - """ - Detail the properties and entities (program, accounts, and cards) associated - with an existing authorization rule (Auth Rule). - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not auth_rule_token: - raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") - return self._get( - f"/auth_rules/{auth_rule_token}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRuleRetrieveResponse, - ) - - def update( - self, - auth_rule_token: str, - *, - allowed_countries: List[str] | NotGiven = NOT_GIVEN, - allowed_mcc: List[str] | NotGiven = NOT_GIVEN, - blocked_countries: List[str] | NotGiven = NOT_GIVEN, - blocked_mcc: List[str] | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRule: - """ - Update the properties associated with an existing authorization rule (Auth - Rule). - - Args: - allowed_countries: Array of country codes for which the Auth Rule will permit transactions. Note - that only this field or `blocked_countries` can be used for a given Auth Rule. - - allowed_mcc: Array of merchant category codes for which the Auth Rule will permit - transactions. Note that only this field or `blocked_mcc` can be used for a given - Auth Rule. - - blocked_countries: Array of country codes for which the Auth Rule will automatically decline - transactions. Note that only this field or `allowed_countries` can be used for a - given Auth Rule. - - blocked_mcc: Array of merchant category codes for which the Auth Rule will automatically - decline transactions. Note that only this field or `allowed_mcc` can be used for - a given Auth Rule. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not auth_rule_token: - raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") - return self._put( - f"/auth_rules/{auth_rule_token}", - body=maybe_transform( - { - "allowed_countries": allowed_countries, - "allowed_mcc": allowed_mcc, - "blocked_countries": blocked_countries, - "blocked_mcc": blocked_mcc, - }, - auth_rule_update_params.AuthRuleUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRule, - ) - - def list( - self, - *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncCursorPage[AuthRule]: - """ - Return all of the Auth Rules under the program. - - Args: - ending_before: A cursor representing an item's token before which a page of results should end. - Used to retrieve the previous page of results before this item. - - page_size: Page size (for pagination). - - starting_after: A cursor representing an item's token after which a page of results should - begin. Used to retrieve the next page of results after this item. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get_api_list( - "/auth_rules", - page=SyncCursorPage[AuthRule], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "ending_before": ending_before, - "page_size": page_size, - "starting_after": starting_after, - }, - auth_rule_list_params.AuthRuleListParams, - ), - ), - model=AuthRule, - ) - - def apply( - self, - auth_rule_token: str, - *, - account_tokens: List[str] | NotGiven = NOT_GIVEN, - card_tokens: List[str] | NotGiven = NOT_GIVEN, - program_level: bool | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRule: - """ - Applies an existing authorization rule (Auth Rule) to an program, account, or - card level. - - Args: - account_tokens: Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - - card_tokens: Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - - program_level: Boolean indicating whether the Auth Rule is applied at the program level. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not auth_rule_token: - raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") - return self._post( - f"/auth_rules/{auth_rule_token}/apply", - body=maybe_transform( - { - "account_tokens": account_tokens, - "card_tokens": card_tokens, - "program_level": program_level, - }, - auth_rule_apply_params.AuthRuleApplyParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRule, - ) - - def remove( - self, - *, - account_tokens: List[str] | NotGiven = NOT_GIVEN, - card_tokens: List[str] | NotGiven = NOT_GIVEN, - program_level: bool | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRuleRemoveResponse: - """ - Remove an existing authorization rule (Auth Rule) from an program, account, or - card-level. - - Args: - account_tokens: Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - - card_tokens: Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - - program_level: Boolean indicating whether the Auth Rule is applied at the program level. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._delete( - "/auth_rules/remove", - body=maybe_transform( - { - "account_tokens": account_tokens, - "card_tokens": card_tokens, - "program_level": program_level, - }, - auth_rule_remove_params.AuthRuleRemoveParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRuleRemoveResponse, - ) - - -class AsyncAuthRules(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncAuthRulesWithRawResponse: - return AsyncAuthRulesWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncAuthRulesWithStreamingResponse: - return AsyncAuthRulesWithStreamingResponse(self) - - async def create( - self, - *, - account_tokens: List[str] | NotGiven = NOT_GIVEN, - allowed_countries: List[str] | NotGiven = NOT_GIVEN, - allowed_mcc: List[str] | NotGiven = NOT_GIVEN, - blocked_countries: List[str] | NotGiven = NOT_GIVEN, - blocked_mcc: List[str] | NotGiven = NOT_GIVEN, - card_tokens: List[str] | NotGiven = NOT_GIVEN, - program_level: bool | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRule: - """ - Creates an authorization rule (Auth Rule) and applies it at the program, - account, or card level. - - Args: - account_tokens: Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - - allowed_countries: Countries in which the Auth Rule permits transactions. Note that Lithic - maintains a list of countries in which all transactions are blocked; "allowing" - those countries in an Auth Rule does not override the Lithic-wide restrictions. - - allowed_mcc: Merchant category codes for which the Auth Rule permits transactions. - - blocked_countries: Countries in which the Auth Rule automatically declines transactions. - - blocked_mcc: Merchant category codes for which the Auth Rule automatically declines - transactions. - - card_tokens: Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - - program_level: Boolean indicating whether the Auth Rule is applied at the program level. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/auth_rules", - body=maybe_transform( - { - "account_tokens": account_tokens, - "allowed_countries": allowed_countries, - "allowed_mcc": allowed_mcc, - "blocked_countries": blocked_countries, - "blocked_mcc": blocked_mcc, - "card_tokens": card_tokens, - "program_level": program_level, - }, - auth_rule_create_params.AuthRuleCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRule, - ) - - async def retrieve( - self, - auth_rule_token: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRuleRetrieveResponse: - """ - Detail the properties and entities (program, accounts, and cards) associated - with an existing authorization rule (Auth Rule). - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not auth_rule_token: - raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") - return await self._get( - f"/auth_rules/{auth_rule_token}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRuleRetrieveResponse, - ) - - async def update( - self, - auth_rule_token: str, - *, - allowed_countries: List[str] | NotGiven = NOT_GIVEN, - allowed_mcc: List[str] | NotGiven = NOT_GIVEN, - blocked_countries: List[str] | NotGiven = NOT_GIVEN, - blocked_mcc: List[str] | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRule: - """ - Update the properties associated with an existing authorization rule (Auth - Rule). - - Args: - allowed_countries: Array of country codes for which the Auth Rule will permit transactions. Note - that only this field or `blocked_countries` can be used for a given Auth Rule. - - allowed_mcc: Array of merchant category codes for which the Auth Rule will permit - transactions. Note that only this field or `blocked_mcc` can be used for a given - Auth Rule. - - blocked_countries: Array of country codes for which the Auth Rule will automatically decline - transactions. Note that only this field or `allowed_countries` can be used for a - given Auth Rule. - - blocked_mcc: Array of merchant category codes for which the Auth Rule will automatically - decline transactions. Note that only this field or `allowed_mcc` can be used for - a given Auth Rule. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not auth_rule_token: - raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") - return await self._put( - f"/auth_rules/{auth_rule_token}", - body=maybe_transform( - { - "allowed_countries": allowed_countries, - "allowed_mcc": allowed_mcc, - "blocked_countries": blocked_countries, - "blocked_mcc": blocked_mcc, - }, - auth_rule_update_params.AuthRuleUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRule, - ) - - def list( - self, - *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[AuthRule, AsyncCursorPage[AuthRule]]: - """ - Return all of the Auth Rules under the program. - - Args: - ending_before: A cursor representing an item's token before which a page of results should end. - Used to retrieve the previous page of results before this item. - - page_size: Page size (for pagination). - - starting_after: A cursor representing an item's token after which a page of results should - begin. Used to retrieve the next page of results after this item. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get_api_list( - "/auth_rules", - page=AsyncCursorPage[AuthRule], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "ending_before": ending_before, - "page_size": page_size, - "starting_after": starting_after, - }, - auth_rule_list_params.AuthRuleListParams, - ), - ), - model=AuthRule, - ) - - async def apply( - self, - auth_rule_token: str, - *, - account_tokens: List[str] | NotGiven = NOT_GIVEN, - card_tokens: List[str] | NotGiven = NOT_GIVEN, - program_level: bool | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRule: - """ - Applies an existing authorization rule (Auth Rule) to an program, account, or - card level. - - Args: - account_tokens: Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - - card_tokens: Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - - program_level: Boolean indicating whether the Auth Rule is applied at the program level. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not auth_rule_token: - raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") - return await self._post( - f"/auth_rules/{auth_rule_token}/apply", - body=maybe_transform( - { - "account_tokens": account_tokens, - "card_tokens": card_tokens, - "program_level": program_level, - }, - auth_rule_apply_params.AuthRuleApplyParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRule, - ) - - async def remove( - self, - *, - account_tokens: List[str] | NotGiven = NOT_GIVEN, - card_tokens: List[str] | NotGiven = NOT_GIVEN, - program_level: bool | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthRuleRemoveResponse: - """ - Remove an existing authorization rule (Auth Rule) from an program, account, or - card-level. - - Args: - account_tokens: Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - - card_tokens: Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - - program_level: Boolean indicating whether the Auth Rule is applied at the program level. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._delete( - "/auth_rules/remove", - body=maybe_transform( - { - "account_tokens": account_tokens, - "card_tokens": card_tokens, - "program_level": program_level, - }, - auth_rule_remove_params.AuthRuleRemoveParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AuthRuleRemoveResponse, - ) - - -class AuthRulesWithRawResponse: - def __init__(self, auth_rules: AuthRules) -> None: - self._auth_rules = auth_rules - - self.create = _legacy_response.to_raw_response_wrapper( - auth_rules.create, - ) - self.retrieve = _legacy_response.to_raw_response_wrapper( - auth_rules.retrieve, - ) - self.update = _legacy_response.to_raw_response_wrapper( - auth_rules.update, - ) - self.list = _legacy_response.to_raw_response_wrapper( - auth_rules.list, - ) - self.apply = _legacy_response.to_raw_response_wrapper( - auth_rules.apply, - ) - self.remove = _legacy_response.to_raw_response_wrapper( - auth_rules.remove, - ) - - -class AsyncAuthRulesWithRawResponse: - def __init__(self, auth_rules: AsyncAuthRules) -> None: - self._auth_rules = auth_rules - - self.create = _legacy_response.async_to_raw_response_wrapper( - auth_rules.create, - ) - self.retrieve = _legacy_response.async_to_raw_response_wrapper( - auth_rules.retrieve, - ) - self.update = _legacy_response.async_to_raw_response_wrapper( - auth_rules.update, - ) - self.list = _legacy_response.async_to_raw_response_wrapper( - auth_rules.list, - ) - self.apply = _legacy_response.async_to_raw_response_wrapper( - auth_rules.apply, - ) - self.remove = _legacy_response.async_to_raw_response_wrapper( - auth_rules.remove, - ) - - -class AuthRulesWithStreamingResponse: - def __init__(self, auth_rules: AuthRules) -> None: - self._auth_rules = auth_rules - - self.create = to_streamed_response_wrapper( - auth_rules.create, - ) - self.retrieve = to_streamed_response_wrapper( - auth_rules.retrieve, - ) - self.update = to_streamed_response_wrapper( - auth_rules.update, - ) - self.list = to_streamed_response_wrapper( - auth_rules.list, - ) - self.apply = to_streamed_response_wrapper( - auth_rules.apply, - ) - self.remove = to_streamed_response_wrapper( - auth_rules.remove, - ) - - -class AsyncAuthRulesWithStreamingResponse: - def __init__(self, auth_rules: AsyncAuthRules) -> None: - self._auth_rules = auth_rules - - self.create = async_to_streamed_response_wrapper( - auth_rules.create, - ) - self.retrieve = async_to_streamed_response_wrapper( - auth_rules.retrieve, - ) - self.update = async_to_streamed_response_wrapper( - auth_rules.update, - ) - self.list = async_to_streamed_response_wrapper( - auth_rules.list, - ) - self.apply = async_to_streamed_response_wrapper( - auth_rules.apply, - ) - self.remove = async_to_streamed_response_wrapper( - auth_rules.remove, - ) diff --git a/src/lithic/resources/auth_rules/__init__.py b/src/lithic/resources/auth_rules/__init__.py new file mode 100644 index 00000000..21d5015f --- /dev/null +++ b/src/lithic/resources/auth_rules/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .v2 import ( + V2, + AsyncV2, + V2WithRawResponse, + AsyncV2WithRawResponse, + V2WithStreamingResponse, + AsyncV2WithStreamingResponse, +) +from .auth_rules import ( + AuthRules, + AsyncAuthRules, + AuthRulesWithRawResponse, + AsyncAuthRulesWithRawResponse, + AuthRulesWithStreamingResponse, + AsyncAuthRulesWithStreamingResponse, +) + +__all__ = [ + "V2", + "AsyncV2", + "V2WithRawResponse", + "AsyncV2WithRawResponse", + "V2WithStreamingResponse", + "AsyncV2WithStreamingResponse", + "AuthRules", + "AsyncAuthRules", + "AuthRulesWithRawResponse", + "AsyncAuthRulesWithRawResponse", + "AuthRulesWithStreamingResponse", + "AsyncAuthRulesWithStreamingResponse", +] diff --git a/src/lithic/resources/auth_rules/auth_rules.py b/src/lithic/resources/auth_rules/auth_rules.py new file mode 100644 index 00000000..e7293a53 --- /dev/null +++ b/src/lithic/resources/auth_rules/auth_rules.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .v2.v2 import ( + V2, + AsyncV2, + V2WithRawResponse, + AsyncV2WithRawResponse, + V2WithStreamingResponse, + AsyncV2WithStreamingResponse, +) +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["AuthRules", "AsyncAuthRules"] + + +class AuthRules(SyncAPIResource): + @cached_property + def v2(self) -> V2: + return V2(self._client) + + @cached_property + def with_raw_response(self) -> AuthRulesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AuthRulesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AuthRulesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AuthRulesWithStreamingResponse(self) + + +class AsyncAuthRules(AsyncAPIResource): + @cached_property + def v2(self) -> AsyncV2: + return AsyncV2(self._client) + + @cached_property + def with_raw_response(self) -> AsyncAuthRulesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncAuthRulesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAuthRulesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncAuthRulesWithStreamingResponse(self) + + +class AuthRulesWithRawResponse: + def __init__(self, auth_rules: AuthRules) -> None: + self._auth_rules = auth_rules + + @cached_property + def v2(self) -> V2WithRawResponse: + return V2WithRawResponse(self._auth_rules.v2) + + +class AsyncAuthRulesWithRawResponse: + def __init__(self, auth_rules: AsyncAuthRules) -> None: + self._auth_rules = auth_rules + + @cached_property + def v2(self) -> AsyncV2WithRawResponse: + return AsyncV2WithRawResponse(self._auth_rules.v2) + + +class AuthRulesWithStreamingResponse: + def __init__(self, auth_rules: AuthRules) -> None: + self._auth_rules = auth_rules + + @cached_property + def v2(self) -> V2WithStreamingResponse: + return V2WithStreamingResponse(self._auth_rules.v2) + + +class AsyncAuthRulesWithStreamingResponse: + def __init__(self, auth_rules: AsyncAuthRules) -> None: + self._auth_rules = auth_rules + + @cached_property + def v2(self) -> AsyncV2WithStreamingResponse: + return AsyncV2WithStreamingResponse(self._auth_rules.v2) diff --git a/src/lithic/resources/auth_rules/v2/__init__.py b/src/lithic/resources/auth_rules/v2/__init__.py new file mode 100644 index 00000000..aa9d53c0 --- /dev/null +++ b/src/lithic/resources/auth_rules/v2/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .v2 import ( + V2, + AsyncV2, + V2WithRawResponse, + AsyncV2WithRawResponse, + V2WithStreamingResponse, + AsyncV2WithStreamingResponse, +) +from .backtests import ( + Backtests, + AsyncBacktests, + BacktestsWithRawResponse, + AsyncBacktestsWithRawResponse, + BacktestsWithStreamingResponse, + AsyncBacktestsWithStreamingResponse, +) + +__all__ = [ + "Backtests", + "AsyncBacktests", + "BacktestsWithRawResponse", + "AsyncBacktestsWithRawResponse", + "BacktestsWithStreamingResponse", + "AsyncBacktestsWithStreamingResponse", + "V2", + "AsyncV2", + "V2WithRawResponse", + "AsyncV2WithRawResponse", + "V2WithStreamingResponse", + "AsyncV2WithStreamingResponse", +] diff --git a/src/lithic/resources/auth_rules/v2/backtests.py b/src/lithic/resources/auth_rules/v2/backtests.py new file mode 100644 index 00000000..5cade849 --- /dev/null +++ b/src/lithic/resources/auth_rules/v2/backtests.py @@ -0,0 +1,361 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.auth_rules.v2 import backtest_create_params +from ....types.auth_rules.v2.backtest_results import BacktestResults +from ....types.auth_rules.v2.backtest_create_response import BacktestCreateResponse + +__all__ = ["Backtests", "AsyncBacktests"] + + +class Backtests(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BacktestsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return BacktestsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BacktestsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return BacktestsWithStreamingResponse(self) + + def create( + self, + auth_rule_token: str, + *, + end: Union[str, datetime] | Omit = omit, + start: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BacktestCreateResponse: + """ + Initiates a request to asynchronously generate a backtest for an Auth rule. + During backtesting, both the active version (if one exists) and the draft + version of the Auth Rule are evaluated by replaying historical transaction data + against the rule's conditions. This process allows customers to simulate and + understand the effects of proposed rule changes before deployment. The generated + backtest report provides detailed results showing whether the draft version of + the Auth Rule would have approved or declined historical transactions which were + processed during the backtest period. These reports help evaluate how changes to + rule configurations might affect overall transaction approval rates. + + The generated backtest report will be delivered asynchronously through a webhook + with `event_type` = `auth_rules.backtest_report.created`. See the docs on + setting up [webhook subscriptions](https://docs.lithic.com/docs/events-api). It + is also possible to request backtest reports on-demand through the + `/v2/auth_rules/{auth_rule_token}/backtests/{auth_rule_backtest_token}` + endpoint. + + Lithic currently supports backtesting for `CONDITIONAL_BLOCK` / + `CONDITIONAL_ACTION` rules. Backtesting for `VELOCITY_LIMIT` rules is generally + not supported. In specific cases (i.e. where Lithic has pre-calculated the + requested velocity metrics for historical transactions), a backtest may be + feasible. However, such cases are uncommon and customers should not anticipate + support for velocity backtests under most configurations. If a historical + transaction does not feature the required inputs to evaluate the rule, then it + will not be included in the final backtest report. + + Args: + end: The end time of the backtest. + + start: The start time of the backtest. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._post( + f"/v2/auth_rules/{auth_rule_token}/backtests", + body=maybe_transform( + { + "end": end, + "start": start, + }, + backtest_create_params.BacktestCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BacktestCreateResponse, + ) + + def retrieve( + self, + auth_rule_backtest_token: str, + *, + auth_rule_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BacktestResults: + """ + Returns the backtest results of an Auth rule (if available). + + Backtesting is an asynchronous process that requires time to complete. If a + customer retrieves the backtest results using this endpoint before the report is + fully generated, the response will return null for `results.current_version` and + `results.draft_version`. Customers are advised to wait for the backtest creation + process to complete (as indicated by the webhook event + auth_rules.backtest_report.created) before retrieving results from this + endpoint. + + Backtesting is an asynchronous process, while the backtest is being processed, + results will not be available which will cause `results.current_version` and + `results.draft_version` objects to contain `null`. The entries in `results` will + also always represent the configuration of the rule at the time requests are + made to this endpoint. For example, the results for `current_version` in the + served backtest report will be consistent with which version of the rule is + currently activated in the respective event stream, regardless of which version + of the rule was active in the event stream at the time a backtest is requested. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + if not auth_rule_backtest_token: + raise ValueError( + f"Expected a non-empty value for `auth_rule_backtest_token` but received {auth_rule_backtest_token!r}" + ) + return self._get( + f"/v2/auth_rules/{auth_rule_token}/backtests/{auth_rule_backtest_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BacktestResults, + ) + + +class AsyncBacktests(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBacktestsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncBacktestsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBacktestsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncBacktestsWithStreamingResponse(self) + + async def create( + self, + auth_rule_token: str, + *, + end: Union[str, datetime] | Omit = omit, + start: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BacktestCreateResponse: + """ + Initiates a request to asynchronously generate a backtest for an Auth rule. + During backtesting, both the active version (if one exists) and the draft + version of the Auth Rule are evaluated by replaying historical transaction data + against the rule's conditions. This process allows customers to simulate and + understand the effects of proposed rule changes before deployment. The generated + backtest report provides detailed results showing whether the draft version of + the Auth Rule would have approved or declined historical transactions which were + processed during the backtest period. These reports help evaluate how changes to + rule configurations might affect overall transaction approval rates. + + The generated backtest report will be delivered asynchronously through a webhook + with `event_type` = `auth_rules.backtest_report.created`. See the docs on + setting up [webhook subscriptions](https://docs.lithic.com/docs/events-api). It + is also possible to request backtest reports on-demand through the + `/v2/auth_rules/{auth_rule_token}/backtests/{auth_rule_backtest_token}` + endpoint. + + Lithic currently supports backtesting for `CONDITIONAL_BLOCK` / + `CONDITIONAL_ACTION` rules. Backtesting for `VELOCITY_LIMIT` rules is generally + not supported. In specific cases (i.e. where Lithic has pre-calculated the + requested velocity metrics for historical transactions), a backtest may be + feasible. However, such cases are uncommon and customers should not anticipate + support for velocity backtests under most configurations. If a historical + transaction does not feature the required inputs to evaluate the rule, then it + will not be included in the final backtest report. + + Args: + end: The end time of the backtest. + + start: The start time of the backtest. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._post( + f"/v2/auth_rules/{auth_rule_token}/backtests", + body=await async_maybe_transform( + { + "end": end, + "start": start, + }, + backtest_create_params.BacktestCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BacktestCreateResponse, + ) + + async def retrieve( + self, + auth_rule_backtest_token: str, + *, + auth_rule_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BacktestResults: + """ + Returns the backtest results of an Auth rule (if available). + + Backtesting is an asynchronous process that requires time to complete. If a + customer retrieves the backtest results using this endpoint before the report is + fully generated, the response will return null for `results.current_version` and + `results.draft_version`. Customers are advised to wait for the backtest creation + process to complete (as indicated by the webhook event + auth_rules.backtest_report.created) before retrieving results from this + endpoint. + + Backtesting is an asynchronous process, while the backtest is being processed, + results will not be available which will cause `results.current_version` and + `results.draft_version` objects to contain `null`. The entries in `results` will + also always represent the configuration of the rule at the time requests are + made to this endpoint. For example, the results for `current_version` in the + served backtest report will be consistent with which version of the rule is + currently activated in the respective event stream, regardless of which version + of the rule was active in the event stream at the time a backtest is requested. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + if not auth_rule_backtest_token: + raise ValueError( + f"Expected a non-empty value for `auth_rule_backtest_token` but received {auth_rule_backtest_token!r}" + ) + return await self._get( + f"/v2/auth_rules/{auth_rule_token}/backtests/{auth_rule_backtest_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BacktestResults, + ) + + +class BacktestsWithRawResponse: + def __init__(self, backtests: Backtests) -> None: + self._backtests = backtests + + self.create = _legacy_response.to_raw_response_wrapper( + backtests.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + backtests.retrieve, + ) + + +class AsyncBacktestsWithRawResponse: + def __init__(self, backtests: AsyncBacktests) -> None: + self._backtests = backtests + + self.create = _legacy_response.async_to_raw_response_wrapper( + backtests.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + backtests.retrieve, + ) + + +class BacktestsWithStreamingResponse: + def __init__(self, backtests: Backtests) -> None: + self._backtests = backtests + + self.create = to_streamed_response_wrapper( + backtests.create, + ) + self.retrieve = to_streamed_response_wrapper( + backtests.retrieve, + ) + + +class AsyncBacktestsWithStreamingResponse: + def __init__(self, backtests: AsyncBacktests) -> None: + self._backtests = backtests + + self.create = async_to_streamed_response_wrapper( + backtests.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + backtests.retrieve, + ) diff --git a/src/lithic/resources/auth_rules/v2/v2.py b/src/lithic/resources/auth_rules/v2/v2.py new file mode 100644 index 00000000..6e078eb2 --- /dev/null +++ b/src/lithic/resources/auth_rules/v2/v2.py @@ -0,0 +1,1655 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import date +from typing_extensions import Literal, overload + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import required_args, maybe_transform, async_maybe_transform +from .backtests import ( + Backtests, + AsyncBacktests, + BacktestsWithRawResponse, + AsyncBacktestsWithRawResponse, + BacktestsWithStreamingResponse, + AsyncBacktestsWithStreamingResponse, +) +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.auth_rules import ( + EventStream, + v2_list_params, + v2_draft_params, + v2_create_params, + v2_update_params, + v2_retrieve_report_params, + v2_retrieve_features_params, +) +from ....types.auth_rules.auth_rule import AuthRule +from ....types.auth_rules.event_stream import EventStream +from ....types.auth_rules.v2_retrieve_report_response import V2RetrieveReportResponse +from ....types.auth_rules.v2_retrieve_features_response import V2RetrieveFeaturesResponse + +__all__ = ["V2", "AsyncV2"] + + +class V2(SyncAPIResource): + @cached_property + def backtests(self) -> Backtests: + return Backtests(self._client) + + @cached_property + def with_raw_response(self) -> V2WithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return V2WithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> V2WithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return V2WithStreamingResponse(self) + + @overload + def create( + self, + *, + parameters: v2_create_params.AccountLevelRuleParameters, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + event_stream: EventStream | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new V2 Auth rule in draft mode + + Args: + parameters: Parameters for the Auth Rule + + type: The type of Auth Rule. For certain rule types, this determines the event stream + during which it will be evaluated. For rules that can be applied to one of + several event streams, the effective one is defined by the separate + `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + + account_tokens: Account tokens to which the Auth Rule applies. + + business_account_tokens: Business Account tokens to which the Auth Rule applies. + + event_stream: The event stream during which the rule will be evaluated. + + name: Auth Rule Name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + card_tokens: SequenceNotStr[str], + parameters: v2_create_params.CardLevelRuleParameters, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + event_stream: EventStream | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new V2 Auth rule in draft mode + + Args: + card_tokens: Card tokens to which the Auth Rule applies. + + parameters: Parameters for the Auth Rule + + type: The type of Auth Rule. For certain rule types, this determines the event stream + during which it will be evaluated. For rules that can be applied to one of + several event streams, the effective one is defined by the separate + `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + + event_stream: The event stream during which the rule will be evaluated. + + name: Auth Rule Name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + parameters: v2_create_params.ProgramLevelRuleParameters, + program_level: bool, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + event_stream: EventStream | Omit = omit, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new V2 Auth rule in draft mode + + Args: + parameters: Parameters for the Auth Rule + + program_level: Whether the Auth Rule applies to all authorizations on the card program. + + type: The type of Auth Rule. For certain rule types, this determines the event stream + during which it will be evaluated. For rules that can be applied to one of + several event streams, the effective one is defined by the separate + `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + + event_stream: The event stream during which the rule will be evaluated. + + excluded_card_tokens: Card tokens to which the Auth Rule does not apply. + + name: Auth Rule Name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args( + ["parameters", "type"], ["card_tokens", "parameters", "type"], ["parameters", "program_level", "type"] + ) + def create( + self, + *, + parameters: v2_create_params.AccountLevelRuleParameters + | v2_create_params.CardLevelRuleParameters + | v2_create_params.ProgramLevelRuleParameters, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + event_stream: EventStream | Omit = omit, + name: Optional[str] | Omit = omit, + card_tokens: SequenceNotStr[str] | Omit = omit, + program_level: bool | Omit = omit, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + return self._post( + "/v2/auth_rules", + body=maybe_transform( + { + "parameters": parameters, + "type": type, + "account_tokens": account_tokens, + "business_account_tokens": business_account_tokens, + "event_stream": event_stream, + "name": name, + "card_tokens": card_tokens, + "program_level": program_level, + "excluded_card_tokens": excluded_card_tokens, + }, + v2_create_params.V2CreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + def retrieve( + self, + auth_rule_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Fetches a V2 Auth rule by its token + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._get( + f"/v2/auth_rules/{auth_rule_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + @overload + def update( + self, + auth_rule_token: str, + *, + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Updates a V2 Auth rule's properties + + If `account_tokens`, `card_tokens`, `program_level`, or `excluded_card_tokens` + is provided, this will replace existing associations with the provided list of + entities. + + Args: + account_tokens: Account tokens to which the Auth Rule applies. + + business_account_tokens: Business Account tokens to which the Auth Rule applies. + + name: Auth Rule Name + + state: The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def update( + self, + auth_rule_token: str, + *, + card_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Updates a V2 Auth rule's properties + + If `account_tokens`, `card_tokens`, `program_level`, or `excluded_card_tokens` + is provided, this will replace existing associations with the provided list of + entities. + + Args: + card_tokens: Card tokens to which the Auth Rule applies. + + name: Auth Rule Name + + state: The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def update( + self, + auth_rule_token: str, + *, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + program_level: bool | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Updates a V2 Auth rule's properties + + If `account_tokens`, `card_tokens`, `program_level`, or `excluded_card_tokens` + is provided, this will replace existing associations with the provided list of + entities. + + Args: + excluded_card_tokens: Card tokens to which the Auth Rule does not apply. + + name: Auth Rule Name + + program_level: Whether the Auth Rule applies to all authorizations on the card program. + + state: The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + def update( + self, + auth_rule_token: str, + *, + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + card_tokens: SequenceNotStr[str] | Omit = omit, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + program_level: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._patch( + f"/v2/auth_rules/{auth_rule_token}", + body=maybe_transform( + { + "account_tokens": account_tokens, + "business_account_tokens": business_account_tokens, + "name": name, + "state": state, + "card_tokens": card_tokens, + "excluded_card_tokens": excluded_card_tokens, + "program_level": program_level, + }, + v2_update_params.V2UpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + def list( + self, + *, + account_token: str | Omit = omit, + business_account_token: str | Omit = omit, + card_token: str | Omit = omit, + ending_before: str | Omit = omit, + event_stream: EventStream | Omit = omit, + event_streams: List[EventStream] | Omit = omit, + page_size: int | Omit = omit, + scope: Literal["PROGRAM", "ACCOUNT", "BUSINESS_ACCOUNT", "CARD", "ANY"] | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[AuthRule]: + """ + Lists V2 Auth rules + + Args: + account_token: Only return Auth Rules that are bound to the provided account token. + + business_account_token: Only return Auth Rules that are bound to the provided business account token. + + card_token: Only return Auth Rules that are bound to the provided card token. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + event_stream: Deprecated: Use event_streams instead. Only return Auth rules that are executed + during the provided event stream. + + event_streams: Only return Auth rules that are executed during any of the provided event + streams. If event_streams and event_stream are specified, the values will be + combined. + + page_size: Page size (for pagination). + + scope: Only return Auth Rules that are bound to the provided scope. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v2/auth_rules", + page=SyncCursorPage[AuthRule], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "business_account_token": business_account_token, + "card_token": card_token, + "ending_before": ending_before, + "event_stream": event_stream, + "event_streams": event_streams, + "page_size": page_size, + "scope": scope, + "starting_after": starting_after, + }, + v2_list_params.V2ListParams, + ), + ), + model=AuthRule, + ) + + def delete( + self, + auth_rule_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Deletes a V2 Auth rule + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._delete( + f"/v2/auth_rules/{auth_rule_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def draft( + self, + auth_rule_token: str, + *, + parameters: Optional[v2_draft_params.Parameters] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new draft version of a rule that will be ran in shadow mode. + + This can also be utilized to reset the draft parameters, causing a draft version + to no longer be ran in shadow mode. + + Args: + parameters: Parameters for the Auth Rule + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._post( + f"/v2/auth_rules/{auth_rule_token}/draft", + body=maybe_transform({"parameters": parameters}, v2_draft_params.V2DraftParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + def promote( + self, + auth_rule_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Promotes the draft version of an Auth rule to the currently active version such + that it is enforced in the respective stream. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._post( + f"/v2/auth_rules/{auth_rule_token}/promote", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + def retrieve_features( + self, + auth_rule_token: str, + *, + account_token: str | Omit = omit, + card_token: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> V2RetrieveFeaturesResponse: + """ + Fetches the current calculated Feature values for the given Auth Rule + + This only calculates the features for the active version. + + - VelocityLimit Rules calculates the current Velocity Feature data. This + requires a `card_token` or `account_token` matching what the rule is Scoped + to. + - ConditionalBlock Rules calculates the CARD*TRANSACTION_COUNT*\\** attributes on + the rule. This requires a `card_token` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._get( + f"/v2/auth_rules/{auth_rule_token}/features", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "card_token": card_token, + }, + v2_retrieve_features_params.V2RetrieveFeaturesParams, + ), + ), + cast_to=V2RetrieveFeaturesResponse, + ) + + def retrieve_report( + self, + auth_rule_token: str, + *, + begin: Union[str, date], + end: Union[str, date], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> V2RetrieveReportResponse: + """ + Retrieves a performance report for an Auth rule containing daily statistics and + evaluation outcomes. + + **Time Range Limitations:** + + - Reports are supported for the past 3 months only + - Maximum interval length is 1 month + - Report data is available only through the previous day in UTC (current day + data is not available) + + The report provides daily statistics for both current and draft versions of the + Auth rule, including approval, decline, and challenge counts along with sample + events. + + Args: + begin: Start date for the report + + end: End date for the report + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return self._get( + f"/v2/auth_rules/{auth_rule_token}/report", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + }, + v2_retrieve_report_params.V2RetrieveReportParams, + ), + ), + cast_to=V2RetrieveReportResponse, + ) + + +class AsyncV2(AsyncAPIResource): + @cached_property + def backtests(self) -> AsyncBacktests: + return AsyncBacktests(self._client) + + @cached_property + def with_raw_response(self) -> AsyncV2WithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncV2WithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncV2WithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncV2WithStreamingResponse(self) + + @overload + async def create( + self, + *, + parameters: v2_create_params.AccountLevelRuleParameters, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + event_stream: EventStream | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new V2 Auth rule in draft mode + + Args: + parameters: Parameters for the Auth Rule + + type: The type of Auth Rule. For certain rule types, this determines the event stream + during which it will be evaluated. For rules that can be applied to one of + several event streams, the effective one is defined by the separate + `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + + account_tokens: Account tokens to which the Auth Rule applies. + + business_account_tokens: Business Account tokens to which the Auth Rule applies. + + event_stream: The event stream during which the rule will be evaluated. + + name: Auth Rule Name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + card_tokens: SequenceNotStr[str], + parameters: v2_create_params.CardLevelRuleParameters, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + event_stream: EventStream | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new V2 Auth rule in draft mode + + Args: + card_tokens: Card tokens to which the Auth Rule applies. + + parameters: Parameters for the Auth Rule + + type: The type of Auth Rule. For certain rule types, this determines the event stream + during which it will be evaluated. For rules that can be applied to one of + several event streams, the effective one is defined by the separate + `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + + event_stream: The event stream during which the rule will be evaluated. + + name: Auth Rule Name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + parameters: v2_create_params.ProgramLevelRuleParameters, + program_level: bool, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + event_stream: EventStream | Omit = omit, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new V2 Auth rule in draft mode + + Args: + parameters: Parameters for the Auth Rule + + program_level: Whether the Auth Rule applies to all authorizations on the card program. + + type: The type of Auth Rule. For certain rule types, this determines the event stream + during which it will be evaluated. For rules that can be applied to one of + several event streams, the effective one is defined by the separate + `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + + event_stream: The event stream during which the rule will be evaluated. + + excluded_card_tokens: Card tokens to which the Auth Rule does not apply. + + name: Auth Rule Name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args( + ["parameters", "type"], ["card_tokens", "parameters", "type"], ["parameters", "program_level", "type"] + ) + async def create( + self, + *, + parameters: v2_create_params.AccountLevelRuleParameters + | v2_create_params.CardLevelRuleParameters + | v2_create_params.ProgramLevelRuleParameters, + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"], + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + event_stream: EventStream | Omit = omit, + name: Optional[str] | Omit = omit, + card_tokens: SequenceNotStr[str] | Omit = omit, + program_level: bool | Omit = omit, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + return await self._post( + "/v2/auth_rules", + body=await async_maybe_transform( + { + "parameters": parameters, + "type": type, + "account_tokens": account_tokens, + "business_account_tokens": business_account_tokens, + "event_stream": event_stream, + "name": name, + "card_tokens": card_tokens, + "program_level": program_level, + "excluded_card_tokens": excluded_card_tokens, + }, + v2_create_params.V2CreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + async def retrieve( + self, + auth_rule_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Fetches a V2 Auth rule by its token + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._get( + f"/v2/auth_rules/{auth_rule_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + @overload + async def update( + self, + auth_rule_token: str, + *, + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Updates a V2 Auth rule's properties + + If `account_tokens`, `card_tokens`, `program_level`, or `excluded_card_tokens` + is provided, this will replace existing associations with the provided list of + entities. + + Args: + account_tokens: Account tokens to which the Auth Rule applies. + + business_account_tokens: Business Account tokens to which the Auth Rule applies. + + name: Auth Rule Name + + state: The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def update( + self, + auth_rule_token: str, + *, + card_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Updates a V2 Auth rule's properties + + If `account_tokens`, `card_tokens`, `program_level`, or `excluded_card_tokens` + is provided, this will replace existing associations with the provided list of + entities. + + Args: + card_tokens: Card tokens to which the Auth Rule applies. + + name: Auth Rule Name + + state: The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def update( + self, + auth_rule_token: str, + *, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + program_level: bool | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Updates a V2 Auth rule's properties + + If `account_tokens`, `card_tokens`, `program_level`, or `excluded_card_tokens` + is provided, this will replace existing associations with the provided list of + entities. + + Args: + excluded_card_tokens: Card tokens to which the Auth Rule does not apply. + + name: Auth Rule Name + + program_level: Whether the Auth Rule applies to all authorizations on the card program. + + state: The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + async def update( + self, + auth_rule_token: str, + *, + account_tokens: SequenceNotStr[str] | Omit = omit, + business_account_tokens: SequenceNotStr[str] | Omit = omit, + name: Optional[str] | Omit = omit, + state: Literal["INACTIVE"] | Omit = omit, + card_tokens: SequenceNotStr[str] | Omit = omit, + excluded_card_tokens: SequenceNotStr[str] | Omit = omit, + program_level: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._patch( + f"/v2/auth_rules/{auth_rule_token}", + body=await async_maybe_transform( + { + "account_tokens": account_tokens, + "business_account_tokens": business_account_tokens, + "name": name, + "state": state, + "card_tokens": card_tokens, + "excluded_card_tokens": excluded_card_tokens, + "program_level": program_level, + }, + v2_update_params.V2UpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + def list( + self, + *, + account_token: str | Omit = omit, + business_account_token: str | Omit = omit, + card_token: str | Omit = omit, + ending_before: str | Omit = omit, + event_stream: EventStream | Omit = omit, + event_streams: List[EventStream] | Omit = omit, + page_size: int | Omit = omit, + scope: Literal["PROGRAM", "ACCOUNT", "BUSINESS_ACCOUNT", "CARD", "ANY"] | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AuthRule, AsyncCursorPage[AuthRule]]: + """ + Lists V2 Auth rules + + Args: + account_token: Only return Auth Rules that are bound to the provided account token. + + business_account_token: Only return Auth Rules that are bound to the provided business account token. + + card_token: Only return Auth Rules that are bound to the provided card token. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + event_stream: Deprecated: Use event_streams instead. Only return Auth rules that are executed + during the provided event stream. + + event_streams: Only return Auth rules that are executed during any of the provided event + streams. If event_streams and event_stream are specified, the values will be + combined. + + page_size: Page size (for pagination). + + scope: Only return Auth Rules that are bound to the provided scope. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v2/auth_rules", + page=AsyncCursorPage[AuthRule], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "business_account_token": business_account_token, + "card_token": card_token, + "ending_before": ending_before, + "event_stream": event_stream, + "event_streams": event_streams, + "page_size": page_size, + "scope": scope, + "starting_after": starting_after, + }, + v2_list_params.V2ListParams, + ), + ), + model=AuthRule, + ) + + async def delete( + self, + auth_rule_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Deletes a V2 Auth rule + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._delete( + f"/v2/auth_rules/{auth_rule_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def draft( + self, + auth_rule_token: str, + *, + parameters: Optional[v2_draft_params.Parameters] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Creates a new draft version of a rule that will be ran in shadow mode. + + This can also be utilized to reset the draft parameters, causing a draft version + to no longer be ran in shadow mode. + + Args: + parameters: Parameters for the Auth Rule + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._post( + f"/v2/auth_rules/{auth_rule_token}/draft", + body=await async_maybe_transform({"parameters": parameters}, v2_draft_params.V2DraftParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + async def promote( + self, + auth_rule_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthRule: + """ + Promotes the draft version of an Auth rule to the currently active version such + that it is enforced in the respective stream. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._post( + f"/v2/auth_rules/{auth_rule_token}/promote", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthRule, + ) + + async def retrieve_features( + self, + auth_rule_token: str, + *, + account_token: str | Omit = omit, + card_token: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> V2RetrieveFeaturesResponse: + """ + Fetches the current calculated Feature values for the given Auth Rule + + This only calculates the features for the active version. + + - VelocityLimit Rules calculates the current Velocity Feature data. This + requires a `card_token` or `account_token` matching what the rule is Scoped + to. + - ConditionalBlock Rules calculates the CARD*TRANSACTION_COUNT*\\** attributes on + the rule. This requires a `card_token` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._get( + f"/v2/auth_rules/{auth_rule_token}/features", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "account_token": account_token, + "card_token": card_token, + }, + v2_retrieve_features_params.V2RetrieveFeaturesParams, + ), + ), + cast_to=V2RetrieveFeaturesResponse, + ) + + async def retrieve_report( + self, + auth_rule_token: str, + *, + begin: Union[str, date], + end: Union[str, date], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> V2RetrieveReportResponse: + """ + Retrieves a performance report for an Auth rule containing daily statistics and + evaluation outcomes. + + **Time Range Limitations:** + + - Reports are supported for the past 3 months only + - Maximum interval length is 1 month + - Report data is available only through the previous day in UTC (current day + data is not available) + + The report provides daily statistics for both current and draft versions of the + Auth rule, including approval, decline, and challenge counts along with sample + events. + + Args: + begin: Start date for the report + + end: End date for the report + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not auth_rule_token: + raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") + return await self._get( + f"/v2/auth_rules/{auth_rule_token}/report", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "begin": begin, + "end": end, + }, + v2_retrieve_report_params.V2RetrieveReportParams, + ), + ), + cast_to=V2RetrieveReportResponse, + ) + + +class V2WithRawResponse: + def __init__(self, v2: V2) -> None: + self._v2 = v2 + + self.create = _legacy_response.to_raw_response_wrapper( + v2.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + v2.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + v2.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + v2.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + v2.delete, + ) + self.draft = _legacy_response.to_raw_response_wrapper( + v2.draft, + ) + self.promote = _legacy_response.to_raw_response_wrapper( + v2.promote, + ) + self.retrieve_features = _legacy_response.to_raw_response_wrapper( + v2.retrieve_features, + ) + self.retrieve_report = _legacy_response.to_raw_response_wrapper( + v2.retrieve_report, + ) + + @cached_property + def backtests(self) -> BacktestsWithRawResponse: + return BacktestsWithRawResponse(self._v2.backtests) + + +class AsyncV2WithRawResponse: + def __init__(self, v2: AsyncV2) -> None: + self._v2 = v2 + + self.create = _legacy_response.async_to_raw_response_wrapper( + v2.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + v2.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + v2.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + v2.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + v2.delete, + ) + self.draft = _legacy_response.async_to_raw_response_wrapper( + v2.draft, + ) + self.promote = _legacy_response.async_to_raw_response_wrapper( + v2.promote, + ) + self.retrieve_features = _legacy_response.async_to_raw_response_wrapper( + v2.retrieve_features, + ) + self.retrieve_report = _legacy_response.async_to_raw_response_wrapper( + v2.retrieve_report, + ) + + @cached_property + def backtests(self) -> AsyncBacktestsWithRawResponse: + return AsyncBacktestsWithRawResponse(self._v2.backtests) + + +class V2WithStreamingResponse: + def __init__(self, v2: V2) -> None: + self._v2 = v2 + + self.create = to_streamed_response_wrapper( + v2.create, + ) + self.retrieve = to_streamed_response_wrapper( + v2.retrieve, + ) + self.update = to_streamed_response_wrapper( + v2.update, + ) + self.list = to_streamed_response_wrapper( + v2.list, + ) + self.delete = to_streamed_response_wrapper( + v2.delete, + ) + self.draft = to_streamed_response_wrapper( + v2.draft, + ) + self.promote = to_streamed_response_wrapper( + v2.promote, + ) + self.retrieve_features = to_streamed_response_wrapper( + v2.retrieve_features, + ) + self.retrieve_report = to_streamed_response_wrapper( + v2.retrieve_report, + ) + + @cached_property + def backtests(self) -> BacktestsWithStreamingResponse: + return BacktestsWithStreamingResponse(self._v2.backtests) + + +class AsyncV2WithStreamingResponse: + def __init__(self, v2: AsyncV2) -> None: + self._v2 = v2 + + self.create = async_to_streamed_response_wrapper( + v2.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + v2.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + v2.update, + ) + self.list = async_to_streamed_response_wrapper( + v2.list, + ) + self.delete = async_to_streamed_response_wrapper( + v2.delete, + ) + self.draft = async_to_streamed_response_wrapper( + v2.draft, + ) + self.promote = async_to_streamed_response_wrapper( + v2.promote, + ) + self.retrieve_features = async_to_streamed_response_wrapper( + v2.retrieve_features, + ) + self.retrieve_report = async_to_streamed_response_wrapper( + v2.retrieve_report, + ) + + @cached_property + def backtests(self) -> AsyncBacktestsWithStreamingResponse: + return AsyncBacktestsWithStreamingResponse(self._v2.backtests) diff --git a/src/lithic/resources/auth_stream_enrollment.py b/src/lithic/resources/auth_stream_enrollment.py index 231dcbee..a35d6680 100644 --- a/src/lithic/resources/auth_stream_enrollment.py +++ b/src/lithic/resources/auth_stream_enrollment.py @@ -1,18 +1,16 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import httpx from .. import _legacy_response -from ..types import AuthStreamSecret -from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from .._types import Body, Query, Headers, NoneType, NotGiven, not_given from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from .._base_client import ( - make_request_options, -) +from .._base_client import make_request_options +from ..types.auth_stream_secret import AuthStreamSecret __all__ = ["AuthStreamEnrollment", "AsyncAuthStreamEnrollment"] @@ -20,10 +18,21 @@ class AuthStreamEnrollment(SyncAPIResource): @cached_property def with_raw_response(self) -> AuthStreamEnrollmentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AuthStreamEnrollmentWithRawResponse(self) @cached_property def with_streaming_response(self) -> AuthStreamEnrollmentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AuthStreamEnrollmentWithStreamingResponse(self) def retrieve_secret( @@ -34,7 +43,7 @@ def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AuthStreamSecret: """Retrieve the ASA HMAC secret key. @@ -46,7 +55,7 @@ def retrieve_secret( for more detail about verifying ASA webhooks. """ return self._get( - "/auth_stream/secret", + "/v1/auth_stream/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -61,7 +70,7 @@ def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Generate a new ASA HMAC secret key. @@ -71,7 +80,7 @@ def rotate_secret( request to retrieve the new secret key. """ return self._post( - "/auth_stream/secret/rotate", + "/v1/auth_stream/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -82,10 +91,21 @@ def rotate_secret( class AsyncAuthStreamEnrollment(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncAuthStreamEnrollmentWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncAuthStreamEnrollmentWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncAuthStreamEnrollmentWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncAuthStreamEnrollmentWithStreamingResponse(self) async def retrieve_secret( @@ -96,7 +116,7 @@ async def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AuthStreamSecret: """Retrieve the ASA HMAC secret key. @@ -108,7 +128,7 @@ async def retrieve_secret( for more detail about verifying ASA webhooks. """ return await self._get( - "/auth_stream/secret", + "/v1/auth_stream/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -123,7 +143,7 @@ async def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Generate a new ASA HMAC secret key. @@ -133,7 +153,7 @@ async def rotate_secret( request to retrieve the new secret key. """ return await self._post( - "/auth_stream/secret/rotate", + "/v1/auth_stream/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/lithic/resources/balances.py b/src/lithic/resources/balances.py index ff18ab1c..b1a74b82 100644 --- a/src/lithic/resources/balances.py +++ b/src/lithic/resources/balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,17 +9,15 @@ import httpx from .. import _legacy_response -from ..types import Balance, balance_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..types import balance_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncSinglePage, AsyncSinglePage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.balance import Balance __all__ = ["Balances", "AsyncBalances"] @@ -27,27 +25,39 @@ class Balances(SyncAPIResource): @cached_property def with_raw_response(self) -> BalancesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return BalancesWithRawResponse(self) @cached_property def with_streaming_response(self) -> BalancesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return BalancesWithStreamingResponse(self) def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - balance_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + balance_date: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncSinglePage[Balance]: """ - Get the balances for a program or a given end-user account + Get the balances for a program, business, or a given end-user account Args: account_token: List balances for all financial accounts of a given account_token. @@ -55,6 +65,8 @@ def list( balance_date: UTC date and time of the balances to retrieve. Defaults to latest available balances + business_account_token: List balances for all financial accounts of a given business_account_token. + financial_account_type: List balances for a given Financial Account type. extra_headers: Send extra headers @@ -66,7 +78,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/balances", + "/v1/balances", page=SyncSinglePage[Balance], options=make_request_options( extra_headers=extra_headers, @@ -77,6 +89,7 @@ def list( { "account_token": account_token, "balance_date": balance_date, + "business_account_token": business_account_token, "financial_account_type": financial_account_type, }, balance_list_params.BalanceListParams, @@ -89,27 +102,39 @@ def list( class AsyncBalances(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncBalancesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncBalancesWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncBalancesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncBalancesWithStreamingResponse(self) def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - balance_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + balance_date: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Balance, AsyncSinglePage[Balance]]: """ - Get the balances for a program or a given end-user account + Get the balances for a program, business, or a given end-user account Args: account_token: List balances for all financial accounts of a given account_token. @@ -117,6 +142,8 @@ def list( balance_date: UTC date and time of the balances to retrieve. Defaults to latest available balances + business_account_token: List balances for all financial accounts of a given business_account_token. + financial_account_type: List balances for a given Financial Account type. extra_headers: Send extra headers @@ -128,7 +155,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/balances", + "/v1/balances", page=AsyncSinglePage[Balance], options=make_request_options( extra_headers=extra_headers, @@ -139,6 +166,7 @@ def list( { "account_token": account_token, "balance_date": balance_date, + "business_account_token": business_account_token, "financial_account_type": financial_account_type, }, balance_list_params.BalanceListParams, diff --git a/src/lithic/resources/book_transfers.py b/src/lithic/resources/book_transfers.py new file mode 100644 index 00000000..f2eb20ef --- /dev/null +++ b/src/lithic/resources/book_transfers.py @@ -0,0 +1,724 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from .. import _legacy_response +from ..types import book_transfer_list_params, book_transfer_create_params, book_transfer_reverse_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.book_transfer_response import BookTransferResponse + +__all__ = ["BookTransfers", "AsyncBookTransfers"] + + +class BookTransfers(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BookTransfersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return BookTransfersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BookTransfersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return BookTransfersWithStreamingResponse(self) + + def create( + self, + *, + amount: int, + category: Literal[ + "ADJUSTMENT", + "BALANCE_OR_FUNDING", + "DERECOGNITION", + "DISPUTE", + "FEE", + "INTERNAL", + "REWARD", + "PROGRAM_FUNDING", + "TRANSFER", + ], + from_financial_account_token: str, + subtype: str, + to_financial_account_token: str, + type: Literal[ + "ATM_BALANCE_INQUIRY", + "ATM_WITHDRAWAL", + "ATM_DECLINE", + "INTERNATIONAL_ATM_WITHDRAWAL", + "INACTIVITY", + "STATEMENT", + "MONTHLY", + "QUARTERLY", + "ANNUAL", + "CUSTOMER_SERVICE", + "ACCOUNT_MAINTENANCE", + "ACCOUNT_ACTIVATION", + "ACCOUNT_CLOSURE", + "CARD_REPLACEMENT", + "CARD_DELIVERY", + "CARD_CREATE", + "CURRENCY_CONVERSION", + "INTEREST", + "LATE_PAYMENT", + "BILL_PAYMENT", + "CASH_BACK", + "ACCOUNT_TO_ACCOUNT", + "CARD_TO_CARD", + "DISBURSE", + "BILLING_ERROR", + "LOSS_WRITE_OFF", + "EXPIRED_CARD", + "EARLY_DERECOGNITION", + "ESCHEATMENT", + "INACTIVITY_FEE_DOWN", + "PROVISIONAL_CREDIT", + "DISPUTE_WON", + "SERVICE", + "TRANSFER", + "COLLECTION", + ], + token: str | Omit = omit, + external_id: str | Omit = omit, + memo: str | Omit = omit, + on_closed_account: Literal["FAIL", "USE_SUSPENSE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BookTransferResponse: + """ + Book transfer funds between two financial accounts or between a financial + account and card + + Args: + amount: Amount to be transferred in the currency's smallest unit (e.g., cents for USD). + This should always be a positive value. + + from_financial_account_token: Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + + subtype: The program specific subtype code for the specified category/type. + + to_financial_account_token: Globally unique identifier for the financial account or card that will receive + the funds. Accepted type dependent on the program's use case. + + type: Type of the book transfer + + token: Customer-provided token that will serve as an idempotency token. This token will + become the transaction token. + + external_id: External ID defined by the customer + + memo: Optional descriptor for the transfer. + + on_closed_account: What to do if the financial account is closed when posting an operation + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/book_transfers", + body=maybe_transform( + { + "amount": amount, + "category": category, + "from_financial_account_token": from_financial_account_token, + "subtype": subtype, + "to_financial_account_token": to_financial_account_token, + "type": type, + "token": token, + "external_id": external_id, + "memo": memo, + "on_closed_account": on_closed_account, + }, + book_transfer_create_params.BookTransferCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BookTransferResponse, + ) + + def retrieve( + self, + book_transfer_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BookTransferResponse: + """ + Get book transfer by token + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not book_transfer_token: + raise ValueError( + f"Expected a non-empty value for `book_transfer_token` but received {book_transfer_token!r}" + ) + return self._get( + f"/v1/book_transfers/{book_transfer_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BookTransferResponse, + ) + + def list( + self, + *, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "ADJUSTMENT", + "BALANCE_OR_FUNDING", + "DERECOGNITION", + "DISPUTE", + "FEE", + "INTERNAL", + "REWARD", + "PROGRAM_FUNDING", + "TRANSFER", + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "SETTLED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[BookTransferResponse]: + """List book transfers + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + category: Book Transfer category to be returned. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + + page_size: Page size (for pagination). + + result: Book transfer result to be returned. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Book transfer status to be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/book_transfers", + page=SyncCursorPage[BookTransferResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "result": result, + "starting_after": starting_after, + "status": status, + }, + book_transfer_list_params.BookTransferListParams, + ), + ), + model=BookTransferResponse, + ) + + def reverse( + self, + book_transfer_token: str, + *, + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BookTransferResponse: + """ + Reverse a book transfer + + Args: + memo: Optional descriptor for the reversal. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not book_transfer_token: + raise ValueError( + f"Expected a non-empty value for `book_transfer_token` but received {book_transfer_token!r}" + ) + return self._post( + f"/v1/book_transfers/{book_transfer_token}/reverse", + body=maybe_transform({"memo": memo}, book_transfer_reverse_params.BookTransferReverseParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BookTransferResponse, + ) + + +class AsyncBookTransfers(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBookTransfersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncBookTransfersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBookTransfersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncBookTransfersWithStreamingResponse(self) + + async def create( + self, + *, + amount: int, + category: Literal[ + "ADJUSTMENT", + "BALANCE_OR_FUNDING", + "DERECOGNITION", + "DISPUTE", + "FEE", + "INTERNAL", + "REWARD", + "PROGRAM_FUNDING", + "TRANSFER", + ], + from_financial_account_token: str, + subtype: str, + to_financial_account_token: str, + type: Literal[ + "ATM_BALANCE_INQUIRY", + "ATM_WITHDRAWAL", + "ATM_DECLINE", + "INTERNATIONAL_ATM_WITHDRAWAL", + "INACTIVITY", + "STATEMENT", + "MONTHLY", + "QUARTERLY", + "ANNUAL", + "CUSTOMER_SERVICE", + "ACCOUNT_MAINTENANCE", + "ACCOUNT_ACTIVATION", + "ACCOUNT_CLOSURE", + "CARD_REPLACEMENT", + "CARD_DELIVERY", + "CARD_CREATE", + "CURRENCY_CONVERSION", + "INTEREST", + "LATE_PAYMENT", + "BILL_PAYMENT", + "CASH_BACK", + "ACCOUNT_TO_ACCOUNT", + "CARD_TO_CARD", + "DISBURSE", + "BILLING_ERROR", + "LOSS_WRITE_OFF", + "EXPIRED_CARD", + "EARLY_DERECOGNITION", + "ESCHEATMENT", + "INACTIVITY_FEE_DOWN", + "PROVISIONAL_CREDIT", + "DISPUTE_WON", + "SERVICE", + "TRANSFER", + "COLLECTION", + ], + token: str | Omit = omit, + external_id: str | Omit = omit, + memo: str | Omit = omit, + on_closed_account: Literal["FAIL", "USE_SUSPENSE"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BookTransferResponse: + """ + Book transfer funds between two financial accounts or between a financial + account and card + + Args: + amount: Amount to be transferred in the currency's smallest unit (e.g., cents for USD). + This should always be a positive value. + + from_financial_account_token: Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + + subtype: The program specific subtype code for the specified category/type. + + to_financial_account_token: Globally unique identifier for the financial account or card that will receive + the funds. Accepted type dependent on the program's use case. + + type: Type of the book transfer + + token: Customer-provided token that will serve as an idempotency token. This token will + become the transaction token. + + external_id: External ID defined by the customer + + memo: Optional descriptor for the transfer. + + on_closed_account: What to do if the financial account is closed when posting an operation + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/book_transfers", + body=await async_maybe_transform( + { + "amount": amount, + "category": category, + "from_financial_account_token": from_financial_account_token, + "subtype": subtype, + "to_financial_account_token": to_financial_account_token, + "type": type, + "token": token, + "external_id": external_id, + "memo": memo, + "on_closed_account": on_closed_account, + }, + book_transfer_create_params.BookTransferCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BookTransferResponse, + ) + + async def retrieve( + self, + book_transfer_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BookTransferResponse: + """ + Get book transfer by token + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not book_transfer_token: + raise ValueError( + f"Expected a non-empty value for `book_transfer_token` but received {book_transfer_token!r}" + ) + return await self._get( + f"/v1/book_transfers/{book_transfer_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BookTransferResponse, + ) + + def list( + self, + *, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "ADJUSTMENT", + "BALANCE_OR_FUNDING", + "DERECOGNITION", + "DISPUTE", + "FEE", + "INTERNAL", + "REWARD", + "PROGRAM_FUNDING", + "TRANSFER", + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "SETTLED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[BookTransferResponse, AsyncCursorPage[BookTransferResponse]]: + """List book transfers + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + category: Book Transfer category to be returned. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + + page_size: Page size (for pagination). + + result: Book transfer result to be returned. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Book transfer status to be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/book_transfers", + page=AsyncCursorPage[BookTransferResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "result": result, + "starting_after": starting_after, + "status": status, + }, + book_transfer_list_params.BookTransferListParams, + ), + ), + model=BookTransferResponse, + ) + + async def reverse( + self, + book_transfer_token: str, + *, + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BookTransferResponse: + """ + Reverse a book transfer + + Args: + memo: Optional descriptor for the reversal. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not book_transfer_token: + raise ValueError( + f"Expected a non-empty value for `book_transfer_token` but received {book_transfer_token!r}" + ) + return await self._post( + f"/v1/book_transfers/{book_transfer_token}/reverse", + body=await async_maybe_transform({"memo": memo}, book_transfer_reverse_params.BookTransferReverseParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BookTransferResponse, + ) + + +class BookTransfersWithRawResponse: + def __init__(self, book_transfers: BookTransfers) -> None: + self._book_transfers = book_transfers + + self.create = _legacy_response.to_raw_response_wrapper( + book_transfers.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + book_transfers.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + book_transfers.list, + ) + self.reverse = _legacy_response.to_raw_response_wrapper( + book_transfers.reverse, + ) + + +class AsyncBookTransfersWithRawResponse: + def __init__(self, book_transfers: AsyncBookTransfers) -> None: + self._book_transfers = book_transfers + + self.create = _legacy_response.async_to_raw_response_wrapper( + book_transfers.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + book_transfers.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + book_transfers.list, + ) + self.reverse = _legacy_response.async_to_raw_response_wrapper( + book_transfers.reverse, + ) + + +class BookTransfersWithStreamingResponse: + def __init__(self, book_transfers: BookTransfers) -> None: + self._book_transfers = book_transfers + + self.create = to_streamed_response_wrapper( + book_transfers.create, + ) + self.retrieve = to_streamed_response_wrapper( + book_transfers.retrieve, + ) + self.list = to_streamed_response_wrapper( + book_transfers.list, + ) + self.reverse = to_streamed_response_wrapper( + book_transfers.reverse, + ) + + +class AsyncBookTransfersWithStreamingResponse: + def __init__(self, book_transfers: AsyncBookTransfers) -> None: + self._book_transfers = book_transfers + + self.create = async_to_streamed_response_wrapper( + book_transfers.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + book_transfers.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + book_transfers.list, + ) + self.reverse = async_to_streamed_response_wrapper( + book_transfers.reverse, + ) diff --git a/src/lithic/resources/card_bulk_orders.py b/src/lithic/resources/card_bulk_orders.py new file mode 100644 index 00000000..7caaad9d --- /dev/null +++ b/src/lithic/resources/card_bulk_orders.py @@ -0,0 +1,516 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from .. import _legacy_response +from ..types import card_bulk_order_list_params, card_bulk_order_create_params, card_bulk_order_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.card_bulk_order import CardBulkOrder + +__all__ = ["CardBulkOrders", "AsyncCardBulkOrders"] + + +class CardBulkOrders(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CardBulkOrdersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return CardBulkOrdersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CardBulkOrdersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return CardBulkOrdersWithStreamingResponse(self) + + def create( + self, + *, + customer_product_id: str, + shipping_address: object, + shipping_method: Literal["BULK_EXPEDITED"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardBulkOrder: + """Create a new bulk order for physical card shipments **[BETA]**. + + Cards can be + added to the order via the POST /v1/cards endpoint by specifying the + bulk_order_token. Lock the order via PATCH + /v1/card_bulk_orders/{bulk_order_token} to prepare for shipment. Please work + with your Customer Success Manager and card personalization bureau to ensure + bulk shipping is supported for your program. + + Args: + customer_product_id: Customer-specified product configuration for physical card manufacturing. This + must be configured with Lithic before use + + shipping_address: Shipping address for all cards in this bulk order + + shipping_method: Shipping method for all cards in this bulk order + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/card_bulk_orders", + body=maybe_transform( + { + "customer_product_id": customer_product_id, + "shipping_address": shipping_address, + "shipping_method": shipping_method, + }, + card_bulk_order_create_params.CardBulkOrderCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardBulkOrder, + ) + + def retrieve( + self, + bulk_order_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardBulkOrder: + """ + Retrieve a specific bulk order by token **[BETA]** + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not bulk_order_token: + raise ValueError(f"Expected a non-empty value for `bulk_order_token` but received {bulk_order_token!r}") + return self._get( + f"/v1/card_bulk_orders/{bulk_order_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardBulkOrder, + ) + + def update( + self, + bulk_order_token: str, + *, + status: Literal["LOCKED"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardBulkOrder: + """Update a bulk order **[BETA]**. + + Primarily used to lock the order, preventing + additional cards from being added + + Args: + status: Status to update the bulk order to. Use LOCKED to finalize the order + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not bulk_order_token: + raise ValueError(f"Expected a non-empty value for `bulk_order_token` but received {bulk_order_token!r}") + return self._patch( + f"/v1/card_bulk_orders/{bulk_order_token}", + body=maybe_transform({"status": status}, card_bulk_order_update_params.CardBulkOrderUpdateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardBulkOrder, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[CardBulkOrder]: + """ + List bulk orders for physical card shipments **[BETA]** + + Args: + begin: Date string in RFC 3339 format. Only entries created after the specified time + will be included. UTC time zone. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/card_bulk_orders", + page=SyncCursorPage[CardBulkOrder], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + card_bulk_order_list_params.CardBulkOrderListParams, + ), + ), + model=CardBulkOrder, + ) + + +class AsyncCardBulkOrders(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCardBulkOrdersWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncCardBulkOrdersWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCardBulkOrdersWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncCardBulkOrdersWithStreamingResponse(self) + + async def create( + self, + *, + customer_product_id: str, + shipping_address: object, + shipping_method: Literal["BULK_EXPEDITED"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardBulkOrder: + """Create a new bulk order for physical card shipments **[BETA]**. + + Cards can be + added to the order via the POST /v1/cards endpoint by specifying the + bulk_order_token. Lock the order via PATCH + /v1/card_bulk_orders/{bulk_order_token} to prepare for shipment. Please work + with your Customer Success Manager and card personalization bureau to ensure + bulk shipping is supported for your program. + + Args: + customer_product_id: Customer-specified product configuration for physical card manufacturing. This + must be configured with Lithic before use + + shipping_address: Shipping address for all cards in this bulk order + + shipping_method: Shipping method for all cards in this bulk order + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/card_bulk_orders", + body=await async_maybe_transform( + { + "customer_product_id": customer_product_id, + "shipping_address": shipping_address, + "shipping_method": shipping_method, + }, + card_bulk_order_create_params.CardBulkOrderCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardBulkOrder, + ) + + async def retrieve( + self, + bulk_order_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardBulkOrder: + """ + Retrieve a specific bulk order by token **[BETA]** + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not bulk_order_token: + raise ValueError(f"Expected a non-empty value for `bulk_order_token` but received {bulk_order_token!r}") + return await self._get( + f"/v1/card_bulk_orders/{bulk_order_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardBulkOrder, + ) + + async def update( + self, + bulk_order_token: str, + *, + status: Literal["LOCKED"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardBulkOrder: + """Update a bulk order **[BETA]**. + + Primarily used to lock the order, preventing + additional cards from being added + + Args: + status: Status to update the bulk order to. Use LOCKED to finalize the order + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not bulk_order_token: + raise ValueError(f"Expected a non-empty value for `bulk_order_token` but received {bulk_order_token!r}") + return await self._patch( + f"/v1/card_bulk_orders/{bulk_order_token}", + body=await async_maybe_transform( + {"status": status}, card_bulk_order_update_params.CardBulkOrderUpdateParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CardBulkOrder, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CardBulkOrder, AsyncCursorPage[CardBulkOrder]]: + """ + List bulk orders for physical card shipments **[BETA]** + + Args: + begin: Date string in RFC 3339 format. Only entries created after the specified time + will be included. UTC time zone. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/card_bulk_orders", + page=AsyncCursorPage[CardBulkOrder], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + card_bulk_order_list_params.CardBulkOrderListParams, + ), + ), + model=CardBulkOrder, + ) + + +class CardBulkOrdersWithRawResponse: + def __init__(self, card_bulk_orders: CardBulkOrders) -> None: + self._card_bulk_orders = card_bulk_orders + + self.create = _legacy_response.to_raw_response_wrapper( + card_bulk_orders.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + card_bulk_orders.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + card_bulk_orders.update, + ) + self.list = _legacy_response.to_raw_response_wrapper( + card_bulk_orders.list, + ) + + +class AsyncCardBulkOrdersWithRawResponse: + def __init__(self, card_bulk_orders: AsyncCardBulkOrders) -> None: + self._card_bulk_orders = card_bulk_orders + + self.create = _legacy_response.async_to_raw_response_wrapper( + card_bulk_orders.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + card_bulk_orders.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + card_bulk_orders.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + card_bulk_orders.list, + ) + + +class CardBulkOrdersWithStreamingResponse: + def __init__(self, card_bulk_orders: CardBulkOrders) -> None: + self._card_bulk_orders = card_bulk_orders + + self.create = to_streamed_response_wrapper( + card_bulk_orders.create, + ) + self.retrieve = to_streamed_response_wrapper( + card_bulk_orders.retrieve, + ) + self.update = to_streamed_response_wrapper( + card_bulk_orders.update, + ) + self.list = to_streamed_response_wrapper( + card_bulk_orders.list, + ) + + +class AsyncCardBulkOrdersWithStreamingResponse: + def __init__(self, card_bulk_orders: AsyncCardBulkOrders) -> None: + self._card_bulk_orders = card_bulk_orders + + self.create = async_to_streamed_response_wrapper( + card_bulk_orders.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + card_bulk_orders.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + card_bulk_orders.update, + ) + self.list = async_to_streamed_response_wrapper( + card_bulk_orders.list, + ) diff --git a/src/lithic/resources/card_product.py b/src/lithic/resources/card_product.py deleted file mode 100644 index 857a808d..00000000 --- a/src/lithic/resources/card_product.py +++ /dev/null @@ -1,111 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import httpx - -from .. import _legacy_response -from ..types import CardProductCreditDetailResponse -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from .._base_client import ( - make_request_options, -) - -__all__ = ["CardProduct", "AsyncCardProduct"] - - -class CardProduct(SyncAPIResource): - @cached_property - def with_raw_response(self) -> CardProductWithRawResponse: - return CardProductWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> CardProductWithStreamingResponse: - return CardProductWithStreamingResponse(self) - - def credit_detail( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> CardProductCreditDetailResponse: - """Get the Credit Detail for the card product""" - return self._get( - "/card_product/credit_detail", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=CardProductCreditDetailResponse, - ) - - -class AsyncCardProduct(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncCardProductWithRawResponse: - return AsyncCardProductWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncCardProductWithStreamingResponse: - return AsyncCardProductWithStreamingResponse(self) - - async def credit_detail( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> CardProductCreditDetailResponse: - """Get the Credit Detail for the card product""" - return await self._get( - "/card_product/credit_detail", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=CardProductCreditDetailResponse, - ) - - -class CardProductWithRawResponse: - def __init__(self, card_product: CardProduct) -> None: - self._card_product = card_product - - self.credit_detail = _legacy_response.to_raw_response_wrapper( - card_product.credit_detail, - ) - - -class AsyncCardProductWithRawResponse: - def __init__(self, card_product: AsyncCardProduct) -> None: - self._card_product = card_product - - self.credit_detail = _legacy_response.async_to_raw_response_wrapper( - card_product.credit_detail, - ) - - -class CardProductWithStreamingResponse: - def __init__(self, card_product: CardProduct) -> None: - self._card_product = card_product - - self.credit_detail = to_streamed_response_wrapper( - card_product.credit_detail, - ) - - -class AsyncCardProductWithStreamingResponse: - def __init__(self, card_product: AsyncCardProduct) -> None: - self._card_product = card_product - - self.credit_detail = async_to_streamed_response_wrapper( - card_product.credit_detail, - ) diff --git a/src/lithic/resources/card_programs.py b/src/lithic/resources/card_programs.py index 8338431c..8a9728d6 100644 --- a/src/lithic/resources/card_programs.py +++ b/src/lithic/resources/card_programs.py @@ -1,21 +1,19 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import httpx from .. import _legacy_response -from ..types import CardProgram, card_program_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..types import card_program_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.card_program import CardProgram __all__ = ["CardPrograms", "AsyncCardPrograms"] @@ -23,10 +21,21 @@ class CardPrograms(SyncAPIResource): @cached_property def with_raw_response(self) -> CardProgramsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return CardProgramsWithRawResponse(self) @cached_property def with_streaming_response(self) -> CardProgramsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return CardProgramsWithStreamingResponse(self) def retrieve( @@ -38,7 +47,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardProgram: """ Get card program. @@ -55,7 +64,7 @@ def retrieve( if not card_program_token: raise ValueError(f"Expected a non-empty value for `card_program_token` but received {card_program_token!r}") return self._get( - f"/card_programs/{card_program_token}", + f"/v1/card_programs/{card_program_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -65,15 +74,15 @@ def retrieve( def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[CardProgram]: """ List card programs. @@ -96,7 +105,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/card_programs", + "/v1/card_programs", page=SyncCursorPage[CardProgram], options=make_request_options( extra_headers=extra_headers, @@ -119,10 +128,21 @@ def list( class AsyncCardPrograms(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncCardProgramsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncCardProgramsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncCardProgramsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncCardProgramsWithStreamingResponse(self) async def retrieve( @@ -134,7 +154,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardProgram: """ Get card program. @@ -151,7 +171,7 @@ async def retrieve( if not card_program_token: raise ValueError(f"Expected a non-empty value for `card_program_token` but received {card_program_token!r}") return await self._get( - f"/card_programs/{card_program_token}", + f"/v1/card_programs/{card_program_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -161,15 +181,15 @@ async def retrieve( def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[CardProgram, AsyncCursorPage[CardProgram]]: """ List card programs. @@ -192,7 +212,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/card_programs", + "/v1/card_programs", page=AsyncCursorPage[CardProgram], options=make_request_options( extra_headers=extra_headers, diff --git a/src/lithic/resources/cards/__init__.py b/src/lithic/resources/cards/__init__.py index 9159556e..71ca7811 100644 --- a/src/lithic/resources/cards/__init__.py +++ b/src/lithic/resources/cards/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .cards import ( Cards, @@ -16,14 +16,6 @@ BalancesWithStreamingResponse, AsyncBalancesWithStreamingResponse, ) -from .aggregate_balances import ( - AggregateBalances, - AsyncAggregateBalances, - AggregateBalancesWithRawResponse, - AsyncAggregateBalancesWithRawResponse, - AggregateBalancesWithStreamingResponse, - AsyncAggregateBalancesWithStreamingResponse, -) from .financial_transactions import ( FinancialTransactions, AsyncFinancialTransactions, @@ -34,12 +26,6 @@ ) __all__ = [ - "AggregateBalances", - "AsyncAggregateBalances", - "AggregateBalancesWithRawResponse", - "AsyncAggregateBalancesWithRawResponse", - "AggregateBalancesWithStreamingResponse", - "AsyncAggregateBalancesWithStreamingResponse", "Balances", "AsyncBalances", "BalancesWithRawResponse", diff --git a/src/lithic/resources/cards/aggregate_balances.py b/src/lithic/resources/cards/aggregate_balances.py deleted file mode 100644 index d89c7f60..00000000 --- a/src/lithic/resources/cards/aggregate_balances.py +++ /dev/null @@ -1,170 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import httpx - -from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ...pagination import SyncSinglePage, AsyncSinglePage -from ...types.cards import AggregateBalanceListResponse, aggregate_balance_list_params -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) - -__all__ = ["AggregateBalances", "AsyncAggregateBalances"] - - -class AggregateBalances(SyncAPIResource): - @cached_property - def with_raw_response(self) -> AggregateBalancesWithRawResponse: - return AggregateBalancesWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AggregateBalancesWithStreamingResponse: - return AggregateBalancesWithStreamingResponse(self) - - def list( - self, - *, - account_token: str | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncSinglePage[AggregateBalanceListResponse]: - """ - Get the aggregated card balance across all end-user accounts. - - Args: - account_token: Cardholder to retrieve aggregate balances for. - - business_account_token: Business to retrieve aggregate balances for. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get_api_list( - "/cards/aggregate_balances", - page=SyncSinglePage[AggregateBalanceListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "account_token": account_token, - "business_account_token": business_account_token, - }, - aggregate_balance_list_params.AggregateBalanceListParams, - ), - ), - model=AggregateBalanceListResponse, - ) - - -class AsyncAggregateBalances(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncAggregateBalancesWithRawResponse: - return AsyncAggregateBalancesWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncAggregateBalancesWithStreamingResponse: - return AsyncAggregateBalancesWithStreamingResponse(self) - - def list( - self, - *, - account_token: str | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[AggregateBalanceListResponse, AsyncSinglePage[AggregateBalanceListResponse]]: - """ - Get the aggregated card balance across all end-user accounts. - - Args: - account_token: Cardholder to retrieve aggregate balances for. - - business_account_token: Business to retrieve aggregate balances for. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get_api_list( - "/cards/aggregate_balances", - page=AsyncSinglePage[AggregateBalanceListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "account_token": account_token, - "business_account_token": business_account_token, - }, - aggregate_balance_list_params.AggregateBalanceListParams, - ), - ), - model=AggregateBalanceListResponse, - ) - - -class AggregateBalancesWithRawResponse: - def __init__(self, aggregate_balances: AggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = _legacy_response.to_raw_response_wrapper( - aggregate_balances.list, - ) - - -class AsyncAggregateBalancesWithRawResponse: - def __init__(self, aggregate_balances: AsyncAggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = _legacy_response.async_to_raw_response_wrapper( - aggregate_balances.list, - ) - - -class AggregateBalancesWithStreamingResponse: - def __init__(self, aggregate_balances: AggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = to_streamed_response_wrapper( - aggregate_balances.list, - ) - - -class AsyncAggregateBalancesWithStreamingResponse: - def __init__(self, aggregate_balances: AsyncAggregateBalances) -> None: - self._aggregate_balances = aggregate_balances - - self.list = async_to_streamed_response_wrapper( - aggregate_balances.list, - ) diff --git a/src/lithic/resources/cards/balances.py b/src/lithic/resources/cards/balances.py index dbafa920..44784aed 100644 --- a/src/lithic/resources/cards/balances.py +++ b/src/lithic/resources/cards/balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -8,18 +8,15 @@ import httpx from ... import _legacy_response -from ...types import Balance -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncSinglePage, AsyncSinglePage from ...types.cards import balance_list_params -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) +from ..._base_client import AsyncPaginator, make_request_options +from ...types.financial_account_balance import FinancialAccountBalance __all__ = ["Balances", "AsyncBalances"] @@ -27,25 +24,36 @@ class Balances(SyncAPIResource): @cached_property def with_raw_response(self) -> BalancesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return BalancesWithRawResponse(self) @cached_property def with_streaming_response(self) -> BalancesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return BalancesWithStreamingResponse(self) def list( self, card_token: str, *, - balance_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - last_transaction_event_token: str | NotGiven = NOT_GIVEN, + balance_date: Union[str, datetime] | Omit = omit, + last_transaction_event_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncSinglePage[Balance]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncSinglePage[FinancialAccountBalance]: """ Get the balances for a given card. @@ -67,8 +75,8 @@ def list( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._get_api_list( - f"/cards/{card_token}/balances", - page=SyncSinglePage[Balance], + f"/v1/cards/{card_token}/balances", + page=SyncSinglePage[FinancialAccountBalance], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -82,32 +90,43 @@ def list( balance_list_params.BalanceListParams, ), ), - model=Balance, + model=FinancialAccountBalance, ) class AsyncBalances(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncBalancesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncBalancesWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncBalancesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncBalancesWithStreamingResponse(self) def list( self, card_token: str, *, - balance_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - last_transaction_event_token: str | NotGiven = NOT_GIVEN, + balance_date: Union[str, datetime] | Omit = omit, + last_transaction_event_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[Balance, AsyncSinglePage[Balance]]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[FinancialAccountBalance, AsyncSinglePage[FinancialAccountBalance]]: """ Get the balances for a given card. @@ -129,8 +148,8 @@ def list( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._get_api_list( - f"/cards/{card_token}/balances", - page=AsyncSinglePage[Balance], + f"/v1/cards/{card_token}/balances", + page=AsyncSinglePage[FinancialAccountBalance], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -144,7 +163,7 @@ def list( balance_list_params.BalanceListParams, ), ), - model=Balance, + model=FinancialAccountBalance, ) diff --git a/src/lithic/resources/cards/cards.py b/src/lithic/resources/cards/cards.py index d2c1234b..f9686d84 100644 --- a/src/lithic/resources/cards/cards.py +++ b/src/lithic/resources/cards/cards.py @@ -1,25 +1,16 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -import hmac -import json -import base64 -import hashlib -from typing import Union -from datetime import datetime, timezone, timedelta +from typing import Any, Union, cast +from datetime import datetime from typing_extensions import Literal import httpx -from httpx import URL from ... import _legacy_response from ...types import ( - Card, - CardSpendLimits, SpendLimitDuration, - CardProvisionResponse, - shared_params, card_list_params, card_embed_params, card_renew_params, @@ -27,11 +18,12 @@ card_update_params, card_reissue_params, card_provision_params, - card_get_embed_url_params, card_search_by_pan_params, + card_web_provision_params, + card_convert_physical_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform, strip_not_given +from ..._types import Body, Omit, Query, Headers, NotGiven, Base64FileInput, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform from .balances import ( Balances, AsyncBalances, @@ -44,19 +36,9 @@ from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncCursorPage, AsyncCursorPage -from ..._base_client import ( - AsyncPaginator, - _merge_mappings, - make_request_options, -) -from .aggregate_balances import ( - AggregateBalances, - AsyncAggregateBalances, - AggregateBalancesWithRawResponse, - AsyncAggregateBalancesWithRawResponse, - AggregateBalancesWithStreamingResponse, - AsyncAggregateBalancesWithStreamingResponse, -) +from ...types.card import Card +from ..._base_client import AsyncPaginator, make_request_options +from ...types.non_pci_card import NonPCICard from .financial_transactions import ( FinancialTransactions, AsyncFinancialTransactions, @@ -65,15 +47,17 @@ FinancialTransactionsWithStreamingResponse, AsyncFinancialTransactionsWithStreamingResponse, ) +from ...types.card_spend_limits import CardSpendLimits +from ...types.spend_limit_duration import SpendLimitDuration +from ...types.shared_params.carrier import Carrier +from ...types.card_provision_response import CardProvisionResponse +from ...types.card_web_provision_response import CardWebProvisionResponse +from ...types.shared_params.shipping_address import ShippingAddress __all__ = ["Cards", "AsyncCards"] class Cards(SyncAPIResource): - @cached_property - def aggregate_balances(self) -> AggregateBalances: - return AggregateBalances(self._client) - @cached_property def balances(self) -> Balances: return Balances(self._client) @@ -84,42 +68,72 @@ def financial_transactions(self) -> FinancialTransactions: @cached_property def with_raw_response(self) -> CardsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return CardsWithRawResponse(self) @cached_property def with_streaming_response(self) -> CardsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return CardsWithStreamingResponse(self) def create( self, *, - type: Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL"], - account_token: str | NotGiven = NOT_GIVEN, - card_program_token: str | NotGiven = NOT_GIVEN, - carrier: shared_params.Carrier | NotGiven = NOT_GIVEN, - digital_card_art_token: str | NotGiven = NOT_GIVEN, - exp_month: str | NotGiven = NOT_GIVEN, - exp_year: str | NotGiven = NOT_GIVEN, - memo: str | NotGiven = NOT_GIVEN, - pin: str | NotGiven = NOT_GIVEN, - product_id: str | NotGiven = NOT_GIVEN, - replacement_for: str | NotGiven = NOT_GIVEN, - shipping_address: shared_params.ShippingAddress | NotGiven = NOT_GIVEN, - shipping_method: Literal["2_DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] - | NotGiven = NOT_GIVEN, - spend_limit: int | NotGiven = NOT_GIVEN, - spend_limit_duration: SpendLimitDuration | NotGiven = NOT_GIVEN, - state: Literal["OPEN", "PAUSED"] | NotGiven = NOT_GIVEN, + type: Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL", "UNLOCKED", "DIGITAL_WALLET"], + account_token: str | Omit = omit, + bulk_order_token: str | Omit = omit, + card_program_token: str | Omit = omit, + carrier: Carrier | Omit = omit, + digital_card_art_token: str | Omit = omit, + exp_month: str | Omit = omit, + exp_year: str | Omit = omit, + memo: str | Omit = omit, + pin: str | Omit = omit, + product_id: str | Omit = omit, + replacement_account_token: str | Omit = omit, + replacement_comment: str | Omit = omit, + replacement_for: str | Omit = omit, + replacement_substatus: Literal[ + "LOST", + "COMPROMISED", + "DAMAGED", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "SUSPICIOUS_ACTIVITY", + "INTERNAL_REVIEW", + "EXPIRED", + "UNDELIVERABLE", + "OTHER", + ] + | Omit = omit, + shipping_address: ShippingAddress | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, + spend_limit: int | Omit = omit, + spend_limit_duration: SpendLimitDuration | Omit = omit, + state: Literal["OPEN", "PAUSED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """Create a new virtual or physical card. - Parameters `pin`, `shipping_address`, and + Parameters `shipping_address` and `product_id` only apply to physical cards. Args: @@ -136,12 +150,20 @@ def create( - `SINGLE_USE` - Card is closed upon first successful authorization. - `MERCHANT_LOCKED` - _[Deprecated]_ Card is locked to the first merchant that successfully authorizes the card. + - `UNLOCKED` - _[Deprecated]_ Similar behavior to VIRTUAL cards, please use + VIRTUAL instead. + - `DIGITAL_WALLET` - _[Deprecated]_ Similar behavior to VIRTUAL cards, please + use VIRTUAL instead. account_token: Globally unique identifier for the account that the card will be associated with. Required for programs enrolling users using the [/account_holders endpoint](https://docs.lithic.com/docs/account-holders-kyc). See [Managing Your Program](doc:managing-your-program) for more information. + bulk_order_token: Globally unique identifier for an existing bulk order to associate this card + with. When specified, the card will be added to the bulk order for batch + shipment. Only applicable to cards of type PHYSICAL + card_program_token: For card programs with more than one BIN range. This must be configured with Lithic before use. Identifies the card program/BIN range under which to create the card. If omitted, will utilize the program's default `card_program_token`. @@ -160,19 +182,57 @@ def create( exp_year: Four digit (yyyy) expiry year. If neither `exp_month` nor `exp_year` is provided, an expiration date will be generated. - memo: Friendly name to identify the card. We recommend against using this field to - store JSON data as it can cause unexpected behavior. + memo: Friendly name to identify the card. - pin: Encrypted PIN block (in base64). Only applies to cards of type `PHYSICAL` and + pin: Encrypted PIN block (in base64). Applies to cards of type `PHYSICAL` and `VIRTUAL`. See - [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block-enterprise). + [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block). product_id: Only applicable to cards of type `PHYSICAL`. This must be configured with Lithic before use. Specifies the configuration (i.e., physical card art) that the card should be manufactured with. - replacement_for: Only applicable to cards of type `PHYSICAL`. Globally unique identifier for the - card that this physical card will replace. + replacement_account_token: Restricted field limited to select use cases. Lithic will reach out directly if + this field should be used. Globally unique identifier for the replacement card's + account. If this field is specified, `replacement_for` must also be specified. + If `replacement_for` is specified and this field is omitted, the replacement + card's account will be inferred from the card being replaced. + + replacement_comment: Additional context or information related to the card that this card will + replace. + + replacement_for: Globally unique identifier for the card that this card will replace. If the card + type is `PHYSICAL` it will be replaced by a `PHYSICAL` card. If the card type is + `VIRTUAL` it will be replaced by a `VIRTUAL` card. + + replacement_substatus: + Card state substatus values for the card that this card will replace: + + - `LOST` - The physical card is no longer in the cardholder's possession due to + being lost or never received by the cardholder. + - `COMPROMISED` - Card information has been exposed, potentially leading to + unauthorized access. This may involve physical card theft, cloning, or online + data breaches. + - `DAMAGED` - The physical card is not functioning properly, such as having chip + failures or a demagnetized magnetic stripe. + - `END_USER_REQUEST` - The cardholder requested the closure of the card for + reasons unrelated to fraud or damage, such as switching to a different product + or closing the account. + - `ISSUER_REQUEST` - The issuer closed the card for reasons unrelated to fraud + or damage, such as account inactivity, product or policy changes, or + technology upgrades. + - `NOT_ACTIVE` - The card hasn’t had any transaction activity for a specified + period, applicable to statuses like `PAUSED` or `CLOSED`. + - `SUSPICIOUS_ACTIVITY` - The card has one or more suspicious transactions or + activities that require review. This can involve prompting the cardholder to + confirm legitimate use or report confirmed fraud. + - `INTERNAL_REVIEW` - The card is temporarily paused pending further internal + review. + - `EXPIRED` - The card has expired and has been closed without being reissued. + - `UNDELIVERABLE` - The card cannot be delivered to the cardholder and has been + returned. + - `OTHER` - The reason for the status does not fall into any of the above + categories. A comment should be provided to specify the reason. shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of options besides `STANDARD` require additional permissions. @@ -182,16 +242,19 @@ def create( - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping - spend_limit: Amount (in cents) to limit approved authorizations. Transaction requests above - the spend limit will be declined. Note that a spend limit of 0 is effectively no - limit, and should only be used to reset or remove a prior limit. Only a limit of - 1 or above will result in declined transactions due to checks against the card - limit. + spend_limit: Amount (in cents) to limit approved authorizations (e.g. 100000 would be a + $1,000 limit). Transaction requests above the spend limit will be declined. Note + that a spend limit of 0 is effectively no limit, and should only be used to + reset or remove a prior limit. Only a limit of 1 or above will result in + declined transactions due to checks against the card limit. spend_limit_duration: Spend limit duration values: @@ -224,11 +287,12 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/cards", + "/v1/cards", body=maybe_transform( { "type": type, "account_token": account_token, + "bulk_order_token": bulk_order_token, "card_program_token": card_program_token, "carrier": carrier, "digital_card_art_token": digital_card_art_token, @@ -237,7 +301,10 @@ def create( "memo": memo, "pin": pin, "product_id": product_id, + "replacement_account_token": replacement_account_token, + "replacement_comment": replacement_comment, "replacement_for": replacement_for, + "replacement_substatus": replacement_substatus, "shipping_address": shipping_address, "shipping_method": shipping_method, "spend_limit": spend_limit, @@ -261,7 +328,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """ Get card configuration such as spend limit and state. @@ -278,7 +345,7 @@ def retrieve( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._get( - f"/cards/{card_token}", + f"/v1/cards/{card_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -289,49 +356,69 @@ def update( self, card_token: str, *, - auth_rule_token: str | NotGiven = NOT_GIVEN, - digital_card_art_token: str | NotGiven = NOT_GIVEN, - memo: str | NotGiven = NOT_GIVEN, - pin: str | NotGiven = NOT_GIVEN, - spend_limit: int | NotGiven = NOT_GIVEN, - spend_limit_duration: SpendLimitDuration | NotGiven = NOT_GIVEN, - state: Literal["CLOSED", "OPEN", "PAUSED"] | NotGiven = NOT_GIVEN, + comment: str | Omit = omit, + digital_card_art_token: str | Omit = omit, + memo: str | Omit = omit, + network_program_token: str | Omit = omit, + pin: str | Omit = omit, + pin_status: Literal["OK"] | Omit = omit, + spend_limit: int | Omit = omit, + spend_limit_duration: SpendLimitDuration | Omit = omit, + state: Literal["CLOSED", "OPEN", "PAUSED"] | Omit = omit, + substatus: Literal[ + "LOST", + "COMPROMISED", + "DAMAGED", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "SUSPICIOUS_ACTIVITY", + "INTERNAL_REVIEW", + "EXPIRED", + "UNDELIVERABLE", + "OTHER", + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """Update the specified properties of the card. Unsupplied properties will remain - unchanged. `pin` parameter only applies to physical cards. + unchanged. _Note: setting a card to a `CLOSED` state is a final action that cannot be undone._ Args: - auth_rule_token: Identifier for any Auth Rules that will be applied to transactions taking place - with the card. + comment: Additional context or information related to the card. digital_card_art_token: Specifies the digital card art to be displayed in the user’s digital wallet after tokenization. This artwork must be approved by Mastercard and configured by Lithic to use. See [Flexible Card Art Guide](https://docs.lithic.com/docs/about-digital-wallets#flexible-card-art). - memo: Friendly name to identify the card. We recommend against using this field to - store JSON data as it can cause unexpected behavior. + memo: Friendly name to identify the card. + + network_program_token: Globally unique identifier for the card's network program. Currently applicable + to Visa cards participating in Account Level Management only. pin: Encrypted PIN block (in base64). Only applies to cards of type `PHYSICAL` and - `VIRTUAL`. See - [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block-enterprise). + `VIRTUAL`. Changing PIN also resets PIN status to `OK`. See + [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block). + + pin_status: Indicates if a card is blocked due a PIN status issue (e.g. excessive incorrect + attempts). Can only be set to `OK` to unblock a card. - spend_limit: Amount (in cents) to limit approved authorizations. Transaction requests above - the spend limit will be declined. Note that a spend limit of 0 is effectively no - limit, and should only be used to reset or remove a prior limit. Only a limit of - 1 or above will result in declined transactions due to checks against the card - limit. + spend_limit: Amount (in cents) to limit approved authorizations (e.g. 100000 would be a + $1,000 limit). Transaction requests above the spend limit will be declined. Note + that a spend limit of 0 is effectively no limit, and should only be used to + reset or remove a prior limit. Only a limit of 1 or above will result in + declined transactions due to checks against the card limit. spend_limit_duration: Spend limit duration values: @@ -357,6 +444,35 @@ def update( - `PAUSED` - Card will decline authorizations, but can be resumed at a later time. + substatus: + Card state substatus values: + + - `LOST` - The physical card is no longer in the cardholder's possession due to + being lost or never received by the cardholder. + - `COMPROMISED` - Card information has been exposed, potentially leading to + unauthorized access. This may involve physical card theft, cloning, or online + data breaches. + - `DAMAGED` - The physical card is not functioning properly, such as having chip + failures or a demagnetized magnetic stripe. + - `END_USER_REQUEST` - The cardholder requested the closure of the card for + reasons unrelated to fraud or damage, such as switching to a different product + or closing the account. + - `ISSUER_REQUEST` - The issuer closed the card for reasons unrelated to fraud + or damage, such as account inactivity, product or policy changes, or + technology upgrades. + - `NOT_ACTIVE` - The card hasn’t had any transaction activity for a specified + period, applicable to statuses like `PAUSED` or `CLOSED`. + - `SUSPICIOUS_ACTIVITY` - The card has one or more suspicious transactions or + activities that require review. This can involve prompting the cardholder to + confirm legitimate use or report confirmed fraud. + - `INTERNAL_REVIEW` - The card is temporarily paused pending further internal + review. + - `EXPIRED` - The card has expired and has been closed without being reissued. + - `UNDELIVERABLE` - The card cannot be delivered to the cardholder and has been + returned. + - `OTHER` - The reason for the status does not fall into any of the above + categories. A comment should be provided to specify the reason. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -368,16 +484,19 @@ def update( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._patch( - f"/cards/{card_token}", + f"/v1/cards/{card_token}", body=maybe_transform( { - "auth_rule_token": auth_rule_token, + "comment": comment, "digital_card_art_token": digital_card_art_token, "memo": memo, + "network_program_token": network_program_token, "pin": pin, + "pin_status": pin_status, "spend_limit": spend_limit, "spend_limit_duration": spend_limit_duration, "state": state, + "substatus": substatus, }, card_update_params.CardUpdateParams, ), @@ -390,20 +509,21 @@ def update( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - state: Literal["CLOSED", "OPEN", "PAUSED", "PENDING_ACTIVATION", "PENDING_FULFILLMENT"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + memo: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + state: Literal["CLOSED", "OPEN", "PAUSED", "PENDING_ACTIVATION", "PENDING_FULFILLMENT"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncCursorPage[Card]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[NonPCICard]: """ List cards. @@ -419,6 +539,8 @@ def list( ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. + memo: Returns cards containing the specified partial or full memo text. + page_size: Page size (for pagination). starting_after: A cursor representing an item's token after which a page of results should @@ -435,8 +557,8 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/cards", - page=SyncCursorPage[Card], + "/v1/cards", + page=SyncCursorPage[NonPCICard], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -448,6 +570,7 @@ def list( "begin": begin, "end": end, "ending_before": ending_before, + "memo": memo, "page_size": page_size, "starting_after": starting_after, "state": state, @@ -455,7 +578,90 @@ def list( card_list_params.CardListParams, ), ), - model=Card, + model=NonPCICard, + ) + + def convert_physical( + self, + card_token: str, + *, + shipping_address: ShippingAddress, + carrier: Carrier | Omit = omit, + product_id: str | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Card: + """Convert a virtual card into a physical card and manufacture it. + + Customer must + supply relevant fields for physical card creation including `product_id`, + `carrier`, `shipping_method`, and `shipping_address`. The card token will be + unchanged. The card's type will be altered to `PHYSICAL`. The card will be set + to state `PENDING_FULFILLMENT` and fulfilled at next fulfillment cycle. Virtual + cards created on card programs which do not support physical cards cannot be + converted. The card program cannot be changed as part of the conversion. Cards + must be in an `OPEN` state to be converted. Only applies to cards of type + `VIRTUAL` (or existing cards with deprecated types of `DIGITAL_WALLET` and + `UNLOCKED`). + + Args: + shipping_address: The shipping address this card will be sent to. + + carrier: If omitted, the previous carrier will be used. + + product_id: Specifies the configuration (e.g. physical card art) that the card should be + manufactured with, and only applies to cards of type `PHYSICAL`. This must be + configured with Lithic before use. + + shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of + options besides `STANDARD` require additional permissions. + + - `STANDARD` - USPS regular mail or similar international option, with no + tracking + - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, + with tracking + - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with + tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not card_token: + raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") + return self._post( + f"/v1/cards/{card_token}/convert_physical", + body=maybe_transform( + { + "shipping_address": shipping_address, + "carrier": carrier, + "product_id": product_id, + "shipping_method": shipping_method, + }, + card_convert_physical_params.CardConvertPhysicalParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Card, ) def embed( @@ -468,7 +674,7 @@ def embed( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> str: """ Handling full card PANs and CVV codes requires that you comply with the Payment @@ -480,9 +686,10 @@ def embed( that we provide, optionally styled in the customer's branding using a specified css stylesheet. A user's browser makes the request directly to api.lithic.com, so card PANs and CVVs never touch the API customer's servers while full card - data is displayed to their end-users. The response contains an HTML document. - This means that the url for the request can be inserted straight into the `src` - attribute of an iframe. + data is displayed to their end-users. The response contains an HTML document + (see Embedded Card UI or Changelog for upcoming changes in January). This means + that the url for the request can be inserted straight into the `src` attribute + of an iframe. ```html - ``` - - You should compute the request payload on the server side. You can render it (or - the whole iframe) on the server or make an ajax call from your front end code, - but **do not ever embed your API key into front end code, as doing so introduces - a serious security vulnerability**. - """ - # Default expiration of 1 minute from now. - if isinstance(expiration, NotGiven): - expiration = datetime.now(timezone.utc) + timedelta(minutes=1) - - query = maybe_transform( - strip_not_given( - { - "css": css, - "token": token, - "expiration": expiration, - "target_origin": target_origin, - } - ), - card_get_embed_url_params.CardGetEmbedURLParams, - ) - serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) - params = { - "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), - "hmac": base64.b64encode( - hmac.new( - key=bytes(self._client.api_key, "utf-8"), - msg=bytes(serialized, "utf-8"), - digestmod=hashlib.sha256, - ).digest() - ).decode("utf-8"), - } - - # Copied nearly directly from httpx.BaseClient._merge_url - base_url = self._client.base_url - raw_path = base_url.raw_path + URL("embed/card").raw_path - return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) - def provision( self, card_token: str, *, - certificate: str | NotGiven = NOT_GIVEN, - digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | NotGiven = NOT_GIVEN, - nonce: str | NotGiven = NOT_GIVEN, - nonce_signature: str | NotGiven = NOT_GIVEN, + certificate: Union[str, Base64FileInput] | Omit = omit, + client_device_id: str | Omit = omit, + client_wallet_account_id: str | Omit = omit, + digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | Omit = omit, + nonce: Union[str, Base64FileInput] | Omit = omit, + nonce_signature: Union[str, Base64FileInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardProvisionResponse: """ Allow your cardholders to directly add payment cards to the device's digital @@ -670,6 +768,14 @@ def provision( encoded in PEM format with headers `(-----BEGIN CERTIFICATE-----)` and trailers omitted. Provided by the device's wallet. + client_device_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Stable device identification set by the wallet + provider. + + client_wallet_account_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Consumer ID that identifies the wallet account + holder entity. + digital_wallet: Name of digital wallet provider. nonce: Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only @@ -691,10 +797,12 @@ def provision( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._post( - f"/cards/{card_token}/provision", + f"/v1/cards/{card_token}/provision", body=maybe_transform( { "certificate": certificate, + "client_device_id": client_device_id, + "client_wallet_account_id": client_wallet_account_id, "digital_wallet": digital_wallet, "nonce": nonce, "nonce_signature": nonce_signature, @@ -711,22 +819,27 @@ def reissue( self, card_token: str, *, - carrier: shared_params.Carrier | NotGiven = NOT_GIVEN, - product_id: str | NotGiven = NOT_GIVEN, - shipping_address: shared_params.ShippingAddress | NotGiven = NOT_GIVEN, - shipping_method: Literal["2-DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] - | NotGiven = NOT_GIVEN, + carrier: Carrier | Omit = omit, + product_id: str | Omit = omit, + shipping_address: ShippingAddress | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: - """ - Initiate print and shipment of a duplicate physical card. + """Initiate print and shipment of a duplicate physical card (e.g. - Only applies to cards of type `PHYSICAL`. + card is + physically damaged). The PAN, expiry, and CVC2 will remain the same and the + original card can continue to be used until the new card is activated. Only + applies to cards of type `PHYSICAL`. A card can be reissued or renewed a total + of 8 times. Args: carrier: If omitted, the previous carrier will be used. @@ -737,18 +850,21 @@ def reissue( shipping_address: If omitted, the previous shipping address will be used. - shipping_method: Shipping method for the card. Use of options besides `STANDARD` require - additional permissions. + shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of + options besides `STANDARD` require additional permissions. - `STANDARD` - USPS regular mail or similar international option, with no tracking - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping extra_headers: Send extra headers @@ -761,7 +877,7 @@ def reissue( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._post( - f"/cards/{card_token}/reissue", + f"/v1/cards/{card_token}/reissue", body=maybe_transform( { "carrier": carrier, @@ -781,24 +897,33 @@ def renew( self, card_token: str, *, - shipping_address: shared_params.ShippingAddress, - carrier: shared_params.Carrier | NotGiven = NOT_GIVEN, - exp_month: str | NotGiven = NOT_GIVEN, - exp_year: str | NotGiven = NOT_GIVEN, - product_id: str | NotGiven = NOT_GIVEN, - shipping_method: Literal["2-DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] - | NotGiven = NOT_GIVEN, + shipping_address: ShippingAddress, + carrier: Carrier | Omit = omit, + exp_month: str | Omit = omit, + exp_year: str | Omit = omit, + product_id: str | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: - """ - Initiate print and shipment of a renewed physical card. - - Only applies to cards of type `PHYSICAL`. + """Applies to card types `PHYSICAL` and `VIRTUAL`. + + For `PHYSICAL`, creates a new + card with the same card token and PAN, but updated expiry and CVC2 code. The + original card will keep working for card-present transactions until the new card + is activated. For card-not-present transactions, the original card details + (expiry, CVC2) will also keep working until the new card is activated. A + `PHYSICAL` card can be reissued or renewed a total of 8 times. For `VIRTUAL`, + the card will retain the same card token and PAN and receive an updated expiry + and CVC2 code. `product_id`, `shipping_method`, `shipping_address`, `carrier` + are only relevant for renewing `PHYSICAL` cards. Args: shipping_address: The shipping address this card will be sent to. @@ -815,18 +940,21 @@ def renew( manufactured with, and only applies to cards of type `PHYSICAL`. This must be configured with Lithic before use. - shipping_method: Shipping method for the card. Use of options besides `STANDARD` require - additional permissions. + shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of + options besides `STANDARD` require additional permissions. - `STANDARD` - USPS regular mail or similar international option, with no tracking - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping extra_headers: Send extra headers @@ -839,7 +967,7 @@ def renew( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._post( - f"/cards/{card_token}/renew", + f"/v1/cards/{card_token}/renew", body=maybe_transform( { "shipping_address": shipping_address, @@ -866,7 +994,7 @@ def retrieve_spend_limits( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardSpendLimits: """ Get a Card's available spend limit, which is based on the spend limit configured @@ -886,7 +1014,7 @@ def retrieve_spend_limits( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._get( - f"/cards/{card_token}/spend_limits", + f"/v1/cards/{card_token}/spend_limits", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -902,7 +1030,7 @@ def search_by_pan( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """Get card configuration such as spend limit and state. @@ -924,7 +1052,7 @@ def search_by_pan( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/cards/search_by_pan", + "/v1/cards/search_by_pan", body=maybe_transform({"pan": pan}, card_search_by_pan_params.CardSearchByPanParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -932,12 +1060,75 @@ def search_by_pan( cast_to=Card, ) + def web_provision( + self, + card_token: str, + *, + client_device_id: str | Omit = omit, + client_wallet_account_id: str | Omit = omit, + digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY"] | Omit = omit, + server_session_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardWebProvisionResponse: + """ + Allow your cardholders to directly add payment cards to the device's digital + wallet from a browser on the web. Currently only suported for Apple Pay. + + This requires some additional setup and configuration. Please + [Contact Us](https://lithic.com/contact) or your Customer Success representative + for more information. + + Args: + client_device_id: Only applicable if `digital_wallet` is GOOGLE_PAY. Google Pay Web Push + Provisioning device identifier required for the tokenization flow + + client_wallet_account_id: Only applicable if `digital_wallet` is GOOGLE_PAY. Google Pay Web Push + Provisioning wallet account identifier required for the tokenization flow + + digital_wallet: Name of digital wallet provider. + + server_session_id: Only applicable if `digital_wallet` is GOOGLE_PAY. Google Pay Web Push + Provisioning session identifier required for the FPAN flow. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not card_token: + raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") + return cast( + CardWebProvisionResponse, + self._post( + f"/v1/cards/{card_token}/web_provision", + body=maybe_transform( + { + "client_device_id": client_device_id, + "client_wallet_account_id": client_wallet_account_id, + "digital_wallet": digital_wallet, + "server_session_id": server_session_id, + }, + card_web_provision_params.CardWebProvisionParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, CardWebProvisionResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + class AsyncCards(AsyncAPIResource): - @cached_property - def aggregate_balances(self) -> AsyncAggregateBalances: - return AsyncAggregateBalances(self._client) - @cached_property def balances(self) -> AsyncBalances: return AsyncBalances(self._client) @@ -948,42 +1139,72 @@ def financial_transactions(self) -> AsyncFinancialTransactions: @cached_property def with_raw_response(self) -> AsyncCardsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncCardsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncCardsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncCardsWithStreamingResponse(self) async def create( self, *, - type: Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL"], - account_token: str | NotGiven = NOT_GIVEN, - card_program_token: str | NotGiven = NOT_GIVEN, - carrier: shared_params.Carrier | NotGiven = NOT_GIVEN, - digital_card_art_token: str | NotGiven = NOT_GIVEN, - exp_month: str | NotGiven = NOT_GIVEN, - exp_year: str | NotGiven = NOT_GIVEN, - memo: str | NotGiven = NOT_GIVEN, - pin: str | NotGiven = NOT_GIVEN, - product_id: str | NotGiven = NOT_GIVEN, - replacement_for: str | NotGiven = NOT_GIVEN, - shipping_address: shared_params.ShippingAddress | NotGiven = NOT_GIVEN, - shipping_method: Literal["2_DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] - | NotGiven = NOT_GIVEN, - spend_limit: int | NotGiven = NOT_GIVEN, - spend_limit_duration: SpendLimitDuration | NotGiven = NOT_GIVEN, - state: Literal["OPEN", "PAUSED"] | NotGiven = NOT_GIVEN, + type: Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL", "UNLOCKED", "DIGITAL_WALLET"], + account_token: str | Omit = omit, + bulk_order_token: str | Omit = omit, + card_program_token: str | Omit = omit, + carrier: Carrier | Omit = omit, + digital_card_art_token: str | Omit = omit, + exp_month: str | Omit = omit, + exp_year: str | Omit = omit, + memo: str | Omit = omit, + pin: str | Omit = omit, + product_id: str | Omit = omit, + replacement_account_token: str | Omit = omit, + replacement_comment: str | Omit = omit, + replacement_for: str | Omit = omit, + replacement_substatus: Literal[ + "LOST", + "COMPROMISED", + "DAMAGED", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "SUSPICIOUS_ACTIVITY", + "INTERNAL_REVIEW", + "EXPIRED", + "UNDELIVERABLE", + "OTHER", + ] + | Omit = omit, + shipping_address: ShippingAddress | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, + spend_limit: int | Omit = omit, + spend_limit_duration: SpendLimitDuration | Omit = omit, + state: Literal["OPEN", "PAUSED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """Create a new virtual or physical card. - Parameters `pin`, `shipping_address`, and + Parameters `shipping_address` and `product_id` only apply to physical cards. Args: @@ -1000,12 +1221,20 @@ async def create( - `SINGLE_USE` - Card is closed upon first successful authorization. - `MERCHANT_LOCKED` - _[Deprecated]_ Card is locked to the first merchant that successfully authorizes the card. + - `UNLOCKED` - _[Deprecated]_ Similar behavior to VIRTUAL cards, please use + VIRTUAL instead. + - `DIGITAL_WALLET` - _[Deprecated]_ Similar behavior to VIRTUAL cards, please + use VIRTUAL instead. account_token: Globally unique identifier for the account that the card will be associated with. Required for programs enrolling users using the [/account_holders endpoint](https://docs.lithic.com/docs/account-holders-kyc). See [Managing Your Program](doc:managing-your-program) for more information. + bulk_order_token: Globally unique identifier for an existing bulk order to associate this card + with. When specified, the card will be added to the bulk order for batch + shipment. Only applicable to cards of type PHYSICAL + card_program_token: For card programs with more than one BIN range. This must be configured with Lithic before use. Identifies the card program/BIN range under which to create the card. If omitted, will utilize the program's default `card_program_token`. @@ -1024,19 +1253,57 @@ async def create( exp_year: Four digit (yyyy) expiry year. If neither `exp_month` nor `exp_year` is provided, an expiration date will be generated. - memo: Friendly name to identify the card. We recommend against using this field to - store JSON data as it can cause unexpected behavior. + memo: Friendly name to identify the card. - pin: Encrypted PIN block (in base64). Only applies to cards of type `PHYSICAL` and + pin: Encrypted PIN block (in base64). Applies to cards of type `PHYSICAL` and `VIRTUAL`. See - [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block-enterprise). + [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block). product_id: Only applicable to cards of type `PHYSICAL`. This must be configured with Lithic before use. Specifies the configuration (i.e., physical card art) that the card should be manufactured with. - replacement_for: Only applicable to cards of type `PHYSICAL`. Globally unique identifier for the - card that this physical card will replace. + replacement_account_token: Restricted field limited to select use cases. Lithic will reach out directly if + this field should be used. Globally unique identifier for the replacement card's + account. If this field is specified, `replacement_for` must also be specified. + If `replacement_for` is specified and this field is omitted, the replacement + card's account will be inferred from the card being replaced. + + replacement_comment: Additional context or information related to the card that this card will + replace. + + replacement_for: Globally unique identifier for the card that this card will replace. If the card + type is `PHYSICAL` it will be replaced by a `PHYSICAL` card. If the card type is + `VIRTUAL` it will be replaced by a `VIRTUAL` card. + + replacement_substatus: + Card state substatus values for the card that this card will replace: + + - `LOST` - The physical card is no longer in the cardholder's possession due to + being lost or never received by the cardholder. + - `COMPROMISED` - Card information has been exposed, potentially leading to + unauthorized access. This may involve physical card theft, cloning, or online + data breaches. + - `DAMAGED` - The physical card is not functioning properly, such as having chip + failures or a demagnetized magnetic stripe. + - `END_USER_REQUEST` - The cardholder requested the closure of the card for + reasons unrelated to fraud or damage, such as switching to a different product + or closing the account. + - `ISSUER_REQUEST` - The issuer closed the card for reasons unrelated to fraud + or damage, such as account inactivity, product or policy changes, or + technology upgrades. + - `NOT_ACTIVE` - The card hasn’t had any transaction activity for a specified + period, applicable to statuses like `PAUSED` or `CLOSED`. + - `SUSPICIOUS_ACTIVITY` - The card has one or more suspicious transactions or + activities that require review. This can involve prompting the cardholder to + confirm legitimate use or report confirmed fraud. + - `INTERNAL_REVIEW` - The card is temporarily paused pending further internal + review. + - `EXPIRED` - The card has expired and has been closed without being reissued. + - `UNDELIVERABLE` - The card cannot be delivered to the cardholder and has been + returned. + - `OTHER` - The reason for the status does not fall into any of the above + categories. A comment should be provided to specify the reason. shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of options besides `STANDARD` require additional permissions. @@ -1046,16 +1313,19 @@ async def create( - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping - spend_limit: Amount (in cents) to limit approved authorizations. Transaction requests above - the spend limit will be declined. Note that a spend limit of 0 is effectively no - limit, and should only be used to reset or remove a prior limit. Only a limit of - 1 or above will result in declined transactions due to checks against the card - limit. + spend_limit: Amount (in cents) to limit approved authorizations (e.g. 100000 would be a + $1,000 limit). Transaction requests above the spend limit will be declined. Note + that a spend limit of 0 is effectively no limit, and should only be used to + reset or remove a prior limit. Only a limit of 1 or above will result in + declined transactions due to checks against the card limit. spend_limit_duration: Spend limit duration values: @@ -1088,11 +1358,12 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/cards", - body=maybe_transform( + "/v1/cards", + body=await async_maybe_transform( { "type": type, "account_token": account_token, + "bulk_order_token": bulk_order_token, "card_program_token": card_program_token, "carrier": carrier, "digital_card_art_token": digital_card_art_token, @@ -1101,7 +1372,10 @@ async def create( "memo": memo, "pin": pin, "product_id": product_id, + "replacement_account_token": replacement_account_token, + "replacement_comment": replacement_comment, "replacement_for": replacement_for, + "replacement_substatus": replacement_substatus, "shipping_address": shipping_address, "shipping_method": shipping_method, "spend_limit": spend_limit, @@ -1125,7 +1399,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """ Get card configuration such as spend limit and state. @@ -1142,7 +1416,7 @@ async def retrieve( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._get( - f"/cards/{card_token}", + f"/v1/cards/{card_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1153,49 +1427,69 @@ async def update( self, card_token: str, *, - auth_rule_token: str | NotGiven = NOT_GIVEN, - digital_card_art_token: str | NotGiven = NOT_GIVEN, - memo: str | NotGiven = NOT_GIVEN, - pin: str | NotGiven = NOT_GIVEN, - spend_limit: int | NotGiven = NOT_GIVEN, - spend_limit_duration: SpendLimitDuration | NotGiven = NOT_GIVEN, - state: Literal["CLOSED", "OPEN", "PAUSED"] | NotGiven = NOT_GIVEN, + comment: str | Omit = omit, + digital_card_art_token: str | Omit = omit, + memo: str | Omit = omit, + network_program_token: str | Omit = omit, + pin: str | Omit = omit, + pin_status: Literal["OK"] | Omit = omit, + spend_limit: int | Omit = omit, + spend_limit_duration: SpendLimitDuration | Omit = omit, + state: Literal["CLOSED", "OPEN", "PAUSED"] | Omit = omit, + substatus: Literal[ + "LOST", + "COMPROMISED", + "DAMAGED", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "SUSPICIOUS_ACTIVITY", + "INTERNAL_REVIEW", + "EXPIRED", + "UNDELIVERABLE", + "OTHER", + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """Update the specified properties of the card. Unsupplied properties will remain - unchanged. `pin` parameter only applies to physical cards. + unchanged. _Note: setting a card to a `CLOSED` state is a final action that cannot be undone._ Args: - auth_rule_token: Identifier for any Auth Rules that will be applied to transactions taking place - with the card. + comment: Additional context or information related to the card. digital_card_art_token: Specifies the digital card art to be displayed in the user’s digital wallet after tokenization. This artwork must be approved by Mastercard and configured by Lithic to use. See [Flexible Card Art Guide](https://docs.lithic.com/docs/about-digital-wallets#flexible-card-art). - memo: Friendly name to identify the card. We recommend against using this field to - store JSON data as it can cause unexpected behavior. + memo: Friendly name to identify the card. + + network_program_token: Globally unique identifier for the card's network program. Currently applicable + to Visa cards participating in Account Level Management only. pin: Encrypted PIN block (in base64). Only applies to cards of type `PHYSICAL` and - `VIRTUAL`. See - [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block-enterprise). + `VIRTUAL`. Changing PIN also resets PIN status to `OK`. See + [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block). - spend_limit: Amount (in cents) to limit approved authorizations. Transaction requests above - the spend limit will be declined. Note that a spend limit of 0 is effectively no - limit, and should only be used to reset or remove a prior limit. Only a limit of - 1 or above will result in declined transactions due to checks against the card - limit. + pin_status: Indicates if a card is blocked due a PIN status issue (e.g. excessive incorrect + attempts). Can only be set to `OK` to unblock a card. + + spend_limit: Amount (in cents) to limit approved authorizations (e.g. 100000 would be a + $1,000 limit). Transaction requests above the spend limit will be declined. Note + that a spend limit of 0 is effectively no limit, and should only be used to + reset or remove a prior limit. Only a limit of 1 or above will result in + declined transactions due to checks against the card limit. spend_limit_duration: Spend limit duration values: @@ -1221,6 +1515,35 @@ async def update( - `PAUSED` - Card will decline authorizations, but can be resumed at a later time. + substatus: + Card state substatus values: + + - `LOST` - The physical card is no longer in the cardholder's possession due to + being lost or never received by the cardholder. + - `COMPROMISED` - Card information has been exposed, potentially leading to + unauthorized access. This may involve physical card theft, cloning, or online + data breaches. + - `DAMAGED` - The physical card is not functioning properly, such as having chip + failures or a demagnetized magnetic stripe. + - `END_USER_REQUEST` - The cardholder requested the closure of the card for + reasons unrelated to fraud or damage, such as switching to a different product + or closing the account. + - `ISSUER_REQUEST` - The issuer closed the card for reasons unrelated to fraud + or damage, such as account inactivity, product or policy changes, or + technology upgrades. + - `NOT_ACTIVE` - The card hasn’t had any transaction activity for a specified + period, applicable to statuses like `PAUSED` or `CLOSED`. + - `SUSPICIOUS_ACTIVITY` - The card has one or more suspicious transactions or + activities that require review. This can involve prompting the cardholder to + confirm legitimate use or report confirmed fraud. + - `INTERNAL_REVIEW` - The card is temporarily paused pending further internal + review. + - `EXPIRED` - The card has expired and has been closed without being reissued. + - `UNDELIVERABLE` - The card cannot be delivered to the cardholder and has been + returned. + - `OTHER` - The reason for the status does not fall into any of the above + categories. A comment should be provided to specify the reason. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1232,16 +1555,19 @@ async def update( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._patch( - f"/cards/{card_token}", - body=maybe_transform( + f"/v1/cards/{card_token}", + body=await async_maybe_transform( { - "auth_rule_token": auth_rule_token, + "comment": comment, "digital_card_art_token": digital_card_art_token, "memo": memo, + "network_program_token": network_program_token, "pin": pin, + "pin_status": pin_status, "spend_limit": spend_limit, "spend_limit_duration": spend_limit_duration, "state": state, + "substatus": substatus, }, card_update_params.CardUpdateParams, ), @@ -1254,20 +1580,21 @@ async def update( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - state: Literal["CLOSED", "OPEN", "PAUSED", "PENDING_ACTIVATION", "PENDING_FULFILLMENT"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + memo: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + state: Literal["CLOSED", "OPEN", "PAUSED", "PENDING_ACTIVATION", "PENDING_FULFILLMENT"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[Card, AsyncCursorPage[Card]]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[NonPCICard, AsyncCursorPage[NonPCICard]]: """ List cards. @@ -1283,6 +1610,8 @@ def list( ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. + memo: Returns cards containing the specified partial or full memo text. + page_size: Page size (for pagination). starting_after: A cursor representing an item's token after which a page of results should @@ -1299,8 +1628,8 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/cards", - page=AsyncCursorPage[Card], + "/v1/cards", + page=AsyncCursorPage[NonPCICard], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1312,6 +1641,7 @@ def list( "begin": begin, "end": end, "ending_before": ending_before, + "memo": memo, "page_size": page_size, "starting_after": starting_after, "state": state, @@ -1319,7 +1649,90 @@ def list( card_list_params.CardListParams, ), ), - model=Card, + model=NonPCICard, + ) + + async def convert_physical( + self, + card_token: str, + *, + shipping_address: ShippingAddress, + carrier: Carrier | Omit = omit, + product_id: str | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Card: + """Convert a virtual card into a physical card and manufacture it. + + Customer must + supply relevant fields for physical card creation including `product_id`, + `carrier`, `shipping_method`, and `shipping_address`. The card token will be + unchanged. The card's type will be altered to `PHYSICAL`. The card will be set + to state `PENDING_FULFILLMENT` and fulfilled at next fulfillment cycle. Virtual + cards created on card programs which do not support physical cards cannot be + converted. The card program cannot be changed as part of the conversion. Cards + must be in an `OPEN` state to be converted. Only applies to cards of type + `VIRTUAL` (or existing cards with deprecated types of `DIGITAL_WALLET` and + `UNLOCKED`). + + Args: + shipping_address: The shipping address this card will be sent to. + + carrier: If omitted, the previous carrier will be used. + + product_id: Specifies the configuration (e.g. physical card art) that the card should be + manufactured with, and only applies to cards of type `PHYSICAL`. This must be + configured with Lithic before use. + + shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of + options besides `STANDARD` require additional permissions. + + - `STANDARD` - USPS regular mail or similar international option, with no + tracking + - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, + with tracking + - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with + tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not card_token: + raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") + return await self._post( + f"/v1/cards/{card_token}/convert_physical", + body=await async_maybe_transform( + { + "shipping_address": shipping_address, + "carrier": carrier, + "product_id": product_id, + "shipping_method": shipping_method, + }, + card_convert_physical_params.CardConvertPhysicalParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Card, ) async def embed( @@ -1332,7 +1745,7 @@ async def embed( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> str: """ Handling full card PANs and CVV codes requires that you comply with the Payment @@ -1344,9 +1757,10 @@ async def embed( that we provide, optionally styled in the customer's branding using a specified css stylesheet. A user's browser makes the request directly to api.lithic.com, so card PANs and CVVs never touch the API customer's servers while full card - data is displayed to their end-users. The response contains an HTML document. - This means that the url for the request can be inserted straight into the `src` - attribute of an iframe. + data is displayed to their end-users. The response contains an HTML document + (see Embedded Card UI or Changelog for upcoming changes in January). This means + that the url for the request can be inserted straight into the `src` attribute + of an iframe. ```html - ``` - - You should compute the request payload on the server side. You can render it (or - the whole iframe) on the server or make an ajax call from your front end code, - but **do not ever embed your API key into front end code, as doing so introduces - a serious security vulnerability**. - """ - # Default expiration of 1 minute from now. - if isinstance(expiration, NotGiven): - expiration = datetime.now(timezone.utc) + timedelta(minutes=1) - - query = maybe_transform( - strip_not_given( - { - "css": css, - "token": token, - "expiration": expiration, - "target_origin": target_origin, - } - ), - card_get_embed_url_params.CardGetEmbedURLParams, - ) - serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) - params = { - "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), - "hmac": base64.b64encode( - hmac.new( - key=bytes(self._client.api_key, "utf-8"), - msg=bytes(serialized, "utf-8"), - digestmod=hashlib.sha256, - ).digest() - ).decode("utf-8"), - } - - # Copied nearly directly from httpx.BaseClient._merge_url - base_url = self._client.base_url - raw_path = base_url.raw_path + URL("embed/card").raw_path - return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) - async def provision( self, card_token: str, *, - certificate: str | NotGiven = NOT_GIVEN, - digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | NotGiven = NOT_GIVEN, - nonce: str | NotGiven = NOT_GIVEN, - nonce_signature: str | NotGiven = NOT_GIVEN, + certificate: Union[str, Base64FileInput] | Omit = omit, + client_device_id: str | Omit = omit, + client_wallet_account_id: str | Omit = omit, + digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | Omit = omit, + nonce: Union[str, Base64FileInput] | Omit = omit, + nonce_signature: Union[str, Base64FileInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardProvisionResponse: """ Allow your cardholders to directly add payment cards to the device's digital @@ -1534,6 +1839,14 @@ async def provision( encoded in PEM format with headers `(-----BEGIN CERTIFICATE-----)` and trailers omitted. Provided by the device's wallet. + client_device_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Stable device identification set by the wallet + provider. + + client_wallet_account_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Consumer ID that identifies the wallet account + holder entity. + digital_wallet: Name of digital wallet provider. nonce: Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only @@ -1555,10 +1868,12 @@ async def provision( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._post( - f"/cards/{card_token}/provision", - body=maybe_transform( + f"/v1/cards/{card_token}/provision", + body=await async_maybe_transform( { "certificate": certificate, + "client_device_id": client_device_id, + "client_wallet_account_id": client_wallet_account_id, "digital_wallet": digital_wallet, "nonce": nonce, "nonce_signature": nonce_signature, @@ -1575,22 +1890,27 @@ async def reissue( self, card_token: str, *, - carrier: shared_params.Carrier | NotGiven = NOT_GIVEN, - product_id: str | NotGiven = NOT_GIVEN, - shipping_address: shared_params.ShippingAddress | NotGiven = NOT_GIVEN, - shipping_method: Literal["2-DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] - | NotGiven = NOT_GIVEN, + carrier: Carrier | Omit = omit, + product_id: str | Omit = omit, + shipping_address: ShippingAddress | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: - """ - Initiate print and shipment of a duplicate physical card. + """Initiate print and shipment of a duplicate physical card (e.g. - Only applies to cards of type `PHYSICAL`. + card is + physically damaged). The PAN, expiry, and CVC2 will remain the same and the + original card can continue to be used until the new card is activated. Only + applies to cards of type `PHYSICAL`. A card can be reissued or renewed a total + of 8 times. Args: carrier: If omitted, the previous carrier will be used. @@ -1601,18 +1921,21 @@ async def reissue( shipping_address: If omitted, the previous shipping address will be used. - shipping_method: Shipping method for the card. Use of options besides `STANDARD` require - additional permissions. + shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of + options besides `STANDARD` require additional permissions. - `STANDARD` - USPS regular mail or similar international option, with no tracking - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping extra_headers: Send extra headers @@ -1625,8 +1948,8 @@ async def reissue( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._post( - f"/cards/{card_token}/reissue", - body=maybe_transform( + f"/v1/cards/{card_token}/reissue", + body=await async_maybe_transform( { "carrier": carrier, "product_id": product_id, @@ -1645,24 +1968,33 @@ async def renew( self, card_token: str, *, - shipping_address: shared_params.ShippingAddress, - carrier: shared_params.Carrier | NotGiven = NOT_GIVEN, - exp_month: str | NotGiven = NOT_GIVEN, - exp_year: str | NotGiven = NOT_GIVEN, - product_id: str | NotGiven = NOT_GIVEN, - shipping_method: Literal["2-DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] - | NotGiven = NOT_GIVEN, + shipping_address: ShippingAddress, + carrier: Carrier | Omit = omit, + exp_month: str | Omit = omit, + exp_year: str | Omit = omit, + product_id: str | Omit = omit, + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: - """ - Initiate print and shipment of a renewed physical card. - - Only applies to cards of type `PHYSICAL`. + """Applies to card types `PHYSICAL` and `VIRTUAL`. + + For `PHYSICAL`, creates a new + card with the same card token and PAN, but updated expiry and CVC2 code. The + original card will keep working for card-present transactions until the new card + is activated. For card-not-present transactions, the original card details + (expiry, CVC2) will also keep working until the new card is activated. A + `PHYSICAL` card can be reissued or renewed a total of 8 times. For `VIRTUAL`, + the card will retain the same card token and PAN and receive an updated expiry + and CVC2 code. `product_id`, `shipping_method`, `shipping_address`, `carrier` + are only relevant for renewing `PHYSICAL` cards. Args: shipping_address: The shipping address this card will be sent to. @@ -1679,18 +2011,21 @@ async def renew( manufactured with, and only applies to cards of type `PHYSICAL`. This must be configured with Lithic before use. - shipping_method: Shipping method for the card. Use of options besides `STANDARD` require - additional permissions. + shipping_method: Shipping method for the card. Only applies to cards of type PHYSICAL. Use of + options besides `STANDARD` require additional permissions. - `STANDARD` - USPS regular mail or similar international option, with no tracking - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping extra_headers: Send extra headers @@ -1703,8 +2038,8 @@ async def renew( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._post( - f"/cards/{card_token}/renew", - body=maybe_transform( + f"/v1/cards/{card_token}/renew", + body=await async_maybe_transform( { "shipping_address": shipping_address, "carrier": carrier, @@ -1730,7 +2065,7 @@ async def retrieve_spend_limits( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CardSpendLimits: """ Get a Card's available spend limit, which is based on the spend limit configured @@ -1750,7 +2085,7 @@ async def retrieve_spend_limits( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._get( - f"/cards/{card_token}/spend_limits", + f"/v1/cards/{card_token}/spend_limits", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1766,7 +2101,7 @@ async def search_by_pan( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Card: """Get card configuration such as spend limit and state. @@ -1788,14 +2123,81 @@ async def search_by_pan( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/cards/search_by_pan", - body=maybe_transform({"pan": pan}, card_search_by_pan_params.CardSearchByPanParams), + "/v1/cards/search_by_pan", + body=await async_maybe_transform({"pan": pan}, card_search_by_pan_params.CardSearchByPanParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=Card, ) + async def web_provision( + self, + card_token: str, + *, + client_device_id: str | Omit = omit, + client_wallet_account_id: str | Omit = omit, + digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY"] | Omit = omit, + server_session_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CardWebProvisionResponse: + """ + Allow your cardholders to directly add payment cards to the device's digital + wallet from a browser on the web. Currently only suported for Apple Pay. + + This requires some additional setup and configuration. Please + [Contact Us](https://lithic.com/contact) or your Customer Success representative + for more information. + + Args: + client_device_id: Only applicable if `digital_wallet` is GOOGLE_PAY. Google Pay Web Push + Provisioning device identifier required for the tokenization flow + + client_wallet_account_id: Only applicable if `digital_wallet` is GOOGLE_PAY. Google Pay Web Push + Provisioning wallet account identifier required for the tokenization flow + + digital_wallet: Name of digital wallet provider. + + server_session_id: Only applicable if `digital_wallet` is GOOGLE_PAY. Google Pay Web Push + Provisioning session identifier required for the FPAN flow. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not card_token: + raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") + return cast( + CardWebProvisionResponse, + await self._post( + f"/v1/cards/{card_token}/web_provision", + body=await async_maybe_transform( + { + "client_device_id": client_device_id, + "client_wallet_account_id": client_wallet_account_id, + "digital_wallet": digital_wallet, + "server_session_id": server_session_id, + }, + card_web_provision_params.CardWebProvisionParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, CardWebProvisionResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + class CardsWithRawResponse: def __init__(self, cards: Cards) -> None: @@ -1813,6 +2215,9 @@ def __init__(self, cards: Cards) -> None: self.list = _legacy_response.to_raw_response_wrapper( cards.list, ) + self.convert_physical = _legacy_response.to_raw_response_wrapper( + cards.convert_physical, + ) self.embed = _legacy_response.to_raw_response_wrapper( cards.embed, ) @@ -1831,10 +2236,9 @@ def __init__(self, cards: Cards) -> None: self.search_by_pan = _legacy_response.to_raw_response_wrapper( cards.search_by_pan, ) - - @cached_property - def aggregate_balances(self) -> AggregateBalancesWithRawResponse: - return AggregateBalancesWithRawResponse(self._cards.aggregate_balances) + self.web_provision = _legacy_response.to_raw_response_wrapper( + cards.web_provision, + ) @cached_property def balances(self) -> BalancesWithRawResponse: @@ -1861,6 +2265,9 @@ def __init__(self, cards: AsyncCards) -> None: self.list = _legacy_response.async_to_raw_response_wrapper( cards.list, ) + self.convert_physical = _legacy_response.async_to_raw_response_wrapper( + cards.convert_physical, + ) self.embed = _legacy_response.async_to_raw_response_wrapper( cards.embed, ) @@ -1879,10 +2286,9 @@ def __init__(self, cards: AsyncCards) -> None: self.search_by_pan = _legacy_response.async_to_raw_response_wrapper( cards.search_by_pan, ) - - @cached_property - def aggregate_balances(self) -> AsyncAggregateBalancesWithRawResponse: - return AsyncAggregateBalancesWithRawResponse(self._cards.aggregate_balances) + self.web_provision = _legacy_response.async_to_raw_response_wrapper( + cards.web_provision, + ) @cached_property def balances(self) -> AsyncBalancesWithRawResponse: @@ -1909,6 +2315,9 @@ def __init__(self, cards: Cards) -> None: self.list = to_streamed_response_wrapper( cards.list, ) + self.convert_physical = to_streamed_response_wrapper( + cards.convert_physical, + ) self.embed = to_streamed_response_wrapper( cards.embed, ) @@ -1927,10 +2336,9 @@ def __init__(self, cards: Cards) -> None: self.search_by_pan = to_streamed_response_wrapper( cards.search_by_pan, ) - - @cached_property - def aggregate_balances(self) -> AggregateBalancesWithStreamingResponse: - return AggregateBalancesWithStreamingResponse(self._cards.aggregate_balances) + self.web_provision = to_streamed_response_wrapper( + cards.web_provision, + ) @cached_property def balances(self) -> BalancesWithStreamingResponse: @@ -1957,6 +2365,9 @@ def __init__(self, cards: AsyncCards) -> None: self.list = async_to_streamed_response_wrapper( cards.list, ) + self.convert_physical = async_to_streamed_response_wrapper( + cards.convert_physical, + ) self.embed = async_to_streamed_response_wrapper( cards.embed, ) @@ -1975,10 +2386,9 @@ def __init__(self, cards: AsyncCards) -> None: self.search_by_pan = async_to_streamed_response_wrapper( cards.search_by_pan, ) - - @cached_property - def aggregate_balances(self) -> AsyncAggregateBalancesWithStreamingResponse: - return AsyncAggregateBalancesWithStreamingResponse(self._cards.aggregate_balances) + self.web_provision = async_to_streamed_response_wrapper( + cards.web_provision, + ) @cached_property def balances(self) -> AsyncBalancesWithStreamingResponse: diff --git a/src/lithic/resources/cards/financial_transactions.py b/src/lithic/resources/cards/financial_transactions.py index 57864af9..bc97969b 100644 --- a/src/lithic/resources/cards/financial_transactions.py +++ b/src/lithic/resources/cards/financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,18 +9,15 @@ import httpx from ... import _legacy_response -from ...types import FinancialTransaction -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncSinglePage, AsyncSinglePage from ...types.cards import financial_transaction_list_params -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) +from ..._base_client import AsyncPaginator, make_request_options +from ...types.financial_transaction import FinancialTransaction __all__ = ["FinancialTransactions", "AsyncFinancialTransactions"] @@ -28,10 +25,21 @@ class FinancialTransactions(SyncAPIResource): @cached_property def with_raw_response(self) -> FinancialTransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return FinancialTransactionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> FinancialTransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return FinancialTransactionsWithStreamingResponse(self) def retrieve( @@ -44,7 +52,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialTransaction: """ Get the card financial transaction for the provided token. @@ -65,7 +73,7 @@ def retrieve( f"Expected a non-empty value for `financial_transaction_token` but received {financial_transaction_token!r}" ) return self._get( - f"/cards/{card_token}/financial_transactions/{financial_transaction_token}", + f"/v1/cards/{card_token}/financial_transactions/{financial_transaction_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -76,19 +84,19 @@ def list( self, card_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - category: Literal["CARD", "TRANSFER"] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + category: Literal["CARD", "TRANSFER"] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncSinglePage[FinancialTransaction]: """ List the financial transactions for a given card. @@ -123,7 +131,7 @@ def list( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._get_api_list( - f"/cards/{card_token}/financial_transactions", + f"/v1/cards/{card_token}/financial_transactions", page=SyncSinglePage[FinancialTransaction], options=make_request_options( extra_headers=extra_headers, @@ -150,10 +158,21 @@ def list( class AsyncFinancialTransactions(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncFinancialTransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncFinancialTransactionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncFinancialTransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncFinancialTransactionsWithStreamingResponse(self) async def retrieve( @@ -166,7 +185,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialTransaction: """ Get the card financial transaction for the provided token. @@ -187,7 +206,7 @@ async def retrieve( f"Expected a non-empty value for `financial_transaction_token` but received {financial_transaction_token!r}" ) return await self._get( - f"/cards/{card_token}/financial_transactions/{financial_transaction_token}", + f"/v1/cards/{card_token}/financial_transactions/{financial_transaction_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -198,19 +217,19 @@ def list( self, card_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - category: Literal["CARD", "TRANSFER"] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + category: Literal["CARD", "TRANSFER"] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FinancialTransaction, AsyncSinglePage[FinancialTransaction]]: """ List the financial transactions for a given card. @@ -245,7 +264,7 @@ def list( if not card_token: raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return self._get_api_list( - f"/cards/{card_token}/financial_transactions", + f"/v1/cards/{card_token}/financial_transactions", page=AsyncSinglePage[FinancialTransaction], options=make_request_options( extra_headers=extra_headers, diff --git a/src/lithic/resources/credit_products/__init__.py b/src/lithic/resources/credit_products/__init__.py new file mode 100644 index 00000000..f7b13736 --- /dev/null +++ b/src/lithic/resources/credit_products/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .prime_rates import ( + PrimeRates, + AsyncPrimeRates, + PrimeRatesWithRawResponse, + AsyncPrimeRatesWithRawResponse, + PrimeRatesWithStreamingResponse, + AsyncPrimeRatesWithStreamingResponse, +) +from .credit_products import ( + CreditProducts, + AsyncCreditProducts, + CreditProductsWithRawResponse, + AsyncCreditProductsWithRawResponse, + CreditProductsWithStreamingResponse, + AsyncCreditProductsWithStreamingResponse, +) +from .extended_credit import ( + ExtendedCreditResource, + AsyncExtendedCreditResource, + ExtendedCreditResourceWithRawResponse, + AsyncExtendedCreditResourceWithRawResponse, + ExtendedCreditResourceWithStreamingResponse, + AsyncExtendedCreditResourceWithStreamingResponse, +) + +__all__ = [ + "ExtendedCreditResource", + "AsyncExtendedCreditResource", + "ExtendedCreditResourceWithRawResponse", + "AsyncExtendedCreditResourceWithRawResponse", + "ExtendedCreditResourceWithStreamingResponse", + "AsyncExtendedCreditResourceWithStreamingResponse", + "PrimeRates", + "AsyncPrimeRates", + "PrimeRatesWithRawResponse", + "AsyncPrimeRatesWithRawResponse", + "PrimeRatesWithStreamingResponse", + "AsyncPrimeRatesWithStreamingResponse", + "CreditProducts", + "AsyncCreditProducts", + "CreditProductsWithRawResponse", + "AsyncCreditProductsWithRawResponse", + "CreditProductsWithStreamingResponse", + "AsyncCreditProductsWithStreamingResponse", +] diff --git a/src/lithic/resources/credit_products/credit_products.py b/src/lithic/resources/credit_products/credit_products.py new file mode 100644 index 00000000..5f6effe3 --- /dev/null +++ b/src/lithic/resources/credit_products/credit_products.py @@ -0,0 +1,134 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from .prime_rates import ( + PrimeRates, + AsyncPrimeRates, + PrimeRatesWithRawResponse, + AsyncPrimeRatesWithRawResponse, + PrimeRatesWithStreamingResponse, + AsyncPrimeRatesWithStreamingResponse, +) +from .extended_credit import ( + ExtendedCreditResource, + AsyncExtendedCreditResource, + ExtendedCreditResourceWithRawResponse, + AsyncExtendedCreditResourceWithRawResponse, + ExtendedCreditResourceWithStreamingResponse, + AsyncExtendedCreditResourceWithStreamingResponse, +) + +__all__ = ["CreditProducts", "AsyncCreditProducts"] + + +class CreditProducts(SyncAPIResource): + @cached_property + def extended_credit(self) -> ExtendedCreditResource: + return ExtendedCreditResource(self._client) + + @cached_property + def prime_rates(self) -> PrimeRates: + return PrimeRates(self._client) + + @cached_property + def with_raw_response(self) -> CreditProductsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return CreditProductsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CreditProductsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return CreditProductsWithStreamingResponse(self) + + +class AsyncCreditProducts(AsyncAPIResource): + @cached_property + def extended_credit(self) -> AsyncExtendedCreditResource: + return AsyncExtendedCreditResource(self._client) + + @cached_property + def prime_rates(self) -> AsyncPrimeRates: + return AsyncPrimeRates(self._client) + + @cached_property + def with_raw_response(self) -> AsyncCreditProductsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncCreditProductsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCreditProductsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncCreditProductsWithStreamingResponse(self) + + +class CreditProductsWithRawResponse: + def __init__(self, credit_products: CreditProducts) -> None: + self._credit_products = credit_products + + @cached_property + def extended_credit(self) -> ExtendedCreditResourceWithRawResponse: + return ExtendedCreditResourceWithRawResponse(self._credit_products.extended_credit) + + @cached_property + def prime_rates(self) -> PrimeRatesWithRawResponse: + return PrimeRatesWithRawResponse(self._credit_products.prime_rates) + + +class AsyncCreditProductsWithRawResponse: + def __init__(self, credit_products: AsyncCreditProducts) -> None: + self._credit_products = credit_products + + @cached_property + def extended_credit(self) -> AsyncExtendedCreditResourceWithRawResponse: + return AsyncExtendedCreditResourceWithRawResponse(self._credit_products.extended_credit) + + @cached_property + def prime_rates(self) -> AsyncPrimeRatesWithRawResponse: + return AsyncPrimeRatesWithRawResponse(self._credit_products.prime_rates) + + +class CreditProductsWithStreamingResponse: + def __init__(self, credit_products: CreditProducts) -> None: + self._credit_products = credit_products + + @cached_property + def extended_credit(self) -> ExtendedCreditResourceWithStreamingResponse: + return ExtendedCreditResourceWithStreamingResponse(self._credit_products.extended_credit) + + @cached_property + def prime_rates(self) -> PrimeRatesWithStreamingResponse: + return PrimeRatesWithStreamingResponse(self._credit_products.prime_rates) + + +class AsyncCreditProductsWithStreamingResponse: + def __init__(self, credit_products: AsyncCreditProducts) -> None: + self._credit_products = credit_products + + @cached_property + def extended_credit(self) -> AsyncExtendedCreditResourceWithStreamingResponse: + return AsyncExtendedCreditResourceWithStreamingResponse(self._credit_products.extended_credit) + + @cached_property + def prime_rates(self) -> AsyncPrimeRatesWithStreamingResponse: + return AsyncPrimeRatesWithStreamingResponse(self._credit_products.prime_rates) diff --git a/src/lithic/resources/credit_products/extended_credit.py b/src/lithic/resources/credit_products/extended_credit.py new file mode 100644 index 00000000..7b006763 --- /dev/null +++ b/src/lithic/resources/credit_products/extended_credit.py @@ -0,0 +1,163 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.credit_products.extended_credit import ExtendedCredit + +__all__ = ["ExtendedCreditResource", "AsyncExtendedCreditResource"] + + +class ExtendedCreditResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ExtendedCreditResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return ExtendedCreditResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExtendedCreditResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return ExtendedCreditResourceWithStreamingResponse(self) + + def retrieve( + self, + credit_product_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExtendedCredit: + """ + Get the extended credit for a given credit product under a program + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not credit_product_token: + raise ValueError( + f"Expected a non-empty value for `credit_product_token` but received {credit_product_token!r}" + ) + return self._get( + f"/v1/credit_products/{credit_product_token}/extended_credit", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExtendedCredit, + ) + + +class AsyncExtendedCreditResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExtendedCreditResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncExtendedCreditResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExtendedCreditResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncExtendedCreditResourceWithStreamingResponse(self) + + async def retrieve( + self, + credit_product_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExtendedCredit: + """ + Get the extended credit for a given credit product under a program + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not credit_product_token: + raise ValueError( + f"Expected a non-empty value for `credit_product_token` but received {credit_product_token!r}" + ) + return await self._get( + f"/v1/credit_products/{credit_product_token}/extended_credit", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExtendedCredit, + ) + + +class ExtendedCreditResourceWithRawResponse: + def __init__(self, extended_credit: ExtendedCreditResource) -> None: + self._extended_credit = extended_credit + + self.retrieve = _legacy_response.to_raw_response_wrapper( + extended_credit.retrieve, + ) + + +class AsyncExtendedCreditResourceWithRawResponse: + def __init__(self, extended_credit: AsyncExtendedCreditResource) -> None: + self._extended_credit = extended_credit + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + extended_credit.retrieve, + ) + + +class ExtendedCreditResourceWithStreamingResponse: + def __init__(self, extended_credit: ExtendedCreditResource) -> None: + self._extended_credit = extended_credit + + self.retrieve = to_streamed_response_wrapper( + extended_credit.retrieve, + ) + + +class AsyncExtendedCreditResourceWithStreamingResponse: + def __init__(self, extended_credit: AsyncExtendedCreditResource) -> None: + self._extended_credit = extended_credit + + self.retrieve = async_to_streamed_response_wrapper( + extended_credit.retrieve, + ) diff --git a/src/lithic/resources/credit_products/prime_rates.py b/src/lithic/resources/credit_products/prime_rates.py new file mode 100644 index 00000000..8c256481 --- /dev/null +++ b/src/lithic/resources/credit_products/prime_rates.py @@ -0,0 +1,316 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.credit_products import prime_rate_create_params, prime_rate_retrieve_params +from ...types.credit_products.prime_rate_retrieve_response import PrimeRateRetrieveResponse + +__all__ = ["PrimeRates", "AsyncPrimeRates"] + + +class PrimeRates(SyncAPIResource): + @cached_property + def with_raw_response(self) -> PrimeRatesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return PrimeRatesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PrimeRatesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return PrimeRatesWithStreamingResponse(self) + + def create( + self, + credit_product_token: str, + *, + effective_date: Union[str, date], + rate: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Post Credit Product Prime Rate + + Args: + credit_product_token: Globally unique identifier for credit products. + + effective_date: Date the rate goes into effect + + rate: The rate in decimal format + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not credit_product_token: + raise ValueError( + f"Expected a non-empty value for `credit_product_token` but received {credit_product_token!r}" + ) + return self._post( + f"/v1/credit_products/{credit_product_token}/prime_rates", + body=maybe_transform( + { + "effective_date": effective_date, + "rate": rate, + }, + prime_rate_create_params.PrimeRateCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def retrieve( + self, + credit_product_token: str, + *, + ending_before: Union[str, date] | Omit = omit, + starting_after: Union[str, date] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PrimeRateRetrieveResponse: + """ + Get Credit Product Prime Rates + + Args: + credit_product_token: Globally unique identifier for credit products. + + ending_before: The effective date that the prime rates ends before + + starting_after: The effective date that the prime rate starts after + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not credit_product_token: + raise ValueError( + f"Expected a non-empty value for `credit_product_token` but received {credit_product_token!r}" + ) + return self._get( + f"/v1/credit_products/{credit_product_token}/prime_rates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "ending_before": ending_before, + "starting_after": starting_after, + }, + prime_rate_retrieve_params.PrimeRateRetrieveParams, + ), + ), + cast_to=PrimeRateRetrieveResponse, + ) + + +class AsyncPrimeRates(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncPrimeRatesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncPrimeRatesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPrimeRatesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncPrimeRatesWithStreamingResponse(self) + + async def create( + self, + credit_product_token: str, + *, + effective_date: Union[str, date], + rate: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Post Credit Product Prime Rate + + Args: + credit_product_token: Globally unique identifier for credit products. + + effective_date: Date the rate goes into effect + + rate: The rate in decimal format + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not credit_product_token: + raise ValueError( + f"Expected a non-empty value for `credit_product_token` but received {credit_product_token!r}" + ) + return await self._post( + f"/v1/credit_products/{credit_product_token}/prime_rates", + body=await async_maybe_transform( + { + "effective_date": effective_date, + "rate": rate, + }, + prime_rate_create_params.PrimeRateCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def retrieve( + self, + credit_product_token: str, + *, + ending_before: Union[str, date] | Omit = omit, + starting_after: Union[str, date] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PrimeRateRetrieveResponse: + """ + Get Credit Product Prime Rates + + Args: + credit_product_token: Globally unique identifier for credit products. + + ending_before: The effective date that the prime rates ends before + + starting_after: The effective date that the prime rate starts after + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not credit_product_token: + raise ValueError( + f"Expected a non-empty value for `credit_product_token` but received {credit_product_token!r}" + ) + return await self._get( + f"/v1/credit_products/{credit_product_token}/prime_rates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "ending_before": ending_before, + "starting_after": starting_after, + }, + prime_rate_retrieve_params.PrimeRateRetrieveParams, + ), + ), + cast_to=PrimeRateRetrieveResponse, + ) + + +class PrimeRatesWithRawResponse: + def __init__(self, prime_rates: PrimeRates) -> None: + self._prime_rates = prime_rates + + self.create = _legacy_response.to_raw_response_wrapper( + prime_rates.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + prime_rates.retrieve, + ) + + +class AsyncPrimeRatesWithRawResponse: + def __init__(self, prime_rates: AsyncPrimeRates) -> None: + self._prime_rates = prime_rates + + self.create = _legacy_response.async_to_raw_response_wrapper( + prime_rates.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + prime_rates.retrieve, + ) + + +class PrimeRatesWithStreamingResponse: + def __init__(self, prime_rates: PrimeRates) -> None: + self._prime_rates = prime_rates + + self.create = to_streamed_response_wrapper( + prime_rates.create, + ) + self.retrieve = to_streamed_response_wrapper( + prime_rates.retrieve, + ) + + +class AsyncPrimeRatesWithStreamingResponse: + def __init__(self, prime_rates: AsyncPrimeRates) -> None: + self._prime_rates = prime_rates + + self.create = async_to_streamed_response_wrapper( + prime_rates.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + prime_rates.retrieve, + ) diff --git a/src/lithic/resources/digital_card_art.py b/src/lithic/resources/digital_card_art.py index 714026c9..e42ac01b 100644 --- a/src/lithic/resources/digital_card_art.py +++ b/src/lithic/resources/digital_card_art.py @@ -1,21 +1,19 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import httpx from .. import _legacy_response -from ..types import DigitalCardArt, digital_card_art_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..types import digital_card_art_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.digital_card_art import DigitalCardArt __all__ = ["DigitalCardArtResource", "AsyncDigitalCardArtResource"] @@ -23,10 +21,21 @@ class DigitalCardArtResource(SyncAPIResource): @cached_property def with_raw_response(self) -> DigitalCardArtResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return DigitalCardArtResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> DigitalCardArtResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return DigitalCardArtResourceWithStreamingResponse(self) def retrieve( @@ -38,7 +47,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DigitalCardArt: """ Get digital card art by token. @@ -57,7 +66,7 @@ def retrieve( f"Expected a non-empty value for `digital_card_art_token` but received {digital_card_art_token!r}" ) return self._get( - f"/digital_card_art/{digital_card_art_token}", + f"/v1/digital_card_art/{digital_card_art_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -67,15 +76,15 @@ def retrieve( def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[DigitalCardArt]: """ List digital card art. @@ -98,7 +107,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/digital_card_art", + "/v1/digital_card_art", page=SyncCursorPage[DigitalCardArt], options=make_request_options( extra_headers=extra_headers, @@ -121,10 +130,21 @@ def list( class AsyncDigitalCardArtResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncDigitalCardArtResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncDigitalCardArtResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncDigitalCardArtResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncDigitalCardArtResourceWithStreamingResponse(self) async def retrieve( @@ -136,7 +156,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DigitalCardArt: """ Get digital card art by token. @@ -155,7 +175,7 @@ async def retrieve( f"Expected a non-empty value for `digital_card_art_token` but received {digital_card_art_token!r}" ) return await self._get( - f"/digital_card_art/{digital_card_art_token}", + f"/v1/digital_card_art/{digital_card_art_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -165,15 +185,15 @@ async def retrieve( def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[DigitalCardArt, AsyncCursorPage[DigitalCardArt]]: """ List digital card art. @@ -196,7 +216,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/digital_card_art", + "/v1/digital_card_art", page=AsyncCursorPage[DigitalCardArt], options=make_request_options( extra_headers=extra_headers, diff --git a/src/lithic/resources/disputes.py b/src/lithic/resources/disputes.py index 627d2bea..2ed5c7db 100644 --- a/src/lithic/resources/disputes.py +++ b/src/lithic/resources/disputes.py @@ -1,8 +1,8 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing import List, Union +from typing import Union from datetime import datetime from typing_extensions import Literal @@ -10,33 +10,21 @@ from .. import _legacy_response from ..types import ( - Dispute, - DisputeEvidence, dispute_list_params, dispute_create_params, dispute_update_params, dispute_list_evidences_params, dispute_initiate_evidence_upload_params, ) -from .._types import ( - NOT_GIVEN, - Body, - Omit, - Query, - Headers, - NoneType, - NotGiven, - FileTypes, -) -from .._utils import maybe_transform +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.dispute import Dispute +from ..types.dispute_evidence import DisputeEvidence __all__ = ["Disputes", "AsyncDisputes"] @@ -44,10 +32,21 @@ class Disputes(SyncAPIResource): @cached_property def with_raw_response(self) -> DisputesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return DisputesWithRawResponse(self) @cached_property def with_streaming_response(self) -> DisputesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return DisputesWithStreamingResponse(self) def create( @@ -71,14 +70,14 @@ def create( "REFUND_NOT_PROCESSED", ], transaction_token: str, - customer_filed_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - customer_note: str | NotGiven = NOT_GIVEN, + customer_filed_date: Union[str, datetime] | Omit = omit, + customer_note: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """ Initiate a dispute. @@ -103,7 +102,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/disputes", + "/v1/disputes", body=maybe_transform( { "amount": amount, @@ -129,7 +128,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """ Get dispute. @@ -146,7 +145,7 @@ def retrieve( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return self._get( - f"/disputes/{dispute_token}", + f"/v1/disputes/{dispute_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -157,9 +156,9 @@ def update( self, dispute_token: str, *, - amount: int | NotGiven = NOT_GIVEN, - customer_filed_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - customer_note: str | NotGiven = NOT_GIVEN, + amount: int | Omit = omit, + customer_filed_date: Union[str, datetime] | Omit = omit, + customer_note: str | Omit = omit, reason: Literal[ "ATM_CASH_MISDISPENSE", "CANCELLED", @@ -176,13 +175,13 @@ def update( "RECURRING_TRANSACTION_NOT_CANCELLED", "REFUND_NOT_PROCESSED", ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """Update dispute. @@ -208,7 +207,7 @@ def update( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return self._patch( - f"/disputes/{dispute_token}", + f"/v1/disputes/{dispute_token}", body=maybe_transform( { "amount": amount, @@ -227,11 +226,11 @@ def update( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, status: Literal[ "ARBITRATION", "CASE_CLOSED", @@ -242,14 +241,14 @@ def list( "REPRESENTMENT", "SUBMITTED", ] - | NotGiven = NOT_GIVEN, - transaction_tokens: List[str] | NotGiven = NOT_GIVEN, + | Omit = omit, + transaction_tokens: SequenceNotStr[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Dispute]: """List disputes. @@ -283,7 +282,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/disputes", + "/v1/disputes", page=SyncCursorPage[Dispute], options=make_request_options( extra_headers=extra_headers, @@ -315,7 +314,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """ Withdraw dispute. @@ -332,7 +331,7 @@ def delete( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return self._delete( - f"/disputes/{dispute_token}", + f"/v1/disputes/{dispute_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -349,7 +348,7 @@ def delete_evidence( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisputeEvidence: """Soft delete evidence for a dispute. @@ -370,7 +369,7 @@ def delete_evidence( if not evidence_token: raise ValueError(f"Expected a non-empty value for `evidence_token` but received {evidence_token!r}") return self._delete( - f"/disputes/{dispute_token}/evidences/{evidence_token}", + f"/v1/disputes/{dispute_token}/evidences/{evidence_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -381,13 +380,13 @@ def initiate_evidence_upload( self, dispute_token: str, *, - filename: str | NotGiven = NOT_GIVEN, + filename: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisputeEvidence: """Use this endpoint to upload evidences for the dispute. @@ -411,7 +410,7 @@ def initiate_evidence_upload( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return self._post( - f"/disputes/{dispute_token}/evidences", + f"/v1/disputes/{dispute_token}/evidences", body=maybe_transform( {"filename": filename}, dispute_initiate_evidence_upload_params.DisputeInitiateEvidenceUploadParams ), @@ -425,17 +424,17 @@ def list_evidences( self, dispute_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[DisputeEvidence]: """ List evidence metadata for a dispute. @@ -466,7 +465,7 @@ def list_evidences( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return self._get_api_list( - f"/disputes/{dispute_token}/evidences", + f"/v1/disputes/{dispute_token}/evidences", page=SyncCursorPage[DisputeEvidence], options=make_request_options( extra_headers=extra_headers, @@ -497,7 +496,7 @@ def retrieve_evidence( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisputeEvidence: """ Get a dispute's evidence metadata. @@ -516,43 +515,32 @@ def retrieve_evidence( if not evidence_token: raise ValueError(f"Expected a non-empty value for `evidence_token` but received {evidence_token!r}") return self._get( - f"/disputes/{dispute_token}/evidences/{evidence_token}", + f"/v1/disputes/{dispute_token}/evidences/{evidence_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=DisputeEvidence, ) - def upload_evidence( - self, - dispute_token: str, - file: FileTypes, - *, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """ - Initiates the Dispute Evidence Upload, then uploads the file to the returned - `upload_url`. - """ - payload = self._client.disputes.initiate_evidence_upload( - dispute_token, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body - ) - if not payload.upload_url: - raise ValueError("Missing 'upload_url' from response payload") - files = {"file": file} - options = make_request_options(extra_headers={"Authorization": Omit()}) - self._put(payload.upload_url, cast_to=NoneType, body=None, files=files, options=options) - class AsyncDisputes(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncDisputesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncDisputesWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncDisputesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncDisputesWithStreamingResponse(self) async def create( @@ -576,14 +564,14 @@ async def create( "REFUND_NOT_PROCESSED", ], transaction_token: str, - customer_filed_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - customer_note: str | NotGiven = NOT_GIVEN, + customer_filed_date: Union[str, datetime] | Omit = omit, + customer_note: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """ Initiate a dispute. @@ -608,8 +596,8 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/disputes", - body=maybe_transform( + "/v1/disputes", + body=await async_maybe_transform( { "amount": amount, "reason": reason, @@ -634,7 +622,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """ Get dispute. @@ -651,7 +639,7 @@ async def retrieve( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return await self._get( - f"/disputes/{dispute_token}", + f"/v1/disputes/{dispute_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -662,9 +650,9 @@ async def update( self, dispute_token: str, *, - amount: int | NotGiven = NOT_GIVEN, - customer_filed_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - customer_note: str | NotGiven = NOT_GIVEN, + amount: int | Omit = omit, + customer_filed_date: Union[str, datetime] | Omit = omit, + customer_note: str | Omit = omit, reason: Literal[ "ATM_CASH_MISDISPENSE", "CANCELLED", @@ -681,13 +669,13 @@ async def update( "RECURRING_TRANSACTION_NOT_CANCELLED", "REFUND_NOT_PROCESSED", ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """Update dispute. @@ -713,8 +701,8 @@ async def update( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return await self._patch( - f"/disputes/{dispute_token}", - body=maybe_transform( + f"/v1/disputes/{dispute_token}", + body=await async_maybe_transform( { "amount": amount, "customer_filed_date": customer_filed_date, @@ -732,11 +720,11 @@ async def update( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, status: Literal[ "ARBITRATION", "CASE_CLOSED", @@ -747,14 +735,14 @@ def list( "REPRESENTMENT", "SUBMITTED", ] - | NotGiven = NOT_GIVEN, - transaction_tokens: List[str] | NotGiven = NOT_GIVEN, + | Omit = omit, + transaction_tokens: SequenceNotStr[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Dispute, AsyncCursorPage[Dispute]]: """List disputes. @@ -788,7 +776,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/disputes", + "/v1/disputes", page=AsyncCursorPage[Dispute], options=make_request_options( extra_headers=extra_headers, @@ -820,7 +808,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Dispute: """ Withdraw dispute. @@ -837,7 +825,7 @@ async def delete( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return await self._delete( - f"/disputes/{dispute_token}", + f"/v1/disputes/{dispute_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -854,7 +842,7 @@ async def delete_evidence( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisputeEvidence: """Soft delete evidence for a dispute. @@ -875,7 +863,7 @@ async def delete_evidence( if not evidence_token: raise ValueError(f"Expected a non-empty value for `evidence_token` but received {evidence_token!r}") return await self._delete( - f"/disputes/{dispute_token}/evidences/{evidence_token}", + f"/v1/disputes/{dispute_token}/evidences/{evidence_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -886,13 +874,13 @@ async def initiate_evidence_upload( self, dispute_token: str, *, - filename: str | NotGiven = NOT_GIVEN, + filename: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisputeEvidence: """Use this endpoint to upload evidences for the dispute. @@ -916,8 +904,8 @@ async def initiate_evidence_upload( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return await self._post( - f"/disputes/{dispute_token}/evidences", - body=maybe_transform( + f"/v1/disputes/{dispute_token}/evidences", + body=await async_maybe_transform( {"filename": filename}, dispute_initiate_evidence_upload_params.DisputeInitiateEvidenceUploadParams ), options=make_request_options( @@ -930,17 +918,17 @@ def list_evidences( self, dispute_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[DisputeEvidence, AsyncCursorPage[DisputeEvidence]]: """ List evidence metadata for a dispute. @@ -971,7 +959,7 @@ def list_evidences( if not dispute_token: raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return self._get_api_list( - f"/disputes/{dispute_token}/evidences", + f"/v1/disputes/{dispute_token}/evidences", page=AsyncCursorPage[DisputeEvidence], options=make_request_options( extra_headers=extra_headers, @@ -1002,7 +990,7 @@ async def retrieve_evidence( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisputeEvidence: """ Get a dispute's evidence metadata. @@ -1021,35 +1009,13 @@ async def retrieve_evidence( if not evidence_token: raise ValueError(f"Expected a non-empty value for `evidence_token` but received {evidence_token!r}") return await self._get( - f"/disputes/{dispute_token}/evidences/{evidence_token}", + f"/v1/disputes/{dispute_token}/evidences/{evidence_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=DisputeEvidence, ) - async def upload_evidence( - self, - dispute_token: str, - file: FileTypes, - *, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """ - Initiates the Dispute Evidence Upload, then uploads the file to the returned - `upload_url`. - """ - payload = await self._client.disputes.initiate_evidence_upload( - dispute_token, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body - ) - if not payload.upload_url: - raise ValueError("Missing 'upload_url' from response payload") - files = {"file": file} - options = make_request_options(extra_headers={"Authorization": Omit()}) - await self._put(payload.upload_url, cast_to=NoneType, body=None, files=files, options=options) - class DisputesWithRawResponse: def __init__(self, disputes: Disputes) -> None: diff --git a/src/lithic/resources/disputes_v2.py b/src/lithic/resources/disputes_v2.py new file mode 100644 index 00000000..f6433efa --- /dev/null +++ b/src/lithic/resources/disputes_v2.py @@ -0,0 +1,325 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime + +import httpx + +from .. import _legacy_response +from ..types import disputes_v2_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.dispute_v2 import DisputeV2 + +__all__ = ["DisputesV2", "AsyncDisputesV2"] + + +class DisputesV2(SyncAPIResource): + @cached_property + def with_raw_response(self) -> DisputesV2WithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return DisputesV2WithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DisputesV2WithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return DisputesV2WithStreamingResponse(self) + + def retrieve( + self, + dispute_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DisputeV2: + """ + Retrieves a specific dispute by its token. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dispute_token: + raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") + return self._get( + f"/v2/disputes/{dispute_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DisputeV2, + ) + + def list( + self, + *, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + card_token: str | Omit = omit, + disputed_transaction_token: str | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[DisputeV2]: + """ + Returns a paginated list of disputes. + + Args: + account_token: Filter by account token. + + begin: RFC 3339 timestamp for filtering by created date, inclusive. + + card_token: Filter by card token. + + disputed_transaction_token: Filter by the token of the transaction being disputed. Corresponds with + transaction_series.related_transaction_token in the Dispute. + + end: RFC 3339 timestamp for filtering by created date, inclusive. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Number of items to return. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v2/disputes", + page=SyncCursorPage[DisputeV2], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "begin": begin, + "card_token": card_token, + "disputed_transaction_token": disputed_transaction_token, + "end": end, + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + disputes_v2_list_params.DisputesV2ListParams, + ), + ), + model=DisputeV2, + ) + + +class AsyncDisputesV2(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncDisputesV2WithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncDisputesV2WithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDisputesV2WithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncDisputesV2WithStreamingResponse(self) + + async def retrieve( + self, + dispute_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DisputeV2: + """ + Retrieves a specific dispute by its token. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dispute_token: + raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") + return await self._get( + f"/v2/disputes/{dispute_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DisputeV2, + ) + + def list( + self, + *, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + card_token: str | Omit = omit, + disputed_transaction_token: str | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[DisputeV2, AsyncCursorPage[DisputeV2]]: + """ + Returns a paginated list of disputes. + + Args: + account_token: Filter by account token. + + begin: RFC 3339 timestamp for filtering by created date, inclusive. + + card_token: Filter by card token. + + disputed_transaction_token: Filter by the token of the transaction being disputed. Corresponds with + transaction_series.related_transaction_token in the Dispute. + + end: RFC 3339 timestamp for filtering by created date, inclusive. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Number of items to return. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v2/disputes", + page=AsyncCursorPage[DisputeV2], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "account_token": account_token, + "begin": begin, + "card_token": card_token, + "disputed_transaction_token": disputed_transaction_token, + "end": end, + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + disputes_v2_list_params.DisputesV2ListParams, + ), + ), + model=DisputeV2, + ) + + +class DisputesV2WithRawResponse: + def __init__(self, disputes_v2: DisputesV2) -> None: + self._disputes_v2 = disputes_v2 + + self.retrieve = _legacy_response.to_raw_response_wrapper( + disputes_v2.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + disputes_v2.list, + ) + + +class AsyncDisputesV2WithRawResponse: + def __init__(self, disputes_v2: AsyncDisputesV2) -> None: + self._disputes_v2 = disputes_v2 + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + disputes_v2.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + disputes_v2.list, + ) + + +class DisputesV2WithStreamingResponse: + def __init__(self, disputes_v2: DisputesV2) -> None: + self._disputes_v2 = disputes_v2 + + self.retrieve = to_streamed_response_wrapper( + disputes_v2.retrieve, + ) + self.list = to_streamed_response_wrapper( + disputes_v2.list, + ) + + +class AsyncDisputesV2WithStreamingResponse: + def __init__(self, disputes_v2: AsyncDisputesV2) -> None: + self._disputes_v2 = disputes_v2 + + self.retrieve = async_to_streamed_response_wrapper( + disputes_v2.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + disputes_v2.list, + ) diff --git a/src/lithic/resources/events/__init__.py b/src/lithic/resources/events/__init__.py index d788f91f..bb505c94 100644 --- a/src/lithic/resources/events/__init__.py +++ b/src/lithic/resources/events/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .events import ( Events, @@ -16,6 +16,14 @@ SubscriptionsWithStreamingResponse, AsyncSubscriptionsWithStreamingResponse, ) +from .event_subscriptions import ( + EventSubscriptions, + AsyncEventSubscriptions, + EventSubscriptionsWithRawResponse, + AsyncEventSubscriptionsWithRawResponse, + EventSubscriptionsWithStreamingResponse, + AsyncEventSubscriptionsWithStreamingResponse, +) __all__ = [ "Subscriptions", @@ -24,6 +32,12 @@ "AsyncSubscriptionsWithRawResponse", "SubscriptionsWithStreamingResponse", "AsyncSubscriptionsWithStreamingResponse", + "EventSubscriptions", + "AsyncEventSubscriptions", + "EventSubscriptionsWithRawResponse", + "AsyncEventSubscriptionsWithRawResponse", + "EventSubscriptionsWithStreamingResponse", + "AsyncEventSubscriptionsWithStreamingResponse", "Events", "AsyncEvents", "EventsWithRawResponse", diff --git a/src/lithic/resources/events/event_subscriptions.py b/src/lithic/resources/events/event_subscriptions.py new file mode 100644 index 00000000..de49709b --- /dev/null +++ b/src/lithic/resources/events/event_subscriptions.py @@ -0,0 +1,168 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options + +__all__ = ["EventSubscriptions", "AsyncEventSubscriptions"] + + +class EventSubscriptions(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EventSubscriptionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return EventSubscriptionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EventSubscriptionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return EventSubscriptionsWithStreamingResponse(self) + + def resend( + self, + event_subscription_token: str, + *, + event_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Resend an event to an event subscription. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not event_token: + raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") + if not event_subscription_token: + raise ValueError( + f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" + ) + return self._post( + f"/v1/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncEventSubscriptions(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEventSubscriptionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncEventSubscriptionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEventSubscriptionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncEventSubscriptionsWithStreamingResponse(self) + + async def resend( + self, + event_subscription_token: str, + *, + event_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Resend an event to an event subscription. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not event_token: + raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") + if not event_subscription_token: + raise ValueError( + f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" + ) + return await self._post( + f"/v1/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class EventSubscriptionsWithRawResponse: + def __init__(self, event_subscriptions: EventSubscriptions) -> None: + self._event_subscriptions = event_subscriptions + + self.resend = _legacy_response.to_raw_response_wrapper( + event_subscriptions.resend, + ) + + +class AsyncEventSubscriptionsWithRawResponse: + def __init__(self, event_subscriptions: AsyncEventSubscriptions) -> None: + self._event_subscriptions = event_subscriptions + + self.resend = _legacy_response.async_to_raw_response_wrapper( + event_subscriptions.resend, + ) + + +class EventSubscriptionsWithStreamingResponse: + def __init__(self, event_subscriptions: EventSubscriptions) -> None: + self._event_subscriptions = event_subscriptions + + self.resend = to_streamed_response_wrapper( + event_subscriptions.resend, + ) + + +class AsyncEventSubscriptionsWithStreamingResponse: + def __init__(self, event_subscriptions: AsyncEventSubscriptions) -> None: + self._event_subscriptions = event_subscriptions + + self.resend = async_to_streamed_response_wrapper( + event_subscriptions.resend, + ) diff --git a/src/lithic/resources/events/events.py b/src/lithic/resources/events/events.py index 76bb29e4..bba8e35d 100644 --- a/src/lithic/resources/events/events.py +++ b/src/lithic/resources/events/events.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,18 +9,14 @@ import httpx from ... import _legacy_response -from ...types import ( - Event, - MessageAttempt, - event_list_params, - event_list_attempts_params, -) -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from ...types import event_list_params, event_list_attempts_params +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncCursorPage, AsyncCursorPage +from ...types.event import Event from .subscriptions import ( Subscriptions, AsyncSubscriptions, @@ -29,10 +25,16 @@ SubscriptionsWithStreamingResponse, AsyncSubscriptionsWithStreamingResponse, ) -from ..._base_client import ( - AsyncPaginator, - make_request_options, +from ..._base_client import AsyncPaginator, make_request_options +from .event_subscriptions import ( + EventSubscriptions, + AsyncEventSubscriptions, + EventSubscriptionsWithRawResponse, + AsyncEventSubscriptionsWithRawResponse, + EventSubscriptionsWithStreamingResponse, + AsyncEventSubscriptionsWithStreamingResponse, ) +from ...types.message_attempt import MessageAttempt __all__ = ["Events", "AsyncEvents"] @@ -42,12 +44,27 @@ class Events(SyncAPIResource): def subscriptions(self) -> Subscriptions: return Subscriptions(self._client) + @cached_property + def event_subscriptions(self) -> EventSubscriptions: + return EventSubscriptions(self._client) + @cached_property def with_raw_response(self) -> EventsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return EventsWithRawResponse(self) @cached_property def with_streaming_response(self) -> EventsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return EventsWithStreamingResponse(self) def retrieve( @@ -59,7 +76,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Event: """ Get an event. @@ -76,7 +93,7 @@ def retrieve( if not event_token: raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") return self._get( - f"/events/{event_token}", + f"/v1/events/{event_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -86,40 +103,75 @@ def retrieve( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] - | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - with_content: bool | NotGiven = NOT_GIVEN, + | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + with_content: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Event]: """List all events. @@ -153,7 +205,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/events", + "/v1/events", page=SyncCursorPage[Event], options=make_request_options( extra_headers=extra_headers, @@ -180,18 +232,18 @@ def list_attempts( self, event_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[MessageAttempt]: """ List all the message attempts for a given event. @@ -222,7 +274,7 @@ def list_attempts( if not event_token: raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") return self._get_api_list( - f"/events/{event_token}/attempts", + f"/v1/events/{event_token}/attempts", page=SyncCursorPage[MessageAttempt], options=make_request_options( extra_headers=extra_headers, @@ -244,34 +296,33 @@ def list_attempts( model=MessageAttempt, ) - def resend( - self, - event_token: str, - *, - event_subscription_token: str, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """Resend an event to an event subscription.""" - self._post( - f"/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", - options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body), - cast_to=NoneType, - ) - class AsyncEvents(AsyncAPIResource): @cached_property def subscriptions(self) -> AsyncSubscriptions: return AsyncSubscriptions(self._client) + @cached_property + def event_subscriptions(self) -> AsyncEventSubscriptions: + return AsyncEventSubscriptions(self._client) + @cached_property def with_raw_response(self) -> AsyncEventsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncEventsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncEventsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncEventsWithStreamingResponse(self) async def retrieve( @@ -283,7 +334,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Event: """ Get an event. @@ -300,7 +351,7 @@ async def retrieve( if not event_token: raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") return await self._get( - f"/events/{event_token}", + f"/v1/events/{event_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -310,40 +361,75 @@ async def retrieve( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] - | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - with_content: bool | NotGiven = NOT_GIVEN, + | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + with_content: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Event, AsyncCursorPage[Event]]: """List all events. @@ -377,7 +463,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/events", + "/v1/events", page=AsyncCursorPage[Event], options=make_request_options( extra_headers=extra_headers, @@ -404,18 +490,18 @@ def list_attempts( self, event_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[MessageAttempt, AsyncCursorPage[MessageAttempt]]: """ List all the message attempts for a given event. @@ -446,7 +532,7 @@ def list_attempts( if not event_token: raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") return self._get_api_list( - f"/events/{event_token}/attempts", + f"/v1/events/{event_token}/attempts", page=AsyncCursorPage[MessageAttempt], options=make_request_options( extra_headers=extra_headers, @@ -468,22 +554,6 @@ def list_attempts( model=MessageAttempt, ) - async def resend( - self, - event_token: str, - *, - event_subscription_token: str, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """Resend an event to an event subscription.""" - await self._post( - f"/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", - options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body), - cast_to=NoneType, - ) - class EventsWithRawResponse: def __init__(self, events: Events) -> None: @@ -503,6 +573,10 @@ def __init__(self, events: Events) -> None: def subscriptions(self) -> SubscriptionsWithRawResponse: return SubscriptionsWithRawResponse(self._events.subscriptions) + @cached_property + def event_subscriptions(self) -> EventSubscriptionsWithRawResponse: + return EventSubscriptionsWithRawResponse(self._events.event_subscriptions) + class AsyncEventsWithRawResponse: def __init__(self, events: AsyncEvents) -> None: @@ -522,6 +596,10 @@ def __init__(self, events: AsyncEvents) -> None: def subscriptions(self) -> AsyncSubscriptionsWithRawResponse: return AsyncSubscriptionsWithRawResponse(self._events.subscriptions) + @cached_property + def event_subscriptions(self) -> AsyncEventSubscriptionsWithRawResponse: + return AsyncEventSubscriptionsWithRawResponse(self._events.event_subscriptions) + class EventsWithStreamingResponse: def __init__(self, events: Events) -> None: @@ -541,6 +619,10 @@ def __init__(self, events: Events) -> None: def subscriptions(self) -> SubscriptionsWithStreamingResponse: return SubscriptionsWithStreamingResponse(self._events.subscriptions) + @cached_property + def event_subscriptions(self) -> EventSubscriptionsWithStreamingResponse: + return EventSubscriptionsWithStreamingResponse(self._events.event_subscriptions) + class AsyncEventsWithStreamingResponse: def __init__(self, events: AsyncEvents) -> None: @@ -559,3 +641,7 @@ def __init__(self, events: AsyncEvents) -> None: @cached_property def subscriptions(self) -> AsyncSubscriptionsWithStreamingResponse: return AsyncSubscriptionsWithStreamingResponse(self._events.subscriptions) + + @cached_property + def event_subscriptions(self) -> AsyncEventSubscriptionsWithStreamingResponse: + return AsyncEventSubscriptionsWithStreamingResponse(self._events.event_subscriptions) diff --git a/src/lithic/resources/events/subscriptions.py b/src/lithic/resources/events/subscriptions.py index 331a9779..263ee10a 100644 --- a/src/lithic/resources/events/subscriptions.py +++ b/src/lithic/resources/events/subscriptions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,19 +9,14 @@ import httpx from ... import _legacy_response -from ...types import MessageAttempt, EventSubscription -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import maybe_transform +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncCursorPage, AsyncCursorPage -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) +from ..._base_client import AsyncPaginator, make_request_options from ...types.events import ( - SubscriptionRetrieveSecretResponse, subscription_list_params, subscription_create_params, subscription_update_params, @@ -30,6 +25,9 @@ subscription_replay_missing_params, subscription_send_simulated_example_params, ) +from ...types.message_attempt import MessageAttempt +from ...types.event_subscription import EventSubscription +from ...types.events.subscription_retrieve_secret_response import SubscriptionRetrieveSecretResponse __all__ = ["Subscriptions", "AsyncSubscriptions"] @@ -37,46 +35,92 @@ class Subscriptions(SyncAPIResource): @cached_property def with_raw_response(self) -> SubscriptionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return SubscriptionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> SubscriptionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return SubscriptionsWithStreamingResponse(self) def create( self, *, url: str, - description: str | NotGiven = NOT_GIVEN, - disabled: bool | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + disabled: bool | Omit = omit, event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EventSubscription: """ Create a new event subscription. @@ -100,7 +144,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/event_subscriptions", + "/v1/event_subscriptions", body=maybe_transform( { "url": url, @@ -125,7 +169,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EventSubscription: """ Get an event subscription. @@ -144,7 +188,7 @@ def retrieve( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._get( - f"/event_subscriptions/{event_subscription_token}", + f"/v1/event_subscriptions/{event_subscription_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -156,36 +200,71 @@ def update( event_subscription_token: str, *, url: str, - description: str | NotGiven = NOT_GIVEN, - disabled: bool | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + disabled: bool | Omit = omit, event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EventSubscription: """ Update an event subscription. @@ -213,7 +292,7 @@ def update( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._patch( - f"/event_subscriptions/{event_subscription_token}", + f"/v1/event_subscriptions/{event_subscription_token}", body=maybe_transform( { "url": url, @@ -232,15 +311,15 @@ def update( def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[EventSubscription]: """ List all the event subscriptions. @@ -263,7 +342,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/event_subscriptions", + "/v1/event_subscriptions", page=SyncCursorPage[EventSubscription], options=make_request_options( extra_headers=extra_headers, @@ -291,7 +370,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Delete an event subscription. @@ -310,7 +389,7 @@ def delete( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._delete( - f"/event_subscriptions/{event_subscription_token}", + f"/v1/event_subscriptions/{event_subscription_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -321,18 +400,18 @@ def list_attempts( self, event_subscription_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[MessageAttempt]: """ List all the message attempts for a given event subscription. @@ -365,7 +444,7 @@ def list_attempts( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._get_api_list( - f"/event_subscriptions/{event_subscription_token}/attempts", + f"/v1/event_subscriptions/{event_subscription_token}/attempts", page=SyncCursorPage[MessageAttempt], options=make_request_options( extra_headers=extra_headers, @@ -391,14 +470,14 @@ def recover( self, event_subscription_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Resend all failed messages since a given time. @@ -423,7 +502,7 @@ def recover( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._post( - f"/event_subscriptions/{event_subscription_token}/recover", + f"/v1/event_subscriptions/{event_subscription_token}/recover", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -444,14 +523,14 @@ def replay_missing( self, event_subscription_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Replays messages to the endpoint. @@ -481,7 +560,7 @@ def replay_missing( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._post( - f"/event_subscriptions/{event_subscription_token}/replay_missing", + f"/v1/event_subscriptions/{event_subscription_token}/replay_missing", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -507,7 +586,7 @@ def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SubscriptionRetrieveSecretResponse: """ Get the secret for an event subscription. @@ -526,7 +605,7 @@ def retrieve_secret( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._get( - f"/event_subscriptions/{event_subscription_token}/secret", + f"/v1/event_subscriptions/{event_subscription_token}/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -542,7 +621,7 @@ def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Rotate the secret for an event subscription. @@ -563,7 +642,7 @@ def rotate_secret( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._post( - f"/event_subscriptions/{event_subscription_token}/secret/rotate", + f"/v1/event_subscriptions/{event_subscription_token}/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -575,31 +654,66 @@ def send_simulated_example( event_subscription_token: str, *, event_type: Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Send an example message for event. @@ -620,7 +734,7 @@ def send_simulated_example( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._post( - f"/simulate/event_subscriptions/{event_subscription_token}/send_example", + f"/v1/simulate/event_subscriptions/{event_subscription_token}/send_example", body=maybe_transform( {"event_type": event_type}, subscription_send_simulated_example_params.SubscriptionSendSimulatedExampleParams, @@ -635,46 +749,92 @@ def send_simulated_example( class AsyncSubscriptions(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncSubscriptionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncSubscriptionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncSubscriptionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncSubscriptionsWithStreamingResponse(self) async def create( self, *, url: str, - description: str | NotGiven = NOT_GIVEN, - disabled: bool | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + disabled: bool | Omit = omit, event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EventSubscription: """ Create a new event subscription. @@ -698,8 +858,8 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/event_subscriptions", - body=maybe_transform( + "/v1/event_subscriptions", + body=await async_maybe_transform( { "url": url, "description": description, @@ -723,7 +883,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EventSubscription: """ Get an event subscription. @@ -742,7 +902,7 @@ async def retrieve( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._get( - f"/event_subscriptions/{event_subscription_token}", + f"/v1/event_subscriptions/{event_subscription_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -754,36 +914,71 @@ async def update( event_subscription_token: str, *, url: str, - description: str | NotGiven = NOT_GIVEN, - disabled: bool | NotGiven = NOT_GIVEN, + description: str | Omit = omit, + disabled: bool | Omit = omit, event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EventSubscription: """ Update an event subscription. @@ -811,8 +1006,8 @@ async def update( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._patch( - f"/event_subscriptions/{event_subscription_token}", - body=maybe_transform( + f"/v1/event_subscriptions/{event_subscription_token}", + body=await async_maybe_transform( { "url": url, "description": description, @@ -830,15 +1025,15 @@ async def update( def list( self, *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[EventSubscription, AsyncCursorPage[EventSubscription]]: """ List all the event subscriptions. @@ -861,7 +1056,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/event_subscriptions", + "/v1/event_subscriptions", page=AsyncCursorPage[EventSubscription], options=make_request_options( extra_headers=extra_headers, @@ -889,7 +1084,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Delete an event subscription. @@ -908,7 +1103,7 @@ async def delete( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._delete( - f"/event_subscriptions/{event_subscription_token}", + f"/v1/event_subscriptions/{event_subscription_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -919,18 +1114,18 @@ def list_attempts( self, event_subscription_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["FAILED", "PENDING", "SENDING", "SUCCESS"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[MessageAttempt, AsyncCursorPage[MessageAttempt]]: """ List all the message attempts for a given event subscription. @@ -963,7 +1158,7 @@ def list_attempts( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return self._get_api_list( - f"/event_subscriptions/{event_subscription_token}/attempts", + f"/v1/event_subscriptions/{event_subscription_token}/attempts", page=AsyncCursorPage[MessageAttempt], options=make_request_options( extra_headers=extra_headers, @@ -989,14 +1184,14 @@ async def recover( self, event_subscription_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Resend all failed messages since a given time. @@ -1021,13 +1216,13 @@ async def recover( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._post( - f"/event_subscriptions/{event_subscription_token}/recover", + f"/v1/event_subscriptions/{event_subscription_token}/recover", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( { "begin": begin, "end": end, @@ -1042,14 +1237,14 @@ async def replay_missing( self, event_subscription_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Replays messages to the endpoint. @@ -1079,13 +1274,13 @@ async def replay_missing( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._post( - f"/event_subscriptions/{event_subscription_token}/replay_missing", + f"/v1/event_subscriptions/{event_subscription_token}/replay_missing", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( { "begin": begin, "end": end, @@ -1105,7 +1300,7 @@ async def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SubscriptionRetrieveSecretResponse: """ Get the secret for an event subscription. @@ -1124,7 +1319,7 @@ async def retrieve_secret( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._get( - f"/event_subscriptions/{event_subscription_token}/secret", + f"/v1/event_subscriptions/{event_subscription_token}/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1140,7 +1335,7 @@ async def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Rotate the secret for an event subscription. @@ -1161,7 +1356,7 @@ async def rotate_secret( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._post( - f"/event_subscriptions/{event_subscription_token}/secret/rotate", + f"/v1/event_subscriptions/{event_subscription_token}/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1173,31 +1368,66 @@ async def send_simulated_example( event_subscription_token: str, *, event_type: Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Send an example message for event. @@ -1218,8 +1448,8 @@ async def send_simulated_example( f"Expected a non-empty value for `event_subscription_token` but received {event_subscription_token!r}" ) return await self._post( - f"/simulate/event_subscriptions/{event_subscription_token}/send_example", - body=maybe_transform( + f"/v1/simulate/event_subscriptions/{event_subscription_token}/send_example", + body=await async_maybe_transform( {"event_type": event_type}, subscription_send_simulated_example_params.SubscriptionSendSimulatedExampleParams, ), diff --git a/src/lithic/resources/external_bank_accounts/__init__.py b/src/lithic/resources/external_bank_accounts/__init__.py index 135f4be6..7e7201a8 100644 --- a/src/lithic/resources/external_bank_accounts/__init__.py +++ b/src/lithic/resources/external_bank_accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .micro_deposits import ( MicroDeposits, diff --git a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py index aa2580a3..0a3ee175 100644 --- a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py +++ b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py @@ -1,10 +1,10 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing import List, Union, overload +from typing import List, Union from datetime import date -from typing_extensions import Literal +from typing_extensions import Literal, overload import httpx @@ -12,26 +12,19 @@ from ...types import ( OwnerType, VerificationMethod, - ExternalBankAccountAddressParam, - ExternalBankAccountListResponse, - ExternalBankAccountCreateResponse, - ExternalBankAccountUpdateResponse, - ExternalBankAccountRetrieveResponse, - ExternalBankAccountRetryMicroDepositsResponse, external_bank_account_list_params, external_bank_account_create_params, external_bank_account_update_params, + external_bank_account_retry_prenote_params, + external_bank_account_retry_micro_deposits_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import required_args, maybe_transform +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import required_args, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncCursorPage, AsyncCursorPage -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) +from ..._base_client import AsyncPaginator, make_request_options from .micro_deposits import ( MicroDeposits, AsyncMicroDeposits, @@ -40,6 +33,15 @@ MicroDepositsWithStreamingResponse, AsyncMicroDepositsWithStreamingResponse, ) +from ...types.owner_type import OwnerType +from ...types.verification_method import VerificationMethod +from ...types.external_bank_account import ExternalBankAccount +from ...types.external_bank_account_address_param import ExternalBankAccountAddressParam +from ...types.external_bank_account_list_response import ExternalBankAccountListResponse +from ...types.external_bank_account_create_response import ExternalBankAccountCreateResponse +from ...types.external_bank_account_update_response import ExternalBankAccountUpdateResponse +from ...types.external_bank_account_retrieve_response import ExternalBankAccountRetrieveResponse +from ...types.external_bank_account_retry_micro_deposits_response import ExternalBankAccountRetryMicroDepositsResponse __all__ = ["ExternalBankAccounts", "AsyncExternalBankAccounts"] @@ -51,10 +53,21 @@ def micro_deposits(self) -> MicroDeposits: @cached_property def with_raw_response(self) -> ExternalBankAccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return ExternalBankAccountsWithRawResponse(self) @cached_property def with_streaming_response(self) -> ExternalBankAccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return ExternalBankAccountsWithStreamingResponse(self) @overload @@ -64,39 +77,66 @@ def create( account_number: str, country: str, currency: str, + financial_account_token: str, owner: str, owner_type: OwnerType, routing_number: str, type: Literal["CHECKING", "SAVINGS"], verification_method: VerificationMethod, - account_token: str | NotGiven = NOT_GIVEN, - address: ExternalBankAccountAddressParam | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, - verification_enforcement: bool | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, + verification_enforcement: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountCreateResponse: """ Creates an external bank account within a program or Lithic account. Args: - address: Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + account_number: Account Number + + country: The country that the bank account is located in using ISO 3166-1. We will only + accept USA bank accounts e.g., USA + + currency: currency of the external account 3-character alphabetic ISO 4217 code + + financial_account_token: The financial account token of the operating account to fund the micro deposits + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + routing_number: Routing Number + + type: Account Type + + verification_method: Verification Method + + account_token: Indicates which Lithic account the external account is associated with. For + external accounts that are associated with the program, account_token field + returned will be null + + address: Address + + company_id: Optional field that helps identify bank accounts in receipts dob: Date of Birth of the Individual that owns the external bank account - verification_enforcement: Indicates whether verification was enforced for a given association record. For - MICRO_DEPOSIT, option to disable verification if the external bank account has - already been verified before. By default, verification will be required unless - users pass in a value of false + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + user_defined_id: User Defined ID extra_headers: Send extra headers @@ -112,28 +152,140 @@ def create( def create( self, *, + account_number: str, + country: str, + currency: str, owner: str, owner_type: OwnerType, - processor_token: str, - verification_method: VerificationMethod, - account_token: str | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, + routing_number: str, + type: Literal["CHECKING", "SAVINGS"], + verification_method: Literal["EXTERNALLY_VERIFIED"], + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalBankAccountCreateResponse: + """ + Creates an external bank account within a program or Lithic account. + + Args: + account_number: Account Number + + country: The country that the bank account is located in using ISO 3166-1. We will only + accept USA bank accounts e.g., USA + + currency: currency of the external account 3-character alphabetic ISO 4217 code + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + routing_number: Routing Number + + type: Account Type + + verification_method: Verification Method + + account_token: Indicates which Lithic account the external account is associated with. For + external accounts that are associated with the program, account_token field + returned will be null + + address: Address + + company_id: Optional field that helps identify bank accounts in receipts + + dob: Date of Birth of the Individual that owns the external bank account + + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + user_defined_id: User Defined ID + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def create( + self, + *, + account_number: str, + country: str, + currency: str, + owner: str, + owner_type: OwnerType, + routing_number: str, + type: Literal["CHECKING", "SAVINGS"], + verification_method: Literal["UNVERIFIED"], + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountCreateResponse: """ Creates an external bank account within a program or Lithic account. Args: + account_number: Account Number + + country: The country that the bank account is located in using ISO 3166-1. We will only + accept USA bank accounts e.g., USA + + currency: currency of the external account 3-character alphabetic ISO 4217 code + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + routing_number: Routing Number + + type: Account Type + + verification_method: Verification Method + + account_token: Indicates which Lithic account the external account is associated with. For + external accounts that are associated with the program, account_token field + returned will be null + + address: Address + + company_id: Optional field that helps identify bank accounts in receipts + dob: Date of Birth of the Individual that owns the external bank account + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + user_defined_id: User Defined ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -145,6 +297,17 @@ def create( ... @required_args( + [ + "account_number", + "country", + "currency", + "financial_account_token", + "owner", + "owner_type", + "routing_number", + "type", + "verification_method", + ], [ "account_number", "country", @@ -155,42 +318,42 @@ def create( "type", "verification_method", ], - ["owner", "owner_type", "processor_token", "verification_method"], ) def create( self, *, - account_number: str | NotGiven = NOT_GIVEN, - country: str | NotGiven = NOT_GIVEN, - currency: str | NotGiven = NOT_GIVEN, + account_number: str, + country: str, + currency: str, + financial_account_token: str | Omit = omit, owner: str, owner_type: OwnerType, - routing_number: str | NotGiven = NOT_GIVEN, - type: Literal["CHECKING", "SAVINGS"] | NotGiven = NOT_GIVEN, - verification_method: VerificationMethod, - account_token: str | NotGiven = NOT_GIVEN, - address: ExternalBankAccountAddressParam | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, - verification_enforcement: bool | NotGiven = NOT_GIVEN, - processor_token: str | NotGiven = NOT_GIVEN, + routing_number: str, + type: Literal["CHECKING", "SAVINGS"], + verification_method: VerificationMethod | Literal["EXTERNALLY_VERIFIED"] | Literal["UNVERIFIED"], + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Union[str, date] | Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, + verification_enforcement: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountCreateResponse: return self._post( - "/external_bank_accounts", + "/v1/external_bank_accounts", body=maybe_transform( { "account_number": account_number, "country": country, "currency": currency, + "financial_account_token": financial_account_token, "owner": owner, "owner_type": owner_type, "routing_number": routing_number, @@ -204,7 +367,6 @@ def create( "name": name, "user_defined_id": user_defined_id, "verification_enforcement": verification_enforcement, - "processor_token": processor_token, }, external_bank_account_create_params.ExternalBankAccountCreateParams, ), @@ -223,7 +385,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountRetrieveResponse: """ Get the external bank account by token. @@ -242,7 +404,7 @@ def retrieve( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return self._get( - f"/external_bank_accounts/{external_bank_account_token}", + f"/v1/external_bank_accounts/{external_bank_account_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -253,30 +415,43 @@ def update( self, external_bank_account_token: str, *, - address: ExternalBankAccountAddressParam | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - owner: str | NotGiven = NOT_GIVEN, - owner_type: OwnerType | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + owner: str | Omit = omit, + owner_type: OwnerType | Omit = omit, + type: Literal["CHECKING", "SAVINGS"] | Omit = omit, + user_defined_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountUpdateResponse: """ Update the external bank account by token. Args: - address: Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + address: Address + + company_id: Optional field that helps identify bank accounts in receipts dob: Date of Birth of the Individual that owns the external bank account + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + user_defined_id: User Defined ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -290,7 +465,7 @@ def update( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return self._patch( - f"/external_bank_accounts/{external_bank_account_token}", + f"/v1/external_bank_accounts/{external_bank_account_token}", body=maybe_transform( { "address": address, @@ -300,6 +475,7 @@ def update( "name": name, "owner": owner, "owner_type": owner_type, + "type": type, "user_defined_id": user_defined_id, }, external_bank_account_update_params.ExternalBankAccountUpdateParams, @@ -313,22 +489,22 @@ def update( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - account_types: List[Literal["CHECKING", "SAVINGS"]] | NotGiven = NOT_GIVEN, - countries: List[str] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - owner_types: List[OwnerType] | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - states: List[Literal["CLOSED", "ENABLED", "PAUSED"]] | NotGiven = NOT_GIVEN, - verification_states: List[Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"]] - | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + account_types: List[Literal["CHECKING", "SAVINGS"]] | Omit = omit, + countries: SequenceNotStr[str] | Omit = omit, + ending_before: str | Omit = omit, + owner_types: List[OwnerType] | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + states: List[Literal["ENABLED", "CLOSED", "PAUSED"]] | Omit = omit, + verification_states: List[Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"]] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[ExternalBankAccountListResponse]: """ List all the external bank accounts for the provided search criteria. @@ -351,7 +527,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/external_bank_accounts", + "/v1/external_bank_accounts", page=SyncCursorPage[ExternalBankAccountListResponse], options=make_request_options( extra_headers=extra_headers, @@ -380,12 +556,13 @@ def retry_micro_deposits( self, external_bank_account_token: str, *, + financial_account_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountRetryMicroDepositsResponse: """ Retry external bank account micro deposit verification. @@ -404,13 +581,92 @@ def retry_micro_deposits( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return self._post( - f"/external_bank_accounts/{external_bank_account_token}/retry_micro_deposits", + f"/v1/external_bank_accounts/{external_bank_account_token}/retry_micro_deposits", + body=maybe_transform( + {"financial_account_token": financial_account_token}, + external_bank_account_retry_micro_deposits_params.ExternalBankAccountRetryMicroDepositsParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=ExternalBankAccountRetryMicroDepositsResponse, ) + def retry_prenote( + self, + external_bank_account_token: str, + *, + financial_account_token: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalBankAccount: + """ + Retry external bank account prenote verification. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_bank_account_token: + raise ValueError( + f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" + ) + return self._post( + f"/v1/external_bank_accounts/{external_bank_account_token}/retry_prenote", + body=maybe_transform( + {"financial_account_token": financial_account_token}, + external_bank_account_retry_prenote_params.ExternalBankAccountRetryPrenoteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalBankAccount, + ) + + def unpause( + self, + external_bank_account_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalBankAccount: + """ + Unpause an external bank account + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_bank_account_token: + raise ValueError( + f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" + ) + return self._post( + f"/v1/external_bank_accounts/{external_bank_account_token}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalBankAccount, + ) + class AsyncExternalBankAccounts(AsyncAPIResource): @cached_property @@ -419,10 +675,21 @@ def micro_deposits(self) -> AsyncMicroDeposits: @cached_property def with_raw_response(self) -> AsyncExternalBankAccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncExternalBankAccountsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncExternalBankAccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncExternalBankAccountsWithStreamingResponse(self) @overload @@ -432,39 +699,66 @@ async def create( account_number: str, country: str, currency: str, + financial_account_token: str, owner: str, owner_type: OwnerType, routing_number: str, type: Literal["CHECKING", "SAVINGS"], verification_method: VerificationMethod, - account_token: str | NotGiven = NOT_GIVEN, - address: ExternalBankAccountAddressParam | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, - verification_enforcement: bool | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, + verification_enforcement: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountCreateResponse: """ Creates an external bank account within a program or Lithic account. Args: - address: Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + account_number: Account Number + + country: The country that the bank account is located in using ISO 3166-1. We will only + accept USA bank accounts e.g., USA + + currency: currency of the external account 3-character alphabetic ISO 4217 code + + financial_account_token: The financial account token of the operating account to fund the micro deposits + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + routing_number: Routing Number + + type: Account Type + + verification_method: Verification Method + + account_token: Indicates which Lithic account the external account is associated with. For + external accounts that are associated with the program, account_token field + returned will be null + + address: Address + + company_id: Optional field that helps identify bank accounts in receipts dob: Date of Birth of the Individual that owns the external bank account - verification_enforcement: Indicates whether verification was enforced for a given association record. For - MICRO_DEPOSIT, option to disable verification if the external bank account has - already been verified before. By default, verification will be required unless - users pass in a value of false + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + user_defined_id: User Defined ID extra_headers: Send extra headers @@ -480,28 +774,140 @@ async def create( async def create( self, *, + account_number: str, + country: str, + currency: str, owner: str, owner_type: OwnerType, - processor_token: str, - verification_method: VerificationMethod, - account_token: str | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, + routing_number: str, + type: Literal["CHECKING", "SAVINGS"], + verification_method: Literal["EXTERNALLY_VERIFIED"], + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalBankAccountCreateResponse: + """ + Creates an external bank account within a program or Lithic account. + + Args: + account_number: Account Number + + country: The country that the bank account is located in using ISO 3166-1. We will only + accept USA bank accounts e.g., USA + + currency: currency of the external account 3-character alphabetic ISO 4217 code + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + routing_number: Routing Number + + type: Account Type + + verification_method: Verification Method + + account_token: Indicates which Lithic account the external account is associated with. For + external accounts that are associated with the program, account_token field + returned will be null + + address: Address + + company_id: Optional field that helps identify bank accounts in receipts + + dob: Date of Birth of the Individual that owns the external bank account + + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + user_defined_id: User Defined ID + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def create( + self, + *, + account_number: str, + country: str, + currency: str, + owner: str, + owner_type: OwnerType, + routing_number: str, + type: Literal["CHECKING", "SAVINGS"], + verification_method: Literal["UNVERIFIED"], + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountCreateResponse: """ Creates an external bank account within a program or Lithic account. Args: + account_number: Account Number + + country: The country that the bank account is located in using ISO 3166-1. We will only + accept USA bank accounts e.g., USA + + currency: currency of the external account 3-character alphabetic ISO 4217 code + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + routing_number: Routing Number + + type: Account Type + + verification_method: Verification Method + + account_token: Indicates which Lithic account the external account is associated with. For + external accounts that are associated with the program, account_token field + returned will be null + + address: Address + + company_id: Optional field that helps identify bank accounts in receipts + dob: Date of Birth of the Individual that owns the external bank account + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + user_defined_id: User Defined ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -513,6 +919,17 @@ async def create( ... @required_args( + [ + "account_number", + "country", + "currency", + "financial_account_token", + "owner", + "owner_type", + "routing_number", + "type", + "verification_method", + ], [ "account_number", "country", @@ -523,42 +940,42 @@ async def create( "type", "verification_method", ], - ["owner", "owner_type", "processor_token", "verification_method"], ) async def create( self, *, - account_number: str | NotGiven = NOT_GIVEN, - country: str | NotGiven = NOT_GIVEN, - currency: str | NotGiven = NOT_GIVEN, + account_number: str, + country: str, + currency: str, + financial_account_token: str | Omit = omit, owner: str, owner_type: OwnerType, - routing_number: str | NotGiven = NOT_GIVEN, - type: Literal["CHECKING", "SAVINGS"] | NotGiven = NOT_GIVEN, - verification_method: VerificationMethod, - account_token: str | NotGiven = NOT_GIVEN, - address: ExternalBankAccountAddressParam | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, - verification_enforcement: bool | NotGiven = NOT_GIVEN, - processor_token: str | NotGiven = NOT_GIVEN, + routing_number: str, + type: Literal["CHECKING", "SAVINGS"], + verification_method: VerificationMethod | Literal["EXTERNALLY_VERIFIED"] | Literal["UNVERIFIED"], + account_token: str | Omit = omit, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Union[str, date] | Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + user_defined_id: str | Omit = omit, + verification_enforcement: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountCreateResponse: return await self._post( - "/external_bank_accounts", - body=maybe_transform( + "/v1/external_bank_accounts", + body=await async_maybe_transform( { "account_number": account_number, "country": country, "currency": currency, + "financial_account_token": financial_account_token, "owner": owner, "owner_type": owner_type, "routing_number": routing_number, @@ -572,7 +989,6 @@ async def create( "name": name, "user_defined_id": user_defined_id, "verification_enforcement": verification_enforcement, - "processor_token": processor_token, }, external_bank_account_create_params.ExternalBankAccountCreateParams, ), @@ -591,7 +1007,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountRetrieveResponse: """ Get the external bank account by token. @@ -610,7 +1026,7 @@ async def retrieve( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return await self._get( - f"/external_bank_accounts/{external_bank_account_token}", + f"/v1/external_bank_accounts/{external_bank_account_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -621,30 +1037,43 @@ async def update( self, external_bank_account_token: str, *, - address: ExternalBankAccountAddressParam | NotGiven = NOT_GIVEN, - company_id: str | NotGiven = NOT_GIVEN, - dob: Union[str, date] | NotGiven = NOT_GIVEN, - doing_business_as: str | NotGiven = NOT_GIVEN, - name: str | NotGiven = NOT_GIVEN, - owner: str | NotGiven = NOT_GIVEN, - owner_type: OwnerType | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, + address: ExternalBankAccountAddressParam | Omit = omit, + company_id: str | Omit = omit, + dob: Union[str, date] | Omit = omit, + doing_business_as: str | Omit = omit, + name: str | Omit = omit, + owner: str | Omit = omit, + owner_type: OwnerType | Omit = omit, + type: Literal["CHECKING", "SAVINGS"] | Omit = omit, + user_defined_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountUpdateResponse: """ Update the external bank account by token. Args: - address: Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + address: Address + + company_id: Optional field that helps identify bank accounts in receipts dob: Date of Birth of the Individual that owns the external bank account + doing_business_as: Doing Business As + + name: The nickname for this External Bank Account + + owner: Legal Name of the business or individual who owns the external account. This + will appear in statements + + owner_type: Owner Type + + user_defined_id: User Defined ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -658,8 +1087,8 @@ async def update( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return await self._patch( - f"/external_bank_accounts/{external_bank_account_token}", - body=maybe_transform( + f"/v1/external_bank_accounts/{external_bank_account_token}", + body=await async_maybe_transform( { "address": address, "company_id": company_id, @@ -668,6 +1097,7 @@ async def update( "name": name, "owner": owner, "owner_type": owner_type, + "type": type, "user_defined_id": user_defined_id, }, external_bank_account_update_params.ExternalBankAccountUpdateParams, @@ -681,22 +1111,22 @@ async def update( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - account_types: List[Literal["CHECKING", "SAVINGS"]] | NotGiven = NOT_GIVEN, - countries: List[str] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - owner_types: List[OwnerType] | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - states: List[Literal["CLOSED", "ENABLED", "PAUSED"]] | NotGiven = NOT_GIVEN, - verification_states: List[Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"]] - | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + account_types: List[Literal["CHECKING", "SAVINGS"]] | Omit = omit, + countries: SequenceNotStr[str] | Omit = omit, + ending_before: str | Omit = omit, + owner_types: List[OwnerType] | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + states: List[Literal["ENABLED", "CLOSED", "PAUSED"]] | Omit = omit, + verification_states: List[Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"]] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ExternalBankAccountListResponse, AsyncCursorPage[ExternalBankAccountListResponse]]: """ List all the external bank accounts for the provided search criteria. @@ -719,7 +1149,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/external_bank_accounts", + "/v1/external_bank_accounts", page=AsyncCursorPage[ExternalBankAccountListResponse], options=make_request_options( extra_headers=extra_headers, @@ -748,12 +1178,13 @@ async def retry_micro_deposits( self, external_bank_account_token: str, *, + financial_account_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ExternalBankAccountRetryMicroDepositsResponse: """ Retry external bank account micro deposit verification. @@ -772,13 +1203,92 @@ async def retry_micro_deposits( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return await self._post( - f"/external_bank_accounts/{external_bank_account_token}/retry_micro_deposits", + f"/v1/external_bank_accounts/{external_bank_account_token}/retry_micro_deposits", + body=await async_maybe_transform( + {"financial_account_token": financial_account_token}, + external_bank_account_retry_micro_deposits_params.ExternalBankAccountRetryMicroDepositsParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=ExternalBankAccountRetryMicroDepositsResponse, ) + async def retry_prenote( + self, + external_bank_account_token: str, + *, + financial_account_token: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalBankAccount: + """ + Retry external bank account prenote verification. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_bank_account_token: + raise ValueError( + f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" + ) + return await self._post( + f"/v1/external_bank_accounts/{external_bank_account_token}/retry_prenote", + body=await async_maybe_transform( + {"financial_account_token": financial_account_token}, + external_bank_account_retry_prenote_params.ExternalBankAccountRetryPrenoteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalBankAccount, + ) + + async def unpause( + self, + external_bank_account_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalBankAccount: + """ + Unpause an external bank account + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_bank_account_token: + raise ValueError( + f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" + ) + return await self._post( + f"/v1/external_bank_accounts/{external_bank_account_token}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalBankAccount, + ) + class ExternalBankAccountsWithRawResponse: def __init__(self, external_bank_accounts: ExternalBankAccounts) -> None: @@ -799,6 +1309,12 @@ def __init__(self, external_bank_accounts: ExternalBankAccounts) -> None: self.retry_micro_deposits = _legacy_response.to_raw_response_wrapper( external_bank_accounts.retry_micro_deposits, ) + self.retry_prenote = _legacy_response.to_raw_response_wrapper( + external_bank_accounts.retry_prenote, + ) + self.unpause = _legacy_response.to_raw_response_wrapper( + external_bank_accounts.unpause, + ) @cached_property def micro_deposits(self) -> MicroDepositsWithRawResponse: @@ -824,6 +1340,12 @@ def __init__(self, external_bank_accounts: AsyncExternalBankAccounts) -> None: self.retry_micro_deposits = _legacy_response.async_to_raw_response_wrapper( external_bank_accounts.retry_micro_deposits, ) + self.retry_prenote = _legacy_response.async_to_raw_response_wrapper( + external_bank_accounts.retry_prenote, + ) + self.unpause = _legacy_response.async_to_raw_response_wrapper( + external_bank_accounts.unpause, + ) @cached_property def micro_deposits(self) -> AsyncMicroDepositsWithRawResponse: @@ -849,6 +1371,12 @@ def __init__(self, external_bank_accounts: ExternalBankAccounts) -> None: self.retry_micro_deposits = to_streamed_response_wrapper( external_bank_accounts.retry_micro_deposits, ) + self.retry_prenote = to_streamed_response_wrapper( + external_bank_accounts.retry_prenote, + ) + self.unpause = to_streamed_response_wrapper( + external_bank_accounts.unpause, + ) @cached_property def micro_deposits(self) -> MicroDepositsWithStreamingResponse: @@ -874,6 +1402,12 @@ def __init__(self, external_bank_accounts: AsyncExternalBankAccounts) -> None: self.retry_micro_deposits = async_to_streamed_response_wrapper( external_bank_accounts.retry_micro_deposits, ) + self.retry_prenote = async_to_streamed_response_wrapper( + external_bank_accounts.retry_prenote, + ) + self.unpause = async_to_streamed_response_wrapper( + external_bank_accounts.unpause, + ) @cached_property def micro_deposits(self) -> AsyncMicroDepositsWithStreamingResponse: diff --git a/src/lithic/resources/external_bank_accounts/micro_deposits.py b/src/lithic/resources/external_bank_accounts/micro_deposits.py index 82066717..ac74374b 100644 --- a/src/lithic/resources/external_bank_accounts/micro_deposits.py +++ b/src/lithic/resources/external_bank_accounts/micro_deposits.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -7,15 +7,14 @@ import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..._base_client import ( - make_request_options, -) -from ...types.external_bank_accounts import MicroDepositCreateResponse, micro_deposit_create_params +from ..._base_client import make_request_options +from ...types.external_bank_accounts import micro_deposit_create_params +from ...types.external_bank_accounts.micro_deposit_create_response import MicroDepositCreateResponse __all__ = ["MicroDeposits", "AsyncMicroDeposits"] @@ -23,10 +22,21 @@ class MicroDeposits(SyncAPIResource): @cached_property def with_raw_response(self) -> MicroDepositsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return MicroDepositsWithRawResponse(self) @cached_property def with_streaming_response(self) -> MicroDepositsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return MicroDepositsWithStreamingResponse(self) def create( @@ -39,7 +49,7 @@ def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MicroDepositCreateResponse: """ Verify the external bank account by providing the micro deposit amounts. @@ -58,7 +68,7 @@ def create( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return self._post( - f"/external_bank_accounts/{external_bank_account_token}/micro_deposits", + f"/v1/external_bank_accounts/{external_bank_account_token}/micro_deposits", body=maybe_transform( {"micro_deposits": micro_deposits}, micro_deposit_create_params.MicroDepositCreateParams ), @@ -72,10 +82,21 @@ def create( class AsyncMicroDeposits(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncMicroDepositsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncMicroDepositsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncMicroDepositsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncMicroDepositsWithStreamingResponse(self) async def create( @@ -88,7 +109,7 @@ async def create( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MicroDepositCreateResponse: """ Verify the external bank account by providing the micro deposit amounts. @@ -107,8 +128,8 @@ async def create( f"Expected a non-empty value for `external_bank_account_token` but received {external_bank_account_token!r}" ) return await self._post( - f"/external_bank_accounts/{external_bank_account_token}/micro_deposits", - body=maybe_transform( + f"/v1/external_bank_accounts/{external_bank_account_token}/micro_deposits", + body=await async_maybe_transform( {"micro_deposits": micro_deposits}, micro_deposit_create_params.MicroDepositCreateParams ), options=make_request_options( diff --git a/src/lithic/resources/external_payments.py b/src/lithic/resources/external_payments.py new file mode 100644 index 00000000..07c51674 --- /dev/null +++ b/src/lithic/resources/external_payments.py @@ -0,0 +1,887 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date, datetime +from typing_extensions import Literal + +import httpx + +from .. import _legacy_response +from ..types import ( + external_payment_list_params, + external_payment_cancel_params, + external_payment_create_params, + external_payment_settle_params, + external_payment_release_params, + external_payment_reverse_params, +) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.external_payment import ExternalPayment + +__all__ = ["ExternalPayments", "AsyncExternalPayments"] + + +class ExternalPayments(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ExternalPaymentsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return ExternalPaymentsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExternalPaymentsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return ExternalPaymentsWithStreamingResponse(self) + + def create( + self, + *, + amount: int, + category: Literal[ + "EXTERNAL_WIRE", "EXTERNAL_ACH", "EXTERNAL_CHECK", "EXTERNAL_FEDNOW", "EXTERNAL_RTP", "EXTERNAL_TRANSFER" + ], + effective_date: Union[str, date], + financial_account_token: str, + payment_type: Literal["DEPOSIT", "WITHDRAWAL"], + token: str | Omit = omit, + memo: str | Omit = omit, + progress_to: Literal["SETTLED", "RELEASED"] | Omit = omit, + user_defined_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Create external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/external_payments", + body=maybe_transform( + { + "amount": amount, + "category": category, + "effective_date": effective_date, + "financial_account_token": financial_account_token, + "payment_type": payment_type, + "token": token, + "memo": memo, + "progress_to": progress_to, + "user_defined_id": user_defined_id, + }, + external_payment_create_params.ExternalPaymentCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + def retrieve( + self, + external_payment_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Get external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return self._get( + f"/v1/external_payments/{external_payment_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "EXTERNAL_WIRE", "EXTERNAL_ACH", "EXTERNAL_CHECK", "EXTERNAL_FEDNOW", "EXTERNAL_RTP", "EXTERNAL_TRANSFER" + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ExternalPayment]: + """List external payments + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + category: External Payment category to be returned. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + + page_size: Page size (for pagination). + + result: External Payment result to be returned. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Book transfer status to be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/external_payments", + page=SyncCursorPage[ExternalPayment], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "result": result, + "starting_after": starting_after, + "status": status, + }, + external_payment_list_params.ExternalPaymentListParams, + ), + ), + model=ExternalPayment, + ) + + def cancel( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Cancel external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return self._post( + f"/v1/external_payments/{external_payment_token}/cancel", + body=maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + external_payment_cancel_params.ExternalPaymentCancelParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + def release( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Release external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return self._post( + f"/v1/external_payments/{external_payment_token}/release", + body=maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + external_payment_release_params.ExternalPaymentReleaseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + def reverse( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Reverse external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return self._post( + f"/v1/external_payments/{external_payment_token}/reverse", + body=maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + external_payment_reverse_params.ExternalPaymentReverseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + def settle( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + progress_to: Literal["SETTLED", "RELEASED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Settle external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return self._post( + f"/v1/external_payments/{external_payment_token}/settle", + body=maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + "progress_to": progress_to, + }, + external_payment_settle_params.ExternalPaymentSettleParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + +class AsyncExternalPayments(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExternalPaymentsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncExternalPaymentsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExternalPaymentsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncExternalPaymentsWithStreamingResponse(self) + + async def create( + self, + *, + amount: int, + category: Literal[ + "EXTERNAL_WIRE", "EXTERNAL_ACH", "EXTERNAL_CHECK", "EXTERNAL_FEDNOW", "EXTERNAL_RTP", "EXTERNAL_TRANSFER" + ], + effective_date: Union[str, date], + financial_account_token: str, + payment_type: Literal["DEPOSIT", "WITHDRAWAL"], + token: str | Omit = omit, + memo: str | Omit = omit, + progress_to: Literal["SETTLED", "RELEASED"] | Omit = omit, + user_defined_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Create external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/external_payments", + body=await async_maybe_transform( + { + "amount": amount, + "category": category, + "effective_date": effective_date, + "financial_account_token": financial_account_token, + "payment_type": payment_type, + "token": token, + "memo": memo, + "progress_to": progress_to, + "user_defined_id": user_defined_id, + }, + external_payment_create_params.ExternalPaymentCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + async def retrieve( + self, + external_payment_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Get external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return await self._get( + f"/v1/external_payments/{external_payment_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "EXTERNAL_WIRE", "EXTERNAL_ACH", "EXTERNAL_CHECK", "EXTERNAL_FEDNOW", "EXTERNAL_RTP", "EXTERNAL_TRANSFER" + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ExternalPayment, AsyncCursorPage[ExternalPayment]]: + """List external payments + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + category: External Payment category to be returned. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + + page_size: Page size (for pagination). + + result: External Payment result to be returned. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Book transfer status to be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/external_payments", + page=AsyncCursorPage[ExternalPayment], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "result": result, + "starting_after": starting_after, + "status": status, + }, + external_payment_list_params.ExternalPaymentListParams, + ), + ), + model=ExternalPayment, + ) + + async def cancel( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Cancel external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return await self._post( + f"/v1/external_payments/{external_payment_token}/cancel", + body=await async_maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + external_payment_cancel_params.ExternalPaymentCancelParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + async def release( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Release external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return await self._post( + f"/v1/external_payments/{external_payment_token}/release", + body=await async_maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + external_payment_release_params.ExternalPaymentReleaseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + async def reverse( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Reverse external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return await self._post( + f"/v1/external_payments/{external_payment_token}/reverse", + body=await async_maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + external_payment_reverse_params.ExternalPaymentReverseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + async def settle( + self, + external_payment_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + progress_to: Literal["SETTLED", "RELEASED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExternalPayment: + """ + Settle external payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_payment_token: + raise ValueError( + f"Expected a non-empty value for `external_payment_token` but received {external_payment_token!r}" + ) + return await self._post( + f"/v1/external_payments/{external_payment_token}/settle", + body=await async_maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + "progress_to": progress_to, + }, + external_payment_settle_params.ExternalPaymentSettleParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExternalPayment, + ) + + +class ExternalPaymentsWithRawResponse: + def __init__(self, external_payments: ExternalPayments) -> None: + self._external_payments = external_payments + + self.create = _legacy_response.to_raw_response_wrapper( + external_payments.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + external_payments.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + external_payments.list, + ) + self.cancel = _legacy_response.to_raw_response_wrapper( + external_payments.cancel, + ) + self.release = _legacy_response.to_raw_response_wrapper( + external_payments.release, + ) + self.reverse = _legacy_response.to_raw_response_wrapper( + external_payments.reverse, + ) + self.settle = _legacy_response.to_raw_response_wrapper( + external_payments.settle, + ) + + +class AsyncExternalPaymentsWithRawResponse: + def __init__(self, external_payments: AsyncExternalPayments) -> None: + self._external_payments = external_payments + + self.create = _legacy_response.async_to_raw_response_wrapper( + external_payments.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + external_payments.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + external_payments.list, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + external_payments.cancel, + ) + self.release = _legacy_response.async_to_raw_response_wrapper( + external_payments.release, + ) + self.reverse = _legacy_response.async_to_raw_response_wrapper( + external_payments.reverse, + ) + self.settle = _legacy_response.async_to_raw_response_wrapper( + external_payments.settle, + ) + + +class ExternalPaymentsWithStreamingResponse: + def __init__(self, external_payments: ExternalPayments) -> None: + self._external_payments = external_payments + + self.create = to_streamed_response_wrapper( + external_payments.create, + ) + self.retrieve = to_streamed_response_wrapper( + external_payments.retrieve, + ) + self.list = to_streamed_response_wrapper( + external_payments.list, + ) + self.cancel = to_streamed_response_wrapper( + external_payments.cancel, + ) + self.release = to_streamed_response_wrapper( + external_payments.release, + ) + self.reverse = to_streamed_response_wrapper( + external_payments.reverse, + ) + self.settle = to_streamed_response_wrapper( + external_payments.settle, + ) + + +class AsyncExternalPaymentsWithStreamingResponse: + def __init__(self, external_payments: AsyncExternalPayments) -> None: + self._external_payments = external_payments + + self.create = async_to_streamed_response_wrapper( + external_payments.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + external_payments.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + external_payments.list, + ) + self.cancel = async_to_streamed_response_wrapper( + external_payments.cancel, + ) + self.release = async_to_streamed_response_wrapper( + external_payments.release, + ) + self.reverse = async_to_streamed_response_wrapper( + external_payments.reverse, + ) + self.settle = async_to_streamed_response_wrapper( + external_payments.settle, + ) diff --git a/src/lithic/resources/financial_accounts/__init__.py b/src/lithic/resources/financial_accounts/__init__.py index e9c62a23..ad35c907 100644 --- a/src/lithic/resources/financial_accounts/__init__.py +++ b/src/lithic/resources/financial_accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .balances import ( Balances, @@ -8,6 +8,14 @@ BalancesWithStreamingResponse, AsyncBalancesWithStreamingResponse, ) +from .loan_tapes import ( + LoanTapes, + AsyncLoanTapes, + LoanTapesWithRawResponse, + AsyncLoanTapesWithRawResponse, + LoanTapesWithStreamingResponse, + AsyncLoanTapesWithStreamingResponse, +) from .statements import ( Statements, AsyncStatements, @@ -24,6 +32,14 @@ FinancialAccountsWithStreamingResponse, AsyncFinancialAccountsWithStreamingResponse, ) +from .credit_configuration import ( + CreditConfiguration, + AsyncCreditConfiguration, + CreditConfigurationWithRawResponse, + AsyncCreditConfigurationWithRawResponse, + CreditConfigurationWithStreamingResponse, + AsyncCreditConfigurationWithStreamingResponse, +) from .financial_transactions import ( FinancialTransactions, AsyncFinancialTransactions, @@ -46,12 +62,24 @@ "AsyncFinancialTransactionsWithRawResponse", "FinancialTransactionsWithStreamingResponse", "AsyncFinancialTransactionsWithStreamingResponse", + "CreditConfiguration", + "AsyncCreditConfiguration", + "CreditConfigurationWithRawResponse", + "AsyncCreditConfigurationWithRawResponse", + "CreditConfigurationWithStreamingResponse", + "AsyncCreditConfigurationWithStreamingResponse", "Statements", "AsyncStatements", "StatementsWithRawResponse", "AsyncStatementsWithRawResponse", "StatementsWithStreamingResponse", "AsyncStatementsWithStreamingResponse", + "LoanTapes", + "AsyncLoanTapes", + "LoanTapesWithRawResponse", + "AsyncLoanTapesWithRawResponse", + "LoanTapesWithStreamingResponse", + "AsyncLoanTapesWithStreamingResponse", "FinancialAccounts", "AsyncFinancialAccounts", "FinancialAccountsWithRawResponse", diff --git a/src/lithic/resources/financial_accounts/balances.py b/src/lithic/resources/financial_accounts/balances.py index 8742575f..d280a862 100644 --- a/src/lithic/resources/financial_accounts/balances.py +++ b/src/lithic/resources/financial_accounts/balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -8,18 +8,15 @@ import httpx from ... import _legacy_response -from ...types import Balance -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncSinglePage, AsyncSinglePage -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) +from ..._base_client import AsyncPaginator, make_request_options from ...types.financial_accounts import balance_list_params +from ...types.financial_account_balance import FinancialAccountBalance __all__ = ["Balances", "AsyncBalances"] @@ -27,25 +24,36 @@ class Balances(SyncAPIResource): @cached_property def with_raw_response(self) -> BalancesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return BalancesWithRawResponse(self) @cached_property def with_streaming_response(self) -> BalancesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return BalancesWithStreamingResponse(self) def list( self, financial_account_token: str, *, - balance_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - last_transaction_event_token: str | NotGiven = NOT_GIVEN, + balance_date: Union[str, datetime] | Omit = omit, + last_transaction_event_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncSinglePage[Balance]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncSinglePage[FinancialAccountBalance]: """ Get the balances for a given financial account. @@ -69,8 +77,8 @@ def list( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._get_api_list( - f"/financial_accounts/{financial_account_token}/balances", - page=SyncSinglePage[Balance], + f"/v1/financial_accounts/{financial_account_token}/balances", + page=SyncSinglePage[FinancialAccountBalance], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -84,32 +92,43 @@ def list( balance_list_params.BalanceListParams, ), ), - model=Balance, + model=FinancialAccountBalance, ) class AsyncBalances(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncBalancesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncBalancesWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncBalancesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncBalancesWithStreamingResponse(self) def list( self, financial_account_token: str, *, - balance_date: Union[str, datetime] | NotGiven = NOT_GIVEN, - last_transaction_event_token: str | NotGiven = NOT_GIVEN, + balance_date: Union[str, datetime] | Omit = omit, + last_transaction_event_token: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[Balance, AsyncSinglePage[Balance]]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[FinancialAccountBalance, AsyncSinglePage[FinancialAccountBalance]]: """ Get the balances for a given financial account. @@ -133,8 +152,8 @@ def list( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._get_api_list( - f"/financial_accounts/{financial_account_token}/balances", - page=AsyncSinglePage[Balance], + f"/v1/financial_accounts/{financial_account_token}/balances", + page=AsyncSinglePage[FinancialAccountBalance], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -148,7 +167,7 @@ def list( balance_list_params.BalanceListParams, ), ), - model=Balance, + model=FinancialAccountBalance, ) diff --git a/src/lithic/resources/financial_accounts/credit_configuration.py b/src/lithic/resources/financial_accounts/credit_configuration.py new file mode 100644 index 00000000..0ab13dbe --- /dev/null +++ b/src/lithic/resources/financial_accounts/credit_configuration.py @@ -0,0 +1,285 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.financial_accounts import credit_configuration_update_params +from ...types.financial_accounts.financial_account_credit_config import FinancialAccountCreditConfig + +__all__ = ["CreditConfiguration", "AsyncCreditConfiguration"] + + +class CreditConfiguration(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CreditConfigurationWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return CreditConfigurationWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CreditConfigurationWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return CreditConfigurationWithStreamingResponse(self) + + def retrieve( + self, + financial_account_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FinancialAccountCreditConfig: + """ + Get an Account's credit configuration + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._get( + f"/v1/financial_accounts/{financial_account_token}/credit_configuration", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FinancialAccountCreditConfig, + ) + + def update( + self, + financial_account_token: str, + *, + auto_collection_configuration: credit_configuration_update_params.AutoCollectionConfiguration | Omit = omit, + credit_limit: int | Omit = omit, + credit_product_token: str | Omit = omit, + external_bank_account_token: str | Omit = omit, + tier: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FinancialAccountCreditConfig: + """ + Update an account's credit configuration + + Args: + credit_product_token: Globally unique identifier for the credit product + + tier: Tier to assign to a financial account + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._patch( + f"/v1/financial_accounts/{financial_account_token}/credit_configuration", + body=maybe_transform( + { + "auto_collection_configuration": auto_collection_configuration, + "credit_limit": credit_limit, + "credit_product_token": credit_product_token, + "external_bank_account_token": external_bank_account_token, + "tier": tier, + }, + credit_configuration_update_params.CreditConfigurationUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FinancialAccountCreditConfig, + ) + + +class AsyncCreditConfiguration(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCreditConfigurationWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncCreditConfigurationWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCreditConfigurationWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncCreditConfigurationWithStreamingResponse(self) + + async def retrieve( + self, + financial_account_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FinancialAccountCreditConfig: + """ + Get an Account's credit configuration + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return await self._get( + f"/v1/financial_accounts/{financial_account_token}/credit_configuration", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FinancialAccountCreditConfig, + ) + + async def update( + self, + financial_account_token: str, + *, + auto_collection_configuration: credit_configuration_update_params.AutoCollectionConfiguration | Omit = omit, + credit_limit: int | Omit = omit, + credit_product_token: str | Omit = omit, + external_bank_account_token: str | Omit = omit, + tier: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FinancialAccountCreditConfig: + """ + Update an account's credit configuration + + Args: + credit_product_token: Globally unique identifier for the credit product + + tier: Tier to assign to a financial account + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return await self._patch( + f"/v1/financial_accounts/{financial_account_token}/credit_configuration", + body=await async_maybe_transform( + { + "auto_collection_configuration": auto_collection_configuration, + "credit_limit": credit_limit, + "credit_product_token": credit_product_token, + "external_bank_account_token": external_bank_account_token, + "tier": tier, + }, + credit_configuration_update_params.CreditConfigurationUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FinancialAccountCreditConfig, + ) + + +class CreditConfigurationWithRawResponse: + def __init__(self, credit_configuration: CreditConfiguration) -> None: + self._credit_configuration = credit_configuration + + self.retrieve = _legacy_response.to_raw_response_wrapper( + credit_configuration.retrieve, + ) + self.update = _legacy_response.to_raw_response_wrapper( + credit_configuration.update, + ) + + +class AsyncCreditConfigurationWithRawResponse: + def __init__(self, credit_configuration: AsyncCreditConfiguration) -> None: + self._credit_configuration = credit_configuration + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + credit_configuration.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + credit_configuration.update, + ) + + +class CreditConfigurationWithStreamingResponse: + def __init__(self, credit_configuration: CreditConfiguration) -> None: + self._credit_configuration = credit_configuration + + self.retrieve = to_streamed_response_wrapper( + credit_configuration.retrieve, + ) + self.update = to_streamed_response_wrapper( + credit_configuration.update, + ) + + +class AsyncCreditConfigurationWithStreamingResponse: + def __init__(self, credit_configuration: AsyncCreditConfiguration) -> None: + self._credit_configuration = credit_configuration + + self.retrieve = async_to_streamed_response_wrapper( + credit_configuration.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + credit_configuration.update, + ) diff --git a/src/lithic/resources/financial_accounts/financial_accounts.py b/src/lithic/resources/financial_accounts/financial_accounts.py index 6d29a6f9..327e32f8 100644 --- a/src/lithic/resources/financial_accounts/financial_accounts.py +++ b/src/lithic/resources/financial_accounts/financial_accounts.py @@ -1,20 +1,22 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations +from typing import Optional from typing_extensions import Literal import httpx from ... import _legacy_response from ...types import ( - FinancialAccount, financial_account_list_params, financial_account_create_params, financial_account_update_params, + financial_account_update_status_params, + financial_account_register_account_number_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform from .balances import ( Balances, AsyncBalances, @@ -24,7 +26,27 @@ AsyncBalancesWithStreamingResponse, ) from ..._compat import cached_property -from .statements import ( +from .loan_tapes import ( + LoanTapes, + AsyncLoanTapes, + LoanTapesWithRawResponse, + AsyncLoanTapesWithRawResponse, + LoanTapesWithStreamingResponse, + AsyncLoanTapesWithStreamingResponse, +) +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncSinglePage, AsyncSinglePage +from ..._base_client import AsyncPaginator, make_request_options +from .credit_configuration import ( + CreditConfiguration, + AsyncCreditConfiguration, + CreditConfigurationWithRawResponse, + AsyncCreditConfigurationWithRawResponse, + CreditConfigurationWithStreamingResponse, + AsyncCreditConfigurationWithStreamingResponse, +) +from .statements.statements import ( Statements, AsyncStatements, StatementsWithRawResponse, @@ -32,14 +54,6 @@ StatementsWithStreamingResponse, AsyncStatementsWithStreamingResponse, ) -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ...pagination import SyncSinglePage, AsyncSinglePage -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) -from .statements.statements import Statements, AsyncStatements from .financial_transactions import ( FinancialTransactions, AsyncFinancialTransactions, @@ -48,6 +62,7 @@ FinancialTransactionsWithStreamingResponse, AsyncFinancialTransactionsWithStreamingResponse, ) +from ...types.financial_account import FinancialAccount __all__ = ["FinancialAccounts", "AsyncFinancialAccounts"] @@ -61,16 +76,35 @@ def balances(self) -> Balances: def financial_transactions(self) -> FinancialTransactions: return FinancialTransactions(self._client) + @cached_property + def credit_configuration(self) -> CreditConfiguration: + return CreditConfiguration(self._client) + @cached_property def statements(self) -> Statements: return Statements(self._client) + @cached_property + def loan_tapes(self) -> LoanTapes: + return LoanTapes(self._client) + @cached_property def with_raw_response(self) -> FinancialAccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return FinancialAccountsWithRawResponse(self) @cached_property def with_streaming_response(self) -> FinancialAccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return FinancialAccountsWithStreamingResponse(self) def create( @@ -78,13 +112,14 @@ def create( *, nickname: str, type: Literal["OPERATING"], - account_token: str | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + is_for_benefit_of: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialAccount: """ Create a new financial account @@ -99,12 +134,13 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/financial_accounts", + "/v1/financial_accounts", body=maybe_transform( { "nickname": nickname, "type": type, "account_token": account_token, + "is_for_benefit_of": is_for_benefit_of, }, financial_account_create_params.FinancialAccountCreateParams, ), @@ -123,7 +159,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialAccount: """ Get a financial account @@ -142,7 +178,7 @@ def retrieve( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._get( - f"/financial_accounts/{financial_account_token}", + f"/v1/financial_accounts/{financial_account_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -153,13 +189,13 @@ def update( self, financial_account_token: str, *, - nickname: str | NotGiven = NOT_GIVEN, + nickname: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialAccount: """ Update a financial account @@ -178,7 +214,7 @@ def update( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._patch( - f"/financial_accounts/{financial_account_token}", + f"/v1/financial_accounts/{financial_account_token}", body=maybe_transform({"nickname": nickname}, financial_account_update_params.FinancialAccountUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -189,15 +225,15 @@ def update( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, - type: Literal["ISSUING", "OPERATING", "RESERVE"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + business_account_token: str | Omit = omit, + type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncSinglePage[FinancialAccount]: """ Retrieve information on your financial accounts including routing and account @@ -219,7 +255,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/financial_accounts", + "/v1/financial_accounts", page=SyncSinglePage[FinancialAccount], options=make_request_options( extra_headers=extra_headers, @@ -238,6 +274,94 @@ def list( model=FinancialAccount, ) + def register_account_number( + self, + financial_account_token: str, + *, + account_number: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Register account number + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._post( + f"/v1/financial_accounts/{financial_account_token}/register_account_number", + body=maybe_transform( + {"account_number": account_number}, + financial_account_register_account_number_params.FinancialAccountRegisterAccountNumberParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def update_status( + self, + financial_account_token: str, + *, + status: Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"], + substatus: Optional[Literal["CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "CHARGED_OFF_DELINQUENT"]], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FinancialAccount: + """ + Update financial account status + + Args: + status: Status of the financial account + + substatus: Substatus for the financial account + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._post( + f"/v1/financial_accounts/{financial_account_token}/update_status", + body=maybe_transform( + { + "status": status, + "substatus": substatus, + }, + financial_account_update_status_params.FinancialAccountUpdateStatusParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FinancialAccount, + ) + class AsyncFinancialAccounts(AsyncAPIResource): @cached_property @@ -248,16 +372,35 @@ def balances(self) -> AsyncBalances: def financial_transactions(self) -> AsyncFinancialTransactions: return AsyncFinancialTransactions(self._client) + @cached_property + def credit_configuration(self) -> AsyncCreditConfiguration: + return AsyncCreditConfiguration(self._client) + @cached_property def statements(self) -> AsyncStatements: return AsyncStatements(self._client) + @cached_property + def loan_tapes(self) -> AsyncLoanTapes: + return AsyncLoanTapes(self._client) + @cached_property def with_raw_response(self) -> AsyncFinancialAccountsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncFinancialAccountsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncFinancialAccountsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncFinancialAccountsWithStreamingResponse(self) async def create( @@ -265,13 +408,14 @@ async def create( *, nickname: str, type: Literal["OPERATING"], - account_token: str | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + is_for_benefit_of: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialAccount: """ Create a new financial account @@ -286,12 +430,13 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/financial_accounts", - body=maybe_transform( + "/v1/financial_accounts", + body=await async_maybe_transform( { "nickname": nickname, "type": type, "account_token": account_token, + "is_for_benefit_of": is_for_benefit_of, }, financial_account_create_params.FinancialAccountCreateParams, ), @@ -310,7 +455,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialAccount: """ Get a financial account @@ -329,7 +474,7 @@ async def retrieve( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return await self._get( - f"/financial_accounts/{financial_account_token}", + f"/v1/financial_accounts/{financial_account_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -340,13 +485,13 @@ async def update( self, financial_account_token: str, *, - nickname: str | NotGiven = NOT_GIVEN, + nickname: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialAccount: """ Update a financial account @@ -365,8 +510,10 @@ async def update( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return await self._patch( - f"/financial_accounts/{financial_account_token}", - body=maybe_transform({"nickname": nickname}, financial_account_update_params.FinancialAccountUpdateParams), + f"/v1/financial_accounts/{financial_account_token}", + body=await async_maybe_transform( + {"nickname": nickname}, financial_account_update_params.FinancialAccountUpdateParams + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -376,15 +523,15 @@ async def update( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - business_account_token: str | NotGiven = NOT_GIVEN, - type: Literal["ISSUING", "OPERATING", "RESERVE"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + business_account_token: str | Omit = omit, + type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FinancialAccount, AsyncSinglePage[FinancialAccount]]: """ Retrieve information on your financial accounts including routing and account @@ -406,7 +553,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/financial_accounts", + "/v1/financial_accounts", page=AsyncSinglePage[FinancialAccount], options=make_request_options( extra_headers=extra_headers, @@ -425,6 +572,94 @@ def list( model=FinancialAccount, ) + async def register_account_number( + self, + financial_account_token: str, + *, + account_number: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Register account number + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return await self._post( + f"/v1/financial_accounts/{financial_account_token}/register_account_number", + body=await async_maybe_transform( + {"account_number": account_number}, + financial_account_register_account_number_params.FinancialAccountRegisterAccountNumberParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def update_status( + self, + financial_account_token: str, + *, + status: Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"], + substatus: Optional[Literal["CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "CHARGED_OFF_DELINQUENT"]], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FinancialAccount: + """ + Update financial account status + + Args: + status: Status of the financial account + + substatus: Substatus for the financial account + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return await self._post( + f"/v1/financial_accounts/{financial_account_token}/update_status", + body=await async_maybe_transform( + { + "status": status, + "substatus": substatus, + }, + financial_account_update_status_params.FinancialAccountUpdateStatusParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FinancialAccount, + ) + class FinancialAccountsWithRawResponse: def __init__(self, financial_accounts: FinancialAccounts) -> None: @@ -442,6 +677,12 @@ def __init__(self, financial_accounts: FinancialAccounts) -> None: self.list = _legacy_response.to_raw_response_wrapper( financial_accounts.list, ) + self.register_account_number = _legacy_response.to_raw_response_wrapper( + financial_accounts.register_account_number, + ) + self.update_status = _legacy_response.to_raw_response_wrapper( + financial_accounts.update_status, + ) @cached_property def balances(self) -> BalancesWithRawResponse: @@ -451,10 +692,18 @@ def balances(self) -> BalancesWithRawResponse: def financial_transactions(self) -> FinancialTransactionsWithRawResponse: return FinancialTransactionsWithRawResponse(self._financial_accounts.financial_transactions) + @cached_property + def credit_configuration(self) -> CreditConfigurationWithRawResponse: + return CreditConfigurationWithRawResponse(self._financial_accounts.credit_configuration) + @cached_property def statements(self) -> StatementsWithRawResponse: return StatementsWithRawResponse(self._financial_accounts.statements) + @cached_property + def loan_tapes(self) -> LoanTapesWithRawResponse: + return LoanTapesWithRawResponse(self._financial_accounts.loan_tapes) + class AsyncFinancialAccountsWithRawResponse: def __init__(self, financial_accounts: AsyncFinancialAccounts) -> None: @@ -472,6 +721,12 @@ def __init__(self, financial_accounts: AsyncFinancialAccounts) -> None: self.list = _legacy_response.async_to_raw_response_wrapper( financial_accounts.list, ) + self.register_account_number = _legacy_response.async_to_raw_response_wrapper( + financial_accounts.register_account_number, + ) + self.update_status = _legacy_response.async_to_raw_response_wrapper( + financial_accounts.update_status, + ) @cached_property def balances(self) -> AsyncBalancesWithRawResponse: @@ -481,10 +736,18 @@ def balances(self) -> AsyncBalancesWithRawResponse: def financial_transactions(self) -> AsyncFinancialTransactionsWithRawResponse: return AsyncFinancialTransactionsWithRawResponse(self._financial_accounts.financial_transactions) + @cached_property + def credit_configuration(self) -> AsyncCreditConfigurationWithRawResponse: + return AsyncCreditConfigurationWithRawResponse(self._financial_accounts.credit_configuration) + @cached_property def statements(self) -> AsyncStatementsWithRawResponse: return AsyncStatementsWithRawResponse(self._financial_accounts.statements) + @cached_property + def loan_tapes(self) -> AsyncLoanTapesWithRawResponse: + return AsyncLoanTapesWithRawResponse(self._financial_accounts.loan_tapes) + class FinancialAccountsWithStreamingResponse: def __init__(self, financial_accounts: FinancialAccounts) -> None: @@ -502,6 +765,12 @@ def __init__(self, financial_accounts: FinancialAccounts) -> None: self.list = to_streamed_response_wrapper( financial_accounts.list, ) + self.register_account_number = to_streamed_response_wrapper( + financial_accounts.register_account_number, + ) + self.update_status = to_streamed_response_wrapper( + financial_accounts.update_status, + ) @cached_property def balances(self) -> BalancesWithStreamingResponse: @@ -511,10 +780,18 @@ def balances(self) -> BalancesWithStreamingResponse: def financial_transactions(self) -> FinancialTransactionsWithStreamingResponse: return FinancialTransactionsWithStreamingResponse(self._financial_accounts.financial_transactions) + @cached_property + def credit_configuration(self) -> CreditConfigurationWithStreamingResponse: + return CreditConfigurationWithStreamingResponse(self._financial_accounts.credit_configuration) + @cached_property def statements(self) -> StatementsWithStreamingResponse: return StatementsWithStreamingResponse(self._financial_accounts.statements) + @cached_property + def loan_tapes(self) -> LoanTapesWithStreamingResponse: + return LoanTapesWithStreamingResponse(self._financial_accounts.loan_tapes) + class AsyncFinancialAccountsWithStreamingResponse: def __init__(self, financial_accounts: AsyncFinancialAccounts) -> None: @@ -532,6 +809,12 @@ def __init__(self, financial_accounts: AsyncFinancialAccounts) -> None: self.list = async_to_streamed_response_wrapper( financial_accounts.list, ) + self.register_account_number = async_to_streamed_response_wrapper( + financial_accounts.register_account_number, + ) + self.update_status = async_to_streamed_response_wrapper( + financial_accounts.update_status, + ) @cached_property def balances(self) -> AsyncBalancesWithStreamingResponse: @@ -541,6 +824,14 @@ def balances(self) -> AsyncBalancesWithStreamingResponse: def financial_transactions(self) -> AsyncFinancialTransactionsWithStreamingResponse: return AsyncFinancialTransactionsWithStreamingResponse(self._financial_accounts.financial_transactions) + @cached_property + def credit_configuration(self) -> AsyncCreditConfigurationWithStreamingResponse: + return AsyncCreditConfigurationWithStreamingResponse(self._financial_accounts.credit_configuration) + @cached_property def statements(self) -> AsyncStatementsWithStreamingResponse: return AsyncStatementsWithStreamingResponse(self._financial_accounts.statements) + + @cached_property + def loan_tapes(self) -> AsyncLoanTapesWithStreamingResponse: + return AsyncLoanTapesWithStreamingResponse(self._financial_accounts.loan_tapes) diff --git a/src/lithic/resources/financial_accounts/financial_transactions.py b/src/lithic/resources/financial_accounts/financial_transactions.py index 4065f43c..3df4d47c 100644 --- a/src/lithic/resources/financial_accounts/financial_transactions.py +++ b/src/lithic/resources/financial_accounts/financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,18 +9,15 @@ import httpx from ... import _legacy_response -from ...types import FinancialTransaction -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncSinglePage, AsyncSinglePage -from ..._base_client import ( - AsyncPaginator, - make_request_options, -) +from ..._base_client import AsyncPaginator, make_request_options from ...types.financial_accounts import financial_transaction_list_params +from ...types.financial_transaction import FinancialTransaction __all__ = ["FinancialTransactions", "AsyncFinancialTransactions"] @@ -28,10 +25,21 @@ class FinancialTransactions(SyncAPIResource): @cached_property def with_raw_response(self) -> FinancialTransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return FinancialTransactionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> FinancialTransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return FinancialTransactionsWithStreamingResponse(self) def retrieve( @@ -44,7 +52,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialTransaction: """ Get the financial transaction for the provided token. @@ -67,7 +75,7 @@ def retrieve( f"Expected a non-empty value for `financial_transaction_token` but received {financial_transaction_token!r}" ) return self._get( - f"/financial_accounts/{financial_account_token}/financial_transactions/{financial_transaction_token}", + f"/v1/financial_accounts/{financial_account_token}/financial_transactions/{financial_transaction_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -78,19 +86,19 @@ def list( self, financial_account_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - category: Literal["ACH", "CARD", "TRANSFER"] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + category: Literal["ACH", "CARD", "INTERNAL", "TRANSFER"] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncSinglePage[FinancialTransaction]: """ List the financial transactions for a given financial account. @@ -127,7 +135,7 @@ def list( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._get_api_list( - f"/financial_accounts/{financial_account_token}/financial_transactions", + f"/v1/financial_accounts/{financial_account_token}/financial_transactions", page=SyncSinglePage[FinancialTransaction], options=make_request_options( extra_headers=extra_headers, @@ -154,10 +162,21 @@ def list( class AsyncFinancialTransactions(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncFinancialTransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncFinancialTransactionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncFinancialTransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncFinancialTransactionsWithStreamingResponse(self) async def retrieve( @@ -170,7 +189,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> FinancialTransaction: """ Get the financial transaction for the provided token. @@ -193,7 +212,7 @@ async def retrieve( f"Expected a non-empty value for `financial_transaction_token` but received {financial_transaction_token!r}" ) return await self._get( - f"/financial_accounts/{financial_account_token}/financial_transactions/{financial_transaction_token}", + f"/v1/financial_accounts/{financial_account_token}/financial_transactions/{financial_transaction_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -204,19 +223,19 @@ def list( self, financial_account_token: str, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - category: Literal["ACH", "CARD", "TRANSFER"] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | NotGiven = NOT_GIVEN, + begin: Union[str, datetime] | Omit = omit, + category: Literal["ACH", "CARD", "INTERNAL", "TRANSFER"] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[FinancialTransaction, AsyncSinglePage[FinancialTransaction]]: """ List the financial transactions for a given financial account. @@ -253,7 +272,7 @@ def list( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._get_api_list( - f"/financial_accounts/{financial_account_token}/financial_transactions", + f"/v1/financial_accounts/{financial_account_token}/financial_transactions", page=AsyncSinglePage[FinancialTransaction], options=make_request_options( extra_headers=extra_headers, diff --git a/src/lithic/resources/financial_accounts/loan_tapes.py b/src/lithic/resources/financial_accounts/loan_tapes.py new file mode 100644 index 00000000..96d40fe3 --- /dev/null +++ b/src/lithic/resources/financial_accounts/loan_tapes.py @@ -0,0 +1,335 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncCursorPage, AsyncCursorPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.financial_accounts import loan_tape_list_params +from ...types.financial_accounts.loan_tape import LoanTape + +__all__ = ["LoanTapes", "AsyncLoanTapes"] + + +class LoanTapes(SyncAPIResource): + @cached_property + def with_raw_response(self) -> LoanTapesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return LoanTapesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LoanTapesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return LoanTapesWithStreamingResponse(self) + + def retrieve( + self, + loan_tape_token: str, + *, + financial_account_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoanTape: + """ + Get a specific loan tape for a given financial account. + + Args: + financial_account_token: Globally unique identifier for financial account. + + loan_tape_token: Globally unique identifier for loan tape. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not loan_tape_token: + raise ValueError(f"Expected a non-empty value for `loan_tape_token` but received {loan_tape_token!r}") + return self._get( + f"/v1/financial_accounts/{financial_account_token}/loan_tapes/{loan_tape_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LoanTape, + ) + + def list( + self, + financial_account_token: str, + *, + begin: Union[str, date] | Omit = omit, + end: Union[str, date] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[LoanTape]: + """ + List the loan tapes for a given financial account. + + Args: + financial_account_token: Globally unique identifier for financial account. + + begin: Date string in RFC 3339 format. Only entries created after the specified date + will be included. + + end: Date string in RFC 3339 format. Only entries created before the specified date + will be included. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._get_api_list( + f"/v1/financial_accounts/{financial_account_token}/loan_tapes", + page=SyncCursorPage[LoanTape], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + loan_tape_list_params.LoanTapeListParams, + ), + ), + model=LoanTape, + ) + + +class AsyncLoanTapes(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncLoanTapesWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncLoanTapesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLoanTapesWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncLoanTapesWithStreamingResponse(self) + + async def retrieve( + self, + loan_tape_token: str, + *, + financial_account_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoanTape: + """ + Get a specific loan tape for a given financial account. + + Args: + financial_account_token: Globally unique identifier for financial account. + + loan_tape_token: Globally unique identifier for loan tape. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + if not loan_tape_token: + raise ValueError(f"Expected a non-empty value for `loan_tape_token` but received {loan_tape_token!r}") + return await self._get( + f"/v1/financial_accounts/{financial_account_token}/loan_tapes/{loan_tape_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LoanTape, + ) + + def list( + self, + financial_account_token: str, + *, + begin: Union[str, date] | Omit = omit, + end: Union[str, date] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[LoanTape, AsyncCursorPage[LoanTape]]: + """ + List the loan tapes for a given financial account. + + Args: + financial_account_token: Globally unique identifier for financial account. + + begin: Date string in RFC 3339 format. Only entries created after the specified date + will be included. + + end: Date string in RFC 3339 format. Only entries created before the specified date + will be included. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not financial_account_token: + raise ValueError( + f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" + ) + return self._get_api_list( + f"/v1/financial_accounts/{financial_account_token}/loan_tapes", + page=AsyncCursorPage[LoanTape], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + loan_tape_list_params.LoanTapeListParams, + ), + ), + model=LoanTape, + ) + + +class LoanTapesWithRawResponse: + def __init__(self, loan_tapes: LoanTapes) -> None: + self._loan_tapes = loan_tapes + + self.retrieve = _legacy_response.to_raw_response_wrapper( + loan_tapes.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + loan_tapes.list, + ) + + +class AsyncLoanTapesWithRawResponse: + def __init__(self, loan_tapes: AsyncLoanTapes) -> None: + self._loan_tapes = loan_tapes + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + loan_tapes.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + loan_tapes.list, + ) + + +class LoanTapesWithStreamingResponse: + def __init__(self, loan_tapes: LoanTapes) -> None: + self._loan_tapes = loan_tapes + + self.retrieve = to_streamed_response_wrapper( + loan_tapes.retrieve, + ) + self.list = to_streamed_response_wrapper( + loan_tapes.list, + ) + + +class AsyncLoanTapesWithStreamingResponse: + def __init__(self, loan_tapes: AsyncLoanTapes) -> None: + self._loan_tapes = loan_tapes + + self.retrieve = async_to_streamed_response_wrapper( + loan_tapes.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + loan_tapes.list, + ) diff --git a/src/lithic/resources/financial_accounts/statements/__init__.py b/src/lithic/resources/financial_accounts/statements/__init__.py index dbc3ec38..2882f28f 100644 --- a/src/lithic/resources/financial_accounts/statements/__init__.py +++ b/src/lithic/resources/financial_accounts/statements/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .line_items import ( LineItems, diff --git a/src/lithic/resources/financial_accounts/statements/line_items.py b/src/lithic/resources/financial_accounts/statements/line_items.py index a0080864..64cc1d97 100644 --- a/src/lithic/resources/financial_accounts/statements/line_items.py +++ b/src/lithic/resources/financial_accounts/statements/line_items.py @@ -1,21 +1,19 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import httpx from .... import _legacy_response -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ...._utils import maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ....pagination import SyncCursorPage, AsyncCursorPage -from ...._base_client import ( - AsyncPaginator, - make_request_options, -) -from ....types.financial_accounts.statements import LineItemListResponse, line_item_list_params +from ...._base_client import AsyncPaginator, make_request_options +from ....types.financial_accounts.statements import line_item_list_params +from ....types.financial_accounts.statements.statement_line_items import Data __all__ = ["LineItems", "AsyncLineItems"] @@ -23,10 +21,21 @@ class LineItems(SyncAPIResource): @cached_property def with_raw_response(self) -> LineItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return LineItemsWithRawResponse(self) @cached_property def with_streaming_response(self) -> LineItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return LineItemsWithStreamingResponse(self) def list( @@ -34,20 +43,24 @@ def list( statement_token: str, *, financial_account_token: str, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncCursorPage[LineItemListResponse]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[Data]: """ List the line items for a given statement within a given financial account. Args: + financial_account_token: Globally unique identifier for financial account. + + statement_token: Globally unique identifier for statements. + ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. @@ -71,8 +84,8 @@ def list( if not statement_token: raise ValueError(f"Expected a non-empty value for `statement_token` but received {statement_token!r}") return self._get_api_list( - f"/financial_accounts/{financial_account_token}/statements/{statement_token}/line_items", - page=SyncCursorPage[LineItemListResponse], + f"/v1/financial_accounts/{financial_account_token}/statements/{statement_token}/line_items", + page=SyncCursorPage[Data], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -87,17 +100,28 @@ def list( line_item_list_params.LineItemListParams, ), ), - model=LineItemListResponse, + model=Data, ) class AsyncLineItems(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncLineItemsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncLineItemsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncLineItemsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncLineItemsWithStreamingResponse(self) def list( @@ -105,20 +129,24 @@ def list( statement_token: str, *, financial_account_token: str, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[LineItemListResponse, AsyncCursorPage[LineItemListResponse]]: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Data, AsyncCursorPage[Data]]: """ List the line items for a given statement within a given financial account. Args: + financial_account_token: Globally unique identifier for financial account. + + statement_token: Globally unique identifier for statements. + ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. @@ -142,8 +170,8 @@ def list( if not statement_token: raise ValueError(f"Expected a non-empty value for `statement_token` but received {statement_token!r}") return self._get_api_list( - f"/financial_accounts/{financial_account_token}/statements/{statement_token}/line_items", - page=AsyncCursorPage[LineItemListResponse], + f"/v1/financial_accounts/{financial_account_token}/statements/{statement_token}/line_items", + page=AsyncCursorPage[Data], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -158,7 +186,7 @@ def list( line_item_list_params.LineItemListParams, ), ), - model=LineItemListResponse, + model=Data, ) diff --git a/src/lithic/resources/financial_accounts/statements/statements.py b/src/lithic/resources/financial_accounts/statements/statements.py index 3af46fe2..08afc6ea 100644 --- a/src/lithic/resources/financial_accounts/statements/statements.py +++ b/src/lithic/resources/financial_accounts/statements/statements.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -8,7 +8,7 @@ import httpx from .... import _legacy_response -from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ...._utils import maybe_transform from ...._compat import cached_property from .line_items import ( @@ -22,11 +22,9 @@ from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ....pagination import SyncCursorPage, AsyncCursorPage -from ...._base_client import ( - AsyncPaginator, - make_request_options, -) -from ....types.financial_accounts import Statement, statement_list_params +from ...._base_client import AsyncPaginator, make_request_options +from ....types.financial_accounts import statement_list_params +from ....types.financial_accounts.statement import Statement __all__ = ["Statements", "AsyncStatements"] @@ -38,10 +36,21 @@ def line_items(self) -> LineItems: @cached_property def with_raw_response(self) -> StatementsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return StatementsWithRawResponse(self) @cached_property def with_streaming_response(self) -> StatementsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return StatementsWithStreamingResponse(self) def retrieve( @@ -54,12 +63,16 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Statement: """ Get a specific statement for a given financial account. Args: + financial_account_token: Globally unique identifier for financial account. + + statement_token: Globally unique identifier for statements. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -75,7 +88,7 @@ def retrieve( if not statement_token: raise ValueError(f"Expected a non-empty value for `statement_token` but received {statement_token!r}") return self._get( - f"/financial_accounts/{financial_account_token}/statements/{statement_token}", + f"/v1/financial_accounts/{financial_account_token}/statements/{statement_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -86,22 +99,25 @@ def list( self, financial_account_token: str, *, - begin: Union[str, date] | NotGiven = NOT_GIVEN, - end: Union[str, date] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, date] | Omit = omit, + end: Union[str, date] | Omit = omit, + ending_before: str | Omit = omit, + include_initial_statements: bool | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Statement]: """ List the statements for a given financial account. Args: + financial_account_token: Globally unique identifier for financial account. + begin: Date string in RFC 3339 format. Only entries created after the specified date will be included. @@ -111,6 +127,8 @@ def list( ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. + include_initial_statements: Whether to include the initial statement. It is not included by default. + page_size: Page size (for pagination). starting_after: A cursor representing an item's token after which a page of results should @@ -129,7 +147,7 @@ def list( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._get_api_list( - f"/financial_accounts/{financial_account_token}/statements", + f"/v1/financial_accounts/{financial_account_token}/statements", page=SyncCursorPage[Statement], options=make_request_options( extra_headers=extra_headers, @@ -141,6 +159,7 @@ def list( "begin": begin, "end": end, "ending_before": ending_before, + "include_initial_statements": include_initial_statements, "page_size": page_size, "starting_after": starting_after, }, @@ -158,10 +177,21 @@ def line_items(self) -> AsyncLineItems: @cached_property def with_raw_response(self) -> AsyncStatementsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncStatementsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncStatementsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncStatementsWithStreamingResponse(self) async def retrieve( @@ -174,12 +204,16 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Statement: """ Get a specific statement for a given financial account. Args: + financial_account_token: Globally unique identifier for financial account. + + statement_token: Globally unique identifier for statements. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -195,7 +229,7 @@ async def retrieve( if not statement_token: raise ValueError(f"Expected a non-empty value for `statement_token` but received {statement_token!r}") return await self._get( - f"/financial_accounts/{financial_account_token}/statements/{statement_token}", + f"/v1/financial_accounts/{financial_account_token}/statements/{statement_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -206,22 +240,25 @@ def list( self, financial_account_token: str, *, - begin: Union[str, date] | NotGiven = NOT_GIVEN, - end: Union[str, date] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + begin: Union[str, date] | Omit = omit, + end: Union[str, date] | Omit = omit, + ending_before: str | Omit = omit, + include_initial_statements: bool | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Statement, AsyncCursorPage[Statement]]: """ List the statements for a given financial account. Args: + financial_account_token: Globally unique identifier for financial account. + begin: Date string in RFC 3339 format. Only entries created after the specified date will be included. @@ -231,6 +268,8 @@ def list( ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. + include_initial_statements: Whether to include the initial statement. It is not included by default. + page_size: Page size (for pagination). starting_after: A cursor representing an item's token after which a page of results should @@ -249,7 +288,7 @@ def list( f"Expected a non-empty value for `financial_account_token` but received {financial_account_token!r}" ) return self._get_api_list( - f"/financial_accounts/{financial_account_token}/statements", + f"/v1/financial_accounts/{financial_account_token}/statements", page=AsyncCursorPage[Statement], options=make_request_options( extra_headers=extra_headers, @@ -261,6 +300,7 @@ def list( "begin": begin, "end": end, "ending_before": ending_before, + "include_initial_statements": include_initial_statements, "page_size": page_size, "starting_after": starting_after, }, diff --git a/src/lithic/resources/fraud/__init__.py b/src/lithic/resources/fraud/__init__.py new file mode 100644 index 00000000..5ebcc75e --- /dev/null +++ b/src/lithic/resources/fraud/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .fraud import ( + Fraud, + AsyncFraud, + FraudWithRawResponse, + AsyncFraudWithRawResponse, + FraudWithStreamingResponse, + AsyncFraudWithStreamingResponse, +) +from .transactions import ( + Transactions, + AsyncTransactions, + TransactionsWithRawResponse, + AsyncTransactionsWithRawResponse, + TransactionsWithStreamingResponse, + AsyncTransactionsWithStreamingResponse, +) + +__all__ = [ + "Transactions", + "AsyncTransactions", + "TransactionsWithRawResponse", + "AsyncTransactionsWithRawResponse", + "TransactionsWithStreamingResponse", + "AsyncTransactionsWithStreamingResponse", + "Fraud", + "AsyncFraud", + "FraudWithRawResponse", + "AsyncFraudWithRawResponse", + "FraudWithStreamingResponse", + "AsyncFraudWithStreamingResponse", +] diff --git a/src/lithic/resources/fraud/fraud.py b/src/lithic/resources/fraud/fraud.py new file mode 100644 index 00000000..ba851033 --- /dev/null +++ b/src/lithic/resources/fraud/fraud.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from .transactions import ( + Transactions, + AsyncTransactions, + TransactionsWithRawResponse, + AsyncTransactionsWithRawResponse, + TransactionsWithStreamingResponse, + AsyncTransactionsWithStreamingResponse, +) + +__all__ = ["Fraud", "AsyncFraud"] + + +class Fraud(SyncAPIResource): + @cached_property + def transactions(self) -> Transactions: + return Transactions(self._client) + + @cached_property + def with_raw_response(self) -> FraudWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return FraudWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FraudWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return FraudWithStreamingResponse(self) + + +class AsyncFraud(AsyncAPIResource): + @cached_property + def transactions(self) -> AsyncTransactions: + return AsyncTransactions(self._client) + + @cached_property + def with_raw_response(self) -> AsyncFraudWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncFraudWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFraudWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncFraudWithStreamingResponse(self) + + +class FraudWithRawResponse: + def __init__(self, fraud: Fraud) -> None: + self._fraud = fraud + + @cached_property + def transactions(self) -> TransactionsWithRawResponse: + return TransactionsWithRawResponse(self._fraud.transactions) + + +class AsyncFraudWithRawResponse: + def __init__(self, fraud: AsyncFraud) -> None: + self._fraud = fraud + + @cached_property + def transactions(self) -> AsyncTransactionsWithRawResponse: + return AsyncTransactionsWithRawResponse(self._fraud.transactions) + + +class FraudWithStreamingResponse: + def __init__(self, fraud: Fraud) -> None: + self._fraud = fraud + + @cached_property + def transactions(self) -> TransactionsWithStreamingResponse: + return TransactionsWithStreamingResponse(self._fraud.transactions) + + +class AsyncFraudWithStreamingResponse: + def __init__(self, fraud: AsyncFraud) -> None: + self._fraud = fraud + + @cached_property + def transactions(self) -> AsyncTransactionsWithStreamingResponse: + return AsyncTransactionsWithStreamingResponse(self._fraud.transactions) diff --git a/src/lithic/resources/fraud/transactions.py b/src/lithic/resources/fraud/transactions.py new file mode 100644 index 00000000..1f3e6e2d --- /dev/null +++ b/src/lithic/resources/fraud/transactions.py @@ -0,0 +1,348 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ... import _legacy_response +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...types.fraud import transaction_report_params +from ..._base_client import make_request_options +from ...types.fraud.transaction_report_response import TransactionReportResponse +from ...types.fraud.transaction_retrieve_response import TransactionRetrieveResponse + +__all__ = ["Transactions", "AsyncTransactions"] + + +class Transactions(SyncAPIResource): + @cached_property + def with_raw_response(self) -> TransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return TransactionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return TransactionsWithStreamingResponse(self) + + def retrieve( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TransactionRetrieveResponse: + """ + Retrieve a fraud report for a specific transaction identified by its unique + transaction token. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return self._get( + f"/v1/fraud/transactions/{transaction_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TransactionRetrieveResponse, + ) + + def report( + self, + transaction_token: str, + *, + fraud_status: Literal["SUSPECTED_FRAUD", "FRAUDULENT", "NOT_FRAUDULENT"], + comment: str | Omit = omit, + fraud_type: Literal[ + "FIRST_PARTY_FRAUD", "ACCOUNT_TAKEOVER", "CARD_COMPROMISED", "IDENTITY_THEFT", "CARDHOLDER_MANIPULATION" + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TransactionReportResponse: + """ + Report fraud for a specific transaction token by providing details such as fraud + type, fraud status, and any additional comments. + + Args: + fraud_status: The fraud status of the transaction, string (enum) supporting the following + values: + + - `SUSPECTED_FRAUD`: The transaction is suspected to be fraudulent, but this + hasn’t been confirmed. + - `FRAUDULENT`: The transaction is confirmed to be fraudulent. A transaction may + immediately be moved into this state, or be graduated into this state from the + `SUSPECTED_FRAUD` state. + - `NOT_FRAUDULENT`: The transaction is (explicitly) marked as not fraudulent. A + transaction may immediately be moved into this state, or be graduated into + this state from the `SUSPECTED_FRAUD` state. + + comment: Optional field providing additional information or context about why the + transaction is considered fraudulent. + + fraud_type: Specifies the type or category of fraud that the transaction is suspected or + confirmed to involve, string (enum) supporting the following values: + + - `FIRST_PARTY_FRAUD`: First-party fraud occurs when a legitimate account or + cardholder intentionally misuses financial services for personal gain. This + includes actions such as disputing legitimate transactions to obtain a refund, + abusing return policies, or defaulting on credit obligations without intent to + repay. + - `ACCOUNT_TAKEOVER`: Account takeover fraud occurs when a fraudster gains + unauthorized access to an existing account, modifies account settings, and + carries out fraudulent transactions. + - `CARD_COMPROMISED`: Card compromised fraud occurs when a fraudster gains + access to card details without taking over the account, such as through + physical card theft, cloning, or online data breaches. + - `IDENTITY_THEFT`: Identity theft fraud occurs when a fraudster uses stolen + personal information, such as Social Security numbers or addresses, to open + accounts, apply for loans, or conduct financial transactions in someone's + name. + - `CARDHOLDER_MANIPULATION`: This type of fraud occurs when a fraudster + manipulates or coerces a legitimate cardholder into unauthorized transactions, + often through social engineering tactics. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return self._post( + f"/v1/fraud/transactions/{transaction_token}", + body=maybe_transform( + { + "fraud_status": fraud_status, + "comment": comment, + "fraud_type": fraud_type, + }, + transaction_report_params.TransactionReportParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TransactionReportResponse, + ) + + +class AsyncTransactions(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncTransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncTransactionsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncTransactionsWithStreamingResponse(self) + + async def retrieve( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TransactionRetrieveResponse: + """ + Retrieve a fraud report for a specific transaction identified by its unique + transaction token. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return await self._get( + f"/v1/fraud/transactions/{transaction_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TransactionRetrieveResponse, + ) + + async def report( + self, + transaction_token: str, + *, + fraud_status: Literal["SUSPECTED_FRAUD", "FRAUDULENT", "NOT_FRAUDULENT"], + comment: str | Omit = omit, + fraud_type: Literal[ + "FIRST_PARTY_FRAUD", "ACCOUNT_TAKEOVER", "CARD_COMPROMISED", "IDENTITY_THEFT", "CARDHOLDER_MANIPULATION" + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TransactionReportResponse: + """ + Report fraud for a specific transaction token by providing details such as fraud + type, fraud status, and any additional comments. + + Args: + fraud_status: The fraud status of the transaction, string (enum) supporting the following + values: + + - `SUSPECTED_FRAUD`: The transaction is suspected to be fraudulent, but this + hasn’t been confirmed. + - `FRAUDULENT`: The transaction is confirmed to be fraudulent. A transaction may + immediately be moved into this state, or be graduated into this state from the + `SUSPECTED_FRAUD` state. + - `NOT_FRAUDULENT`: The transaction is (explicitly) marked as not fraudulent. A + transaction may immediately be moved into this state, or be graduated into + this state from the `SUSPECTED_FRAUD` state. + + comment: Optional field providing additional information or context about why the + transaction is considered fraudulent. + + fraud_type: Specifies the type or category of fraud that the transaction is suspected or + confirmed to involve, string (enum) supporting the following values: + + - `FIRST_PARTY_FRAUD`: First-party fraud occurs when a legitimate account or + cardholder intentionally misuses financial services for personal gain. This + includes actions such as disputing legitimate transactions to obtain a refund, + abusing return policies, or defaulting on credit obligations without intent to + repay. + - `ACCOUNT_TAKEOVER`: Account takeover fraud occurs when a fraudster gains + unauthorized access to an existing account, modifies account settings, and + carries out fraudulent transactions. + - `CARD_COMPROMISED`: Card compromised fraud occurs when a fraudster gains + access to card details without taking over the account, such as through + physical card theft, cloning, or online data breaches. + - `IDENTITY_THEFT`: Identity theft fraud occurs when a fraudster uses stolen + personal information, such as Social Security numbers or addresses, to open + accounts, apply for loans, or conduct financial transactions in someone's + name. + - `CARDHOLDER_MANIPULATION`: This type of fraud occurs when a fraudster + manipulates or coerces a legitimate cardholder into unauthorized transactions, + often through social engineering tactics. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return await self._post( + f"/v1/fraud/transactions/{transaction_token}", + body=await async_maybe_transform( + { + "fraud_status": fraud_status, + "comment": comment, + "fraud_type": fraud_type, + }, + transaction_report_params.TransactionReportParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TransactionReportResponse, + ) + + +class TransactionsWithRawResponse: + def __init__(self, transactions: Transactions) -> None: + self._transactions = transactions + + self.retrieve = _legacy_response.to_raw_response_wrapper( + transactions.retrieve, + ) + self.report = _legacy_response.to_raw_response_wrapper( + transactions.report, + ) + + +class AsyncTransactionsWithRawResponse: + def __init__(self, transactions: AsyncTransactions) -> None: + self._transactions = transactions + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + transactions.retrieve, + ) + self.report = _legacy_response.async_to_raw_response_wrapper( + transactions.report, + ) + + +class TransactionsWithStreamingResponse: + def __init__(self, transactions: Transactions) -> None: + self._transactions = transactions + + self.retrieve = to_streamed_response_wrapper( + transactions.retrieve, + ) + self.report = to_streamed_response_wrapper( + transactions.report, + ) + + +class AsyncTransactionsWithStreamingResponse: + def __init__(self, transactions: AsyncTransactions) -> None: + self._transactions = transactions + + self.retrieve = async_to_streamed_response_wrapper( + transactions.retrieve, + ) + self.report = async_to_streamed_response_wrapper( + transactions.report, + ) diff --git a/src/lithic/resources/funding_events.py b/src/lithic/resources/funding_events.py new file mode 100644 index 00000000..44599b96 --- /dev/null +++ b/src/lithic/resources/funding_events.py @@ -0,0 +1,367 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .. import _legacy_response +from ..types import funding_event_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.funding_event import FundingEvent +from ..types.funding_event_retrieve_details_response import FundingEventRetrieveDetailsResponse + +__all__ = ["FundingEvents", "AsyncFundingEvents"] + + +class FundingEvents(SyncAPIResource): + @cached_property + def with_raw_response(self) -> FundingEventsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return FundingEventsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FundingEventsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return FundingEventsWithStreamingResponse(self) + + def retrieve( + self, + funding_event_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FundingEvent: + """ + Get funding event for program by id + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not funding_event_token: + raise ValueError( + f"Expected a non-empty value for `funding_event_token` but received {funding_event_token!r}" + ) + return self._get( + f"/v1/funding_events/{funding_event_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FundingEvent, + ) + + def list( + self, + *, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[FundingEvent]: + """ + Get all funding events for program + + Args: + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/funding_events", + page=SyncCursorPage[FundingEvent], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + funding_event_list_params.FundingEventListParams, + ), + ), + model=FundingEvent, + ) + + def retrieve_details( + self, + funding_event_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FundingEventRetrieveDetailsResponse: + """ + Get funding event details by id + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not funding_event_token: + raise ValueError( + f"Expected a non-empty value for `funding_event_token` but received {funding_event_token!r}" + ) + return self._get( + f"/v1/funding_events/{funding_event_token}/details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FundingEventRetrieveDetailsResponse, + ) + + +class AsyncFundingEvents(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncFundingEventsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncFundingEventsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFundingEventsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncFundingEventsWithStreamingResponse(self) + + async def retrieve( + self, + funding_event_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FundingEvent: + """ + Get funding event for program by id + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not funding_event_token: + raise ValueError( + f"Expected a non-empty value for `funding_event_token` but received {funding_event_token!r}" + ) + return await self._get( + f"/v1/funding_events/{funding_event_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FundingEvent, + ) + + def list( + self, + *, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[FundingEvent, AsyncCursorPage[FundingEvent]]: + """ + Get all funding events for program + + Args: + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/funding_events", + page=AsyncCursorPage[FundingEvent], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "ending_before": ending_before, + "page_size": page_size, + "starting_after": starting_after, + }, + funding_event_list_params.FundingEventListParams, + ), + ), + model=FundingEvent, + ) + + async def retrieve_details( + self, + funding_event_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FundingEventRetrieveDetailsResponse: + """ + Get funding event details by id + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not funding_event_token: + raise ValueError( + f"Expected a non-empty value for `funding_event_token` but received {funding_event_token!r}" + ) + return await self._get( + f"/v1/funding_events/{funding_event_token}/details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FundingEventRetrieveDetailsResponse, + ) + + +class FundingEventsWithRawResponse: + def __init__(self, funding_events: FundingEvents) -> None: + self._funding_events = funding_events + + self.retrieve = _legacy_response.to_raw_response_wrapper( + funding_events.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + funding_events.list, + ) + self.retrieve_details = _legacy_response.to_raw_response_wrapper( + funding_events.retrieve_details, + ) + + +class AsyncFundingEventsWithRawResponse: + def __init__(self, funding_events: AsyncFundingEvents) -> None: + self._funding_events = funding_events + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + funding_events.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + funding_events.list, + ) + self.retrieve_details = _legacy_response.async_to_raw_response_wrapper( + funding_events.retrieve_details, + ) + + +class FundingEventsWithStreamingResponse: + def __init__(self, funding_events: FundingEvents) -> None: + self._funding_events = funding_events + + self.retrieve = to_streamed_response_wrapper( + funding_events.retrieve, + ) + self.list = to_streamed_response_wrapper( + funding_events.list, + ) + self.retrieve_details = to_streamed_response_wrapper( + funding_events.retrieve_details, + ) + + +class AsyncFundingEventsWithStreamingResponse: + def __init__(self, funding_events: AsyncFundingEvents) -> None: + self._funding_events = funding_events + + self.retrieve = async_to_streamed_response_wrapper( + funding_events.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + funding_events.list, + ) + self.retrieve_details = async_to_streamed_response_wrapper( + funding_events.retrieve_details, + ) diff --git a/src/lithic/resources/management_operations.py b/src/lithic/resources/management_operations.py new file mode 100644 index 00000000..641599fb --- /dev/null +++ b/src/lithic/resources/management_operations.py @@ -0,0 +1,652 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date, datetime +from typing_extensions import Literal + +import httpx + +from .. import _legacy_response +from ..types import ( + management_operation_list_params, + management_operation_create_params, + management_operation_reverse_params, +) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.management_operation_transaction import ManagementOperationTransaction + +__all__ = ["ManagementOperations", "AsyncManagementOperations"] + + +class ManagementOperations(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ManagementOperationsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return ManagementOperationsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ManagementOperationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return ManagementOperationsWithStreamingResponse(self) + + def create( + self, + *, + amount: int, + category: Literal[ + "MANAGEMENT_FEE", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_REWARD", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISBURSEMENT", + ], + direction: Literal["CREDIT", "DEBIT"], + effective_date: Union[str, date], + event_type: Literal[ + "LOSS_WRITE_OFF", + "CASH_BACK", + "CASH_BACK_REVERSAL", + "CURRENCY_CONVERSION", + "CURRENCY_CONVERSION_REVERSAL", + "INTEREST", + "INTEREST_REVERSAL", + "LATE_PAYMENT", + "LATE_PAYMENT_REVERSAL", + "BILLING_ERROR", + "BILLING_ERROR_REVERSAL", + "PROVISIONAL_CREDIT", + "PROVISIONAL_CREDIT_REVERSAL", + "RETURNED_PAYMENT", + "RETURNED_PAYMENT_REVERSAL", + "DISPUTE_WON", + "DISPUTE_WON_REVERSAL", + "DISBURSE", + "DISBURSE_REVERSAL", + "ANNUAL", + "ANNUAL_REVERSAL", + "QUARTERLY", + "QUARTERLY_REVERSAL", + "MONTHLY", + "MONTHLY_REVERSAL", + ], + financial_account_token: str, + token: str | Omit = omit, + memo: str | Omit = omit, + on_closed_account: Literal["FAIL", "USE_SUSPENSE"] | Omit = omit, + subtype: str | Omit = omit, + user_defined_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ManagementOperationTransaction: + """ + Create management operation + + Args: + on_closed_account: What to do if the financial account is closed when posting an operation + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/management_operations", + body=maybe_transform( + { + "amount": amount, + "category": category, + "direction": direction, + "effective_date": effective_date, + "event_type": event_type, + "financial_account_token": financial_account_token, + "token": token, + "memo": memo, + "on_closed_account": on_closed_account, + "subtype": subtype, + "user_defined_id": user_defined_id, + }, + management_operation_create_params.ManagementOperationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagementOperationTransaction, + ) + + def retrieve( + self, + management_operation_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ManagementOperationTransaction: + """ + Get management operation + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not management_operation_token: + raise ValueError( + f"Expected a non-empty value for `management_operation_token` but received {management_operation_token!r}" + ) + return self._get( + f"/v1/management_operations/{management_operation_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagementOperationTransaction, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "MANAGEMENT_FEE", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_REWARD", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISBURSEMENT", + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ManagementOperationTransaction]: + """List management operations + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + category: Management operation category to be returned. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Globally unique identifier for the financial account. Accepted type dependent on + the program's use case. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Management operation status to be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/management_operations", + page=SyncCursorPage[ManagementOperationTransaction], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "starting_after": starting_after, + "status": status, + }, + management_operation_list_params.ManagementOperationListParams, + ), + ), + model=ManagementOperationTransaction, + ) + + def reverse( + self, + management_operation_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ManagementOperationTransaction: + """ + Reverse a management operation + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not management_operation_token: + raise ValueError( + f"Expected a non-empty value for `management_operation_token` but received {management_operation_token!r}" + ) + return self._post( + f"/v1/management_operations/{management_operation_token}/reverse", + body=maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + management_operation_reverse_params.ManagementOperationReverseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagementOperationTransaction, + ) + + +class AsyncManagementOperations(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncManagementOperationsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncManagementOperationsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncManagementOperationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncManagementOperationsWithStreamingResponse(self) + + async def create( + self, + *, + amount: int, + category: Literal[ + "MANAGEMENT_FEE", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_REWARD", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISBURSEMENT", + ], + direction: Literal["CREDIT", "DEBIT"], + effective_date: Union[str, date], + event_type: Literal[ + "LOSS_WRITE_OFF", + "CASH_BACK", + "CASH_BACK_REVERSAL", + "CURRENCY_CONVERSION", + "CURRENCY_CONVERSION_REVERSAL", + "INTEREST", + "INTEREST_REVERSAL", + "LATE_PAYMENT", + "LATE_PAYMENT_REVERSAL", + "BILLING_ERROR", + "BILLING_ERROR_REVERSAL", + "PROVISIONAL_CREDIT", + "PROVISIONAL_CREDIT_REVERSAL", + "RETURNED_PAYMENT", + "RETURNED_PAYMENT_REVERSAL", + "DISPUTE_WON", + "DISPUTE_WON_REVERSAL", + "DISBURSE", + "DISBURSE_REVERSAL", + "ANNUAL", + "ANNUAL_REVERSAL", + "QUARTERLY", + "QUARTERLY_REVERSAL", + "MONTHLY", + "MONTHLY_REVERSAL", + ], + financial_account_token: str, + token: str | Omit = omit, + memo: str | Omit = omit, + on_closed_account: Literal["FAIL", "USE_SUSPENSE"] | Omit = omit, + subtype: str | Omit = omit, + user_defined_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ManagementOperationTransaction: + """ + Create management operation + + Args: + on_closed_account: What to do if the financial account is closed when posting an operation + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/management_operations", + body=await async_maybe_transform( + { + "amount": amount, + "category": category, + "direction": direction, + "effective_date": effective_date, + "event_type": event_type, + "financial_account_token": financial_account_token, + "token": token, + "memo": memo, + "on_closed_account": on_closed_account, + "subtype": subtype, + "user_defined_id": user_defined_id, + }, + management_operation_create_params.ManagementOperationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagementOperationTransaction, + ) + + async def retrieve( + self, + management_operation_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ManagementOperationTransaction: + """ + Get management operation + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not management_operation_token: + raise ValueError( + f"Expected a non-empty value for `management_operation_token` but received {management_operation_token!r}" + ) + return await self._get( + f"/v1/management_operations/{management_operation_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagementOperationTransaction, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal[ + "MANAGEMENT_FEE", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_REWARD", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISBURSEMENT", + ] + | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ManagementOperationTransaction, AsyncCursorPage[ManagementOperationTransaction]]: + """List management operations + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + category: Management operation category to be returned. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + financial_account_token: Globally unique identifier for the financial account. Accepted type dependent on + the program's use case. + + page_size: Page size (for pagination). + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + status: Management operation status to be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/management_operations", + page=AsyncCursorPage[ManagementOperationTransaction], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "business_account_token": business_account_token, + "category": category, + "end": end, + "ending_before": ending_before, + "financial_account_token": financial_account_token, + "page_size": page_size, + "starting_after": starting_after, + "status": status, + }, + management_operation_list_params.ManagementOperationListParams, + ), + ), + model=ManagementOperationTransaction, + ) + + async def reverse( + self, + management_operation_token: str, + *, + effective_date: Union[str, date], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ManagementOperationTransaction: + """ + Reverse a management operation + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not management_operation_token: + raise ValueError( + f"Expected a non-empty value for `management_operation_token` but received {management_operation_token!r}" + ) + return await self._post( + f"/v1/management_operations/{management_operation_token}/reverse", + body=await async_maybe_transform( + { + "effective_date": effective_date, + "memo": memo, + }, + management_operation_reverse_params.ManagementOperationReverseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagementOperationTransaction, + ) + + +class ManagementOperationsWithRawResponse: + def __init__(self, management_operations: ManagementOperations) -> None: + self._management_operations = management_operations + + self.create = _legacy_response.to_raw_response_wrapper( + management_operations.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + management_operations.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + management_operations.list, + ) + self.reverse = _legacy_response.to_raw_response_wrapper( + management_operations.reverse, + ) + + +class AsyncManagementOperationsWithRawResponse: + def __init__(self, management_operations: AsyncManagementOperations) -> None: + self._management_operations = management_operations + + self.create = _legacy_response.async_to_raw_response_wrapper( + management_operations.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + management_operations.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + management_operations.list, + ) + self.reverse = _legacy_response.async_to_raw_response_wrapper( + management_operations.reverse, + ) + + +class ManagementOperationsWithStreamingResponse: + def __init__(self, management_operations: ManagementOperations) -> None: + self._management_operations = management_operations + + self.create = to_streamed_response_wrapper( + management_operations.create, + ) + self.retrieve = to_streamed_response_wrapper( + management_operations.retrieve, + ) + self.list = to_streamed_response_wrapper( + management_operations.list, + ) + self.reverse = to_streamed_response_wrapper( + management_operations.reverse, + ) + + +class AsyncManagementOperationsWithStreamingResponse: + def __init__(self, management_operations: AsyncManagementOperations) -> None: + self._management_operations = management_operations + + self.create = async_to_streamed_response_wrapper( + management_operations.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + management_operations.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + management_operations.list, + ) + self.reverse = async_to_streamed_response_wrapper( + management_operations.reverse, + ) diff --git a/src/lithic/resources/network_programs.py b/src/lithic/resources/network_programs.py new file mode 100644 index 00000000..2b0fb8aa --- /dev/null +++ b/src/lithic/resources/network_programs.py @@ -0,0 +1,289 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime + +import httpx + +from .. import _legacy_response +from ..types import network_program_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..pagination import SyncSinglePage, AsyncSinglePage +from .._base_client import AsyncPaginator, make_request_options +from ..types.network_program import NetworkProgram + +__all__ = ["NetworkPrograms", "AsyncNetworkPrograms"] + + +class NetworkPrograms(SyncAPIResource): + @cached_property + def with_raw_response(self) -> NetworkProgramsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return NetworkProgramsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> NetworkProgramsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return NetworkProgramsWithStreamingResponse(self) + + def retrieve( + self, + network_program_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NetworkProgram: + """ + Get network program. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not network_program_token: + raise ValueError( + f"Expected a non-empty value for `network_program_token` but received {network_program_token!r}" + ) + return self._get( + f"/v1/network_programs/{network_program_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NetworkProgram, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + page_size: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncSinglePage[NetworkProgram]: + """List network programs. + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + page_size: Page size (for pagination). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/network_programs", + page=SyncSinglePage[NetworkProgram], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "page_size": page_size, + }, + network_program_list_params.NetworkProgramListParams, + ), + ), + model=NetworkProgram, + ) + + +class AsyncNetworkPrograms(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncNetworkProgramsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncNetworkProgramsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncNetworkProgramsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncNetworkProgramsWithStreamingResponse(self) + + async def retrieve( + self, + network_program_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NetworkProgram: + """ + Get network program. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not network_program_token: + raise ValueError( + f"Expected a non-empty value for `network_program_token` but received {network_program_token!r}" + ) + return await self._get( + f"/v1/network_programs/{network_program_token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NetworkProgram, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + page_size: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[NetworkProgram, AsyncSinglePage[NetworkProgram]]: + """List network programs. + + Args: + begin: Date string in RFC 3339 format. + + Only entries created after the specified time + will be included. UTC time zone. + + end: Date string in RFC 3339 format. Only entries created before the specified time + will be included. UTC time zone. + + page_size: Page size (for pagination). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/network_programs", + page=AsyncSinglePage[NetworkProgram], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "page_size": page_size, + }, + network_program_list_params.NetworkProgramListParams, + ), + ), + model=NetworkProgram, + ) + + +class NetworkProgramsWithRawResponse: + def __init__(self, network_programs: NetworkPrograms) -> None: + self._network_programs = network_programs + + self.retrieve = _legacy_response.to_raw_response_wrapper( + network_programs.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + network_programs.list, + ) + + +class AsyncNetworkProgramsWithRawResponse: + def __init__(self, network_programs: AsyncNetworkPrograms) -> None: + self._network_programs = network_programs + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + network_programs.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + network_programs.list, + ) + + +class NetworkProgramsWithStreamingResponse: + def __init__(self, network_programs: NetworkPrograms) -> None: + self._network_programs = network_programs + + self.retrieve = to_streamed_response_wrapper( + network_programs.retrieve, + ) + self.list = to_streamed_response_wrapper( + network_programs.list, + ) + + +class AsyncNetworkProgramsWithStreamingResponse: + def __init__(self, network_programs: AsyncNetworkPrograms) -> None: + self._network_programs = network_programs + + self.retrieve = async_to_streamed_response_wrapper( + network_programs.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + network_programs.list, + ) diff --git a/src/lithic/resources/payments.py b/src/lithic/resources/payments.py index b0aea627..59d0d978 100644 --- a/src/lithic/resources/payments.py +++ b/src/lithic/resources/payments.py @@ -1,35 +1,37 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing import Union -from datetime import datetime +from typing import Union, Optional +from datetime import date, datetime from typing_extensions import Literal import httpx from .. import _legacy_response from ..types import ( - Payment, - PaymentRetryResponse, - PaymentCreateResponse, - PaymentSimulateReturnResponse, - PaymentSimulateReleaseResponse, payment_list_params, payment_create_params, + payment_return_params, + payment_simulate_action_params, payment_simulate_return_params, + payment_simulate_receipt_params, payment_simulate_release_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.payment import Payment +from ..types.payment_retry_response import PaymentRetryResponse +from ..types.payment_create_response import PaymentCreateResponse +from ..types.payment_simulate_action_response import PaymentSimulateActionResponse +from ..types.payment_simulate_return_response import PaymentSimulateReturnResponse +from ..types.payment_simulate_receipt_response import PaymentSimulateReceiptResponse +from ..types.payment_simulate_release_response import PaymentSimulateReleaseResponse __all__ = ["Payments", "AsyncPayments"] @@ -37,10 +39,21 @@ class Payments(SyncAPIResource): @cached_property def with_raw_response(self) -> PaymentsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return PaymentsWithRawResponse(self) @cached_property def with_streaming_response(self) -> PaymentsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return PaymentsWithStreamingResponse(self) def create( @@ -52,15 +65,15 @@ def create( method: Literal["ACH_NEXT_DAY", "ACH_SAME_DAY"], method_attributes: payment_create_params.MethodAttributes, type: Literal["COLLECTION", "PAYMENT"], - token: str | NotGiven = NOT_GIVEN, - memo: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, + token: str | Omit = omit, + memo: str | Omit = omit, + user_defined_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentCreateResponse: """ Initiates a payment between a financial account and an external bank account. @@ -78,7 +91,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/payments", + "/v1/payments", body=maybe_transform( { "amount": amount, @@ -108,7 +121,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Payment: """ Get the payment by token. @@ -125,7 +138,7 @@ def retrieve( if not payment_token: raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") return self._get( - f"/payments/{payment_token}", + f"/v1/payments/{payment_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -135,20 +148,23 @@ def retrieve( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - financial_account_token: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["DECLINED", "PENDING", "RETURNED", "SETTLED"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal["ACH"] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "PENDING", "RETURNED", "SETTLED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Payment]: """ List all the payments for the provided search criteria. @@ -177,7 +193,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/payments", + "/v1/payments", page=SyncCursorPage[Payment], options=make_request_options( extra_headers=extra_headers, @@ -186,7 +202,10 @@ def list( timeout=timeout, query=maybe_transform( { + "account_token": account_token, "begin": begin, + "business_account_token": business_account_token, + "category": category, "end": end, "ending_before": ending_before, "financial_account_token": financial_account_token, @@ -210,7 +229,7 @@ def retry( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentRetryResponse: """ Retry an origination which has been returned. @@ -227,13 +246,216 @@ def retry( if not payment_token: raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") return self._post( - f"/payments/{payment_token}/retry", + f"/v1/payments/{payment_token}/retry", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=PaymentRetryResponse, ) + def return_( + self, + payment_token: str, + *, + financial_account_token: str, + return_reason_code: str, + addenda: Optional[str] | Omit = omit, + date_of_death: Union[str, date, None] | Omit = omit, + memo: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """Return an ACH payment with a specified return reason code. + + Returns must be + initiated within the time window specified by NACHA rules for each return code + (typically 2 banking days for most codes, 60 calendar days for unauthorized + debits). For a complete list of return codes and their meanings, see the + [ACH Return Reasons documentation](https://docs.lithic.com/docs/ach-overview#ach-return-reasons). + + Note: + + - This endpoint does not modify the state of the financial account associated + with the payment. If you would like to change the account state, use the + [Update financial account status](https://docs.lithic.com/reference/updatefinancialaccountstatus) + endpoint. + - By default this endpoint is not enabled for your account. Please contact your + implementations manager to enable this feature. + + Args: + financial_account_token: Globally unique identifier for the financial account + + return_reason_code: ACH return reason code indicating the reason for returning the payment. + Supported codes include R01-R53 and R80-R85. For a complete list of return codes + and their meanings, see + [ACH Return Reasons](https://docs.lithic.com/docs/ach-overview#ach-return-reasons) + + addenda: Optional additional information about the return. Limited to 44 characters + + date_of_death: Date of death in YYYY-MM-DD format. Required when using return codes **R14** + (representative payee deceased) or **R15** (beneficiary or account holder + deceased) + + memo: Optional memo for the return. Limited to 10 characters + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_token: + raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") + return self._post( + f"/v1/payments/{payment_token}/return", + body=maybe_transform( + { + "financial_account_token": financial_account_token, + "return_reason_code": return_reason_code, + "addenda": addenda, + "date_of_death": date_of_death, + "memo": memo, + }, + payment_return_params.PaymentReturnParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + def simulate_action( + self, + payment_token: str, + *, + event_type: Literal[ + "ACH_ORIGINATION_REVIEWED", + "ACH_ORIGINATION_RELEASED", + "ACH_ORIGINATION_PROCESSED", + "ACH_ORIGINATION_SETTLED", + "ACH_RECEIPT_SETTLED", + "ACH_RECEIPT_RELEASED", + "ACH_RETURN_INITIATED", + "ACH_RETURN_PROCESSED", + "ACH_RETURN_SETTLED", + ], + date_of_death: Union[str, date] | Omit = omit, + decline_reason: Literal[ + "PROGRAM_TRANSACTION_LIMIT_EXCEEDED", "PROGRAM_DAILY_LIMIT_EXCEEDED", "PROGRAM_MONTHLY_LIMIT_EXCEEDED" + ] + | Omit = omit, + return_addenda: str | Omit = omit, + return_reason_code: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentSimulateActionResponse: + """ + Simulate payment lifecycle event + + Args: + event_type: Event Type + + date_of_death: Date of Death for ACH Return + + decline_reason: Decline reason + + return_addenda: Return Addenda + + return_reason_code: Return Reason Code + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_token: + raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") + return self._post( + f"/v1/simulate/payments/{payment_token}/action", + body=maybe_transform( + { + "event_type": event_type, + "date_of_death": date_of_death, + "decline_reason": decline_reason, + "return_addenda": return_addenda, + "return_reason_code": return_reason_code, + }, + payment_simulate_action_params.PaymentSimulateActionParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentSimulateActionResponse, + ) + + def simulate_receipt( + self, + *, + token: str, + amount: int, + financial_account_token: str, + receipt_type: Literal["RECEIPT_CREDIT", "RECEIPT_DEBIT"], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentSimulateReceiptResponse: + """ + Simulates a receipt of a Payment. + + Args: + token: Customer-generated payment token used to uniquely identify the simulated payment + + amount: Amount + + financial_account_token: Financial Account Token + + receipt_type: Receipt Type + + memo: Memo + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/simulate/payments/receipt", + body=maybe_transform( + { + "token": token, + "amount": amount, + "financial_account_token": financial_account_token, + "receipt_type": receipt_type, + "memo": memo, + }, + payment_simulate_receipt_params.PaymentSimulateReceiptParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentSimulateReceiptResponse, + ) + def simulate_release( self, *, @@ -243,12 +465,14 @@ def simulate_release( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentSimulateReleaseResponse: """ Simulates a release of a Payment. Args: + payment_token: Payment Token + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -258,7 +482,7 @@ def simulate_release( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/payments/release", + "/v1/simulate/payments/release", body=maybe_transform( {"payment_token": payment_token}, payment_simulate_release_params.PaymentSimulateReleaseParams ), @@ -272,18 +496,22 @@ def simulate_return( self, *, payment_token: str, - return_reason_code: str | NotGiven = NOT_GIVEN, + return_reason_code: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentSimulateReturnResponse: """ Simulates a return of a Payment. Args: + payment_token: Payment Token + + return_reason_code: Return Reason Code + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -293,7 +521,7 @@ def simulate_return( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/payments/return", + "/v1/simulate/payments/return", body=maybe_transform( { "payment_token": payment_token, @@ -311,10 +539,21 @@ def simulate_return( class AsyncPayments(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncPaymentsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncPaymentsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncPaymentsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncPaymentsWithStreamingResponse(self) async def create( @@ -326,15 +565,15 @@ async def create( method: Literal["ACH_NEXT_DAY", "ACH_SAME_DAY"], method_attributes: payment_create_params.MethodAttributes, type: Literal["COLLECTION", "PAYMENT"], - token: str | NotGiven = NOT_GIVEN, - memo: str | NotGiven = NOT_GIVEN, - user_defined_id: str | NotGiven = NOT_GIVEN, + token: str | Omit = omit, + memo: str | Omit = omit, + user_defined_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentCreateResponse: """ Initiates a payment between a financial account and an external bank account. @@ -352,8 +591,8 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/payments", - body=maybe_transform( + "/v1/payments", + body=await async_maybe_transform( { "amount": amount, "external_bank_account_token": external_bank_account_token, @@ -382,7 +621,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Payment: """ Get the payment by token. @@ -399,7 +638,7 @@ async def retrieve( if not payment_token: raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") return await self._get( - f"/payments/{payment_token}", + f"/v1/payments/{payment_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -409,20 +648,23 @@ async def retrieve( def list( self, *, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - financial_account_token: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, - status: Literal["DECLINED", "PENDING", "RETURNED", "SETTLED"] | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + business_account_token: str | Omit = omit, + category: Literal["ACH"] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + financial_account_token: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["DECLINED", "PENDING", "RETURNED", "SETTLED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Payment, AsyncCursorPage[Payment]]: """ List all the payments for the provided search criteria. @@ -451,7 +693,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/payments", + "/v1/payments", page=AsyncCursorPage[Payment], options=make_request_options( extra_headers=extra_headers, @@ -460,7 +702,10 @@ def list( timeout=timeout, query=maybe_transform( { + "account_token": account_token, "begin": begin, + "business_account_token": business_account_token, + "category": category, "end": end, "ending_before": ending_before, "financial_account_token": financial_account_token, @@ -484,7 +729,7 @@ async def retry( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentRetryResponse: """ Retry an origination which has been returned. @@ -501,13 +746,216 @@ async def retry( if not payment_token: raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") return await self._post( - f"/payments/{payment_token}/retry", + f"/v1/payments/{payment_token}/retry", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=PaymentRetryResponse, ) + async def return_( + self, + payment_token: str, + *, + financial_account_token: str, + return_reason_code: str, + addenda: Optional[str] | Omit = omit, + date_of_death: Union[str, date, None] | Omit = omit, + memo: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """Return an ACH payment with a specified return reason code. + + Returns must be + initiated within the time window specified by NACHA rules for each return code + (typically 2 banking days for most codes, 60 calendar days for unauthorized + debits). For a complete list of return codes and their meanings, see the + [ACH Return Reasons documentation](https://docs.lithic.com/docs/ach-overview#ach-return-reasons). + + Note: + + - This endpoint does not modify the state of the financial account associated + with the payment. If you would like to change the account state, use the + [Update financial account status](https://docs.lithic.com/reference/updatefinancialaccountstatus) + endpoint. + - By default this endpoint is not enabled for your account. Please contact your + implementations manager to enable this feature. + + Args: + financial_account_token: Globally unique identifier for the financial account + + return_reason_code: ACH return reason code indicating the reason for returning the payment. + Supported codes include R01-R53 and R80-R85. For a complete list of return codes + and their meanings, see + [ACH Return Reasons](https://docs.lithic.com/docs/ach-overview#ach-return-reasons) + + addenda: Optional additional information about the return. Limited to 44 characters + + date_of_death: Date of death in YYYY-MM-DD format. Required when using return codes **R14** + (representative payee deceased) or **R15** (beneficiary or account holder + deceased) + + memo: Optional memo for the return. Limited to 10 characters + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_token: + raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") + return await self._post( + f"/v1/payments/{payment_token}/return", + body=await async_maybe_transform( + { + "financial_account_token": financial_account_token, + "return_reason_code": return_reason_code, + "addenda": addenda, + "date_of_death": date_of_death, + "memo": memo, + }, + payment_return_params.PaymentReturnParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + async def simulate_action( + self, + payment_token: str, + *, + event_type: Literal[ + "ACH_ORIGINATION_REVIEWED", + "ACH_ORIGINATION_RELEASED", + "ACH_ORIGINATION_PROCESSED", + "ACH_ORIGINATION_SETTLED", + "ACH_RECEIPT_SETTLED", + "ACH_RECEIPT_RELEASED", + "ACH_RETURN_INITIATED", + "ACH_RETURN_PROCESSED", + "ACH_RETURN_SETTLED", + ], + date_of_death: Union[str, date] | Omit = omit, + decline_reason: Literal[ + "PROGRAM_TRANSACTION_LIMIT_EXCEEDED", "PROGRAM_DAILY_LIMIT_EXCEEDED", "PROGRAM_MONTHLY_LIMIT_EXCEEDED" + ] + | Omit = omit, + return_addenda: str | Omit = omit, + return_reason_code: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentSimulateActionResponse: + """ + Simulate payment lifecycle event + + Args: + event_type: Event Type + + date_of_death: Date of Death for ACH Return + + decline_reason: Decline reason + + return_addenda: Return Addenda + + return_reason_code: Return Reason Code + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_token: + raise ValueError(f"Expected a non-empty value for `payment_token` but received {payment_token!r}") + return await self._post( + f"/v1/simulate/payments/{payment_token}/action", + body=await async_maybe_transform( + { + "event_type": event_type, + "date_of_death": date_of_death, + "decline_reason": decline_reason, + "return_addenda": return_addenda, + "return_reason_code": return_reason_code, + }, + payment_simulate_action_params.PaymentSimulateActionParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentSimulateActionResponse, + ) + + async def simulate_receipt( + self, + *, + token: str, + amount: int, + financial_account_token: str, + receipt_type: Literal["RECEIPT_CREDIT", "RECEIPT_DEBIT"], + memo: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentSimulateReceiptResponse: + """ + Simulates a receipt of a Payment. + + Args: + token: Customer-generated payment token used to uniquely identify the simulated payment + + amount: Amount + + financial_account_token: Financial Account Token + + receipt_type: Receipt Type + + memo: Memo + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/simulate/payments/receipt", + body=await async_maybe_transform( + { + "token": token, + "amount": amount, + "financial_account_token": financial_account_token, + "receipt_type": receipt_type, + "memo": memo, + }, + payment_simulate_receipt_params.PaymentSimulateReceiptParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentSimulateReceiptResponse, + ) + async def simulate_release( self, *, @@ -517,12 +965,14 @@ async def simulate_release( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentSimulateReleaseResponse: """ Simulates a release of a Payment. Args: + payment_token: Payment Token + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -532,8 +982,8 @@ async def simulate_release( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/payments/release", - body=maybe_transform( + "/v1/simulate/payments/release", + body=await async_maybe_transform( {"payment_token": payment_token}, payment_simulate_release_params.PaymentSimulateReleaseParams ), options=make_request_options( @@ -546,18 +996,22 @@ async def simulate_return( self, *, payment_token: str, - return_reason_code: str | NotGiven = NOT_GIVEN, + return_reason_code: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PaymentSimulateReturnResponse: """ Simulates a return of a Payment. Args: + payment_token: Payment Token + + return_reason_code: Return Reason Code + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -567,8 +1021,8 @@ async def simulate_return( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/payments/return", - body=maybe_transform( + "/v1/simulate/payments/return", + body=await async_maybe_transform( { "payment_token": payment_token, "return_reason_code": return_reason_code, @@ -598,6 +1052,15 @@ def __init__(self, payments: Payments) -> None: self.retry = _legacy_response.to_raw_response_wrapper( payments.retry, ) + self.return_ = _legacy_response.to_raw_response_wrapper( + payments.return_, + ) + self.simulate_action = _legacy_response.to_raw_response_wrapper( + payments.simulate_action, + ) + self.simulate_receipt = _legacy_response.to_raw_response_wrapper( + payments.simulate_receipt, + ) self.simulate_release = _legacy_response.to_raw_response_wrapper( payments.simulate_release, ) @@ -622,6 +1085,15 @@ def __init__(self, payments: AsyncPayments) -> None: self.retry = _legacy_response.async_to_raw_response_wrapper( payments.retry, ) + self.return_ = _legacy_response.async_to_raw_response_wrapper( + payments.return_, + ) + self.simulate_action = _legacy_response.async_to_raw_response_wrapper( + payments.simulate_action, + ) + self.simulate_receipt = _legacy_response.async_to_raw_response_wrapper( + payments.simulate_receipt, + ) self.simulate_release = _legacy_response.async_to_raw_response_wrapper( payments.simulate_release, ) @@ -646,6 +1118,15 @@ def __init__(self, payments: Payments) -> None: self.retry = to_streamed_response_wrapper( payments.retry, ) + self.return_ = to_streamed_response_wrapper( + payments.return_, + ) + self.simulate_action = to_streamed_response_wrapper( + payments.simulate_action, + ) + self.simulate_receipt = to_streamed_response_wrapper( + payments.simulate_receipt, + ) self.simulate_release = to_streamed_response_wrapper( payments.simulate_release, ) @@ -670,6 +1151,15 @@ def __init__(self, payments: AsyncPayments) -> None: self.retry = async_to_streamed_response_wrapper( payments.retry, ) + self.return_ = async_to_streamed_response_wrapper( + payments.return_, + ) + self.simulate_action = async_to_streamed_response_wrapper( + payments.simulate_action, + ) + self.simulate_receipt = async_to_streamed_response_wrapper( + payments.simulate_receipt, + ) self.simulate_release = async_to_streamed_response_wrapper( payments.simulate_release, ) diff --git a/src/lithic/resources/reports/__init__.py b/src/lithic/resources/reports/__init__.py index 64661c80..d7bcc5cf 100644 --- a/src/lithic/resources/reports/__init__.py +++ b/src/lithic/resources/reports/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .reports import ( Reports, diff --git a/src/lithic/resources/reports/reports.py b/src/lithic/resources/reports/reports.py index 6df9b401..89700efd 100644 --- a/src/lithic/resources/reports/reports.py +++ b/src/lithic/resources/reports/reports.py @@ -1,9 +1,10 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from ..._compat import cached_property -from .settlement import ( +from ..._resource import SyncAPIResource, AsyncAPIResource +from .settlement.settlement import ( Settlement, AsyncSettlement, SettlementWithRawResponse, @@ -11,7 +12,6 @@ SettlementWithStreamingResponse, AsyncSettlementWithStreamingResponse, ) -from ..._resource import SyncAPIResource, AsyncAPIResource __all__ = ["Reports", "AsyncReports"] @@ -23,10 +23,21 @@ def settlement(self) -> Settlement: @cached_property def with_raw_response(self) -> ReportsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return ReportsWithRawResponse(self) @cached_property def with_streaming_response(self) -> ReportsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return ReportsWithStreamingResponse(self) @@ -37,10 +48,21 @@ def settlement(self) -> AsyncSettlement: @cached_property def with_raw_response(self) -> AsyncReportsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncReportsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncReportsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncReportsWithStreamingResponse(self) diff --git a/src/lithic/resources/reports/settlement/__init__.py b/src/lithic/resources/reports/settlement/__init__.py new file mode 100644 index 00000000..c9a3f798 --- /dev/null +++ b/src/lithic/resources/reports/settlement/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .settlement import ( + Settlement, + AsyncSettlement, + SettlementWithRawResponse, + AsyncSettlementWithRawResponse, + SettlementWithStreamingResponse, + AsyncSettlementWithStreamingResponse, +) +from .network_totals import ( + NetworkTotals, + AsyncNetworkTotals, + NetworkTotalsWithRawResponse, + AsyncNetworkTotalsWithRawResponse, + NetworkTotalsWithStreamingResponse, + AsyncNetworkTotalsWithStreamingResponse, +) + +__all__ = [ + "NetworkTotals", + "AsyncNetworkTotals", + "NetworkTotalsWithRawResponse", + "AsyncNetworkTotalsWithRawResponse", + "NetworkTotalsWithStreamingResponse", + "AsyncNetworkTotalsWithStreamingResponse", + "Settlement", + "AsyncSettlement", + "SettlementWithRawResponse", + "AsyncSettlementWithRawResponse", + "SettlementWithStreamingResponse", + "AsyncSettlementWithStreamingResponse", +] diff --git a/src/lithic/resources/reports/settlement/network_totals.py b/src/lithic/resources/reports/settlement/network_totals.py new file mode 100644 index 00000000..4d4f9172 --- /dev/null +++ b/src/lithic/resources/reports/settlement/network_totals.py @@ -0,0 +1,358 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date, datetime +from typing_extensions import Literal + +import httpx + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.network_total import NetworkTotal +from ....types.reports.settlement import network_total_list_params + +__all__ = ["NetworkTotals", "AsyncNetworkTotals"] + + +class NetworkTotals(SyncAPIResource): + @cached_property + def with_raw_response(self) -> NetworkTotalsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return NetworkTotalsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> NetworkTotalsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return NetworkTotalsWithStreamingResponse(self) + + def retrieve( + self, + token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NetworkTotal: + """Retrieve a specific network total record by token. + + Not available in sandbox. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not token: + raise ValueError(f"Expected a non-empty value for `token` but received {token!r}") + return self._get( + f"/v1/reports/settlement/network_totals/{token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NetworkTotal, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + institution_id: str | Omit = omit, + network: Literal["VISA", "MASTERCARD", "MAESTRO", "INTERLINK"] | Omit = omit, + page_size: int | Omit = omit, + report_date: Union[str, date] | Omit = omit, + report_date_begin: Union[str, date] | Omit = omit, + report_date_end: Union[str, date] | Omit = omit, + settlement_institution_id: str | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[NetworkTotal]: + """List network total records with optional filters. + + Not available in sandbox. + + Args: + begin: Datetime in RFC 3339 format. Only entries created after the specified time will + be included. UTC time zone. + + end: Datetime in RFC 3339 format. Only entries created before the specified time will + be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + institution_id: Institution ID to filter on. + + network: Network to filter on. + + page_size: Number of records per page. + + report_date: Singular report date to filter on (YYYY-MM-DD). Cannot be populated in + conjunction with report_date_begin or report_date_end. + + report_date_begin: Earliest report date to filter on, inclusive (YYYY-MM-DD). + + report_date_end: Latest report date to filter on, inclusive (YYYY-MM-DD). + + settlement_institution_id: Settlement institution ID to filter on. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/reports/settlement/network_totals", + page=SyncCursorPage[NetworkTotal], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "ending_before": ending_before, + "institution_id": institution_id, + "network": network, + "page_size": page_size, + "report_date": report_date, + "report_date_begin": report_date_begin, + "report_date_end": report_date_end, + "settlement_institution_id": settlement_institution_id, + "starting_after": starting_after, + }, + network_total_list_params.NetworkTotalListParams, + ), + ), + model=NetworkTotal, + ) + + +class AsyncNetworkTotals(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncNetworkTotalsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncNetworkTotalsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncNetworkTotalsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncNetworkTotalsWithStreamingResponse(self) + + async def retrieve( + self, + token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NetworkTotal: + """Retrieve a specific network total record by token. + + Not available in sandbox. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not token: + raise ValueError(f"Expected a non-empty value for `token` but received {token!r}") + return await self._get( + f"/v1/reports/settlement/network_totals/{token}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NetworkTotal, + ) + + def list( + self, + *, + begin: Union[str, datetime] | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + institution_id: str | Omit = omit, + network: Literal["VISA", "MASTERCARD", "MAESTRO", "INTERLINK"] | Omit = omit, + page_size: int | Omit = omit, + report_date: Union[str, date] | Omit = omit, + report_date_begin: Union[str, date] | Omit = omit, + report_date_end: Union[str, date] | Omit = omit, + settlement_institution_id: str | Omit = omit, + starting_after: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[NetworkTotal, AsyncCursorPage[NetworkTotal]]: + """List network total records with optional filters. + + Not available in sandbox. + + Args: + begin: Datetime in RFC 3339 format. Only entries created after the specified time will + be included. UTC time zone. + + end: Datetime in RFC 3339 format. Only entries created before the specified time will + be included. UTC time zone. + + ending_before: A cursor representing an item's token before which a page of results should end. + Used to retrieve the previous page of results before this item. + + institution_id: Institution ID to filter on. + + network: Network to filter on. + + page_size: Number of records per page. + + report_date: Singular report date to filter on (YYYY-MM-DD). Cannot be populated in + conjunction with report_date_begin or report_date_end. + + report_date_begin: Earliest report date to filter on, inclusive (YYYY-MM-DD). + + report_date_end: Latest report date to filter on, inclusive (YYYY-MM-DD). + + settlement_institution_id: Settlement institution ID to filter on. + + starting_after: A cursor representing an item's token after which a page of results should + begin. Used to retrieve the next page of results after this item. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/reports/settlement/network_totals", + page=AsyncCursorPage[NetworkTotal], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "begin": begin, + "end": end, + "ending_before": ending_before, + "institution_id": institution_id, + "network": network, + "page_size": page_size, + "report_date": report_date, + "report_date_begin": report_date_begin, + "report_date_end": report_date_end, + "settlement_institution_id": settlement_institution_id, + "starting_after": starting_after, + }, + network_total_list_params.NetworkTotalListParams, + ), + ), + model=NetworkTotal, + ) + + +class NetworkTotalsWithRawResponse: + def __init__(self, network_totals: NetworkTotals) -> None: + self._network_totals = network_totals + + self.retrieve = _legacy_response.to_raw_response_wrapper( + network_totals.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + network_totals.list, + ) + + +class AsyncNetworkTotalsWithRawResponse: + def __init__(self, network_totals: AsyncNetworkTotals) -> None: + self._network_totals = network_totals + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + network_totals.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + network_totals.list, + ) + + +class NetworkTotalsWithStreamingResponse: + def __init__(self, network_totals: NetworkTotals) -> None: + self._network_totals = network_totals + + self.retrieve = to_streamed_response_wrapper( + network_totals.retrieve, + ) + self.list = to_streamed_response_wrapper( + network_totals.list, + ) + + +class AsyncNetworkTotalsWithStreamingResponse: + def __init__(self, network_totals: AsyncNetworkTotals) -> None: + self._network_totals = network_totals + + self.retrieve = async_to_streamed_response_wrapper( + network_totals.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + network_totals.list, + ) diff --git a/src/lithic/resources/reports/settlement.py b/src/lithic/resources/reports/settlement/settlement.py similarity index 68% rename from src/lithic/resources/reports/settlement.py rename to src/lithic/resources/reports/settlement/settlement.py index 3010d53e..81a34b04 100644 --- a/src/lithic/resources/reports/settlement.py +++ b/src/lithic/resources/reports/settlement/settlement.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -7,45 +7,66 @@ import httpx -from ... import _legacy_response -from ...types import SettlementDetail, SettlementReport -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ...pagination import SyncCursorPage, AsyncCursorPage -from ..._base_client import ( - AsyncPaginator, - make_request_options, +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from .network_totals import ( + NetworkTotals, + AsyncNetworkTotals, + NetworkTotalsWithRawResponse, + AsyncNetworkTotalsWithRawResponse, + NetworkTotalsWithStreamingResponse, + AsyncNetworkTotalsWithStreamingResponse, ) -from ...types.reports import settlement_list_details_params +from ...._base_client import AsyncPaginator, make_request_options +from ....types.reports import settlement_list_details_params +from ....types.settlement_detail import SettlementDetail +from ....types.settlement_report import SettlementReport __all__ = ["Settlement", "AsyncSettlement"] class Settlement(SyncAPIResource): + @cached_property + def network_totals(self) -> NetworkTotals: + return NetworkTotals(self._client) + @cached_property def with_raw_response(self) -> SettlementWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return SettlementWithRawResponse(self) @cached_property def with_streaming_response(self) -> SettlementWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return SettlementWithStreamingResponse(self) def list_details( self, report_date: Union[str, date], *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[SettlementDetail]: """ List details. @@ -54,7 +75,7 @@ def list_details( ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. - page_size: Page size (for pagination). + page_size: Number of records per page. starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. @@ -70,7 +91,7 @@ def list_details( if not report_date: raise ValueError(f"Expected a non-empty value for `report_date` but received {report_date!r}") return self._get_api_list( - f"/reports/settlement/details/{report_date}", + f"/v1/reports/settlement/details/{report_date}", page=SyncCursorPage[SettlementDetail], options=make_request_options( extra_headers=extra_headers, @@ -98,10 +119,11 @@ def summary( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SettlementReport: - """ - Get the settlement report for a specified report date. + """Get the settlement report for a specified report date. + + Not available in sandbox. Args: extra_headers: Send extra headers @@ -115,7 +137,7 @@ def summary( if not report_date: raise ValueError(f"Expected a non-empty value for `report_date` but received {report_date!r}") return self._get( - f"/reports/settlement/summary/{report_date}", + f"/v1/reports/settlement/summary/{report_date}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -124,27 +146,42 @@ def summary( class AsyncSettlement(AsyncAPIResource): + @cached_property + def network_totals(self) -> AsyncNetworkTotals: + return AsyncNetworkTotals(self._client) + @cached_property def with_raw_response(self) -> AsyncSettlementWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncSettlementWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncSettlementWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncSettlementWithStreamingResponse(self) def list_details( self, report_date: Union[str, date], *, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[SettlementDetail, AsyncCursorPage[SettlementDetail]]: """ List details. @@ -153,7 +190,7 @@ def list_details( ending_before: A cursor representing an item's token before which a page of results should end. Used to retrieve the previous page of results before this item. - page_size: Page size (for pagination). + page_size: Number of records per page. starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. @@ -169,7 +206,7 @@ def list_details( if not report_date: raise ValueError(f"Expected a non-empty value for `report_date` but received {report_date!r}") return self._get_api_list( - f"/reports/settlement/details/{report_date}", + f"/v1/reports/settlement/details/{report_date}", page=AsyncCursorPage[SettlementDetail], options=make_request_options( extra_headers=extra_headers, @@ -197,10 +234,11 @@ async def summary( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SettlementReport: - """ - Get the settlement report for a specified report date. + """Get the settlement report for a specified report date. + + Not available in sandbox. Args: extra_headers: Send extra headers @@ -214,7 +252,7 @@ async def summary( if not report_date: raise ValueError(f"Expected a non-empty value for `report_date` but received {report_date!r}") return await self._get( - f"/reports/settlement/summary/{report_date}", + f"/v1/reports/settlement/summary/{report_date}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -233,6 +271,10 @@ def __init__(self, settlement: Settlement) -> None: settlement.summary, ) + @cached_property + def network_totals(self) -> NetworkTotalsWithRawResponse: + return NetworkTotalsWithRawResponse(self._settlement.network_totals) + class AsyncSettlementWithRawResponse: def __init__(self, settlement: AsyncSettlement) -> None: @@ -245,6 +287,10 @@ def __init__(self, settlement: AsyncSettlement) -> None: settlement.summary, ) + @cached_property + def network_totals(self) -> AsyncNetworkTotalsWithRawResponse: + return AsyncNetworkTotalsWithRawResponse(self._settlement.network_totals) + class SettlementWithStreamingResponse: def __init__(self, settlement: Settlement) -> None: @@ -257,6 +303,10 @@ def __init__(self, settlement: Settlement) -> None: settlement.summary, ) + @cached_property + def network_totals(self) -> NetworkTotalsWithStreamingResponse: + return NetworkTotalsWithStreamingResponse(self._settlement.network_totals) + class AsyncSettlementWithStreamingResponse: def __init__(self, settlement: AsyncSettlement) -> None: @@ -268,3 +318,7 @@ def __init__(self, settlement: AsyncSettlement) -> None: self.summary = async_to_streamed_response_wrapper( settlement.summary, ) + + @cached_property + def network_totals(self) -> AsyncNetworkTotalsWithStreamingResponse: + return AsyncNetworkTotalsWithStreamingResponse(self._settlement.network_totals) diff --git a/src/lithic/resources/responder_endpoints.py b/src/lithic/resources/responder_endpoints.py index 4e9d4d8b..720e77e6 100644 --- a/src/lithic/resources/responder_endpoints.py +++ b/src/lithic/resources/responder_endpoints.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -8,20 +8,18 @@ from .. import _legacy_response from ..types import ( - ResponderEndpointStatus, - ResponderEndpointCreateResponse, responder_endpoint_create_params, responder_endpoint_delete_params, responder_endpoint_check_status_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from .._utils import maybe_transform +from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from .._base_client import ( - make_request_options, -) +from .._base_client import make_request_options +from ..types.responder_endpoint_status import ResponderEndpointStatus +from ..types.responder_endpoint_create_response import ResponderEndpointCreateResponse __all__ = ["ResponderEndpoints", "AsyncResponderEndpoints"] @@ -29,23 +27,34 @@ class ResponderEndpoints(SyncAPIResource): @cached_property def with_raw_response(self) -> ResponderEndpointsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return ResponderEndpointsWithRawResponse(self) @cached_property def with_streaming_response(self) -> ResponderEndpointsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return ResponderEndpointsWithStreamingResponse(self) def create( self, *, - type: Literal["AUTH_STREAM_ACCESS", "THREE_DS_DECISIONING", "TOKENIZATION_DECISIONING"] | NotGiven = NOT_GIVEN, - url: str | NotGiven = NOT_GIVEN, + type: Literal["AUTH_STREAM_ACCESS", "THREE_DS_DECISIONING", "TOKENIZATION_DECISIONING"] | Omit = omit, + url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ResponderEndpointCreateResponse: """ Enroll a responder endpoint @@ -64,7 +73,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/responder_endpoints", + "/v1/responder_endpoints", body=maybe_transform( { "type": type, @@ -87,7 +96,7 @@ def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Disenroll a responder endpoint @@ -104,7 +113,7 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ return self._delete( - "/responder_endpoints", + "/v1/responder_endpoints", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -124,7 +133,7 @@ def check_status( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ResponderEndpointStatus: """ Check the status of a responder endpoint @@ -141,7 +150,7 @@ def check_status( timeout: Override the client-level default timeout for this request, in seconds """ return self._get( - "/responder_endpoints", + "/v1/responder_endpoints", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -158,23 +167,34 @@ def check_status( class AsyncResponderEndpoints(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncResponderEndpointsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncResponderEndpointsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncResponderEndpointsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncResponderEndpointsWithStreamingResponse(self) async def create( self, *, - type: Literal["AUTH_STREAM_ACCESS", "THREE_DS_DECISIONING", "TOKENIZATION_DECISIONING"] | NotGiven = NOT_GIVEN, - url: str | NotGiven = NOT_GIVEN, + type: Literal["AUTH_STREAM_ACCESS", "THREE_DS_DECISIONING", "TOKENIZATION_DECISIONING"] | Omit = omit, + url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ResponderEndpointCreateResponse: """ Enroll a responder endpoint @@ -193,8 +213,8 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/responder_endpoints", - body=maybe_transform( + "/v1/responder_endpoints", + body=await async_maybe_transform( { "type": type, "url": url, @@ -216,7 +236,7 @@ async def delete( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ Disenroll a responder endpoint @@ -233,13 +253,15 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ return await self._delete( - "/responder_endpoints", + "/v1/responder_endpoints", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"type": type}, responder_endpoint_delete_params.ResponderEndpointDeleteParams), + query=await async_maybe_transform( + {"type": type}, responder_endpoint_delete_params.ResponderEndpointDeleteParams + ), ), cast_to=NoneType, ) @@ -253,7 +275,7 @@ async def check_status( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ResponderEndpointStatus: """ Check the status of a responder endpoint @@ -270,13 +292,13 @@ async def check_status( timeout: Override the client-level default timeout for this request, in seconds """ return await self._get( - "/responder_endpoints", + "/v1/responder_endpoints", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( {"type": type}, responder_endpoint_check_status_params.ResponderEndpointCheckStatusParams ), ), diff --git a/src/lithic/resources/three_ds/__init__.py b/src/lithic/resources/three_ds/__init__.py index 32d6cf86..517059d5 100644 --- a/src/lithic/resources/three_ds/__init__.py +++ b/src/lithic/resources/three_ds/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .three_ds import ( ThreeDS, diff --git a/src/lithic/resources/three_ds/authentication.py b/src/lithic/resources/three_ds/authentication.py index a84141ce..f2a6c75c 100644 --- a/src/lithic/resources/three_ds/authentication.py +++ b/src/lithic/resources/three_ds/authentication.py @@ -1,23 +1,21 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations +from typing_extensions import Literal + import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..._base_client import ( - make_request_options, -) -from ...types.three_ds import ( - AuthenticationRetrieveResponse, - AuthenticationSimulateResponse, - authentication_simulate_params, -) +from ..._base_client import make_request_options +from ...types.three_ds import authentication_simulate_params, authentication_simulate_otp_entry_params +from ...types.three_ds_authentication import ThreeDSAuthentication +from ...types.three_ds.authentication_simulate_response import AuthenticationSimulateResponse __all__ = ["Authentication", "AsyncAuthentication"] @@ -25,10 +23,21 @@ class Authentication(SyncAPIResource): @cached_property def with_raw_response(self) -> AuthenticationWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AuthenticationWithRawResponse(self) @cached_property def with_streaming_response(self) -> AuthenticationWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AuthenticationWithStreamingResponse(self) def retrieve( @@ -40,8 +49,8 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthenticationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThreeDSAuthentication: """ Get 3DS Authentication by token @@ -59,11 +68,11 @@ def retrieve( f"Expected a non-empty value for `three_ds_authentication_token` but received {three_ds_authentication_token!r}" ) return self._get( - f"/three_ds_authentication/{three_ds_authentication_token}", + f"/v1/three_ds_authentication/{three_ds_authentication_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AuthenticationRetrieveResponse, + cast_to=ThreeDSAuthentication, ) def simulate( @@ -72,22 +81,32 @@ def simulate( merchant: authentication_simulate_params.Merchant, pan: str, transaction: authentication_simulate_params.Transaction, + card_expiry_check: Literal["MATCH", "MISMATCH", "NOT_PRESENT"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AuthenticationSimulateResponse: """ Simulates a 3DS authentication request from the payment network as if it came from an ACS. If you're configured for 3DS Customer Decisioning, simulating authentications requires your customer decisioning endpoint to be set up - properly (respond with a valid JSON). + properly (respond with a valid JSON). If the authentication decision is to + challenge, ensure that the account holder associated with the card transaction + has a valid phone number configured to receive the OTP code via SMS. Args: + merchant: Merchant information for the simulated transaction + pan: Sixteen digit card number. + transaction: Transaction details for the simulation + + card_expiry_check: When set will use the following values as part of the Simulated Authentication. + When not set defaults to MATCH + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -97,12 +116,13 @@ def simulate( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/three_ds_authentication/simulate", + "/v1/three_ds_authentication/simulate", body=maybe_transform( { "merchant": merchant, "pan": pan, "transaction": transaction, + "card_expiry_check": card_expiry_check, }, authentication_simulate_params.AuthenticationSimulateParams, ), @@ -112,14 +132,73 @@ def simulate( cast_to=AuthenticationSimulateResponse, ) + def simulate_otp_entry( + self, + *, + token: str, + otp: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Endpoint for simulating entering OTP into 3DS Challenge UI. + + A call to + [/v1/three_ds_authentication/simulate](https://docs.lithic.com/reference/postsimulateauthentication) + that resulted in triggered SMS-OTP challenge must precede. Only a single attempt + is supported; upon entering OTP, the challenge is either approved or declined. + + Args: + token: A unique token returned as part of a /v1/three_ds_authentication/simulate call + that resulted in PENDING_CHALLENGE authentication result. + + otp: The OTP entered by the cardholder + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/three_ds_decisioning/simulate/enter_otp", + body=maybe_transform( + { + "token": token, + "otp": otp, + }, + authentication_simulate_otp_entry_params.AuthenticationSimulateOtpEntryParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class AsyncAuthentication(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncAuthenticationWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncAuthenticationWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncAuthenticationWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncAuthenticationWithStreamingResponse(self) async def retrieve( @@ -131,8 +210,8 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AuthenticationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThreeDSAuthentication: """ Get 3DS Authentication by token @@ -150,11 +229,11 @@ async def retrieve( f"Expected a non-empty value for `three_ds_authentication_token` but received {three_ds_authentication_token!r}" ) return await self._get( - f"/three_ds_authentication/{three_ds_authentication_token}", + f"/v1/three_ds_authentication/{three_ds_authentication_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AuthenticationRetrieveResponse, + cast_to=ThreeDSAuthentication, ) async def simulate( @@ -163,22 +242,32 @@ async def simulate( merchant: authentication_simulate_params.Merchant, pan: str, transaction: authentication_simulate_params.Transaction, + card_expiry_check: Literal["MATCH", "MISMATCH", "NOT_PRESENT"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AuthenticationSimulateResponse: """ Simulates a 3DS authentication request from the payment network as if it came from an ACS. If you're configured for 3DS Customer Decisioning, simulating authentications requires your customer decisioning endpoint to be set up - properly (respond with a valid JSON). + properly (respond with a valid JSON). If the authentication decision is to + challenge, ensure that the account holder associated with the card transaction + has a valid phone number configured to receive the OTP code via SMS. Args: + merchant: Merchant information for the simulated transaction + pan: Sixteen digit card number. + transaction: Transaction details for the simulation + + card_expiry_check: When set will use the following values as part of the Simulated Authentication. + When not set defaults to MATCH + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -188,12 +277,13 @@ async def simulate( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/three_ds_authentication/simulate", - body=maybe_transform( + "/v1/three_ds_authentication/simulate", + body=await async_maybe_transform( { "merchant": merchant, "pan": pan, "transaction": transaction, + "card_expiry_check": card_expiry_check, }, authentication_simulate_params.AuthenticationSimulateParams, ), @@ -203,6 +293,54 @@ async def simulate( cast_to=AuthenticationSimulateResponse, ) + async def simulate_otp_entry( + self, + *, + token: str, + otp: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Endpoint for simulating entering OTP into 3DS Challenge UI. + + A call to + [/v1/three_ds_authentication/simulate](https://docs.lithic.com/reference/postsimulateauthentication) + that resulted in triggered SMS-OTP challenge must precede. Only a single attempt + is supported; upon entering OTP, the challenge is either approved or declined. + + Args: + token: A unique token returned as part of a /v1/three_ds_authentication/simulate call + that resulted in PENDING_CHALLENGE authentication result. + + otp: The OTP entered by the cardholder + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/three_ds_decisioning/simulate/enter_otp", + body=await async_maybe_transform( + { + "token": token, + "otp": otp, + }, + authentication_simulate_otp_entry_params.AuthenticationSimulateOtpEntryParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class AuthenticationWithRawResponse: def __init__(self, authentication: Authentication) -> None: @@ -214,6 +352,9 @@ def __init__(self, authentication: Authentication) -> None: self.simulate = _legacy_response.to_raw_response_wrapper( authentication.simulate, ) + self.simulate_otp_entry = _legacy_response.to_raw_response_wrapper( + authentication.simulate_otp_entry, + ) class AsyncAuthenticationWithRawResponse: @@ -226,6 +367,9 @@ def __init__(self, authentication: AsyncAuthentication) -> None: self.simulate = _legacy_response.async_to_raw_response_wrapper( authentication.simulate, ) + self.simulate_otp_entry = _legacy_response.async_to_raw_response_wrapper( + authentication.simulate_otp_entry, + ) class AuthenticationWithStreamingResponse: @@ -238,6 +382,9 @@ def __init__(self, authentication: Authentication) -> None: self.simulate = to_streamed_response_wrapper( authentication.simulate, ) + self.simulate_otp_entry = to_streamed_response_wrapper( + authentication.simulate_otp_entry, + ) class AsyncAuthenticationWithStreamingResponse: @@ -250,3 +397,6 @@ def __init__(self, authentication: AsyncAuthentication) -> None: self.simulate = async_to_streamed_response_wrapper( authentication.simulate, ) + self.simulate_otp_entry = async_to_streamed_response_wrapper( + authentication.simulate_otp_entry, + ) diff --git a/src/lithic/resources/three_ds/decisioning.py b/src/lithic/resources/three_ds/decisioning.py index 34349c91..476e2cd9 100644 --- a/src/lithic/resources/three_ds/decisioning.py +++ b/src/lithic/resources/three_ds/decisioning.py @@ -1,18 +1,19 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import httpx from ... import _legacy_response -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..._base_client import ( - make_request_options, -) -from ...types.three_ds import DecisioningRetrieveSecretResponse +from ..._base_client import make_request_options +from ...types.three_ds import ChallengeResult, decisioning_challenge_response_params +from ...types.three_ds.challenge_result import ChallengeResult +from ...types.three_ds.decisioning_retrieve_secret_response import DecisioningRetrieveSecretResponse __all__ = ["Decisioning", "AsyncDecisioning"] @@ -20,12 +21,73 @@ class Decisioning(SyncAPIResource): @cached_property def with_raw_response(self) -> DecisioningWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return DecisioningWithRawResponse(self) @cached_property def with_streaming_response(self) -> DecisioningWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return DecisioningWithStreamingResponse(self) + def challenge_response( + self, + *, + token: str, + challenge_response: ChallengeResult, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Card program's response to a 3DS Challenge Request. + + Challenge Request is emitted + as a webhook + [three_ds_authentication.challenge](https://docs.lithic.com/reference/post_three-ds-authentication-challenge) + and your Card Program needs to be configured with Out of Band (OOB) Challenges + in order to receive it (see https://docs.lithic.com/docs/3ds-challenge-flow for + more information). + + Args: + token: Globally unique identifier for 3DS Authentication that resulted in + PENDING_CHALLENGE authentication result. + + challenge_response: Whether the Cardholder has approved or declined the issued Challenge + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/three_ds_decisioning/challenge_response", + body=maybe_transform( + { + "token": token, + "challenge_response": challenge_response, + }, + decisioning_challenge_response_params.DecisioningChallengeResponseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + def retrieve_secret( self, *, @@ -34,7 +96,7 @@ def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DecisioningRetrieveSecretResponse: """Retrieve the 3DS Decisioning HMAC secret key. @@ -46,7 +108,7 @@ def retrieve_secret( for more detail about verifying 3DS Decisioning requests. """ return self._get( - "/three_ds_decisioning/secret", + "/v1/three_ds_decisioning/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -61,7 +123,7 @@ def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Generate a new 3DS Decisioning HMAC secret key. @@ -71,7 +133,7 @@ def rotate_secret( request to retrieve the new secret key. """ return self._post( - "/three_ds_decisioning/secret/rotate", + "/v1/three_ds_decisioning/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -82,12 +144,73 @@ def rotate_secret( class AsyncDecisioning(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncDecisioningWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncDecisioningWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncDecisioningWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncDecisioningWithStreamingResponse(self) + async def challenge_response( + self, + *, + token: str, + challenge_response: ChallengeResult, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """Card program's response to a 3DS Challenge Request. + + Challenge Request is emitted + as a webhook + [three_ds_authentication.challenge](https://docs.lithic.com/reference/post_three-ds-authentication-challenge) + and your Card Program needs to be configured with Out of Band (OOB) Challenges + in order to receive it (see https://docs.lithic.com/docs/3ds-challenge-flow for + more information). + + Args: + token: Globally unique identifier for 3DS Authentication that resulted in + PENDING_CHALLENGE authentication result. + + challenge_response: Whether the Cardholder has approved or declined the issued Challenge + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/three_ds_decisioning/challenge_response", + body=await async_maybe_transform( + { + "token": token, + "challenge_response": challenge_response, + }, + decisioning_challenge_response_params.DecisioningChallengeResponseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + async def retrieve_secret( self, *, @@ -96,7 +219,7 @@ async def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DecisioningRetrieveSecretResponse: """Retrieve the 3DS Decisioning HMAC secret key. @@ -108,7 +231,7 @@ async def retrieve_secret( for more detail about verifying 3DS Decisioning requests. """ return await self._get( - "/three_ds_decisioning/secret", + "/v1/three_ds_decisioning/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -123,7 +246,7 @@ async def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """Generate a new 3DS Decisioning HMAC secret key. @@ -133,7 +256,7 @@ async def rotate_secret( request to retrieve the new secret key. """ return await self._post( - "/three_ds_decisioning/secret/rotate", + "/v1/three_ds_decisioning/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -145,6 +268,9 @@ class DecisioningWithRawResponse: def __init__(self, decisioning: Decisioning) -> None: self._decisioning = decisioning + self.challenge_response = _legacy_response.to_raw_response_wrapper( + decisioning.challenge_response, + ) self.retrieve_secret = _legacy_response.to_raw_response_wrapper( decisioning.retrieve_secret, ) @@ -157,6 +283,9 @@ class AsyncDecisioningWithRawResponse: def __init__(self, decisioning: AsyncDecisioning) -> None: self._decisioning = decisioning + self.challenge_response = _legacy_response.async_to_raw_response_wrapper( + decisioning.challenge_response, + ) self.retrieve_secret = _legacy_response.async_to_raw_response_wrapper( decisioning.retrieve_secret, ) @@ -169,6 +298,9 @@ class DecisioningWithStreamingResponse: def __init__(self, decisioning: Decisioning) -> None: self._decisioning = decisioning + self.challenge_response = to_streamed_response_wrapper( + decisioning.challenge_response, + ) self.retrieve_secret = to_streamed_response_wrapper( decisioning.retrieve_secret, ) @@ -181,6 +313,9 @@ class AsyncDecisioningWithStreamingResponse: def __init__(self, decisioning: AsyncDecisioning) -> None: self._decisioning = decisioning + self.challenge_response = async_to_streamed_response_wrapper( + decisioning.challenge_response, + ) self.retrieve_secret = async_to_streamed_response_wrapper( decisioning.retrieve_secret, ) diff --git a/src/lithic/resources/three_ds/three_ds.py b/src/lithic/resources/three_ds/three_ds.py index a4597fb3..ea68a29c 100644 --- a/src/lithic/resources/three_ds/three_ds.py +++ b/src/lithic/resources/three_ds/three_ds.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -35,10 +35,21 @@ def decisioning(self) -> Decisioning: @cached_property def with_raw_response(self) -> ThreeDSWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return ThreeDSWithRawResponse(self) @cached_property def with_streaming_response(self) -> ThreeDSWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return ThreeDSWithStreamingResponse(self) @@ -53,10 +64,21 @@ def decisioning(self) -> AsyncDecisioning: @cached_property def with_raw_response(self) -> AsyncThreeDSWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncThreeDSWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncThreeDSWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncThreeDSWithStreamingResponse(self) diff --git a/src/lithic/resources/tokenization_decisioning.py b/src/lithic/resources/tokenization_decisioning.py index e05c3b57..b52897ce 100644 --- a/src/lithic/resources/tokenization_decisioning.py +++ b/src/lithic/resources/tokenization_decisioning.py @@ -1,18 +1,17 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import httpx from .. import _legacy_response -from ..types import TokenizationSecret, TokenizationDecisioningRotateSecretResponse -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from .._base_client import ( - make_request_options, -) +from .._base_client import make_request_options +from ..types.tokenization_secret import TokenizationSecret +from ..types.tokenization_decisioning_rotate_secret_response import TokenizationDecisioningRotateSecretResponse __all__ = ["TokenizationDecisioning", "AsyncTokenizationDecisioning"] @@ -20,10 +19,21 @@ class TokenizationDecisioning(SyncAPIResource): @cached_property def with_raw_response(self) -> TokenizationDecisioningWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return TokenizationDecisioningWithRawResponse(self) @cached_property def with_streaming_response(self) -> TokenizationDecisioningWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return TokenizationDecisioningWithStreamingResponse(self) def retrieve_secret( @@ -34,7 +44,7 @@ def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TokenizationSecret: """Retrieve the Tokenization Decisioning secret key. @@ -46,7 +56,7 @@ def retrieve_secret( detail about verifying Tokenization Decisioning requests. """ return self._get( - "/tokenization_decisioning/secret", + "/v1/tokenization_decisioning/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -61,7 +71,7 @@ def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TokenizationDecisioningRotateSecretResponse: """Generate a new Tokenization Decisioning secret key. @@ -70,7 +80,7 @@ def rotate_secret( to this endpoint. """ return self._post( - "/tokenization_decisioning/secret/rotate", + "/v1/tokenization_decisioning/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -81,10 +91,21 @@ def rotate_secret( class AsyncTokenizationDecisioning(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTokenizationDecisioningWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncTokenizationDecisioningWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncTokenizationDecisioningWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncTokenizationDecisioningWithStreamingResponse(self) async def retrieve_secret( @@ -95,7 +116,7 @@ async def retrieve_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TokenizationSecret: """Retrieve the Tokenization Decisioning secret key. @@ -107,7 +128,7 @@ async def retrieve_secret( detail about verifying Tokenization Decisioning requests. """ return await self._get( - "/tokenization_decisioning/secret", + "/v1/tokenization_decisioning/secret", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -122,7 +143,7 @@ async def rotate_secret( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TokenizationDecisioningRotateSecretResponse: """Generate a new Tokenization Decisioning secret key. @@ -131,7 +152,7 @@ async def rotate_secret( to this endpoint. """ return await self._post( - "/tokenization_decisioning/secret/rotate", + "/v1/tokenization_decisioning/secret/rotate", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/lithic/resources/tokenizations.py b/src/lithic/resources/tokenizations.py index 2aec5963..4307e20f 100644 --- a/src/lithic/resources/tokenizations.py +++ b/src/lithic/resources/tokenizations.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -10,22 +10,19 @@ from .. import _legacy_response from ..types import ( - Tokenization, - TokenizationRetrieveResponse, - TokenizationSimulateResponse, tokenization_list_params, tokenization_simulate_params, + tokenization_resend_activation_code_params, + tokenization_update_digital_card_art_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform +from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import ( - AsyncPaginator, - make_request_options, -) +from .._base_client import AsyncPaginator, make_request_options +from ..types.tokenization import Tokenization __all__ = ["Tokenizations", "AsyncTokenizations"] @@ -33,10 +30,21 @@ class Tokenizations(SyncAPIResource): @cached_property def with_raw_response(self) -> TokenizationsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return TokenizationsWithRawResponse(self) @cached_property def with_streaming_response(self) -> TokenizationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return TokenizationsWithStreamingResponse(self) def retrieve( @@ -48,8 +56,8 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TokenizationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Tokenization: """ Get tokenization @@ -65,29 +73,30 @@ def retrieve( if not tokenization_token: raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") return self._get( - f"/tokenizations/{tokenization_token}", + f"/v1/tokenizations/{tokenization_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TokenizationRetrieveResponse, + cast_to=Tokenization, ) def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - begin: Union[str, date] | NotGiven = NOT_GIVEN, - card_token: str | NotGiven = NOT_GIVEN, - end: Union[str, date] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, date] | Omit = omit, + card_token: str | Omit = omit, + end: Union[str, date] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT", "ALL"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Tokenization]: """ List card tokenizations @@ -109,6 +118,9 @@ def list( starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. + tokenization_channel: Filter for tokenizations by tokenization channel. If this is not specified, only + DIGITAL_WALLET tokenizations will be returned. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -118,7 +130,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/tokenizations", + "/v1/tokenizations", page=SyncCursorPage[Tokenization], options=make_request_options( extra_headers=extra_headers, @@ -134,6 +146,7 @@ def list( "ending_before": ending_before, "page_size": page_size, "starting_after": starting_after, + "tokenization_channel": tokenization_channel, }, tokenization_list_params.TokenizationListParams, ), @@ -141,24 +154,199 @@ def list( model=Tokenization, ) + def activate( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to activate a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network activates the tokenization, the state will + be updated and a tokenization.updated event will be sent. The endpoint may only + be used on digital wallet tokenizations with status `INACTIVE`, + `PENDING_ACTIVATION`, or `PENDING_2FA`. This will put the tokenization in an + active state, and transactions will be allowed. Reach out at + [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return self._post( + f"/v1/tokenizations/{tokenization_token}/activate", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def deactivate( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to deactivate a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network deactivates the tokenization, the state will + be updated and a tokenization.updated event will be sent. Authorizations + attempted with a deactivated tokenization will be blocked and will not be + forwarded to Lithic from the network. Deactivating the token is a permanent + operation. If the target is a digital wallet tokenization, it will be removed + from its device. Reach out at [lithic.com/contact](https://lithic.com/contact) + for more information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return self._post( + f"/v1/tokenizations/{tokenization_token}/deactivate", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def pause( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to pause a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network pauses the tokenization, the state will be + updated and a tokenization.updated event will be sent. The endpoint may only be + used on tokenizations with status `ACTIVE`. A paused token will prevent + merchants from sending authorizations, and is a temporary status that can be + changed. Reach out at [lithic.com/contact](https://lithic.com/contact) for more + information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return self._post( + f"/v1/tokenizations/{tokenization_token}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def resend_activation_code( + self, + tokenization_token: str, + *, + activation_method_type: Literal["EMAIL_TO_CARDHOLDER_ADDRESS", "TEXT_TO_CARDHOLDER_NUMBER"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + This endpoint is used to ask the card network to send another activation code to + a cardholder that has already tried tokenizing a card. A successful response + indicates that the request was successfully delivered to the card network. The + endpoint may only be used on Mastercard digital wallet tokenizations with status + `INACTIVE`, `PENDING_ACTIVATION`, or `PENDING_2FA`. The network will send a new + activation code to the one of the contact methods provided in the initial + tokenization flow. If a user fails to enter the code correctly 3 times, the + contact method will not be eligible for resending the activation code, and the + cardholder must restart the provision process. Reach out at + [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + activation_method_type: The communication method that the user has selected to use to receive the + authentication code. Supported Values: Sms = "TEXT_TO_CARDHOLDER_NUMBER". Email + = "EMAIL_TO_CARDHOLDER_ADDRESS" + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return self._post( + f"/v1/tokenizations/{tokenization_token}/resend_activation_code", + body=maybe_transform( + {"activation_method_type": activation_method_type}, + tokenization_resend_activation_code_params.TokenizationResendActivationCodeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + def simulate( self, *, cvv: str, expiration_date: str, pan: str, - tokenization_source: Literal["APPLE_PAY", "GOOGLE", "SAMSUNG_PAY"], - account_score: int | NotGiven = NOT_GIVEN, - device_score: int | NotGiven = NOT_GIVEN, - wallet_recommended_decision: Literal["APPROVED", "DECLINED", "REQUIRE_ADDITIONAL_AUTHENTICATION"] - | NotGiven = NOT_GIVEN, + tokenization_source: Literal["APPLE_PAY", "GOOGLE", "SAMSUNG_PAY", "MERCHANT"], + account_score: int | Omit = omit, + device_score: int | Omit = omit, + entity: str | Omit = omit, + wallet_recommended_decision: Literal["APPROVED", "DECLINED", "REQUIRE_ADDITIONAL_AUTHENTICATION"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TokenizationSimulateResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Tokenization: """ This endpoint is used to simulate a card's tokenization in the Digital Wallet and merchant tokenization ecosystem. @@ -178,6 +366,9 @@ def simulate( device_score: The device score (1-5) that represents how the Digital Wallet's view on how reputable an end user's device is. + entity: Optional field to specify the token requestor name for a merchant token + simulation. Ignored when tokenization_source is not MERCHANT. + wallet_recommended_decision: The decision that the Digital Wallet's recommend extra_headers: Send extra headers @@ -189,7 +380,7 @@ def simulate( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/tokenizations", + "/v1/simulate/tokenizations", body=maybe_transform( { "cvv": cvv, @@ -198,6 +389,7 @@ def simulate( "tokenization_source": tokenization_source, "account_score": account_score, "device_score": device_score, + "entity": entity, "wallet_recommended_decision": wallet_recommended_decision, }, tokenization_simulate_params.TokenizationSimulateParams, @@ -205,17 +397,118 @@ def simulate( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TokenizationSimulateResponse, + cast_to=Tokenization, + ) + + def unpause( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to unpause a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network unpauses the tokenization, the state will be + updated and a tokenization.updated event will be sent. The endpoint may only be + used on tokenizations with status `PAUSED`. This will put the tokenization in an + active state, and transactions may resume. Reach out at + [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return self._post( + f"/v1/tokenizations/{tokenization_token}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def update_digital_card_art( + self, + tokenization_token: str, + *, + digital_card_art_token: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Tokenization: + """ + This endpoint is used update the digital card art for a digital wallet + tokenization. A successful response indicates that the card network has updated + the tokenization's art, and the tokenization's `digital_cart_art_token` field + was updated. The endpoint may not be used on tokenizations with status + `DEACTIVATED`. Note that this updates the art for one specific tokenization, not + all tokenizations for a card. New tokenizations for a card will be created with + the art referenced in the card object's `digital_card_art_token` field. Reach + out at [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + digital_card_art_token: Specifies the digital card art to be displayed in the user’s digital wallet for + a tokenization. This artwork must be approved by the network and configured by + Lithic to use. See + [Flexible Card Art Guide](https://docs.lithic.com/docs/about-digital-wallets#flexible-card-art). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return self._post( + f"/v1/tokenizations/{tokenization_token}/update_digital_card_art", + body=maybe_transform( + {"digital_card_art_token": digital_card_art_token}, + tokenization_update_digital_card_art_params.TokenizationUpdateDigitalCardArtParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Tokenization, ) class AsyncTokenizations(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTokenizationsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncTokenizationsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncTokenizationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncTokenizationsWithStreamingResponse(self) async def retrieve( @@ -227,8 +520,8 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TokenizationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Tokenization: """ Get tokenization @@ -244,29 +537,30 @@ async def retrieve( if not tokenization_token: raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") return await self._get( - f"/tokenizations/{tokenization_token}", + f"/v1/tokenizations/{tokenization_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TokenizationRetrieveResponse, + cast_to=Tokenization, ) def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - begin: Union[str, date] | NotGiven = NOT_GIVEN, - card_token: str | NotGiven = NOT_GIVEN, - end: Union[str, date] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, date] | Omit = omit, + card_token: str | Omit = omit, + end: Union[str, date] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + starting_after: str | Omit = omit, + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT", "ALL"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Tokenization, AsyncCursorPage[Tokenization]]: """ List card tokenizations @@ -288,6 +582,9 @@ def list( starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. + tokenization_channel: Filter for tokenizations by tokenization channel. If this is not specified, only + DIGITAL_WALLET tokenizations will be returned. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -297,7 +594,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/tokenizations", + "/v1/tokenizations", page=AsyncCursorPage[Tokenization], options=make_request_options( extra_headers=extra_headers, @@ -313,6 +610,7 @@ def list( "ending_before": ending_before, "page_size": page_size, "starting_after": starting_after, + "tokenization_channel": tokenization_channel, }, tokenization_list_params.TokenizationListParams, ), @@ -320,24 +618,199 @@ def list( model=Tokenization, ) + async def activate( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to activate a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network activates the tokenization, the state will + be updated and a tokenization.updated event will be sent. The endpoint may only + be used on digital wallet tokenizations with status `INACTIVE`, + `PENDING_ACTIVATION`, or `PENDING_2FA`. This will put the tokenization in an + active state, and transactions will be allowed. Reach out at + [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return await self._post( + f"/v1/tokenizations/{tokenization_token}/activate", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def deactivate( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to deactivate a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network deactivates the tokenization, the state will + be updated and a tokenization.updated event will be sent. Authorizations + attempted with a deactivated tokenization will be blocked and will not be + forwarded to Lithic from the network. Deactivating the token is a permanent + operation. If the target is a digital wallet tokenization, it will be removed + from its device. Reach out at [lithic.com/contact](https://lithic.com/contact) + for more information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return await self._post( + f"/v1/tokenizations/{tokenization_token}/deactivate", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def pause( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to pause a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network pauses the tokenization, the state will be + updated and a tokenization.updated event will be sent. The endpoint may only be + used on tokenizations with status `ACTIVE`. A paused token will prevent + merchants from sending authorizations, and is a temporary status that can be + changed. Reach out at [lithic.com/contact](https://lithic.com/contact) for more + information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return await self._post( + f"/v1/tokenizations/{tokenization_token}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def resend_activation_code( + self, + tokenization_token: str, + *, + activation_method_type: Literal["EMAIL_TO_CARDHOLDER_ADDRESS", "TEXT_TO_CARDHOLDER_NUMBER"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + This endpoint is used to ask the card network to send another activation code to + a cardholder that has already tried tokenizing a card. A successful response + indicates that the request was successfully delivered to the card network. The + endpoint may only be used on Mastercard digital wallet tokenizations with status + `INACTIVE`, `PENDING_ACTIVATION`, or `PENDING_2FA`. The network will send a new + activation code to the one of the contact methods provided in the initial + tokenization flow. If a user fails to enter the code correctly 3 times, the + contact method will not be eligible for resending the activation code, and the + cardholder must restart the provision process. Reach out at + [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + activation_method_type: The communication method that the user has selected to use to receive the + authentication code. Supported Values: Sms = "TEXT_TO_CARDHOLDER_NUMBER". Email + = "EMAIL_TO_CARDHOLDER_ADDRESS" + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return await self._post( + f"/v1/tokenizations/{tokenization_token}/resend_activation_code", + body=await async_maybe_transform( + {"activation_method_type": activation_method_type}, + tokenization_resend_activation_code_params.TokenizationResendActivationCodeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + async def simulate( self, *, cvv: str, expiration_date: str, pan: str, - tokenization_source: Literal["APPLE_PAY", "GOOGLE", "SAMSUNG_PAY"], - account_score: int | NotGiven = NOT_GIVEN, - device_score: int | NotGiven = NOT_GIVEN, - wallet_recommended_decision: Literal["APPROVED", "DECLINED", "REQUIRE_ADDITIONAL_AUTHENTICATION"] - | NotGiven = NOT_GIVEN, + tokenization_source: Literal["APPLE_PAY", "GOOGLE", "SAMSUNG_PAY", "MERCHANT"], + account_score: int | Omit = omit, + device_score: int | Omit = omit, + entity: str | Omit = omit, + wallet_recommended_decision: Literal["APPROVED", "DECLINED", "REQUIRE_ADDITIONAL_AUTHENTICATION"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TokenizationSimulateResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Tokenization: """ This endpoint is used to simulate a card's tokenization in the Digital Wallet and merchant tokenization ecosystem. @@ -357,6 +830,9 @@ async def simulate( device_score: The device score (1-5) that represents how the Digital Wallet's view on how reputable an end user's device is. + entity: Optional field to specify the token requestor name for a merchant token + simulation. Ignored when tokenization_source is not MERCHANT. + wallet_recommended_decision: The decision that the Digital Wallet's recommend extra_headers: Send extra headers @@ -368,8 +844,8 @@ async def simulate( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/tokenizations", - body=maybe_transform( + "/v1/simulate/tokenizations", + body=await async_maybe_transform( { "cvv": cvv, "expiration_date": expiration_date, @@ -377,6 +853,7 @@ async def simulate( "tokenization_source": tokenization_source, "account_score": account_score, "device_score": device_score, + "entity": entity, "wallet_recommended_decision": wallet_recommended_decision, }, tokenization_simulate_params.TokenizationSimulateParams, @@ -384,7 +861,97 @@ async def simulate( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TokenizationSimulateResponse, + cast_to=Tokenization, + ) + + async def unpause( + self, + tokenization_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """This endpoint is used to ask the card network to unpause a tokenization. + + A + successful response indicates that the request was successfully delivered to the + card network. When the card network unpauses the tokenization, the state will be + updated and a tokenization.updated event will be sent. The endpoint may only be + used on tokenizations with status `PAUSED`. This will put the tokenization in an + active state, and transactions may resume. Reach out at + [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return await self._post( + f"/v1/tokenizations/{tokenization_token}/unpause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def update_digital_card_art( + self, + tokenization_token: str, + *, + digital_card_art_token: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Tokenization: + """ + This endpoint is used update the digital card art for a digital wallet + tokenization. A successful response indicates that the card network has updated + the tokenization's art, and the tokenization's `digital_cart_art_token` field + was updated. The endpoint may not be used on tokenizations with status + `DEACTIVATED`. Note that this updates the art for one specific tokenization, not + all tokenizations for a card. New tokenizations for a card will be created with + the art referenced in the card object's `digital_card_art_token` field. Reach + out at [lithic.com/contact](https://lithic.com/contact) for more information. + + Args: + digital_card_art_token: Specifies the digital card art to be displayed in the user’s digital wallet for + a tokenization. This artwork must be approved by the network and configured by + Lithic to use. See + [Flexible Card Art Guide](https://docs.lithic.com/docs/about-digital-wallets#flexible-card-art). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tokenization_token: + raise ValueError(f"Expected a non-empty value for `tokenization_token` but received {tokenization_token!r}") + return await self._post( + f"/v1/tokenizations/{tokenization_token}/update_digital_card_art", + body=await async_maybe_transform( + {"digital_card_art_token": digital_card_art_token}, + tokenization_update_digital_card_art_params.TokenizationUpdateDigitalCardArtParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Tokenization, ) @@ -398,9 +965,27 @@ def __init__(self, tokenizations: Tokenizations) -> None: self.list = _legacy_response.to_raw_response_wrapper( tokenizations.list, ) + self.activate = _legacy_response.to_raw_response_wrapper( + tokenizations.activate, + ) + self.deactivate = _legacy_response.to_raw_response_wrapper( + tokenizations.deactivate, + ) + self.pause = _legacy_response.to_raw_response_wrapper( + tokenizations.pause, + ) + self.resend_activation_code = _legacy_response.to_raw_response_wrapper( + tokenizations.resend_activation_code, + ) self.simulate = _legacy_response.to_raw_response_wrapper( tokenizations.simulate, ) + self.unpause = _legacy_response.to_raw_response_wrapper( + tokenizations.unpause, + ) + self.update_digital_card_art = _legacy_response.to_raw_response_wrapper( + tokenizations.update_digital_card_art, + ) class AsyncTokenizationsWithRawResponse: @@ -413,9 +998,27 @@ def __init__(self, tokenizations: AsyncTokenizations) -> None: self.list = _legacy_response.async_to_raw_response_wrapper( tokenizations.list, ) + self.activate = _legacy_response.async_to_raw_response_wrapper( + tokenizations.activate, + ) + self.deactivate = _legacy_response.async_to_raw_response_wrapper( + tokenizations.deactivate, + ) + self.pause = _legacy_response.async_to_raw_response_wrapper( + tokenizations.pause, + ) + self.resend_activation_code = _legacy_response.async_to_raw_response_wrapper( + tokenizations.resend_activation_code, + ) self.simulate = _legacy_response.async_to_raw_response_wrapper( tokenizations.simulate, ) + self.unpause = _legacy_response.async_to_raw_response_wrapper( + tokenizations.unpause, + ) + self.update_digital_card_art = _legacy_response.async_to_raw_response_wrapper( + tokenizations.update_digital_card_art, + ) class TokenizationsWithStreamingResponse: @@ -428,9 +1031,27 @@ def __init__(self, tokenizations: Tokenizations) -> None: self.list = to_streamed_response_wrapper( tokenizations.list, ) + self.activate = to_streamed_response_wrapper( + tokenizations.activate, + ) + self.deactivate = to_streamed_response_wrapper( + tokenizations.deactivate, + ) + self.pause = to_streamed_response_wrapper( + tokenizations.pause, + ) + self.resend_activation_code = to_streamed_response_wrapper( + tokenizations.resend_activation_code, + ) self.simulate = to_streamed_response_wrapper( tokenizations.simulate, ) + self.unpause = to_streamed_response_wrapper( + tokenizations.unpause, + ) + self.update_digital_card_art = to_streamed_response_wrapper( + tokenizations.update_digital_card_art, + ) class AsyncTokenizationsWithStreamingResponse: @@ -443,6 +1064,24 @@ def __init__(self, tokenizations: AsyncTokenizations) -> None: self.list = async_to_streamed_response_wrapper( tokenizations.list, ) + self.activate = async_to_streamed_response_wrapper( + tokenizations.activate, + ) + self.deactivate = async_to_streamed_response_wrapper( + tokenizations.deactivate, + ) + self.pause = async_to_streamed_response_wrapper( + tokenizations.pause, + ) + self.resend_activation_code = async_to_streamed_response_wrapper( + tokenizations.resend_activation_code, + ) self.simulate = async_to_streamed_response_wrapper( tokenizations.simulate, ) + self.unpause = async_to_streamed_response_wrapper( + tokenizations.unpause, + ) + self.update_digital_card_art = async_to_streamed_response_wrapper( + tokenizations.update_digital_card_art, + ) diff --git a/src/lithic/resources/transactions/__init__.py b/src/lithic/resources/transactions/__init__.py new file mode 100644 index 00000000..eb3253a2 --- /dev/null +++ b/src/lithic/resources/transactions/__init__.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .events import ( + Events, + AsyncEvents, + EventsWithRawResponse, + AsyncEventsWithRawResponse, + EventsWithStreamingResponse, + AsyncEventsWithStreamingResponse, +) +from .transactions import ( + Transactions, + AsyncTransactions, + TransactionsWithRawResponse, + AsyncTransactionsWithRawResponse, + TransactionsWithStreamingResponse, + AsyncTransactionsWithStreamingResponse, +) +from .enhanced_commercial_data import ( + EnhancedCommercialData, + AsyncEnhancedCommercialData, + EnhancedCommercialDataWithRawResponse, + AsyncEnhancedCommercialDataWithRawResponse, + EnhancedCommercialDataWithStreamingResponse, + AsyncEnhancedCommercialDataWithStreamingResponse, +) + +__all__ = [ + "EnhancedCommercialData", + "AsyncEnhancedCommercialData", + "EnhancedCommercialDataWithRawResponse", + "AsyncEnhancedCommercialDataWithRawResponse", + "EnhancedCommercialDataWithStreamingResponse", + "AsyncEnhancedCommercialDataWithStreamingResponse", + "Events", + "AsyncEvents", + "EventsWithRawResponse", + "AsyncEventsWithRawResponse", + "EventsWithStreamingResponse", + "AsyncEventsWithStreamingResponse", + "Transactions", + "AsyncTransactions", + "TransactionsWithRawResponse", + "AsyncTransactionsWithRawResponse", + "TransactionsWithStreamingResponse", + "AsyncTransactionsWithStreamingResponse", +] diff --git a/src/lithic/resources/transactions/enhanced_commercial_data.py b/src/lithic/resources/transactions/enhanced_commercial_data.py new file mode 100644 index 00000000..10325b5d --- /dev/null +++ b/src/lithic/resources/transactions/enhanced_commercial_data.py @@ -0,0 +1,163 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.transactions.enhanced_commercial_data_retrieve_response import EnhancedCommercialDataRetrieveResponse + +__all__ = ["EnhancedCommercialData", "AsyncEnhancedCommercialData"] + + +class EnhancedCommercialData(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EnhancedCommercialDataWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return EnhancedCommercialDataWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EnhancedCommercialDataWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return EnhancedCommercialDataWithStreamingResponse(self) + + def retrieve( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnhancedCommercialDataRetrieveResponse: + """Get all L2/L3 enhanced commercial data associated with a transaction. + + Not + available in sandbox. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return self._get( + f"/v1/transactions/{transaction_token}/enhanced_commercial_data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnhancedCommercialDataRetrieveResponse, + ) + + +class AsyncEnhancedCommercialData(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEnhancedCommercialDataWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncEnhancedCommercialDataWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEnhancedCommercialDataWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncEnhancedCommercialDataWithStreamingResponse(self) + + async def retrieve( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnhancedCommercialDataRetrieveResponse: + """Get all L2/L3 enhanced commercial data associated with a transaction. + + Not + available in sandbox. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return await self._get( + f"/v1/transactions/{transaction_token}/enhanced_commercial_data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnhancedCommercialDataRetrieveResponse, + ) + + +class EnhancedCommercialDataWithRawResponse: + def __init__(self, enhanced_commercial_data: EnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = _legacy_response.to_raw_response_wrapper( + enhanced_commercial_data.retrieve, + ) + + +class AsyncEnhancedCommercialDataWithRawResponse: + def __init__(self, enhanced_commercial_data: AsyncEnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + enhanced_commercial_data.retrieve, + ) + + +class EnhancedCommercialDataWithStreamingResponse: + def __init__(self, enhanced_commercial_data: EnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = to_streamed_response_wrapper( + enhanced_commercial_data.retrieve, + ) + + +class AsyncEnhancedCommercialDataWithStreamingResponse: + def __init__(self, enhanced_commercial_data: AsyncEnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = async_to_streamed_response_wrapper( + enhanced_commercial_data.retrieve, + ) diff --git a/src/lithic/resources/transactions/events/__init__.py b/src/lithic/resources/transactions/events/__init__.py new file mode 100644 index 00000000..6affbb9a --- /dev/null +++ b/src/lithic/resources/transactions/events/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .events import ( + Events, + AsyncEvents, + EventsWithRawResponse, + AsyncEventsWithRawResponse, + EventsWithStreamingResponse, + AsyncEventsWithStreamingResponse, +) +from .enhanced_commercial_data import ( + EnhancedCommercialData, + AsyncEnhancedCommercialData, + EnhancedCommercialDataWithRawResponse, + AsyncEnhancedCommercialDataWithRawResponse, + EnhancedCommercialDataWithStreamingResponse, + AsyncEnhancedCommercialDataWithStreamingResponse, +) + +__all__ = [ + "EnhancedCommercialData", + "AsyncEnhancedCommercialData", + "EnhancedCommercialDataWithRawResponse", + "AsyncEnhancedCommercialDataWithRawResponse", + "EnhancedCommercialDataWithStreamingResponse", + "AsyncEnhancedCommercialDataWithStreamingResponse", + "Events", + "AsyncEvents", + "EventsWithRawResponse", + "AsyncEventsWithRawResponse", + "EventsWithStreamingResponse", + "AsyncEventsWithStreamingResponse", +] diff --git a/src/lithic/resources/transactions/events/enhanced_commercial_data.py b/src/lithic/resources/transactions/events/enhanced_commercial_data.py new file mode 100644 index 00000000..5d48cdb8 --- /dev/null +++ b/src/lithic/resources/transactions/events/enhanced_commercial_data.py @@ -0,0 +1,163 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .... import _legacy_response +from ...._types import Body, Query, Headers, NotGiven, not_given +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...._base_client import make_request_options +from ....types.transactions.events.enhanced_data import EnhancedData + +__all__ = ["EnhancedCommercialData", "AsyncEnhancedCommercialData"] + + +class EnhancedCommercialData(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EnhancedCommercialDataWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return EnhancedCommercialDataWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EnhancedCommercialDataWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return EnhancedCommercialDataWithStreamingResponse(self) + + def retrieve( + self, + event_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnhancedData: + """Get L2/L3 enhanced commercial data associated with a transaction event. + + Not + available in sandbox. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not event_token: + raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") + return self._get( + f"/v1/transactions/events/{event_token}/enhanced_commercial_data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnhancedData, + ) + + +class AsyncEnhancedCommercialData(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEnhancedCommercialDataWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncEnhancedCommercialDataWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEnhancedCommercialDataWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncEnhancedCommercialDataWithStreamingResponse(self) + + async def retrieve( + self, + event_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EnhancedData: + """Get L2/L3 enhanced commercial data associated with a transaction event. + + Not + available in sandbox. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not event_token: + raise ValueError(f"Expected a non-empty value for `event_token` but received {event_token!r}") + return await self._get( + f"/v1/transactions/events/{event_token}/enhanced_commercial_data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EnhancedData, + ) + + +class EnhancedCommercialDataWithRawResponse: + def __init__(self, enhanced_commercial_data: EnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = _legacy_response.to_raw_response_wrapper( + enhanced_commercial_data.retrieve, + ) + + +class AsyncEnhancedCommercialDataWithRawResponse: + def __init__(self, enhanced_commercial_data: AsyncEnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + enhanced_commercial_data.retrieve, + ) + + +class EnhancedCommercialDataWithStreamingResponse: + def __init__(self, enhanced_commercial_data: EnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = to_streamed_response_wrapper( + enhanced_commercial_data.retrieve, + ) + + +class AsyncEnhancedCommercialDataWithStreamingResponse: + def __init__(self, enhanced_commercial_data: AsyncEnhancedCommercialData) -> None: + self._enhanced_commercial_data = enhanced_commercial_data + + self.retrieve = async_to_streamed_response_wrapper( + enhanced_commercial_data.retrieve, + ) diff --git a/src/lithic/resources/transactions/events/events.py b/src/lithic/resources/transactions/events/events.py new file mode 100644 index 00000000..42f61b32 --- /dev/null +++ b/src/lithic/resources/transactions/events/events.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from .enhanced_commercial_data import ( + EnhancedCommercialData, + AsyncEnhancedCommercialData, + EnhancedCommercialDataWithRawResponse, + AsyncEnhancedCommercialDataWithRawResponse, + EnhancedCommercialDataWithStreamingResponse, + AsyncEnhancedCommercialDataWithStreamingResponse, +) + +__all__ = ["Events", "AsyncEvents"] + + +class Events(SyncAPIResource): + @cached_property + def enhanced_commercial_data(self) -> EnhancedCommercialData: + return EnhancedCommercialData(self._client) + + @cached_property + def with_raw_response(self) -> EventsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return EventsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EventsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return EventsWithStreamingResponse(self) + + +class AsyncEvents(AsyncAPIResource): + @cached_property + def enhanced_commercial_data(self) -> AsyncEnhancedCommercialData: + return AsyncEnhancedCommercialData(self._client) + + @cached_property + def with_raw_response(self) -> AsyncEventsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ + return AsyncEventsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEventsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ + return AsyncEventsWithStreamingResponse(self) + + +class EventsWithRawResponse: + def __init__(self, events: Events) -> None: + self._events = events + + @cached_property + def enhanced_commercial_data(self) -> EnhancedCommercialDataWithRawResponse: + return EnhancedCommercialDataWithRawResponse(self._events.enhanced_commercial_data) + + +class AsyncEventsWithRawResponse: + def __init__(self, events: AsyncEvents) -> None: + self._events = events + + @cached_property + def enhanced_commercial_data(self) -> AsyncEnhancedCommercialDataWithRawResponse: + return AsyncEnhancedCommercialDataWithRawResponse(self._events.enhanced_commercial_data) + + +class EventsWithStreamingResponse: + def __init__(self, events: Events) -> None: + self._events = events + + @cached_property + def enhanced_commercial_data(self) -> EnhancedCommercialDataWithStreamingResponse: + return EnhancedCommercialDataWithStreamingResponse(self._events.enhanced_commercial_data) + + +class AsyncEventsWithStreamingResponse: + def __init__(self, events: AsyncEvents) -> None: + self._events = events + + @cached_property + def enhanced_commercial_data(self) -> AsyncEnhancedCommercialDataWithStreamingResponse: + return AsyncEnhancedCommercialDataWithStreamingResponse(self._events.enhanced_commercial_data) diff --git a/src/lithic/resources/transactions.py b/src/lithic/resources/transactions/transactions.py similarity index 60% rename from src/lithic/resources/transactions.py rename to src/lithic/resources/transactions/transactions.py index 2cb36675..3993985b 100644 --- a/src/lithic/resources/transactions.py +++ b/src/lithic/resources/transactions/transactions.py @@ -1,23 +1,16 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations +import typing_extensions from typing import Union from datetime import datetime from typing_extensions import Literal import httpx -from .. import _legacy_response -from ..types import ( - Transaction, - TransactionSimulateVoidResponse, - TransactionSimulateReturnResponse, - TransactionSimulateClearingResponse, - TransactionSimulateAuthorizationResponse, - TransactionSimulateReturnReversalResponse, - TransactionSimulateAuthorizationAdviceResponse, - TransactionSimulateCreditAuthorizationResponse, +from ... import _legacy_response +from ...types import ( transaction_list_params, transaction_simulate_void_params, transaction_simulate_return_params, @@ -26,28 +19,72 @@ transaction_simulate_return_reversal_params, transaction_simulate_authorization_advice_params, transaction_simulate_credit_authorization_params, + transaction_simulate_credit_authorization_advice_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import ( - AsyncPaginator, - make_request_options, +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncCursorPage, AsyncCursorPage +from .events.events import ( + Events, + AsyncEvents, + EventsWithRawResponse, + AsyncEventsWithRawResponse, + EventsWithStreamingResponse, + AsyncEventsWithStreamingResponse, +) +from ..._base_client import AsyncPaginator, make_request_options +from ...types.transaction import Transaction +from .enhanced_commercial_data import ( + EnhancedCommercialData, + AsyncEnhancedCommercialData, + EnhancedCommercialDataWithRawResponse, + AsyncEnhancedCommercialDataWithRawResponse, + EnhancedCommercialDataWithStreamingResponse, + AsyncEnhancedCommercialDataWithStreamingResponse, +) +from ...types.transaction_simulate_void_response import TransactionSimulateVoidResponse +from ...types.transaction_simulate_return_response import TransactionSimulateReturnResponse +from ...types.transaction_simulate_clearing_response import TransactionSimulateClearingResponse +from ...types.transaction_simulate_authorization_response import TransactionSimulateAuthorizationResponse +from ...types.transaction_simulate_return_reversal_response import TransactionSimulateReturnReversalResponse +from ...types.transaction_simulate_authorization_advice_response import TransactionSimulateAuthorizationAdviceResponse +from ...types.transaction_simulate_credit_authorization_response import TransactionSimulateCreditAuthorizationResponse +from ...types.transaction_simulate_credit_authorization_advice_response import ( + TransactionSimulateCreditAuthorizationAdviceResponse, ) __all__ = ["Transactions", "AsyncTransactions"] class Transactions(SyncAPIResource): + @cached_property + def enhanced_commercial_data(self) -> EnhancedCommercialData: + return EnhancedCommercialData(self._client) + + @cached_property + def events(self) -> Events: + return Events(self._client) + @cached_property def with_raw_response(self) -> TransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return TransactionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> TransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return TransactionsWithStreamingResponse(self) def retrieve( @@ -59,10 +96,12 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Transaction: - """ - Get specific card transaction. + """Get a specific card transaction. + + All amounts are in the smallest unit of their + respective currency (e.g., cents for USD). Args: extra_headers: Send extra headers @@ -76,7 +115,7 @@ def retrieve( if not transaction_token: raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") return self._get( - f"/transactions/{transaction_token}", + f"/v1/transactions/{transaction_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -86,23 +125,26 @@ def retrieve( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - card_token: str | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + card_token: str | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["PENDING", "VOIDED", "SETTLED", "DECLINED", "EXPIRED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncCursorPage[Transaction]: - """ - List card transactions. + """List card transactions. + + All amounts are in the smallest unit of their respective + currency (e.g., cents for USD) and inclusive of any acquirer fees. Args: account_token: Filters for transactions associated with a specific account. @@ -126,6 +168,8 @@ def list( starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. + status: Filters for transactions using transaction status field. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -135,7 +179,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/transactions", + "/v1/transactions", page=SyncCursorPage[Transaction], options=make_request_options( extra_headers=extra_headers, @@ -152,6 +196,7 @@ def list( "page_size": page_size, "result": result, "starting_after": starting_after, + "status": status, }, transaction_list_params.TransactionListParams, ), @@ -159,17 +204,51 @@ def list( model=Transaction, ) + def expire_authorization( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Expire authorization + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return self._post( + f"/v1/transactions/{transaction_token}/expire_authorization", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + def simulate_authorization( self, *, amount: int, descriptor: str, pan: str, - mcc: str | NotGiven = NOT_GIVEN, - merchant_acceptor_id: str | NotGiven = NOT_GIVEN, - merchant_amount: int | NotGiven = NOT_GIVEN, - merchant_currency: str | NotGiven = NOT_GIVEN, - partial_approval_capable: bool | NotGiven = NOT_GIVEN, + mcc: str | Omit = omit, + merchant_acceptor_id: str | Omit = omit, + merchant_amount: int | Omit = omit, + merchant_currency: str | Omit = omit, + partial_approval_capable: bool | Omit = omit, + pin: str | Omit = omit, status: Literal[ "AUTHORIZATION", "BALANCE_INQUIRY", @@ -177,28 +256,29 @@ def simulate_authorization( "FINANCIAL_AUTHORIZATION", "FINANCIAL_CREDIT_AUTHORIZATION", ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateAuthorizationResponse: """ - Simulates an authorization request from the payment network as if it came from a - merchant acquirer. If you're configured for ASA, simulating auths requires your - ASA client to be set up properly (respond with a valid JSON to the ASA request). - For users that are not configured for ASA, a daily transaction limit of $5000 - USD is applied by default. This limit can be modified via the + Simulates an authorization request from the card network as if it came from a + merchant acquirer. If you are configured for ASA, simulating authorizations + requires your ASA client to be set up properly, i.e. be able to respond to the + ASA request with a valid JSON. For users that are not configured for ASA, a + daily transaction limit of $5000 USD is applied by default. You can update this + limit via the [update account](https://docs.lithic.com/reference/patchaccountbytoken) endpoint. Args: amount: Amount (in cents) to authorize. For credit authorizations and financial credit authorizations, any value entered will be converted into a negative amount in - the simulated transaction. For example, entering 100 in this field will appear - as a -100 amount in the transaction. For balance inquiries, this field must be + the simulated transaction. For example, entering 100 in this field will result + in a -100 amount in the transaction. For balance inquiries, this field must be set to 0. descriptor: Merchant descriptor. @@ -214,22 +294,25 @@ def simulate_authorization( merchant_amount: Amount of the transaction to be simulated in currency specified in merchant_currency, including any acquirer fees. - merchant_currency: 3-digit alphabetic ISO 4217 currency code. + merchant_currency: 3-character alphabetic ISO 4217 currency code. Note: Simulator only accepts USD, + GBP, EUR and defaults to GBP if another ISO 4217 code is provided partial_approval_capable: Set to true if the terminal is capable of partial approval otherwise false. Partial approval is when part of a transaction is approved and another payment must be used for the remainder. + pin: Simulate entering a PIN. If omitted, PIN check will not be performed. + status: Type of event to simulate. - `AUTHORIZATION` is a dual message purchase authorization, meaning a subsequent clearing step is required to settle the transaction. - - `BALANCE_INQUIRY` is a $0 authorization that includes a request for the - balance held on the card, and is most typically seen when a cardholder - requests to view a card's balance at an ATM. + - `BALANCE_INQUIRY` is a $0 authorization requesting the balance held on the + card, and is most often observed when a cardholder requests to view a card's + balance at an ATM. - `CREDIT_AUTHORIZATION` is a dual message request from a merchant to authorize - a refund or credit, meaning a subsequent clearing step is required to settle - the transaction. + a refund, meaning a subsequent clearing step is required to settle the + transaction. - `FINANCIAL_AUTHORIZATION` is a single message request from a merchant to debit funds immediately (such as an ATM withdrawal), and no subsequent clearing is required to settle the transaction. @@ -246,7 +329,7 @@ def simulate_authorization( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/authorize", + "/v1/simulate/authorize", body=maybe_transform( { "amount": amount, @@ -257,6 +340,7 @@ def simulate_authorization( "merchant_amount": merchant_amount, "merchant_currency": merchant_currency, "partial_approval_capable": partial_approval_capable, + "pin": pin, "status": status, }, transaction_simulate_authorization_params.TransactionSimulateAuthorizationParams, @@ -277,15 +361,15 @@ def simulate_authorization_advice( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateAuthorizationAdviceResponse: """ - Simulates an authorization advice request from the payment network as if it came - from a merchant acquirer. An authorization advice request changes the amount of - the transaction. + Simulates an authorization advice from the card network as if it came from a + merchant acquirer. An authorization advice changes the pending amount of the + transaction. Args: - token: The transaction token returned from the /v1/simulate/authorize response. + token: The transaction token returned from the /v1/simulate/authorize. response. amount: Amount (in cents) to authorize. This amount will override the transaction's amount that was originally set by /v1/simulate/authorize. @@ -299,7 +383,7 @@ def simulate_authorization_advice( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/authorization_advice", + "/v1/simulate/authorization_advice", body=maybe_transform( { "token": token, @@ -317,32 +401,35 @@ def simulate_clearing( self, *, token: str, - amount: int | NotGiven = NOT_GIVEN, + amount: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateClearingResponse: - """Clears an existing authorization. + """Clears an existing authorization, either debit or credit. - After this event, the transaction is no longer - pending. + After this event, the + transaction transitions from `PENDING` to `SETTLED` status. - If no `amount` is supplied to this endpoint, the amount of the transaction will - be captured. Any transaction that has any amount completed at all do not have - access to this behavior. + If `amount` is not set, the full amount of the transaction will be cleared. + Transactions that have already cleared, either partially or fully, cannot be + cleared again using this endpoint. Args: token: The transaction token returned from the /v1/simulate/authorize response. - amount: Amount (in cents) to complete. Typically this will match the original - authorization, but may be more or less. + amount: Amount (in cents) to clear. Typically this will match the amount in the original + authorization, but can be higher or lower. The sign of this amount will + automatically match the sign of the original authorization's amount. For + example, entering 100 in this field will result in a -100 amount in the + transaction, if the original authorization is a credit authorization. - If no amount is supplied to this endpoint, the amount of the transaction will be - captured. Any transaction that has any amount completed at all do not have - access to this behavior. + If `amount` is not set, the full amount of the transaction will be cleared. + Transactions that have already cleared, either partially or fully, cannot be + cleared again using this endpoint. extra_headers: Send extra headers @@ -353,7 +440,7 @@ def simulate_clearing( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/clearing", + "/v1/simulate/clearing", body=maybe_transform( { "token": token, @@ -367,26 +454,26 @@ def simulate_clearing( cast_to=TransactionSimulateClearingResponse, ) + @typing_extensions.deprecated("use `simulate_credit_authorization_advice` instead") def simulate_credit_authorization( self, *, amount: int, descriptor: str, pan: str, - mcc: str | NotGiven = NOT_GIVEN, - merchant_acceptor_id: str | NotGiven = NOT_GIVEN, + mcc: str | Omit = omit, + merchant_acceptor_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateCreditAuthorizationResponse: - """Simulates a credit authorization advice message from the payment network. + """Simulates a credit authorization advice from the card network. - This - message indicates that a credit authorization was approved on your behalf by the - network. + This message + indicates that the network approved a credit authorization on your behalf. Args: amount: Amount (in cents). Any value entered will be converted into a negative amount in @@ -412,7 +499,7 @@ def simulate_credit_authorization( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/credit_authorization_advice", + "/v1/simulate/credit_authorization_advice", body=maybe_transform( { "amount": amount, @@ -429,6 +516,67 @@ def simulate_credit_authorization( cast_to=TransactionSimulateCreditAuthorizationResponse, ) + def simulate_credit_authorization_advice( + self, + *, + amount: int, + descriptor: str, + pan: str, + mcc: str | Omit = omit, + merchant_acceptor_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TransactionSimulateCreditAuthorizationAdviceResponse: + """Simulates a credit authorization advice from the card network. + + This message + indicates that the network approved a credit authorization on your behalf. + + Args: + amount: Amount (in cents). Any value entered will be converted into a negative amount in + the simulated transaction. For example, entering 100 in this field will appear + as a -100 amount in the transaction. + + descriptor: Merchant descriptor. + + pan: Sixteen digit card number. + + mcc: Merchant category code for the transaction to be simulated. A four-digit number + listed in ISO 18245. Supported merchant category codes can be found + [here](https://docs.lithic.com/docs/transactions#merchant-category-codes-mccs). + + merchant_acceptor_id: Unique identifier to identify the payment card acceptor. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/simulate/credit_authorization_advice", + body=maybe_transform( + { + "amount": amount, + "descriptor": descriptor, + "pan": pan, + "mcc": mcc, + "merchant_acceptor_id": merchant_acceptor_id, + }, + transaction_simulate_credit_authorization_advice_params.TransactionSimulateCreditAuthorizationAdviceParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TransactionSimulateCreditAuthorizationAdviceResponse, + ) + def simulate_return( self, *, @@ -440,12 +588,13 @@ def simulate_return( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateReturnResponse: - """Returns (aka refunds) an amount back to a card. + """Returns, or refunds, an amount back to a card. - Returns are cleared immediately - and do not spend time in a `PENDING` state. + Returns simulated via this + endpoint clear immediately, without prior authorization, and result in a + `SETTLED` transaction status. Args: amount: Amount (in cents) to authorize. @@ -463,7 +612,7 @@ def simulate_return( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/return", + "/v1/simulate/return", body=maybe_transform( { "amount": amount, @@ -487,12 +636,13 @@ def simulate_return_reversal( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateReturnReversalResponse: - """ - Voids a settled credit transaction – i.e., a transaction with a negative amount - and `SETTLED` status. These can be credit authorizations that have already - cleared or financial credit authorizations. + """Reverses a return, i.e. + + a credit transaction with a `SETTLED` status. Returns + can be financial credit authorizations, or credit authorizations that have + cleared. Args: token: The transaction token returned from the /v1/simulate/authorize response. @@ -506,7 +656,7 @@ def simulate_return_reversal( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/return_reversal", + "/v1/simulate/return_reversal", body=maybe_transform( {"token": token}, transaction_simulate_return_reversal_params.TransactionSimulateReturnReversalParams ), @@ -520,28 +670,28 @@ def simulate_void( self, *, token: str, - amount: int | NotGiven = NOT_GIVEN, - type: Literal["AUTHORIZATION_EXPIRY", "AUTHORIZATION_REVERSAL"] | NotGiven = NOT_GIVEN, + amount: int | Omit = omit, + type: Literal["AUTHORIZATION_EXPIRY", "AUTHORIZATION_REVERSAL"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateVoidResponse: - """Voids an existing, uncleared (aka pending) authorization. + """Voids a pending authorization. - If amount is not sent - the full amount will be voided. Cannot be used on partially completed - transactions, but can be used on partially voided transactions. _Note that - simulating an authorization expiry on credit authorizations or credit - authorization advice is not currently supported but will be added soon._ + If `amount` is not set, the full amount will be + voided. Can be used on partially voided transactions but not partially cleared + transactions. _Simulating an authorization expiry on credit authorizations or + credit authorization advice is not currently supported but will be added soon._ Args: token: The transaction token returned from the /v1/simulate/authorize response. - amount: Amount (in cents) to void. Typically this will match the original authorization, - but may be less. + amount: Amount (in cents) to void. Typically this will match the amount in the original + authorization, but can be less. Applies to authorization reversals only. An + authorization expiry will always apply to the full pending amount. type: Type of event to simulate. Defaults to `AUTHORIZATION_REVERSAL`. @@ -558,7 +708,7 @@ def simulate_void( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/simulate/void", + "/v1/simulate/void", body=maybe_transform( { "token": token, @@ -575,12 +725,31 @@ def simulate_void( class AsyncTransactions(AsyncAPIResource): + @cached_property + def enhanced_commercial_data(self) -> AsyncEnhancedCommercialData: + return AsyncEnhancedCommercialData(self._client) + + @cached_property + def events(self) -> AsyncEvents: + return AsyncEvents(self._client) + @cached_property def with_raw_response(self) -> AsyncTransactionsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/lithic-com/lithic-python#accessing-raw-response-data-eg-headers + """ return AsyncTransactionsWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncTransactionsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/lithic-com/lithic-python#with_streaming_response + """ return AsyncTransactionsWithStreamingResponse(self) async def retrieve( @@ -592,10 +761,12 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Transaction: - """ - Get specific card transaction. + """Get a specific card transaction. + + All amounts are in the smallest unit of their + respective currency (e.g., cents for USD). Args: extra_headers: Send extra headers @@ -609,7 +780,7 @@ async def retrieve( if not transaction_token: raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") return await self._get( - f"/transactions/{transaction_token}", + f"/v1/transactions/{transaction_token}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -619,23 +790,26 @@ async def retrieve( def list( self, *, - account_token: str | NotGiven = NOT_GIVEN, - begin: Union[str, datetime] | NotGiven = NOT_GIVEN, - card_token: str | NotGiven = NOT_GIVEN, - end: Union[str, datetime] | NotGiven = NOT_GIVEN, - ending_before: str | NotGiven = NOT_GIVEN, - page_size: int | NotGiven = NOT_GIVEN, - result: Literal["APPROVED", "DECLINED"] | NotGiven = NOT_GIVEN, - starting_after: str | NotGiven = NOT_GIVEN, + account_token: str | Omit = omit, + begin: Union[str, datetime] | Omit = omit, + card_token: str | Omit = omit, + end: Union[str, datetime] | Omit = omit, + ending_before: str | Omit = omit, + page_size: int | Omit = omit, + result: Literal["APPROVED", "DECLINED"] | Omit = omit, + starting_after: str | Omit = omit, + status: Literal["PENDING", "VOIDED", "SETTLED", "DECLINED", "EXPIRED"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Transaction, AsyncCursorPage[Transaction]]: - """ - List card transactions. + """List card transactions. + + All amounts are in the smallest unit of their respective + currency (e.g., cents for USD) and inclusive of any acquirer fees. Args: account_token: Filters for transactions associated with a specific account. @@ -659,6 +833,8 @@ def list( starting_after: A cursor representing an item's token after which a page of results should begin. Used to retrieve the next page of results after this item. + status: Filters for transactions using transaction status field. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -668,7 +844,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get_api_list( - "/transactions", + "/v1/transactions", page=AsyncCursorPage[Transaction], options=make_request_options( extra_headers=extra_headers, @@ -685,6 +861,7 @@ def list( "page_size": page_size, "result": result, "starting_after": starting_after, + "status": status, }, transaction_list_params.TransactionListParams, ), @@ -692,17 +869,51 @@ def list( model=Transaction, ) + async def expire_authorization( + self, + transaction_token: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Expire authorization + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not transaction_token: + raise ValueError(f"Expected a non-empty value for `transaction_token` but received {transaction_token!r}") + return await self._post( + f"/v1/transactions/{transaction_token}/expire_authorization", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + async def simulate_authorization( self, *, amount: int, descriptor: str, pan: str, - mcc: str | NotGiven = NOT_GIVEN, - merchant_acceptor_id: str | NotGiven = NOT_GIVEN, - merchant_amount: int | NotGiven = NOT_GIVEN, - merchant_currency: str | NotGiven = NOT_GIVEN, - partial_approval_capable: bool | NotGiven = NOT_GIVEN, + mcc: str | Omit = omit, + merchant_acceptor_id: str | Omit = omit, + merchant_amount: int | Omit = omit, + merchant_currency: str | Omit = omit, + partial_approval_capable: bool | Omit = omit, + pin: str | Omit = omit, status: Literal[ "AUTHORIZATION", "BALANCE_INQUIRY", @@ -710,28 +921,29 @@ async def simulate_authorization( "FINANCIAL_AUTHORIZATION", "FINANCIAL_CREDIT_AUTHORIZATION", ] - | NotGiven = NOT_GIVEN, + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateAuthorizationResponse: """ - Simulates an authorization request from the payment network as if it came from a - merchant acquirer. If you're configured for ASA, simulating auths requires your - ASA client to be set up properly (respond with a valid JSON to the ASA request). - For users that are not configured for ASA, a daily transaction limit of $5000 - USD is applied by default. This limit can be modified via the + Simulates an authorization request from the card network as if it came from a + merchant acquirer. If you are configured for ASA, simulating authorizations + requires your ASA client to be set up properly, i.e. be able to respond to the + ASA request with a valid JSON. For users that are not configured for ASA, a + daily transaction limit of $5000 USD is applied by default. You can update this + limit via the [update account](https://docs.lithic.com/reference/patchaccountbytoken) endpoint. Args: amount: Amount (in cents) to authorize. For credit authorizations and financial credit authorizations, any value entered will be converted into a negative amount in - the simulated transaction. For example, entering 100 in this field will appear - as a -100 amount in the transaction. For balance inquiries, this field must be + the simulated transaction. For example, entering 100 in this field will result + in a -100 amount in the transaction. For balance inquiries, this field must be set to 0. descriptor: Merchant descriptor. @@ -747,22 +959,25 @@ async def simulate_authorization( merchant_amount: Amount of the transaction to be simulated in currency specified in merchant_currency, including any acquirer fees. - merchant_currency: 3-digit alphabetic ISO 4217 currency code. + merchant_currency: 3-character alphabetic ISO 4217 currency code. Note: Simulator only accepts USD, + GBP, EUR and defaults to GBP if another ISO 4217 code is provided partial_approval_capable: Set to true if the terminal is capable of partial approval otherwise false. Partial approval is when part of a transaction is approved and another payment must be used for the remainder. + pin: Simulate entering a PIN. If omitted, PIN check will not be performed. + status: Type of event to simulate. - `AUTHORIZATION` is a dual message purchase authorization, meaning a subsequent clearing step is required to settle the transaction. - - `BALANCE_INQUIRY` is a $0 authorization that includes a request for the - balance held on the card, and is most typically seen when a cardholder - requests to view a card's balance at an ATM. + - `BALANCE_INQUIRY` is a $0 authorization requesting the balance held on the + card, and is most often observed when a cardholder requests to view a card's + balance at an ATM. - `CREDIT_AUTHORIZATION` is a dual message request from a merchant to authorize - a refund or credit, meaning a subsequent clearing step is required to settle - the transaction. + a refund, meaning a subsequent clearing step is required to settle the + transaction. - `FINANCIAL_AUTHORIZATION` is a single message request from a merchant to debit funds immediately (such as an ATM withdrawal), and no subsequent clearing is required to settle the transaction. @@ -779,8 +994,8 @@ async def simulate_authorization( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/authorize", - body=maybe_transform( + "/v1/simulate/authorize", + body=await async_maybe_transform( { "amount": amount, "descriptor": descriptor, @@ -790,6 +1005,7 @@ async def simulate_authorization( "merchant_amount": merchant_amount, "merchant_currency": merchant_currency, "partial_approval_capable": partial_approval_capable, + "pin": pin, "status": status, }, transaction_simulate_authorization_params.TransactionSimulateAuthorizationParams, @@ -810,15 +1026,15 @@ async def simulate_authorization_advice( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateAuthorizationAdviceResponse: """ - Simulates an authorization advice request from the payment network as if it came - from a merchant acquirer. An authorization advice request changes the amount of - the transaction. + Simulates an authorization advice from the card network as if it came from a + merchant acquirer. An authorization advice changes the pending amount of the + transaction. Args: - token: The transaction token returned from the /v1/simulate/authorize response. + token: The transaction token returned from the /v1/simulate/authorize. response. amount: Amount (in cents) to authorize. This amount will override the transaction's amount that was originally set by /v1/simulate/authorize. @@ -832,8 +1048,8 @@ async def simulate_authorization_advice( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/authorization_advice", - body=maybe_transform( + "/v1/simulate/authorization_advice", + body=await async_maybe_transform( { "token": token, "amount": amount, @@ -850,32 +1066,35 @@ async def simulate_clearing( self, *, token: str, - amount: int | NotGiven = NOT_GIVEN, + amount: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateClearingResponse: - """Clears an existing authorization. + """Clears an existing authorization, either debit or credit. - After this event, the transaction is no longer - pending. + After this event, the + transaction transitions from `PENDING` to `SETTLED` status. - If no `amount` is supplied to this endpoint, the amount of the transaction will - be captured. Any transaction that has any amount completed at all do not have - access to this behavior. + If `amount` is not set, the full amount of the transaction will be cleared. + Transactions that have already cleared, either partially or fully, cannot be + cleared again using this endpoint. Args: token: The transaction token returned from the /v1/simulate/authorize response. - amount: Amount (in cents) to complete. Typically this will match the original - authorization, but may be more or less. + amount: Amount (in cents) to clear. Typically this will match the amount in the original + authorization, but can be higher or lower. The sign of this amount will + automatically match the sign of the original authorization's amount. For + example, entering 100 in this field will result in a -100 amount in the + transaction, if the original authorization is a credit authorization. - If no amount is supplied to this endpoint, the amount of the transaction will be - captured. Any transaction that has any amount completed at all do not have - access to this behavior. + If `amount` is not set, the full amount of the transaction will be cleared. + Transactions that have already cleared, either partially or fully, cannot be + cleared again using this endpoint. extra_headers: Send extra headers @@ -886,8 +1105,8 @@ async def simulate_clearing( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/clearing", - body=maybe_transform( + "/v1/simulate/clearing", + body=await async_maybe_transform( { "token": token, "amount": amount, @@ -900,26 +1119,26 @@ async def simulate_clearing( cast_to=TransactionSimulateClearingResponse, ) + @typing_extensions.deprecated("use `simulate_credit_authorization_advice` instead") async def simulate_credit_authorization( self, *, amount: int, descriptor: str, pan: str, - mcc: str | NotGiven = NOT_GIVEN, - merchant_acceptor_id: str | NotGiven = NOT_GIVEN, + mcc: str | Omit = omit, + merchant_acceptor_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateCreditAuthorizationResponse: - """Simulates a credit authorization advice message from the payment network. + """Simulates a credit authorization advice from the card network. - This - message indicates that a credit authorization was approved on your behalf by the - network. + This message + indicates that the network approved a credit authorization on your behalf. Args: amount: Amount (in cents). Any value entered will be converted into a negative amount in @@ -945,8 +1164,8 @@ async def simulate_credit_authorization( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/credit_authorization_advice", - body=maybe_transform( + "/v1/simulate/credit_authorization_advice", + body=await async_maybe_transform( { "amount": amount, "descriptor": descriptor, @@ -962,6 +1181,67 @@ async def simulate_credit_authorization( cast_to=TransactionSimulateCreditAuthorizationResponse, ) + async def simulate_credit_authorization_advice( + self, + *, + amount: int, + descriptor: str, + pan: str, + mcc: str | Omit = omit, + merchant_acceptor_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TransactionSimulateCreditAuthorizationAdviceResponse: + """Simulates a credit authorization advice from the card network. + + This message + indicates that the network approved a credit authorization on your behalf. + + Args: + amount: Amount (in cents). Any value entered will be converted into a negative amount in + the simulated transaction. For example, entering 100 in this field will appear + as a -100 amount in the transaction. + + descriptor: Merchant descriptor. + + pan: Sixteen digit card number. + + mcc: Merchant category code for the transaction to be simulated. A four-digit number + listed in ISO 18245. Supported merchant category codes can be found + [here](https://docs.lithic.com/docs/transactions#merchant-category-codes-mccs). + + merchant_acceptor_id: Unique identifier to identify the payment card acceptor. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/simulate/credit_authorization_advice", + body=await async_maybe_transform( + { + "amount": amount, + "descriptor": descriptor, + "pan": pan, + "mcc": mcc, + "merchant_acceptor_id": merchant_acceptor_id, + }, + transaction_simulate_credit_authorization_advice_params.TransactionSimulateCreditAuthorizationAdviceParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TransactionSimulateCreditAuthorizationAdviceResponse, + ) + async def simulate_return( self, *, @@ -973,12 +1253,13 @@ async def simulate_return( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateReturnResponse: - """Returns (aka refunds) an amount back to a card. + """Returns, or refunds, an amount back to a card. - Returns are cleared immediately - and do not spend time in a `PENDING` state. + Returns simulated via this + endpoint clear immediately, without prior authorization, and result in a + `SETTLED` transaction status. Args: amount: Amount (in cents) to authorize. @@ -996,8 +1277,8 @@ async def simulate_return( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/return", - body=maybe_transform( + "/v1/simulate/return", + body=await async_maybe_transform( { "amount": amount, "descriptor": descriptor, @@ -1020,12 +1301,13 @@ async def simulate_return_reversal( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateReturnReversalResponse: - """ - Voids a settled credit transaction – i.e., a transaction with a negative amount - and `SETTLED` status. These can be credit authorizations that have already - cleared or financial credit authorizations. + """Reverses a return, i.e. + + a credit transaction with a `SETTLED` status. Returns + can be financial credit authorizations, or credit authorizations that have + cleared. Args: token: The transaction token returned from the /v1/simulate/authorize response. @@ -1039,8 +1321,8 @@ async def simulate_return_reversal( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/return_reversal", - body=maybe_transform( + "/v1/simulate/return_reversal", + body=await async_maybe_transform( {"token": token}, transaction_simulate_return_reversal_params.TransactionSimulateReturnReversalParams ), options=make_request_options( @@ -1053,28 +1335,28 @@ async def simulate_void( self, *, token: str, - amount: int | NotGiven = NOT_GIVEN, - type: Literal["AUTHORIZATION_EXPIRY", "AUTHORIZATION_REVERSAL"] | NotGiven = NOT_GIVEN, + amount: int | Omit = omit, + type: Literal["AUTHORIZATION_EXPIRY", "AUTHORIZATION_REVERSAL"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TransactionSimulateVoidResponse: - """Voids an existing, uncleared (aka pending) authorization. + """Voids a pending authorization. - If amount is not sent - the full amount will be voided. Cannot be used on partially completed - transactions, but can be used on partially voided transactions. _Note that - simulating an authorization expiry on credit authorizations or credit - authorization advice is not currently supported but will be added soon._ + If `amount` is not set, the full amount will be + voided. Can be used on partially voided transactions but not partially cleared + transactions. _Simulating an authorization expiry on credit authorizations or + credit authorization advice is not currently supported but will be added soon._ Args: token: The transaction token returned from the /v1/simulate/authorize response. - amount: Amount (in cents) to void. Typically this will match the original authorization, - but may be less. + amount: Amount (in cents) to void. Typically this will match the amount in the original + authorization, but can be less. Applies to authorization reversals only. An + authorization expiry will always apply to the full pending amount. type: Type of event to simulate. Defaults to `AUTHORIZATION_REVERSAL`. @@ -1091,8 +1373,8 @@ async def simulate_void( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/simulate/void", - body=maybe_transform( + "/v1/simulate/void", + body=await async_maybe_transform( { "token": token, "amount": amount, @@ -1117,6 +1399,9 @@ def __init__(self, transactions: Transactions) -> None: self.list = _legacy_response.to_raw_response_wrapper( transactions.list, ) + self.expire_authorization = _legacy_response.to_raw_response_wrapper( + transactions.expire_authorization, + ) self.simulate_authorization = _legacy_response.to_raw_response_wrapper( transactions.simulate_authorization, ) @@ -1126,8 +1411,13 @@ def __init__(self, transactions: Transactions) -> None: self.simulate_clearing = _legacy_response.to_raw_response_wrapper( transactions.simulate_clearing, ) - self.simulate_credit_authorization = _legacy_response.to_raw_response_wrapper( - transactions.simulate_credit_authorization, + self.simulate_credit_authorization = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + transactions.simulate_credit_authorization, # pyright: ignore[reportDeprecated], + ) + ) + self.simulate_credit_authorization_advice = _legacy_response.to_raw_response_wrapper( + transactions.simulate_credit_authorization_advice, ) self.simulate_return = _legacy_response.to_raw_response_wrapper( transactions.simulate_return, @@ -1139,6 +1429,14 @@ def __init__(self, transactions: Transactions) -> None: transactions.simulate_void, ) + @cached_property + def enhanced_commercial_data(self) -> EnhancedCommercialDataWithRawResponse: + return EnhancedCommercialDataWithRawResponse(self._transactions.enhanced_commercial_data) + + @cached_property + def events(self) -> EventsWithRawResponse: + return EventsWithRawResponse(self._transactions.events) + class AsyncTransactionsWithRawResponse: def __init__(self, transactions: AsyncTransactions) -> None: @@ -1150,6 +1448,9 @@ def __init__(self, transactions: AsyncTransactions) -> None: self.list = _legacy_response.async_to_raw_response_wrapper( transactions.list, ) + self.expire_authorization = _legacy_response.async_to_raw_response_wrapper( + transactions.expire_authorization, + ) self.simulate_authorization = _legacy_response.async_to_raw_response_wrapper( transactions.simulate_authorization, ) @@ -1159,8 +1460,13 @@ def __init__(self, transactions: AsyncTransactions) -> None: self.simulate_clearing = _legacy_response.async_to_raw_response_wrapper( transactions.simulate_clearing, ) - self.simulate_credit_authorization = _legacy_response.async_to_raw_response_wrapper( - transactions.simulate_credit_authorization, + self.simulate_credit_authorization = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + transactions.simulate_credit_authorization, # pyright: ignore[reportDeprecated], + ) + ) + self.simulate_credit_authorization_advice = _legacy_response.async_to_raw_response_wrapper( + transactions.simulate_credit_authorization_advice, ) self.simulate_return = _legacy_response.async_to_raw_response_wrapper( transactions.simulate_return, @@ -1172,6 +1478,14 @@ def __init__(self, transactions: AsyncTransactions) -> None: transactions.simulate_void, ) + @cached_property + def enhanced_commercial_data(self) -> AsyncEnhancedCommercialDataWithRawResponse: + return AsyncEnhancedCommercialDataWithRawResponse(self._transactions.enhanced_commercial_data) + + @cached_property + def events(self) -> AsyncEventsWithRawResponse: + return AsyncEventsWithRawResponse(self._transactions.events) + class TransactionsWithStreamingResponse: def __init__(self, transactions: Transactions) -> None: @@ -1183,6 +1497,9 @@ def __init__(self, transactions: Transactions) -> None: self.list = to_streamed_response_wrapper( transactions.list, ) + self.expire_authorization = to_streamed_response_wrapper( + transactions.expire_authorization, + ) self.simulate_authorization = to_streamed_response_wrapper( transactions.simulate_authorization, ) @@ -1192,8 +1509,13 @@ def __init__(self, transactions: Transactions) -> None: self.simulate_clearing = to_streamed_response_wrapper( transactions.simulate_clearing, ) - self.simulate_credit_authorization = to_streamed_response_wrapper( - transactions.simulate_credit_authorization, + self.simulate_credit_authorization = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + transactions.simulate_credit_authorization, # pyright: ignore[reportDeprecated], + ) + ) + self.simulate_credit_authorization_advice = to_streamed_response_wrapper( + transactions.simulate_credit_authorization_advice, ) self.simulate_return = to_streamed_response_wrapper( transactions.simulate_return, @@ -1205,6 +1527,14 @@ def __init__(self, transactions: Transactions) -> None: transactions.simulate_void, ) + @cached_property + def enhanced_commercial_data(self) -> EnhancedCommercialDataWithStreamingResponse: + return EnhancedCommercialDataWithStreamingResponse(self._transactions.enhanced_commercial_data) + + @cached_property + def events(self) -> EventsWithStreamingResponse: + return EventsWithStreamingResponse(self._transactions.events) + class AsyncTransactionsWithStreamingResponse: def __init__(self, transactions: AsyncTransactions) -> None: @@ -1216,6 +1546,9 @@ def __init__(self, transactions: AsyncTransactions) -> None: self.list = async_to_streamed_response_wrapper( transactions.list, ) + self.expire_authorization = async_to_streamed_response_wrapper( + transactions.expire_authorization, + ) self.simulate_authorization = async_to_streamed_response_wrapper( transactions.simulate_authorization, ) @@ -1225,8 +1558,13 @@ def __init__(self, transactions: AsyncTransactions) -> None: self.simulate_clearing = async_to_streamed_response_wrapper( transactions.simulate_clearing, ) - self.simulate_credit_authorization = async_to_streamed_response_wrapper( - transactions.simulate_credit_authorization, + self.simulate_credit_authorization = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + transactions.simulate_credit_authorization, # pyright: ignore[reportDeprecated], + ) + ) + self.simulate_credit_authorization_advice = async_to_streamed_response_wrapper( + transactions.simulate_credit_authorization_advice, ) self.simulate_return = async_to_streamed_response_wrapper( transactions.simulate_return, @@ -1237,3 +1575,11 @@ def __init__(self, transactions: AsyncTransactions) -> None: self.simulate_void = async_to_streamed_response_wrapper( transactions.simulate_void, ) + + @cached_property + def enhanced_commercial_data(self) -> AsyncEnhancedCommercialDataWithStreamingResponse: + return AsyncEnhancedCommercialDataWithStreamingResponse(self._transactions.enhanced_commercial_data) + + @cached_property + def events(self) -> AsyncEventsWithStreamingResponse: + return AsyncEventsWithStreamingResponse(self._transactions.events) diff --git a/src/lithic/resources/webhooks.py b/src/lithic/resources/webhooks.py index 8b5094f8..1a4c72bd 100644 --- a/src/lithic/resources/webhooks.py +++ b/src/lithic/resources/webhooks.py @@ -1,207 +1,69 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -import hmac import json -import math -import base64 -import hashlib -from datetime import datetime, timezone, timedelta - -from .._types import ( - HeadersLike, -) -from .._utils import ( - removeprefix, - get_required_header, -) +from typing import Mapping, cast + +from .._models import construct_type from .._resource import SyncAPIResource, AsyncAPIResource +from .._exceptions import LithicError +from ..types.parsed_webhook_event import ParsedWebhookEvent __all__ = ["Webhooks", "AsyncWebhooks"] class Webhooks(SyncAPIResource): - def unwrap( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> object: - """Validates that the given payload was sent by Lithic and parses the payload.""" - self.verify_signature(payload=payload, headers=headers, secret=secret) - return json.loads(payload) - - def verify_signature( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> None: - """Validates whether or not the webhook payload was sent by Lithic. - - An error will be raised if the webhook payload was not sent by Lithic. - """ - if secret is None: - secret = self._client.webhook_secret - - if secret is None: - raise ValueError( - "The webhook secret must either be set using the env var, LITHIC_WEBHOOK_SECRET, on the client class, Lithic(webhook_secret='123'), or passed to this function" - ) - - try: - whsecret = base64.b64decode(removeprefix(secret, "whsec_")) - except Exception as err: - raise ValueError("Bad secret") from err - - msg_id = get_required_header(headers, "webhook-id") - msg_timestamp = get_required_header(headers, "webhook-timestamp") - - # validate the timestamp - webhook_tolerance = timedelta(minutes=5) - now = datetime.now(tz=timezone.utc) - + def parsed(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> ParsedWebhookEvent: try: - timestamp = datetime.fromtimestamp(float(msg_timestamp), tz=timezone.utc) - except Exception as err: - raise ValueError("Invalid signature headers. Could not convert to timestamp") from err - - # too old - if timestamp < (now - webhook_tolerance): - raise ValueError("Webhook timestamp is too old") - - # too new - if timestamp > (now + webhook_tolerance): - raise ValueError("Webhook timestamp is too new") + from standardwebhooks import Webhook + except ImportError as exc: + raise LithicError("You need to install `lithic[webhooks]` to use this method") from exc - # create the signature - body = payload.decode("utf-8") if isinstance(payload, bytes) else payload - if not isinstance(body, str): # pyright: ignore[reportUnnecessaryIsInstance] - raise ValueError( - "Webhook body should be a string of JSON (or bytes which can be decoded to a utf-8 string), not a parsed dictionary." - ) + if key is None: + key = self._client.webhook_secret + if key is None: + raise ValueError( + "Cannot verify a webhook without a key on either the client's webhook_secret or passed in as an argument" + ) - timestamp_str = str(math.floor(timestamp.replace(tzinfo=timezone.utc).timestamp())) + if not isinstance(headers, dict): + headers = dict(headers) - to_sign = f"{msg_id}.{timestamp_str}.{body}".encode() - expected_signature = hmac.new(whsecret, to_sign, hashlib.sha256).digest() + Webhook(key).verify(payload, headers) - msg_signature = get_required_header(headers, "webhook-signature") - - # Signature header can contain multiple signatures delimited by spaces - passed_sigs = msg_signature.split(" ") - - for versioned_sig in passed_sigs: - values = versioned_sig.split(",") - if len(values) != 2: - # signature is not formatted like {version},{signature} - continue - - (version, signature) = values - - # Only verify prefix v1 - if version != "v1": - continue - - sig_bytes = base64.b64decode(signature) - if hmac.compare_digest(expected_signature, sig_bytes): - # valid! - return None - - raise ValueError("None of the given webhook signatures match the expected signature") + return cast( + ParsedWebhookEvent, + construct_type( + type_=ParsedWebhookEvent, + value=json.loads(payload), + ), + ) class AsyncWebhooks(AsyncAPIResource): - def unwrap( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> object: - """Validates that the given payload was sent by Lithic and parses the payload.""" - self.verify_signature(payload=payload, headers=headers, secret=secret) - return json.loads(payload) - - def verify_signature( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> None: - """Validates whether or not the webhook payload was sent by Lithic. - - An error will be raised if the webhook payload was not sent by Lithic. - """ - if secret is None: - secret = self._client.webhook_secret - - if secret is None: - raise ValueError( - "The webhook secret must either be set using the env var, LITHIC_WEBHOOK_SECRET, on the client class, Lithic(webhook_secret='123'), or passed to this function" - ) - + def parsed(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> ParsedWebhookEvent: try: - whsecret = base64.b64decode(removeprefix(secret, "whsec_")) - except Exception as err: - raise ValueError("Bad secret") from err - - msg_id = get_required_header(headers, "webhook-id") - msg_timestamp = get_required_header(headers, "webhook-timestamp") - - # validate the timestamp - webhook_tolerance = timedelta(minutes=5) - now = datetime.now(tz=timezone.utc) - - try: - timestamp = datetime.fromtimestamp(float(msg_timestamp), tz=timezone.utc) - except Exception as err: - raise ValueError("Invalid signature headers. Could not convert to timestamp") from err - - # too old - if timestamp < (now - webhook_tolerance): - raise ValueError("Webhook timestamp is too old") - - # too new - if timestamp > (now + webhook_tolerance): - raise ValueError("Webhook timestamp is too new") - - # create the signature - body = payload.decode("utf-8") if isinstance(payload, bytes) else payload - if not isinstance(body, str): # pyright: ignore[reportUnnecessaryIsInstance] - raise ValueError( - "Webhook body should be a string of JSON (or bytes which can be decoded to a utf-8 string), not a parsed dictionary." - ) - - timestamp_str = str(math.floor(timestamp.replace(tzinfo=timezone.utc).timestamp())) - - to_sign = f"{msg_id}.{timestamp_str}.{body}".encode() - expected_signature = hmac.new(whsecret, to_sign, hashlib.sha256).digest() - - msg_signature = get_required_header(headers, "webhook-signature") - - # Signature header can contain multiple signatures delimited by spaces - passed_sigs = msg_signature.split(" ") - - for versioned_sig in passed_sigs: - values = versioned_sig.split(",") - if len(values) != 2: - # signature is not formatted like {version},{signature} - continue - - (version, signature) = values - - # Only verify prefix v1 - if version != "v1": - continue - - sig_bytes = base64.b64decode(signature) - if hmac.compare_digest(expected_signature, sig_bytes): - # valid! - return None - - raise ValueError("None of the given webhook signatures match the expected signature") + from standardwebhooks import Webhook + except ImportError as exc: + raise LithicError("You need to install `lithic[webhooks]` to use this method") from exc + + if key is None: + key = self._client.webhook_secret + if key is None: + raise ValueError( + "Cannot verify a webhook without a key on either the client's webhook_secret or passed in as an argument" + ) + + if not isinstance(headers, dict): + headers = dict(headers) + + Webhook(key).verify(payload, headers) + + return cast( + ParsedWebhookEvent, + construct_type( + type_=ParsedWebhookEvent, + value=json.loads(payload), + ), + ) diff --git a/src/lithic/types/__init__.py b/src/lithic/types/__init__.py index bc1b39f6..f52ec88a 100644 --- a/src/lithic/types/__init__.py +++ b/src/lithic/types/__init__.py @@ -1,107 +1,178 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from .card import Card as Card from .event import Event as Event -from .shared import Address as Address, Carrier as Carrier, ShippingAddress as ShippingAddress +from .device import Device as Device +from .shared import ( + Address as Address, + Carrier as Carrier, + Currency as Currency, + Document as Document, + Merchant as Merchant, + FinancialEvent as FinancialEvent, + ShippingAddress as ShippingAddress, + AccountFinancialAccountType as AccountFinancialAccountType, + InstanceFinancialAccountType as InstanceFinancialAccountType, +) from .account import Account as Account from .balance import Balance as Balance from .dispute import Dispute as Dispute from .payment import Payment as Payment -from .auth_rule import AuthRule as AuthRule +from .kyb_param import KYBParam as KYBParam +from .kyc_param import KYCParam as KYCParam from .api_status import APIStatus as APIStatus +from .dispute_v2 import DisputeV2 as DisputeV2 from .owner_type import OwnerType as OwnerType +from .token_info import TokenInfo as TokenInfo from .transaction import Transaction as Transaction from .card_program import CardProgram as CardProgram +from .non_pci_card import NonPCICard as NonPCICard from .tokenization import Tokenization as Tokenization +from .funding_event import FundingEvent as FundingEvent +from .network_total import NetworkTotal as NetworkTotal from .account_holder import AccountHolder as AccountHolder +from .card_bulk_order import CardBulkOrder as CardBulkOrder from .message_attempt import MessageAttempt as MessageAttempt -from .business_account import BusinessAccount as BusinessAccount +from .network_program import NetworkProgram as NetworkProgram from .card_list_params import CardListParams as CardListParams +from .category_details import CategoryDetails as CategoryDetails from .digital_card_art import DigitalCardArt as DigitalCardArt from .dispute_evidence import DisputeEvidence as DisputeEvidence -from .aggregate_balance import AggregateBalance as AggregateBalance +from .external_payment import ExternalPayment as ExternalPayment +from .kyc_exempt_param import KYCExemptParam as KYCExemptParam +from .statement_totals import StatementTotals as StatementTotals from .card_embed_params import CardEmbedParams as CardEmbedParams from .card_renew_params import CardRenewParams as CardRenewParams from .card_spend_limits import CardSpendLimits as CardSpendLimits from .event_list_params import EventListParams as EventListParams +from .external_resource import ExternalResource as ExternalResource from .financial_account import FinancialAccount as FinancialAccount +from .required_document import RequiredDocument as RequiredDocument from .settlement_detail import SettlementDetail as SettlementDetail from .settlement_report import SettlementReport as SettlementReport from .auth_stream_secret import AuthStreamSecret as AuthStreamSecret from .card_create_params import CardCreateParams as CardCreateParams from .card_update_params import CardUpdateParams as CardUpdateParams from .event_subscription import EventSubscription as EventSubscription +from .provision_response import ProvisionResponse as ProvisionResponse +from .wire_party_details import WirePartyDetails as WirePartyDetails from .account_list_params import AccountListParams as AccountListParams from .balance_list_params import BalanceListParams as BalanceListParams from .card_embed_response import CardEmbedResponse as CardEmbedResponse from .card_reissue_params import CardReissueParams as CardReissueParams from .dispute_list_params import DisputeListParams as DisputeListParams -from .event_resend_params import EventResendParams as EventResendParams +from .kyb_business_entity import KYBBusinessEntity as KYBBusinessEntity from .payment_list_params import PaymentListParams as PaymentListParams from .tokenization_secret import TokenizationSecret as TokenizationSecret from .verification_method import VerificationMethod as VerificationMethod from .account_spend_limits import AccountSpendLimits as AccountSpendLimits +from .address_update_param import AddressUpdateParam as AddressUpdateParam +from .internal_transaction import InternalTransaction as InternalTransaction +from .parsed_webhook_event import ParsedWebhookEvent as ParsedWebhookEvent from .spend_limit_duration import SpendLimitDuration as SpendLimitDuration from .account_update_params import AccountUpdateParams as AccountUpdateParams -from .auth_rule_list_params import AuthRuleListParams as AuthRuleListParams from .card_provision_params import CardProvisionParams as CardProvisionParams from .dispute_create_params import DisputeCreateParams as DisputeCreateParams from .dispute_update_params import DisputeUpdateParams as DisputeUpdateParams +from .external_bank_account import ExternalBankAccount as ExternalBankAccount from .financial_transaction import FinancialTransaction as FinancialTransaction from .payment_create_params import PaymentCreateParams as PaymentCreateParams -from .auth_rule_apply_params import AuthRuleApplyParams as AuthRuleApplyParams +from .payment_return_params import PaymentReturnParams as PaymentReturnParams +from .book_transfer_response import BookTransferResponse as BookTransferResponse +from .external_resource_type import ExternalResourceType as ExternalResourceType from .payment_retry_response import PaymentRetryResponse as PaymentRetryResponse -from .account_holder_document import AccountHolderDocument as AccountHolderDocument -from .auth_rule_create_params import AuthRuleCreateParams as AuthRuleCreateParams -from .auth_rule_remove_params import AuthRuleRemoveParams as AuthRuleRemoveParams -from .auth_rule_update_params import AuthRuleUpdateParams as AuthRuleUpdateParams from .card_provision_response import CardProvisionResponse as CardProvisionResponse +from .disputes_v2_list_params import DisputesV2ListParams as DisputesV2ListParams from .payment_create_response import PaymentCreateResponse as PaymentCreateResponse +from .three_ds_authentication import ThreeDSAuthentication as ThreeDSAuthentication +from .tokenization_tfa_reason import TokenizationTfaReason as TokenizationTfaReason from .transaction_list_params import TransactionListParams as TransactionListParams +from .wallet_decisioning_info import WalletDecisioningInfo as WalletDecisioningInfo from .card_program_list_params import CardProgramListParams as CardProgramListParams from .tokenization_list_params import TokenizationListParams as TokenizationListParams -from .auth_rule_remove_response import AuthRuleRemoveResponse as AuthRuleRemoveResponse -from .card_get_embed_url_params import CardGetEmbedURLParams as CardGetEmbedURLParams +from .tokenization_rule_result import TokenizationRuleResult as TokenizationRuleResult +from .asa_request_webhook_event import AsaRequestWebhookEvent as AsaRequestWebhookEvent +from .book_transfer_list_params import BookTransferListParams as BookTransferListParams from .card_search_by_pan_params import CardSearchByPanParams as CardSearchByPanParams +from .card_web_provision_params import CardWebProvisionParams as CardWebProvisionParams +from .cardholder_authentication import CardholderAuthentication as CardholderAuthentication +from .financial_account_balance import FinancialAccountBalance as FinancialAccountBalance +from .funding_event_list_params import FundingEventListParams as FundingEventListParams from .responder_endpoint_status import ResponderEndpointStatus as ResponderEndpointStatus from .account_holder_list_params import AccountHolderListParams as AccountHolderListParams -from .card_get_embed_html_params import CardGetEmbedHTMLParams as CardGetEmbedHTMLParams +from .card_created_webhook_event import CardCreatedWebhookEvent as CardCreatedWebhookEvent +from .card_renewed_webhook_event import CardRenewedWebhookEvent as CardRenewedWebhookEvent +from .card_shipped_webhook_event import CardShippedWebhookEvent as CardShippedWebhookEvent from .event_list_attempts_params import EventListAttemptsParams as EventListAttemptsParams from .settlement_summary_details import SettlementSummaryDetails as SettlementSummaryDetails -from .auth_rule_retrieve_response import AuthRuleRetrieveResponse as AuthRuleRetrieveResponse +from .book_transfer_create_params import BookTransferCreateParams as BookTransferCreateParams +from .card_bulk_order_list_params import CardBulkOrderListParams as CardBulkOrderListParams +from .card_reissued_webhook_event import CardReissuedWebhookEvent as CardReissuedWebhookEvent +from .card_web_provision_response import CardWebProvisionResponse as CardWebProvisionResponse +from .network_program_list_params import NetworkProgramListParams as NetworkProgramListParams +from .tokenization_decline_reason import TokenizationDeclineReason as TokenizationDeclineReason +from .account_activity_list_params import AccountActivityListParams as AccountActivityListParams from .account_holder_create_params import AccountHolderCreateParams as AccountHolderCreateParams from .account_holder_update_params import AccountHolderUpdateParams as AccountHolderUpdateParams +from .book_transfer_reverse_params import BookTransferReverseParams as BookTransferReverseParams +from .card_convert_physical_params import CardConvertPhysicalParams as CardConvertPhysicalParams +from .card_converted_webhook_event import CardConvertedWebhookEvent as CardConvertedWebhookEvent from .digital_card_art_list_params import DigitalCardArtListParams as DigitalCardArtListParams +from .external_payment_list_params import ExternalPaymentListParams as ExternalPaymentListParams from .tokenization_simulate_params import TokenizationSimulateParams as TokenizationSimulateParams -from .aggregate_balance_list_params import AggregateBalanceListParams as AggregateBalanceListParams +from .balance_updated_webhook_event import BalanceUpdatedWebhookEvent as BalanceUpdatedWebhookEvent +from .card_bulk_order_create_params import CardBulkOrderCreateParams as CardBulkOrderCreateParams +from .card_bulk_order_update_params import CardBulkOrderUpdateParams as CardBulkOrderUpdateParams +from .digital_wallet_token_metadata import DigitalWalletTokenMetadata as DigitalWalletTokenMetadata from .dispute_list_evidences_params import DisputeListEvidencesParams as DisputeListEvidencesParams +from .dispute_updated_webhook_event import DisputeUpdatedWebhookEvent as DisputeUpdatedWebhookEvent from .external_bank_account_address import ExternalBankAccountAddress as ExternalBankAccountAddress from .financial_account_list_params import FinancialAccountListParams as FinancialAccountListParams +from .account_activity_list_response import AccountActivityListResponse as AccountActivityListResponse from .account_holder_create_response import AccountHolderCreateResponse as AccountHolderCreateResponse -from .account_holder_resubmit_params import AccountHolderResubmitParams as AccountHolderResubmitParams from .account_holder_update_response import AccountHolderUpdateResponse as AccountHolderUpdateResponse +from .external_payment_cancel_params import ExternalPaymentCancelParams as ExternalPaymentCancelParams +from .external_payment_create_params import ExternalPaymentCreateParams as ExternalPaymentCreateParams +from .external_payment_settle_params import ExternalPaymentSettleParams as ExternalPaymentSettleParams +from .payment_simulate_action_params import PaymentSimulateActionParams as PaymentSimulateActionParams from .payment_simulate_return_params import PaymentSimulateReturnParams as PaymentSimulateReturnParams -from .tokenization_retrieve_response import TokenizationRetrieveResponse as TokenizationRetrieveResponse -from .tokenization_simulate_response import TokenizationSimulateResponse as TokenizationSimulateResponse +from .external_payment_release_params import ExternalPaymentReleaseParams as ExternalPaymentReleaseParams +from .external_payment_reverse_params import ExternalPaymentReverseParams as ExternalPaymentReverseParams from .financial_account_create_params import FinancialAccountCreateParams as FinancialAccountCreateParams from .financial_account_update_params import FinancialAccountUpdateParams as FinancialAccountUpdateParams +from .loan_tape_created_webhook_event import LoanTapeCreatedWebhookEvent as LoanTapeCreatedWebhookEvent +from .loan_tape_updated_webhook_event import LoanTapeUpdatedWebhookEvent as LoanTapeUpdatedWebhookEvent +from .payment_simulate_receipt_params import PaymentSimulateReceiptParams as PaymentSimulateReceiptParams from .payment_simulate_release_params import PaymentSimulateReleaseParams as PaymentSimulateReleaseParams +from .management_operation_list_params import ManagementOperationListParams as ManagementOperationListParams +from .management_operation_transaction import ManagementOperationTransaction as ManagementOperationTransaction +from .payment_simulate_action_response import PaymentSimulateActionResponse as PaymentSimulateActionResponse from .payment_simulate_return_response import PaymentSimulateReturnResponse as PaymentSimulateReturnResponse from .responder_endpoint_create_params import ResponderEndpointCreateParams as ResponderEndpointCreateParams from .responder_endpoint_delete_params import ResponderEndpointDeleteParams as ResponderEndpointDeleteParams +from .statements_created_webhook_event import StatementsCreatedWebhookEvent as StatementsCreatedWebhookEvent from .transaction_simulate_void_params import TransactionSimulateVoidParams as TransactionSimulateVoidParams from .external_bank_account_list_params import ExternalBankAccountListParams as ExternalBankAccountListParams +from .payment_simulate_receipt_response import PaymentSimulateReceiptResponse as PaymentSimulateReceiptResponse from .payment_simulate_release_response import PaymentSimulateReleaseResponse as PaymentSimulateReleaseResponse +from .tokenization_result_webhook_event import TokenizationResultWebhookEvent as TokenizationResultWebhookEvent +from .management_operation_create_params import ManagementOperationCreateParams as ManagementOperationCreateParams from .responder_endpoint_create_response import ResponderEndpointCreateResponse as ResponderEndpointCreateResponse +from .tokenization_updated_webhook_event import TokenizationUpdatedWebhookEvent as TokenizationUpdatedWebhookEvent from .transaction_simulate_return_params import TransactionSimulateReturnParams as TransactionSimulateReturnParams from .transaction_simulate_void_response import TransactionSimulateVoidResponse as TransactionSimulateVoidResponse -from .card_product_credit_detail_response import CardProductCreditDetailResponse as CardProductCreditDetailResponse from .external_bank_account_address_param import ExternalBankAccountAddressParam as ExternalBankAccountAddressParam from .external_bank_account_create_params import ExternalBankAccountCreateParams as ExternalBankAccountCreateParams from .external_bank_account_list_response import ExternalBankAccountListResponse as ExternalBankAccountListResponse from .external_bank_account_update_params import ExternalBankAccountUpdateParams as ExternalBankAccountUpdateParams +from .funding_event_created_webhook_event import FundingEventCreatedWebhookEvent as FundingEventCreatedWebhookEvent +from .management_operation_reverse_params import ManagementOperationReverseParams as ManagementOperationReverseParams +from .network_total_created_webhook_event import NetworkTotalCreatedWebhookEvent as NetworkTotalCreatedWebhookEvent +from .network_total_updated_webhook_event import NetworkTotalUpdatedWebhookEvent as NetworkTotalUpdatedWebhookEvent +from .account_holder_created_webhook_event import AccountHolderCreatedWebhookEvent as AccountHolderCreatedWebhookEvent +from .account_holder_updated_webhook_event import AccountHolderUpdatedWebhookEvent as AccountHolderUpdatedWebhookEvent from .transaction_simulate_clearing_params import TransactionSimulateClearingParams as TransactionSimulateClearingParams from .transaction_simulate_return_response import TransactionSimulateReturnResponse as TransactionSimulateReturnResponse from .account_holder_upload_document_params import ( @@ -116,6 +187,18 @@ from .account_holder_list_documents_response import ( AccountHolderListDocumentsResponse as AccountHolderListDocumentsResponse, ) +from .card_transaction_updated_webhook_event import ( + CardTransactionUpdatedWebhookEvent as CardTransactionUpdatedWebhookEvent, +) +from .external_payment_created_webhook_event import ( + ExternalPaymentCreatedWebhookEvent as ExternalPaymentCreatedWebhookEvent, +) +from .external_payment_updated_webhook_event import ( + ExternalPaymentUpdatedWebhookEvent as ExternalPaymentUpdatedWebhookEvent, +) +from .financial_account_update_status_params import ( + FinancialAccountUpdateStatusParams as FinancialAccountUpdateStatusParams, +) from .responder_endpoint_check_status_params import ( ResponderEndpointCheckStatusParams as ResponderEndpointCheckStatusParams, ) @@ -128,27 +211,132 @@ from .external_bank_account_retrieve_response import ( ExternalBankAccountRetrieveResponse as ExternalBankAccountRetrieveResponse, ) +from .financial_account_created_webhook_event import ( + FinancialAccountCreatedWebhookEvent as FinancialAccountCreatedWebhookEvent, +) +from .financial_account_updated_webhook_event import ( + FinancialAccountUpdatedWebhookEvent as FinancialAccountUpdatedWebhookEvent, +) +from .funding_event_retrieve_details_response import ( + FundingEventRetrieveDetailsResponse as FundingEventRetrieveDetailsResponse, +) +from .settlement_report_updated_webhook_event import ( + SettlementReportUpdatedWebhookEvent as SettlementReportUpdatedWebhookEvent, +) +from .account_holder_verification_webhook_event import ( + AccountHolderVerificationWebhookEvent as AccountHolderVerificationWebhookEvent, +) +from .dispute_transaction_created_webhook_event import ( + DisputeTransactionCreatedWebhookEvent as DisputeTransactionCreatedWebhookEvent, +) +from .dispute_transaction_updated_webhook_event import ( + DisputeTransactionUpdatedWebhookEvent as DisputeTransactionUpdatedWebhookEvent, +) +from .payment_transaction_created_webhook_event import ( + PaymentTransactionCreatedWebhookEvent as PaymentTransactionCreatedWebhookEvent, +) +from .payment_transaction_updated_webhook_event import ( + PaymentTransactionUpdatedWebhookEvent as PaymentTransactionUpdatedWebhookEvent, +) from .transaction_simulate_authorization_params import ( TransactionSimulateAuthorizationParams as TransactionSimulateAuthorizationParams, ) +from .external_bank_account_retry_prenote_params import ( + ExternalBankAccountRetryPrenoteParams as ExternalBankAccountRetryPrenoteParams, +) +from .internal_transaction_created_webhook_event import ( + InternalTransactionCreatedWebhookEvent as InternalTransactionCreatedWebhookEvent, +) +from .internal_transaction_updated_webhook_event import ( + InternalTransactionUpdatedWebhookEvent as InternalTransactionUpdatedWebhookEvent, +) +from .management_operation_created_webhook_event import ( + ManagementOperationCreatedWebhookEvent as ManagementOperationCreatedWebhookEvent, +) +from .management_operation_updated_webhook_event import ( + ManagementOperationUpdatedWebhookEvent as ManagementOperationUpdatedWebhookEvent, +) +from .tokenization_resend_activation_code_params import ( + TokenizationResendActivationCodeParams as TokenizationResendActivationCodeParams, +) +from .external_bank_account_created_webhook_event import ( + ExternalBankAccountCreatedWebhookEvent as ExternalBankAccountCreatedWebhookEvent, +) +from .external_bank_account_updated_webhook_event import ( + ExternalBankAccountUpdatedWebhookEvent as ExternalBankAccountUpdatedWebhookEvent, +) +from .tokenization_approval_request_webhook_event import ( + TokenizationApprovalRequestWebhookEvent as TokenizationApprovalRequestWebhookEvent, +) +from .tokenization_update_digital_card_art_params import ( + TokenizationUpdateDigitalCardArtParams as TokenizationUpdateDigitalCardArtParams, +) from .transaction_simulate_authorization_response import ( TransactionSimulateAuthorizationResponse as TransactionSimulateAuthorizationResponse, ) from .transaction_simulate_return_reversal_params import ( TransactionSimulateReturnReversalParams as TransactionSimulateReturnReversalParams, ) +from .dispute_evidence_upload_failed_webhook_event import ( + DisputeEvidenceUploadFailedWebhookEvent as DisputeEvidenceUploadFailedWebhookEvent, +) +from .account_holder_document_updated_webhook_event import ( + AccountHolderDocumentUpdatedWebhookEvent as AccountHolderDocumentUpdatedWebhookEvent, +) +from .three_ds_authentication_created_webhook_event import ( + ThreeDSAuthenticationCreatedWebhookEvent as ThreeDSAuthenticationCreatedWebhookEvent, +) +from .three_ds_authentication_updated_webhook_event import ( + ThreeDSAuthenticationUpdatedWebhookEvent as ThreeDSAuthenticationUpdatedWebhookEvent, +) from .transaction_simulate_return_reversal_response import ( TransactionSimulateReturnReversalResponse as TransactionSimulateReturnReversalResponse, ) +from .account_activity_retrieve_transaction_response import ( + AccountActivityRetrieveTransactionResponse as AccountActivityRetrieveTransactionResponse, +) +from .tokenization_decisioning_request_webhook_event import ( + TokenizationDecisioningRequestWebhookEvent as TokenizationDecisioningRequestWebhookEvent, +) +from .book_transfer_transaction_created_webhook_event import ( + BookTransferTransactionCreatedWebhookEvent as BookTransferTransactionCreatedWebhookEvent, +) +from .book_transfer_transaction_updated_webhook_event import ( + BookTransferTransactionUpdatedWebhookEvent as BookTransferTransactionUpdatedWebhookEvent, +) +from .three_ds_authentication_challenge_webhook_event import ( + ThreeDSAuthenticationChallengeWebhookEvent as ThreeDSAuthenticationChallengeWebhookEvent, +) from .tokenization_decisioning_rotate_secret_response import ( TokenizationDecisioningRotateSecretResponse as TokenizationDecisioningRotateSecretResponse, ) +from .account_holder_simulate_enrollment_review_params import ( + AccountHolderSimulateEnrollmentReviewParams as AccountHolderSimulateEnrollmentReviewParams, +) +from .auth_rules_backtest_report_created_webhook_event import ( + AuthRulesBacktestReportCreatedWebhookEvent as AuthRulesBacktestReportCreatedWebhookEvent, +) +from .digital_wallet_tokenization_result_webhook_event import ( + DigitalWalletTokenizationResultWebhookEvent as DigitalWalletTokenizationResultWebhookEvent, +) +from .financial_account_register_account_number_params import ( + FinancialAccountRegisterAccountNumberParams as FinancialAccountRegisterAccountNumberParams, +) from .transaction_simulate_authorization_advice_params import ( TransactionSimulateAuthorizationAdviceParams as TransactionSimulateAuthorizationAdviceParams, ) from .transaction_simulate_credit_authorization_params import ( TransactionSimulateCreditAuthorizationParams as TransactionSimulateCreditAuthorizationParams, ) +from .digital_wallet_tokenization_updated_webhook_event import ( + DigitalWalletTokenizationUpdatedWebhookEvent as DigitalWalletTokenizationUpdatedWebhookEvent, +) +from .external_bank_account_retry_micro_deposits_params import ( + ExternalBankAccountRetryMicroDepositsParams as ExternalBankAccountRetryMicroDepositsParams, +) +from .account_holder_simulate_enrollment_review_response import ( + AccountHolderSimulateEnrollmentReviewResponse as AccountHolderSimulateEnrollmentReviewResponse, +) from .transaction_simulate_authorization_advice_response import ( TransactionSimulateAuthorizationAdviceResponse as TransactionSimulateAuthorizationAdviceResponse, ) @@ -158,3 +346,33 @@ from .external_bank_account_retry_micro_deposits_response import ( ExternalBankAccountRetryMicroDepositsResponse as ExternalBankAccountRetryMicroDepositsResponse, ) +from .card_transaction_enhanced_data_created_webhook_event import ( + CardTransactionEnhancedDataCreatedWebhookEvent as CardTransactionEnhancedDataCreatedWebhookEvent, +) +from .card_transaction_enhanced_data_updated_webhook_event import ( + CardTransactionEnhancedDataUpdatedWebhookEvent as CardTransactionEnhancedDataUpdatedWebhookEvent, +) +from .transaction_simulate_credit_authorization_advice_params import ( + TransactionSimulateCreditAuthorizationAdviceParams as TransactionSimulateCreditAuthorizationAdviceParams, +) +from .account_holder_simulate_enrollment_document_review_params import ( + AccountHolderSimulateEnrollmentDocumentReviewParams as AccountHolderSimulateEnrollmentDocumentReviewParams, +) +from .tokenization_two_factor_authentication_code_webhook_event import ( + TokenizationTwoFactorAuthenticationCodeWebhookEvent as TokenizationTwoFactorAuthenticationCodeWebhookEvent, +) +from .transaction_simulate_credit_authorization_advice_response import ( + TransactionSimulateCreditAuthorizationAdviceResponse as TransactionSimulateCreditAuthorizationAdviceResponse, +) +from .digital_wallet_tokenization_approval_request_webhook_event import ( + DigitalWalletTokenizationApprovalRequestWebhookEvent as DigitalWalletTokenizationApprovalRequestWebhookEvent, +) +from .tokenization_two_factor_authentication_code_sent_webhook_event import ( + TokenizationTwoFactorAuthenticationCodeSentWebhookEvent as TokenizationTwoFactorAuthenticationCodeSentWebhookEvent, +) +from .digital_wallet_tokenization_two_factor_authentication_code_webhook_event import ( + DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent as DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent, +) +from .digital_wallet_tokenization_two_factor_authentication_code_sent_webhook_event import ( + DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent as DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent, +) diff --git a/src/lithic/types/account.py b/src/lithic/types/account.py index aa53d79c..7cc0f683 100644 --- a/src/lithic/types/account.py +++ b/src/lithic/types/account.py @@ -1,6 +1,7 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from .._models import BaseModel @@ -9,6 +10,10 @@ class SpendLimit(BaseModel): + """ + Spend limit information for the user containing the daily, monthly, and lifetime spend limit of the account. Any charges to a card owned by this account will be declined once their transaction volume has surpassed the value in the applicable time limit (rolling). A lifetime limit of 0 indicates that the lifetime limit feature is disabled. + """ + daily: int """Daily spend limit (in cents).""" @@ -50,8 +55,9 @@ class VerificationAddress(BaseModel): postal_code: str """Valid postal code. - Only USA ZIP codes are currently supported, entered as a five-digit ZIP or - nine-digit ZIP+4. + Only USA postal codes (ZIP codes) are currently supported, entered as a + five-digit postal code or nine-digit postal code (ZIP+4) using the format + 12345-1234. """ state: str @@ -73,6 +79,9 @@ class Account(BaseModel): this parameter, do not include pagination. """ + created: Optional[datetime] = None + """Timestamp of when the account was created.""" + spend_limit: SpendLimit """ Spend limit information for the user containing the daily, monthly, and lifetime @@ -82,17 +91,75 @@ class Account(BaseModel): feature is disabled. """ - state: Literal["ACTIVE", "PAUSED"] + state: Literal["ACTIVE", "PAUSED", "CLOSED"] """Account state: - `ACTIVE` - Account is able to transact and create new cards. - `PAUSED` - Account will not be able to transact or create new cards. It can be set back to `ACTIVE`. + - `CLOSED` - Account will not be able to transact or create new cards. `CLOSED` + accounts are unable to be transitioned to `ACTIVE` or `PAUSED` states. + Accounts can be manually set to `CLOSED`, or this can be done by Lithic due to + failure to pass KYB/KYC or for risk/compliance reasons. Please contact + [support@lithic.com](mailto:support@lithic.com) if you believe this was done + by mistake. """ account_holder: Optional[AccountHolder] = None auth_rule_tokens: Optional[List[str]] = None - """List of identifiers for the Auth Rule(s) that are applied on the account.""" + """ + List of identifiers for the Auth Rule(s) that are applied on the account. This + field is deprecated and will no longer be populated in the `account_holder` + object. The key will be removed from the schema in a future release. Use the + `/auth_rules` endpoints to fetch Auth Rule information instead. + """ + + cardholder_currency: Optional[str] = None + """3-character alphabetic ISO 4217 code for the currency of the cardholder.""" + + comment: Optional[str] = None + """Additional context or information related to the account.""" + + substatus: Optional[ + Literal[ + "FRAUD_IDENTIFIED", + "SUSPICIOUS_ACTIVITY", + "RISK_VIOLATION", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "INTERNAL_REVIEW", + "OTHER", + ] + ] = None + """Account state substatus values: + + - `FRAUD_IDENTIFIED` - The account has been recognized as being created or used + with stolen or fabricated identity information, encompassing both true + identity theft and synthetic identities. + - `SUSPICIOUS_ACTIVITY` - The account has exhibited suspicious behavior, such as + unauthorized access or fraudulent transactions, necessitating further + investigation. + - `RISK_VIOLATION` - The account has been involved in deliberate misuse by the + legitimate account holder. Examples include disputing valid transactions + without cause, falsely claiming non-receipt of goods, or engaging in + intentional bust-out schemes to exploit account services. + - `END_USER_REQUEST` - The account holder has voluntarily requested the closure + of the account for personal reasons. This encompasses situations such as + bankruptcy, other financial considerations, or the account holder's death. + - `ISSUER_REQUEST` - The issuer has initiated the closure of the account due to + business strategy, risk management, inactivity, product changes, regulatory + concerns, or violations of terms and conditions. + - `NOT_ACTIVE` - The account has not had any transactions or payment activity + within a specified period. This status applies to accounts that are paused or + closed due to inactivity. + - `INTERNAL_REVIEW` - The account is temporarily paused pending further internal + review. In future implementations, this status may prevent clients from + activating the account via APIs until the review is completed. + - `OTHER` - The reason for the account's current status does not fall into any + of the above categories. A comment should be provided to specify the + particular reason. + """ verification_address: Optional[VerificationAddress] = None diff --git a/src/lithic/types/account_activity_list_params.py b/src/lithic/types/account_activity_list_params.py new file mode 100644 index 00000000..7b28515b --- /dev/null +++ b/src/lithic/types/account_activity_list_params.py @@ -0,0 +1,80 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AccountActivityListParams"] + + +class AccountActivityListParams(TypedDict, total=False): + account_token: str + """Filter by account token""" + + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + business_account_token: str + """Filter by business account token""" + + category: Literal[ + "ACH", + "BALANCE_OR_FUNDING", + "FEE", + "REWARD", + "ADJUSTMENT", + "DERECOGNITION", + "DISPUTE", + "CARD", + "EXTERNAL_ACH", + "EXTERNAL_CHECK", + "EXTERNAL_FEDNOW", + "EXTERNAL_RTP", + "EXTERNAL_TRANSFER", + "EXTERNAL_WIRE", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_FEE", + "MANAGEMENT_REWARD", + "MANAGEMENT_DISBURSEMENT", + "PROGRAM_FUNDING", + ] + """Filter by transaction category""" + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + financial_account_token: str + """Filter by financial account token""" + + page_size: int + """Page size (for pagination).""" + + result: Literal["APPROVED", "DECLINED"] + """Filter by transaction result""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ + + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "REVERSED", "SETTLED", "VOIDED"] + """Filter by transaction status""" diff --git a/src/lithic/types/account_activity_list_response.py b/src/lithic/types/account_activity_list_response.py new file mode 100644 index 00000000..33992124 --- /dev/null +++ b/src/lithic/types/account_activity_list_response.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .payment import Payment +from .._models import BaseModel +from .transaction import Transaction +from .external_payment import ExternalPayment +from .book_transfer_response import BookTransferResponse +from .shared.financial_event import FinancialEvent +from .management_operation_transaction import ManagementOperationTransaction + +__all__ = ["AccountActivityListResponse", "FinancialTransaction", "CardTransaction"] + + +class FinancialTransaction(BaseModel): + """Financial transaction with inheritance from unified base transaction""" + + token: str + """Unique identifier for the transaction""" + + category: Literal[ + "ACH", + "BALANCE_OR_FUNDING", + "FEE", + "REWARD", + "ADJUSTMENT", + "DERECOGNITION", + "DISPUTE", + "CARD", + "EXTERNAL_ACH", + "EXTERNAL_CHECK", + "EXTERNAL_FEDNOW", + "EXTERNAL_RTP", + "EXTERNAL_TRANSFER", + "EXTERNAL_WIRE", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_FEE", + "MANAGEMENT_REWARD", + "MANAGEMENT_DISBURSEMENT", + "PROGRAM_FUNDING", + ] + """Transaction category""" + + created: datetime + """ISO 8601 timestamp of when the transaction was created""" + + currency: str + """Currency of the transaction, represented in ISO 4217 format""" + + descriptor: str + """Transaction descriptor""" + + events: List[FinancialEvent] + """List of transaction events""" + + family: Literal["INTERNAL"] + """INTERNAL - Financial Transaction""" + + financial_account_token: str + """Financial account token associated with the transaction""" + + pending_amount: int + """Pending amount in cents""" + + result: Literal["APPROVED", "DECLINED"] + """Transaction result""" + + settled_amount: int + """Settled amount in cents""" + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """The status of the transaction""" + + updated: datetime + """ISO 8601 timestamp of when the transaction was last updated""" + + +class CardTransaction(Transaction): + """Card transaction with ledger base properties""" + + token: str # type: ignore + """Unique identifier for the transaction""" + + created: datetime # type: ignore + """ISO 8601 timestamp of when the transaction was created""" + + family: Literal["CARD"] + """CARD - Card Transaction""" + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] # type: ignore + """The status of the transaction""" + + updated: datetime # type: ignore + """ISO 8601 timestamp of when the transaction was last updated""" + + +AccountActivityListResponse: TypeAlias = Annotated[ + Union[ + FinancialTransaction, + BookTransferResponse, + CardTransaction, + Payment, + ExternalPayment, + ManagementOperationTransaction, + ], + PropertyInfo(discriminator="family"), +] diff --git a/src/lithic/types/account_activity_retrieve_transaction_response.py b/src/lithic/types/account_activity_retrieve_transaction_response.py new file mode 100644 index 00000000..202fd3eb --- /dev/null +++ b/src/lithic/types/account_activity_retrieve_transaction_response.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .payment import Payment +from .._models import BaseModel +from .transaction import Transaction +from .external_payment import ExternalPayment +from .book_transfer_response import BookTransferResponse +from .shared.financial_event import FinancialEvent +from .management_operation_transaction import ManagementOperationTransaction + +__all__ = ["AccountActivityRetrieveTransactionResponse", "FinancialTransaction", "CardTransaction"] + + +class FinancialTransaction(BaseModel): + """Financial transaction with inheritance from unified base transaction""" + + token: str + """Unique identifier for the transaction""" + + category: Literal[ + "ACH", + "BALANCE_OR_FUNDING", + "FEE", + "REWARD", + "ADJUSTMENT", + "DERECOGNITION", + "DISPUTE", + "CARD", + "EXTERNAL_ACH", + "EXTERNAL_CHECK", + "EXTERNAL_FEDNOW", + "EXTERNAL_RTP", + "EXTERNAL_TRANSFER", + "EXTERNAL_WIRE", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_FEE", + "MANAGEMENT_REWARD", + "MANAGEMENT_DISBURSEMENT", + "PROGRAM_FUNDING", + ] + """Transaction category""" + + created: datetime + """ISO 8601 timestamp of when the transaction was created""" + + currency: str + """Currency of the transaction, represented in ISO 4217 format""" + + descriptor: str + """Transaction descriptor""" + + events: List[FinancialEvent] + """List of transaction events""" + + family: Literal["INTERNAL"] + """INTERNAL - Financial Transaction""" + + financial_account_token: str + """Financial account token associated with the transaction""" + + pending_amount: int + """Pending amount in cents""" + + result: Literal["APPROVED", "DECLINED"] + """Transaction result""" + + settled_amount: int + """Settled amount in cents""" + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """The status of the transaction""" + + updated: datetime + """ISO 8601 timestamp of when the transaction was last updated""" + + +class CardTransaction(Transaction): + """Card transaction with ledger base properties""" + + token: str # type: ignore + """Unique identifier for the transaction""" + + created: datetime # type: ignore + """ISO 8601 timestamp of when the transaction was created""" + + family: Literal["CARD"] + """CARD - Card Transaction""" + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] # type: ignore + """The status of the transaction""" + + updated: datetime # type: ignore + """ISO 8601 timestamp of when the transaction was last updated""" + + +AccountActivityRetrieveTransactionResponse: TypeAlias = Annotated[ + Union[ + FinancialTransaction, + BookTransferResponse, + CardTransaction, + Payment, + ExternalPayment, + ManagementOperationTransaction, + ], + PropertyInfo(discriminator="family"), +] diff --git a/src/lithic/types/account_holder.py b/src/lithic/types/account_holder.py index 685da4ec..dbe28588 100644 --- a/src/lithic/types/account_holder.py +++ b/src/lithic/types/account_holder.py @@ -1,11 +1,12 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime from typing_extensions import Literal -from .shared import Address from .._models import BaseModel +from .shared.address import Address +from .required_document import RequiredDocument __all__ = [ "AccountHolder", @@ -25,6 +26,15 @@ class BeneficialOwnerEntity(BaseModel): acceptable; APO/FPO are acceptable. """ + dba_business_name: str + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + entity_token: str + """Globally unique identifier for the entity.""" + government_id: str """Government-issued identification number. @@ -41,43 +51,59 @@ class BeneficialOwnerEntity(BaseModel): format. """ - dba_business_name: Optional[str] = None - """ - Any name that the business operates under that is not its legal business name - (if applicable). - """ - parent_company: Optional[str] = None """Parent company name (if applicable).""" class BeneficialOwnerIndividual(BaseModel): - address: Optional[Address] = None + """Information about an individual associated with an account holder. + + A subset of the information provided via KYC. For example, we do not return the government id. + """ + + address: Address """Individual's current address""" - dob: Optional[str] = None + dob: str """Individual's date of birth, as an RFC 3339 date.""" - email: Optional[str] = None + email: str """Individual's email address.""" - first_name: Optional[str] = None + entity_token: str + """Globally unique identifier for the entity.""" + + first_name: str """Individual's first name, as it appears on government-issued identity documents.""" - last_name: Optional[str] = None + last_name: str """Individual's last name, as it appears on government-issued identity documents.""" - phone_number: Optional[str] = None + phone_number: str """Individual's phone number, entered in E.164 format.""" class BusinessEntity(BaseModel): + """Only present when user_type == "BUSINESS". + + Information about the business for which the account is being opened and KYB is being run. + """ + address: Address """ Business's physical address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. """ + dba_business_name: str + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + entity_token: str + """Globally unique identifier for the entity.""" + government_id: str """Government-issued identification number. @@ -94,64 +120,80 @@ class BusinessEntity(BaseModel): format. """ - dba_business_name: Optional[str] = None - """ - Any name that the business operates under that is not its legal business name - (if applicable). - """ - parent_company: Optional[str] = None """Parent company name (if applicable).""" class ControlPerson(BaseModel): - address: Optional[Address] = None + """ + Only present when user_type == "BUSINESS". + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, + Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access + to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. + """ + + address: Address """Individual's current address""" - dob: Optional[str] = None + dob: str """Individual's date of birth, as an RFC 3339 date.""" - email: Optional[str] = None + email: str """Individual's email address.""" - first_name: Optional[str] = None + entity_token: str + """Globally unique identifier for the entity.""" + + first_name: str """Individual's first name, as it appears on government-issued identity documents.""" - last_name: Optional[str] = None + last_name: str """Individual's last name, as it appears on government-issued identity documents.""" - phone_number: Optional[str] = None + phone_number: str """Individual's phone number, entered in E.164 format.""" class Individual(BaseModel): - address: Optional[Address] = None + """Only present when user_type == "INDIVIDUAL". + + Information about the individual for which the account is being opened and KYC is being run. + """ + + address: Address """Individual's current address""" - dob: Optional[str] = None + dob: str """Individual's date of birth, as an RFC 3339 date.""" - email: Optional[str] = None + email: str """Individual's email address.""" - first_name: Optional[str] = None + entity_token: str + """Globally unique identifier for the entity.""" + + first_name: str """Individual's first name, as it appears on government-issued identity documents.""" - last_name: Optional[str] = None + last_name: str """Individual's last name, as it appears on government-issued identity documents.""" - phone_number: Optional[str] = None + phone_number: str """Individual's phone number, entered in E.164 format.""" class VerificationApplication(BaseModel): + """Information about the most recent identity verification attempt""" + created: Optional[datetime] = None """Timestamp of when the application was created.""" - status: Optional[Literal["ACCEPTED", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"]] = None - """ - KYC and KYB evaluation states. Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` - are only applicable for the `ADVANCED` workflow. + status: Optional[Literal["ACCEPTED", "PENDING_REVIEW", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"]] = None + """KYC and KYB evaluation states. + + Note: + + - `PENDING_REVIEW` is only applicable for the `KYB_BASIC` workflow. """ status_reasons: Optional[ @@ -181,19 +223,23 @@ class AccountHolder(BaseModel): token: str """Globally unique identifier for the account holder.""" + created: datetime + """Timestamp of when the account holder was created.""" + account_token: Optional[str] = None """Globally unique identifier for the account.""" beneficial_owner_entities: Optional[List[BeneficialOwnerEntity]] = None - """Only present when user_type == "BUSINESS". - - List of all entities with >25% ownership in the company. - """ + """Deprecated.""" beneficial_owner_individuals: Optional[List[BeneficialOwnerIndividual]] = None - """Only present when user_type == "BUSINESS". - - List of all individuals with >25% ownership in the company. + """ + Only present when user_type == "BUSINESS". You must submit a list of all direct + and indirect individuals with 25% or more ownership in the company. A maximum of + 4 beneficial owners can be submitted. If no individual owns 25% of the company + you do not need to send beneficial owner information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. """ business_account_token: Optional[str] = None @@ -220,15 +266,12 @@ class AccountHolder(BaseModel): In some cases, this individual could also be a beneficial owner listed above. """ - created: Optional[datetime] = None - """Timestamp of when the account holder was created.""" - email: Optional[str] = None - """ - < Deprecated. Use control_person.email when user_type == "BUSINESS". Use - individual.phone_number when user_type == "INDIVIDUAL". + """(Deprecated. - > Primary email of Account Holder. + Use control_person.email when user_type == "BUSINESS". Use + individual.phone_number when user_type == "INDIVIDUAL".) Primary email of + Account Holder. """ exemption_type: Optional[Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"]] = None @@ -254,19 +297,27 @@ class AccountHolder(BaseModel): """ phone_number: Optional[str] = None + """(Deprecated. + + Use control_person.phone_number when user_type == "BUSINESS". Use + individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of + Account Holder, entered in E.164 format. """ - < Deprecated. Use control_person.phone_number when user_type == "BUSINESS". Use - individual.phone_number when user_type == "INDIVIDUAL". - > Primary phone of Account Holder, entered in E.164 format. + required_documents: Optional[List[RequiredDocument]] = None + """Only present for "KYB_BASIC" workflow. + + A list of documents required for the account holder to be approved. """ - status: Optional[Literal["ACCEPTED", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"]] = None - """ KYC and KYB evaluation states. - Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the - `ADVANCED` workflow. + - `PENDING_REVIEW` is only applicable for the `KYB_BASIC` workflow. """ status_reasons: Optional[ @@ -286,9 +337,9 @@ class AccountHolder(BaseModel): ] ] ] = None - """ Reason for the evaluation status. + Reason for the evaluation status. """ user_type: Optional[Literal["BUSINESS", "INDIVIDUAL"]] = None @@ -296,8 +347,8 @@ class AccountHolder(BaseModel): If the type is "INDIVIDUAL", the "individual" attribute will be present. If the type is "BUSINESS" then the "business_entity", "control_person", - "beneficial_owner_individuals", "beneficial_owner_entities", - "nature_of_business", and "website_url" attributes will be present. + "beneficial_owner_individuals", "nature_of_business", and "website_url" + attributes will be present. """ verification_application: Optional[VerificationApplication] = None diff --git a/src/lithic/types/account_holder_create_params.py b/src/lithic/types/account_holder_create_params.py index eefc7324..c90cfad2 100644 --- a/src/lithic/types/account_holder_create_params.py +++ b/src/lithic/types/account_holder_create_params.py @@ -1,19 +1,24 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing import List, Union, Iterable -from typing_extensions import Literal, Required, TypedDict +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict -from ..types import shared_params +from .._types import SequenceNotStr +from .shared_params.address import Address __all__ = [ "AccountHolderCreateParams", "KYB", - "KYBBeneficialOwnerEntity", "KYBBeneficialOwnerIndividual", "KYBBusinessEntity", "KYBControlPerson", + "KYBBeneficialOwnerEntity", + "KYBDelegated", + "KYBDelegatedBusinessEntity", + "KYBDelegatedBeneficialOwnerIndividual", + "KYBDelegatedControlPerson", "KYC", "KYCIndividual", "KYCExempt", @@ -21,26 +26,14 @@ class KYB(TypedDict, total=False): - beneficial_owner_entities: Required[Iterable[KYBBeneficialOwnerEntity]] - """List of all entities with >25% ownership in the company. - - If no entity or individual owns >25% of the company, and the largest shareholder - is an entity, please identify them in this field. See - [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) - (Section I) for more background. If no business owner is an entity, pass in an - empty list. However, either this parameter or `beneficial_owner_individuals` - must be populated. on entities that should be included. - """ - beneficial_owner_individuals: Required[Iterable[KYBBeneficialOwnerIndividual]] - """List of all individuals with >25% ownership in the company. - - If no entity or individual owns >25% of the company, and the largest shareholder - is an individual, please identify them in this field. See + """ + You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) - (Section I) for more background on individuals that should be included. If no - individual is an entity, pass in an empty list. However, either this parameter - or `beneficial_owner_entities` must be populated. + (Section I) for more background on individuals that should be included. """ business_entity: Required[KYBBusinessEntity] @@ -77,6 +70,9 @@ class KYB(TypedDict, total=False): workflow: Required[Literal["KYB_BASIC", "KYB_BYO"]] """Specifies the type of KYB workflow to run.""" + beneficial_owner_entities: Iterable[KYBBeneficialOwnerEntity] + """Deprecated.""" + external_id: str """ A user provided id that can be used to link an account holder with an external @@ -85,7 +81,7 @@ class KYB(TypedDict, total=False): kyb_passed_timestamp: str """ - An RFC 3339 timestamp indicating when precomputed KYC was completed on the + An RFC 3339 timestamp indicating when precomputed KYB was completed on the business with a pass result. This field is required only if workflow type is `KYB_BYO`. @@ -95,8 +91,48 @@ class KYB(TypedDict, total=False): """Company website URL.""" -class KYBBeneficialOwnerEntity(TypedDict, total=False): - address: Required[shared_params.Address] +class KYBBeneficialOwnerIndividual(TypedDict, total=False): + """Individuals associated with a KYB application. Phone number is optional.""" + + address: Required[Address] + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Required[str] + """Individual's date of birth, as an RFC 3339 date.""" + + email: Required[str] + """ + Individual's email address. If utilizing Lithic for chargeback processing, this + customer email address may be used to communicate dispute status and resolution. + """ + + first_name: Required[str] + """Individual's first name, as it appears on government-issued identity documents.""" + + government_id: Required[str] + """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: Required[str] + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: str + """Individual's phone number, entered in E.164 format.""" + + +class KYBBusinessEntity(TypedDict, total=False): + """ + Information for business for which the account is being opened and KYB is being run. + """ + + address: Required[Address] """ Business's physical address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. @@ -112,7 +148,7 @@ class KYBBeneficialOwnerEntity(TypedDict, total=False): legal_business_name: Required[str] """Legal (formal) business name.""" - phone_numbers: Required[List[str]] + phone_numbers: Required[SequenceNotStr[str]] """ One or more of the business's phone number(s), entered as a list in E.164 format. @@ -128,8 +164,15 @@ class KYBBeneficialOwnerEntity(TypedDict, total=False): """Parent company name (if applicable).""" -class KYBBeneficialOwnerIndividual(TypedDict, total=False): - address: Required[shared_params.Address] +class KYBControlPerson(TypedDict, total=False): + """ + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, + Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access + to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. + See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) (Section II) for more background. + """ + + address: Required[Address] """ Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. @@ -162,8 +205,8 @@ class KYBBeneficialOwnerIndividual(TypedDict, total=False): """Individual's phone number, entered in E.164 format.""" -class KYBBusinessEntity(TypedDict, total=False): - address: Required[shared_params.Address] +class KYBBeneficialOwnerEntity(TypedDict, total=False): + address: Required[Address] """ Business's physical address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. @@ -179,7 +222,7 @@ class KYBBusinessEntity(TypedDict, total=False): legal_business_name: Required[str] """Legal (formal) business name.""" - phone_numbers: Required[List[str]] + phone_numbers: Required[SequenceNotStr[str]] """ One or more of the business's phone number(s), entered as a list in E.164 format. @@ -195,8 +238,138 @@ class KYBBusinessEntity(TypedDict, total=False): """Parent company name (if applicable).""" -class KYBControlPerson(TypedDict, total=False): - address: Required[shared_params.Address] +class KYBDelegated(TypedDict, total=False): + business_entity: Required[KYBDelegatedBusinessEntity] + """Information for business for which the account is being opened.""" + + beneficial_owner_individuals: Iterable[KYBDelegatedBeneficialOwnerIndividual] + """ + You can submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + """ + + control_person: KYBDelegatedControlPerson + """ + An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + """ + + external_id: str + """ + A user provided id that can be used to link an account holder with an external + system + """ + + nature_of_business: str + """ + Short description of the company's line of business (i.e., what does the company + do?). + """ + + tos_timestamp: str + """ + An RFC 3339 timestamp indicating when the account holder accepted the applicable + legal agreements (e.g., cardholder terms) as agreed upon during API customer's + implementation with Lithic. + """ + + website_url: str + """Company website URL.""" + + workflow: Literal["KYB_DELEGATED"] + """Specifies the type of KYB workflow to run.""" + + +class KYBDelegatedBusinessEntity(TypedDict, total=False): + """Information for business for which the account is being opened.""" + + address: Required[Address] + """ + Business's physical address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + + legal_business_name: Required[str] + """Legal (formal) business name.""" + + dba_business_name: str + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + government_id: str + """Government-issued identification number. + + US Federal Employer Identification Numbers (EIN) are currently supported, + entered as full nine-digits, with or without hyphens. + """ + + parent_company: str + """Parent company name (if applicable).""" + + phone_numbers: SequenceNotStr[str] + """ + One or more of the business's phone number(s), entered as a list in E.164 + format. + """ + + +class KYBDelegatedBeneficialOwnerIndividual(TypedDict, total=False): + """Individuals associated with a KYB application. Phone number is optional.""" + + address: Required[Address] + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Required[str] + """Individual's date of birth, as an RFC 3339 date.""" + + email: Required[str] + """ + Individual's email address. If utilizing Lithic for chargeback processing, this + customer email address may be used to communicate dispute status and resolution. + """ + + first_name: Required[str] + """Individual's first name, as it appears on government-issued identity documents.""" + + government_id: Required[str] + """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: Required[str] + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: str + """Individual's phone number, entered in E.164 format.""" + + +class KYBDelegatedControlPerson(TypedDict, total=False): + """ + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, + Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access + to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. + See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) (Section II) for more background. + """ + + address: Required[Address] """ Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. @@ -243,7 +416,7 @@ class KYC(TypedDict, total=False): implementation with Lithic. """ - workflow: Required[Literal["KYC_ADVANCED", "KYC_BASIC", "KYC_BYO"]] + workflow: Required[Literal["KYC_BASIC", "KYC_BYO"]] """Specifies the type of KYC workflow to run.""" external_id: str @@ -262,7 +435,11 @@ class KYC(TypedDict, total=False): class KYCIndividual(TypedDict, total=False): - address: Required[shared_params.Address] + """ + Information on individual for whom the account is being opened and KYC is being run. + """ + + address: Required[Address] """ Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. @@ -296,6 +473,12 @@ class KYCIndividual(TypedDict, total=False): class KYCExempt(TypedDict, total=False): + address: Required[Address] + """ + KYC Exempt user's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + email: Required[str] """The KYC Exempt user's email""" @@ -309,17 +492,11 @@ class KYCExempt(TypedDict, total=False): """The KYC Exempt user's last name""" phone_number: Required[str] - """The KYC Exempt user's phone number""" + """The KYC Exempt user's phone number, entered in E.164 format.""" workflow: Required[Literal["KYC_EXEMPT"]] """Specifies the workflow type. This must be 'KYC_EXEMPT'""" - address: shared_params.Address - """ - KYC Exempt user's current address - PO boxes, UPS drops, and FedEx drops are not - acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. - """ - business_account_token: str """ Only applicable for customers using the KYC-Exempt workflow to enroll authorized @@ -334,4 +511,4 @@ class KYCExempt(TypedDict, total=False): """ -AccountHolderCreateParams = Union[KYB, KYC, KYCExempt] +AccountHolderCreateParams: TypeAlias = Union[KYB, KYBDelegated, KYC, KYCExempt] diff --git a/src/lithic/types/account_holder_create_response.py b/src/lithic/types/account_holder_create_response.py index f98c7caa..e3245796 100644 --- a/src/lithic/types/account_holder_create_response.py +++ b/src/lithic/types/account_holder_create_response.py @@ -1,10 +1,11 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime from typing_extensions import Literal from .._models import BaseModel +from .required_document import RequiredDocument __all__ = ["AccountHolderCreateResponse"] @@ -16,10 +17,12 @@ class AccountHolderCreateResponse(BaseModel): account_token: str """Globally unique identifier for the account.""" - status: Literal["ACCEPTED", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"] - """ - KYC and KYB evaluation states. Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` - are only applicable for the `ADVANCED` workflow. + status: Literal["ACCEPTED", "PENDING_REVIEW", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"] + """KYC and KYB evaluation states. + + Note: + + - `PENDING_REVIEW` is only applicable for the `KYB_BASIC` workflow. """ status_reasons: List[ @@ -35,6 +38,19 @@ class AccountHolderCreateResponse(BaseModel): "OTHER_VERIFICATION_FAILURE", "RISK_THRESHOLD_FAILURE", "WATCHLIST_ALERT_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", ] ] """Reason for the evaluation status.""" @@ -47,3 +63,9 @@ class AccountHolderCreateResponse(BaseModel): Customer-provided token that indicates a relationship with an object outside of the Lithic ecosystem. """ + + required_documents: Optional[List[RequiredDocument]] = None + """Only present for "KYB_BASIC" workflow. + + A list of documents required for the account holder to be approved. + """ diff --git a/src/lithic/types/account_holder_created_webhook_event.py b/src/lithic/types/account_holder_created_webhook_event.py new file mode 100644 index 00000000..b7cfb961 --- /dev/null +++ b/src/lithic/types/account_holder_created_webhook_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .required_document import RequiredDocument + +__all__ = ["AccountHolderCreatedWebhookEvent"] + + +class AccountHolderCreatedWebhookEvent(BaseModel): + event_type: Literal["account_holder.created"] + """The type of event that occurred.""" + + token: Optional[str] = None + """The token of the account_holder that was created.""" + + account_token: Optional[str] = None + """The token of the account that was created.""" + + created: Optional[datetime] = None + """When the account_holder was created""" + + required_documents: Optional[List[RequiredDocument]] = None + + status: Optional[Literal["ACCEPTED", "PENDING_REVIEW"]] = None + """The status of the account_holder that was created.""" + + status_reason: Optional[List[str]] = None diff --git a/src/lithic/types/account_holder_document.py b/src/lithic/types/account_holder_document.py deleted file mode 100644 index d703e7ea..00000000 --- a/src/lithic/types/account_holder_document.py +++ /dev/null @@ -1,52 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import List, Optional -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["AccountHolderDocument", "RequiredDocumentUpload"] - - -class RequiredDocumentUpload(BaseModel): - image_type: Optional[Literal["back", "front"]] = None - """Type of image to upload.""" - - status: Optional[Literal["COMPLETED", "FAILED", "PENDING", "UPLOADED"]] = None - """Status of document image upload.""" - - status_reasons: Optional[ - List[ - Literal[ - "BACK_IMAGE_BLURRY", - "FILE_SIZE_TOO_LARGE", - "FRONT_IMAGE_BLURRY", - "FRONT_IMAGE_GLARE", - "INVALID_FILE_TYPE", - "UNKNOWN_ERROR", - ] - ] - ] = None - - upload_url: Optional[str] = None - """URL to upload document image to. - - Note that the upload URLs expire after 7 days. If an upload URL expires, you can - refresh the URLs by retrieving the document upload from - `GET /account_holders/{account_holder_token}/documents`. - """ - - -class AccountHolderDocument(BaseModel): - token: Optional[str] = None - """Globally unique identifier for the document.""" - - account_holder_token: Optional[str] = None - """Globally unique identifier for the account holder.""" - - document_type: Optional[ - Literal["commercial_license", "drivers_license", "passport", "passport_card", "visa"] - ] = None - """Type of documentation to be submitted for verification.""" - - required_document_uploads: Optional[List[RequiredDocumentUpload]] = None diff --git a/src/lithic/types/account_holder_document_updated_webhook_event.py b/src/lithic/types/account_holder_document_updated_webhook_event.py new file mode 100644 index 00000000..33324314 --- /dev/null +++ b/src/lithic/types/account_holder_document_updated_webhook_event.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AccountHolderDocumentUpdatedWebhookEvent", "RequiredDocumentUpload"] + + +class RequiredDocumentUpload(BaseModel): + """A document upload that belongs to the overall account holder document""" + + token: Optional[str] = None + """The token of the document upload""" + + accepted_entity_status_reasons: Optional[List[str]] = None + + created: Optional[datetime] = None + """When the document upload was created""" + + image_type: Optional[Literal["FRONT", "BACK"]] = None + """The type of image that was uploaded""" + + rejected_entity_status_reasons: Optional[List[str]] = None + + status: Optional[Literal["ACCEPTED", "REJECTED", "PENDING_UPLOAD", "UPLOADED", "PARTIAL_APPROVAL"]] = None + """The status of the document upload""" + + status_reasons: Optional[List[str]] = None + + updated: Optional[datetime] = None + """When the document upload was last updated""" + + +class AccountHolderDocumentUpdatedWebhookEvent(BaseModel): + event_type: Literal["account_holder_document.updated"] + """The type of event that occurred.""" + + token: Optional[str] = None + """The token of the account holder document""" + + account_holder_token: Optional[str] = None + """The token of the account_holder that the document belongs to""" + + created: Optional[datetime] = None + """When the account_holder was created""" + + document_type: Optional[ + Literal[ + "DRIVERS_LICENSE", + "PASSPORT", + "PASSPORT_CARD", + "EIN_LETTER", + "TAX_RETURN", + "OPERATING_AGREEMENT", + "CERTIFICATE_OF_FORMATION", + "CERTIFICATE_OF_GOOD_STANDING", + "ARTICLES_OF_INCORPORATION", + "ARTICLES_OF_ORGANIZATION", + "BYLAWS", + "GOVERNMENT_BUSINESS_LICENSE", + "PARTNERSHIP_AGREEMENT", + "SS4_FORM", + "BANK_STATEMENT", + "UTILITY_BILL_STATEMENT", + "SSN_CARD", + "ITIN_LETTER", + "FINCEN_BOI_REPORT", + ] + ] = None + """Type of documentation to be submitted for verification of an account holder""" + + entity_token: Optional[str] = None + """The token of the entity that the document belongs to""" + + required_document_uploads: Optional[List[RequiredDocumentUpload]] = None diff --git a/src/lithic/types/account_holder_list_documents_response.py b/src/lithic/types/account_holder_list_documents_response.py index 538f46b0..e94d7d58 100644 --- a/src/lithic/types/account_holder_list_documents_response.py +++ b/src/lithic/types/account_holder_list_documents_response.py @@ -1,12 +1,12 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from .._models import BaseModel -from .account_holder_document import AccountHolderDocument +from .shared.document import Document __all__ = ["AccountHolderListDocumentsResponse"] class AccountHolderListDocumentsResponse(BaseModel): - data: Optional[List[AccountHolderDocument]] = None + data: Optional[List[Document]] = None diff --git a/src/lithic/types/account_holder_list_params.py b/src/lithic/types/account_holder_list_params.py index ebd5b9e6..225eb5d6 100644 --- a/src/lithic/types/account_holder_list_params.py +++ b/src/lithic/types/account_holder_list_params.py @@ -1,13 +1,35 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing_extensions import TypedDict +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo __all__ = ["AccountHolderListParams"] class AccountHolderListParams(TypedDict, total=False): + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + email: str + """Email address of the account holder. + + The query must be an exact match, case insensitive. + """ + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + ending_before: str """A cursor representing an item's token before which a page of results should end. @@ -17,9 +39,30 @@ class AccountHolderListParams(TypedDict, total=False): external_id: str """If applicable, represents the external_id associated with the account_holder.""" + first_name: str + """(Individual Account Holders only) The first name of the account holder. + + The query is case insensitive and supports partial matches. + """ + + last_name: str + """(Individual Account Holders only) The last name of the account holder. + + The query is case insensitive and supports partial matches. + """ + + legal_business_name: str + """(Business Account Holders only) The legal business name of the account holder. + + The query is case insensitive and supports partial matches. + """ + limit: int """The number of account_holders to limit the response to.""" + phone_number: str + """Phone number of the account holder. The query must be an exact match.""" + starting_after: str """A cursor representing an item's token after which a page of results should begin. diff --git a/src/lithic/types/account_holder_simulate_enrollment_document_review_params.py b/src/lithic/types/account_holder_simulate_enrollment_document_review_params.py new file mode 100644 index 00000000..8ad2d225 --- /dev/null +++ b/src/lithic/types/account_holder_simulate_enrollment_document_review_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["AccountHolderSimulateEnrollmentDocumentReviewParams"] + + +class AccountHolderSimulateEnrollmentDocumentReviewParams(TypedDict, total=False): + document_upload_token: Required[str] + """The account holder document upload which to perform the simulation upon.""" + + status: Required[Literal["UPLOADED", "ACCEPTED", "REJECTED", "PARTIAL_APPROVAL"]] + """An account holder document's upload status for use within the simulation.""" + + accepted_entity_status_reasons: SequenceNotStr[str] + """A list of status reasons associated with a KYB account holder in PENDING_REVIEW""" + + status_reason: Literal[ + "DOCUMENT_MISSING_REQUIRED_DATA", + "DOCUMENT_UPLOAD_TOO_BLURRY", + "FILE_SIZE_TOO_LARGE", + "INVALID_DOCUMENT_TYPE", + "INVALID_DOCUMENT_UPLOAD", + "INVALID_ENTITY", + "DOCUMENT_EXPIRED", + "DOCUMENT_ISSUED_GREATER_THAN_30_DAYS", + "DOCUMENT_TYPE_NOT_SUPPORTED", + "UNKNOWN_FAILURE_REASON", + "UNKNOWN_ERROR", + ] + """Status reason that will be associated with the simulated account holder status. + + Only required for a `REJECTED` status or `PARTIAL_APPROVAL` status. + """ diff --git a/src/lithic/types/account_holder_simulate_enrollment_review_params.py b/src/lithic/types/account_holder_simulate_enrollment_review_params.py new file mode 100644 index 00000000..d098b2e0 --- /dev/null +++ b/src/lithic/types/account_holder_simulate_enrollment_review_params.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +__all__ = ["AccountHolderSimulateEnrollmentReviewParams"] + + +class AccountHolderSimulateEnrollmentReviewParams(TypedDict, total=False): + account_holder_token: str + """The account holder which to perform the simulation upon.""" + + status: Literal["ACCEPTED", "REJECTED"] + """An account holder's status for use within the simulation.""" + + status_reasons: List[ + Literal[ + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_DOB_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_BLOCKLIST_ALERT_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_ID_VERIFICATION_FAILURE", + "BENEFICIAL_OWNER_INDIVIDUAL_NAME_VERIFICATION_FAILURE", + ] + ] + """Status reason that will be associated with the simulated account holder status. + + Only required for a `REJECTED` status. + """ diff --git a/src/lithic/types/account_holder_simulate_enrollment_review_response.py b/src/lithic/types/account_holder_simulate_enrollment_review_response.py new file mode 100644 index 00000000..56f53d0d --- /dev/null +++ b/src/lithic/types/account_holder_simulate_enrollment_review_response.py @@ -0,0 +1,436 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .required_document import RequiredDocument +from .kyb_business_entity import KYBBusinessEntity + +__all__ = [ + "AccountHolderSimulateEnrollmentReviewResponse", + "BeneficialOwnerIndividual", + "BeneficialOwnerIndividualAddress", + "ControlPerson", + "ControlPersonAddress", + "Individual", + "IndividualAddress", + "VerificationApplication", +] + + +class BeneficialOwnerIndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class BeneficialOwnerIndividual(BaseModel): + address: Optional[BeneficialOwnerIndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class ControlPersonAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class ControlPerson(BaseModel): + """Only present when user_type == "BUSINESS". + + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, + + Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access + + to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. + """ + + address: Optional[ControlPersonAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class IndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class Individual(BaseModel): + """Only present when user_type == "INDIVIDUAL". + + Information about the individual for which the account is being opened and KYC is being run. + """ + + address: Optional[IndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class VerificationApplication(BaseModel): + """Information about the most recent identity verification attempt""" + + created: datetime + """Timestamp of when the application was created.""" + + status: Literal["ACCEPTED", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"] + """KYC and KYB evaluation states. + + Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the + `ADVANCED` workflow. + """ + + status_reasons: List[ + Literal[ + "ADDRESS_VERIFICATION_FAILURE", + "AGE_THRESHOLD_FAILURE", + "COMPLETE_VERIFICATION_FAILURE", + "DOB_VERIFICATION_FAILURE", + "ID_VERIFICATION_FAILURE", + "MAX_DOCUMENT_ATTEMPTS", + "MAX_RESUBMISSION_ATTEMPTS", + "NAME_VERIFICATION_FAILURE", + "OTHER_VERIFICATION_FAILURE", + "RISK_THRESHOLD_FAILURE", + "WATCHLIST_ALERT_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + ] + ] + """Reason for the evaluation status.""" + + updated: datetime + """Timestamp of when the application was last updated.""" + + ky_passed_at: Optional[datetime] = None + """Timestamp of when the application passed the verification process. + + Only present if `status` is `ACCEPTED` + """ + + +class AccountHolderSimulateEnrollmentReviewResponse(BaseModel): + token: Optional[str] = None + """Globally unique identifier for the account holder.""" + + account_token: Optional[str] = None + """Globally unique identifier for the account.""" + + beneficial_owner_entities: Optional[List[KYBBusinessEntity]] = None + """Deprecated.""" + + beneficial_owner_individuals: Optional[List[BeneficialOwnerIndividual]] = None + """Only present when user_type == "BUSINESS". + + You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + """ + + business_account_token: Optional[str] = None + """ + Only applicable for customers using the KYC-Exempt workflow to enroll authorized + users of businesses. Pass the account_token of the enrolled business associated + with the AUTHORIZED_USER in this field. + """ + + business_entity: Optional[KYBBusinessEntity] = None + """Only present when user_type == "BUSINESS". + + Information about the business for which the account is being opened and KYB is + being run. + """ + + control_person: Optional[ControlPerson] = None + """Only present when user_type == "BUSINESS". + + An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, + + Managing Member, General Partner, President, Vice President, or Treasurer). This + can be an executive, or someone who will have program-wide access + + to the cards that Lithic will provide. In some cases, this individual could also + be a beneficial owner listed above. + """ + + created: Optional[datetime] = None + """Timestamp of when the account holder was created.""" + + email: Optional[str] = None + """(Deprecated. + + Use control_person.email when user_type == "BUSINESS". Use + individual.phone_number when user_type == "INDIVIDUAL".) Primary email of + Account Holder. + """ + + exemption_type: Optional[Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"]] = None + """The type of KYC exemption for a KYC-Exempt Account Holder. + + "None" if the account holder is not KYC-Exempt. + """ + + external_id: Optional[str] = None + """ + Customer-provided token that indicates a relationship with an object outside of + the Lithic ecosystem. + """ + + individual: Optional[Individual] = None + """Only present when user_type == "INDIVIDUAL". + + Information about the individual for which the account is being opened and KYC + is being run. + """ + + nature_of_business: Optional[str] = None + """Only present when user_type == "BUSINESS". + + User-submitted description of the business. + """ + + phone_number: Optional[str] = None + """(Deprecated. + + Use control_person.phone_number when user_type == "BUSINESS". Use + individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of + Account Holder, entered in E.164 format. + """ + + required_documents: Optional[List[RequiredDocument]] = None + """Only present for "KYB_BASIC" and "KYC_ADVANCED" workflows. + + A list of documents required for the account holder to be approved. + """ + + status: Optional[Literal["ACCEPTED", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"]] = None + """(Deprecated. + + Use verification_application.status instead) KYC and KYB evaluation states. + + Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the + `ADVANCED` workflow. + """ + + status_reasons: Optional[ + List[ + Literal[ + "ADDRESS_VERIFICATION_FAILURE", + "AGE_THRESHOLD_FAILURE", + "COMPLETE_VERIFICATION_FAILURE", + "DOB_VERIFICATION_FAILURE", + "ID_VERIFICATION_FAILURE", + "MAX_DOCUMENT_ATTEMPTS", + "MAX_RESUBMISSION_ATTEMPTS", + "NAME_VERIFICATION_FAILURE", + "OTHER_VERIFICATION_FAILURE", + "RISK_THRESHOLD_FAILURE", + "WATCHLIST_ALERT_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + ] + ] + ] = None + """(Deprecated. + + Use verification_application.status_reasons) Reason for the evaluation status. + """ + + user_type: Optional[Literal["BUSINESS", "INDIVIDUAL"]] = None + """The type of Account Holder. + + If the type is "INDIVIDUAL", the "individual" attribute will be present. + + If the type is "BUSINESS" then the "business_entity", "control_person", + "beneficial_owner_individuals", "nature_of_business", and "website_url" + attributes will be present. + """ + + verification_application: Optional[VerificationApplication] = None + """Information about the most recent identity verification attempt""" + + website_url: Optional[str] = None + """Only present when user_type == "BUSINESS". Business's primary website.""" diff --git a/src/lithic/types/account_holder_update_params.py b/src/lithic/types/account_holder_update_params.py index cfe22fc2..ed23ca4a 100644 --- a/src/lithic/types/account_holder_update_params.py +++ b/src/lithic/types/account_holder_update_params.py @@ -1,30 +1,322 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing_extensions import TypedDict +from typing import Union, Iterable +from typing_extensions import Required, TypeAlias, TypedDict -__all__ = ["AccountHolderUpdateParams"] +from .._types import SequenceNotStr +from .address_update_param import AddressUpdateParam +__all__ = [ + "AccountHolderUpdateParams", + "KYBPatchRequest", + "KYBPatchRequestBeneficialOwnerEntity", + "KYBPatchRequestBeneficialOwnerIndividual", + "KYBPatchRequestBusinessEntity", + "KYBPatchRequestControlPerson", + "KYCPatchRequest", + "KYCPatchRequestIndividual", + "PatchRequest", +] -class AccountHolderUpdateParams(TypedDict, total=False): - business_account_token: str + +class KYBPatchRequest(TypedDict, total=False): + beneficial_owner_entities: Iterable[KYBPatchRequestBeneficialOwnerEntity] + """Deprecated.""" + + beneficial_owner_individuals: Iterable[KYBPatchRequestBeneficialOwnerIndividual] + """ + You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + """ + + business_entity: KYBPatchRequestBusinessEntity + """ + Information for business for which the account is being opened and KYB is being + run. + """ + + control_person: KYBPatchRequestControlPerson + """ + An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + """ + + external_id: str + """ + A user provided id that can be used to link an account holder with an external + system + """ + + nature_of_business: str + """ + Short description of the company's line of business (i.e., what does the company + do?). + """ + + website_url: str + """Company website URL.""" + + +class KYBPatchRequestBeneficialOwnerEntity(TypedDict, total=False): + entity_token: Required[str] + """Globally unique identifier for an entity.""" + + address: AddressUpdateParam + """ + Business''s physical address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + + dba_business_name: str + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + government_id: str + """Government-issued identification number. + + US Federal Employer Identification Numbers (EIN) are currently supported, + entered as full nine-digits, with or without hyphens. + """ + + legal_business_name: str + """Legal (formal) business name.""" + + parent_company: str + """Parent company name (if applicable).""" + + phone_numbers: SequenceNotStr[str] """ - Only applicable for customers using the KYC-Exempt workflow to enroll authorized - users of businesses. Pass the account_token of the enrolled business associated - with the AUTHORIZED_USER in this field. + One or more of the business's phone number(s), entered as a list in E.164 + format. """ + +class KYBPatchRequestBeneficialOwnerIndividual(TypedDict, total=False): + """Individuals associated with a KYB application. Phone number is optional.""" + + entity_token: Required[str] + """Globally unique identifier for an entity.""" + + address: AddressUpdateParam + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: str + """Individual's date of birth, as an RFC 3339 date.""" + email: str - """Account holder's email address. + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: str + """Individual's first name, as it appears on government-issued identity documents.""" - The primary purpose of this field is for cardholder identification and - verification during the digital wallet tokenization process. + government_id: str """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: str + """Individual's last name, as it appears on government-issued identity documents.""" phone_number: str - """Account holder's phone number, entered in E.164 format. + """Individual's phone number, entered in E.164 format.""" + + +class KYBPatchRequestBusinessEntity(TypedDict, total=False): + """ + Information for business for which the account is being opened and KYB is being run. + """ + + entity_token: Required[str] + """Globally unique identifier for an entity.""" + + address: AddressUpdateParam + """ + Business''s physical address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + + dba_business_name: str + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + government_id: str + """Government-issued identification number. + + US Federal Employer Identification Numbers (EIN) are currently supported, + entered as full nine-digits, with or without hyphens. + """ + + legal_business_name: str + """Legal (formal) business name.""" + + parent_company: str + """Parent company name (if applicable).""" + + phone_numbers: SequenceNotStr[str] + """ + One or more of the business's phone number(s), entered as a list in E.164 + format. + """ + - The primary purpose of this field is for cardholder identification and - verification during the digital wallet tokenization process. +class KYBPatchRequestControlPerson(TypedDict, total=False): """ + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) (Section II) for more background. + """ + + entity_token: Required[str] + """Globally unique identifier for an entity.""" + + address: AddressUpdateParam + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: str + """Individual's date of birth, as an RFC 3339 date.""" + + email: str + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: str + """Individual's first name, as it appears on government-issued identity documents.""" + + government_id: str + """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: str + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: str + """Individual's phone number, entered in E.164 format.""" + + +class KYCPatchRequest(TypedDict, total=False): + external_id: str + """ + A user provided id that can be used to link an account holder with an external + system + """ + + individual: KYCPatchRequestIndividual + """ + Information on the individual for whom the account is being opened and KYC is + being run. + """ + + +class KYCPatchRequestIndividual(TypedDict, total=False): + """ + Information on the individual for whom the account is being opened and KYC is being run. + """ + + entity_token: Required[str] + """Globally unique identifier for an entity.""" + + address: AddressUpdateParam + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: str + """Individual's date of birth, as an RFC 3339 date.""" + + email: str + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: str + """Individual's first name, as it appears on government-issued identity documents.""" + + government_id: str + """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: str + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: str + """Individual's phone number, entered in E.164 format.""" + + +class PatchRequest(TypedDict, total=False): + address: AddressUpdateParam + """Allowed for: KYC-Exempt, BYO-KYC, BYO-KYB.""" + + business_account_token: str + """Allowed for: KYC-Exempt, BYO-KYC. + + The token of the business account to which the account holder is associated. + """ + + email: str + """Allowed for all Account Holders. + + Account holder's email address. The primary purpose of this field is for + cardholder identification and verification during the digital wallet + tokenization process. + """ + + first_name: str + """Allowed for KYC-Exempt, BYO-KYC. Account holder's first name.""" + + last_name: str + """Allowed for KYC-Exempt, BYO-KYC. Account holder's last name.""" + + legal_business_name: str + """Allowed for BYO-KYB. Legal business name of the account holder.""" + + phone_number: str + """Allowed for all Account Holders. + + Account holder's phone number, entered in E.164 format. The primary purpose of + this field is for cardholder identification and verification during the digital + wallet tokenization process. + """ + + +AccountHolderUpdateParams: TypeAlias = Union[KYBPatchRequest, KYCPatchRequest, PatchRequest] diff --git a/src/lithic/types/account_holder_update_response.py b/src/lithic/types/account_holder_update_response.py index c443f0c6..7c8a1340 100644 --- a/src/lithic/types/account_holder_update_response.py +++ b/src/lithic/types/account_holder_update_response.py @@ -1,25 +1,502 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias from .._models import BaseModel +from .required_document import RequiredDocument +from .kyb_business_entity import KYBBusinessEntity -__all__ = ["AccountHolderUpdateResponse"] +__all__ = [ + "AccountHolderUpdateResponse", + "KYBKYCPatchResponse", + "KybkycPatchResponseBeneficialOwnerIndividual", + "KybkycPatchResponseBeneficialOwnerIndividualAddress", + "KYBKYCPatchResponseControlPerson", + "KYBKYCPatchResponseControlPersonAddress", + "KYBKYCPatchResponseIndividual", + "KYBKYCPatchResponseIndividualAddress", + "KYBKYCPatchResponseVerificationApplication", + "PatchResponse", + "PatchResponseAddress", +] -class AccountHolderUpdateResponse(BaseModel): +class KybkycPatchResponseBeneficialOwnerIndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KybkycPatchResponseBeneficialOwnerIndividual(BaseModel): + address: Optional[KybkycPatchResponseBeneficialOwnerIndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYBKYCPatchResponseControlPersonAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYBKYCPatchResponseControlPerson(BaseModel): + """Only present when user_type == "BUSINESS". + + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, + + Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access + + to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. + """ + + address: Optional[KYBKYCPatchResponseControlPersonAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYBKYCPatchResponseIndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYBKYCPatchResponseIndividual(BaseModel): + """Only present when user_type == "INDIVIDUAL". + + Information about the individual for which the account is being opened and KYC is being run. + """ + + address: Optional[KYBKYCPatchResponseIndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYBKYCPatchResponseVerificationApplication(BaseModel): + """Information about the most recent identity verification attempt""" + + created: datetime + """Timestamp of when the application was created.""" + + status: Literal["ACCEPTED", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"] + """KYC and KYB evaluation states. + + Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the + `ADVANCED` workflow. + """ + + status_reasons: List[ + Literal[ + "ADDRESS_VERIFICATION_FAILURE", + "AGE_THRESHOLD_FAILURE", + "COMPLETE_VERIFICATION_FAILURE", + "DOB_VERIFICATION_FAILURE", + "ID_VERIFICATION_FAILURE", + "MAX_DOCUMENT_ATTEMPTS", + "MAX_RESUBMISSION_ATTEMPTS", + "NAME_VERIFICATION_FAILURE", + "OTHER_VERIFICATION_FAILURE", + "RISK_THRESHOLD_FAILURE", + "WATCHLIST_ALERT_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + ] + ] + """Reason for the evaluation status.""" + + updated: datetime + """Timestamp of when the application was last updated.""" + + ky_passed_at: Optional[datetime] = None + """Timestamp of when the application passed the verification process. + + Only present if `status` is `ACCEPTED` + """ + + +class KYBKYCPatchResponse(BaseModel): token: Optional[str] = None - """The token for the account holder that was updated""" + """Globally unique identifier for the account holder.""" + + account_token: Optional[str] = None + """Globally unique identifier for the account.""" + + beneficial_owner_entities: Optional[List[KYBBusinessEntity]] = None + """Deprecated.""" + + beneficial_owner_individuals: Optional[List[KybkycPatchResponseBeneficialOwnerIndividual]] = None + """Only present when user_type == "BUSINESS". + + You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + """ business_account_token: Optional[str] = None """ - Only applicable for customers using the KYC-Exempt workflow to enroll businesses - with authorized users. Pass the account_token of the enrolled business - associated with the AUTHORIZED_USER in this field. + Only applicable for customers using the KYC-Exempt workflow to enroll authorized + users of businesses. Pass the account_token of the enrolled business associated + with the AUTHORIZED_USER in this field. + """ + + business_entity: Optional[KYBBusinessEntity] = None + """Only present when user_type == "BUSINESS". + + Information about the business for which the account is being opened and KYB is + being run. + """ + + control_person: Optional[KYBKYCPatchResponseControlPerson] = None + """Only present when user_type == "BUSINESS". + + An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, + + Managing Member, General Partner, President, Vice President, or Treasurer). This + can be an executive, or someone who will have program-wide access + + to the cards that Lithic will provide. In some cases, this individual could also + be a beneficial owner listed above. """ + created: Optional[datetime] = None + """Timestamp of when the account holder was created.""" + email: Optional[str] = None - """The newly updated email for the account holder""" + """(Deprecated. + + Use control_person.email when user_type == "BUSINESS". Use + individual.phone_number when user_type == "INDIVIDUAL".) Primary email of + Account Holder. + """ + + exemption_type: Optional[Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"]] = None + """The type of KYC exemption for a KYC-Exempt Account Holder. + + "None" if the account holder is not KYC-Exempt. + """ + + external_id: Optional[str] = None + """ + Customer-provided token that indicates a relationship with an object outside of + the Lithic ecosystem. + """ + + individual: Optional[KYBKYCPatchResponseIndividual] = None + """Only present when user_type == "INDIVIDUAL". + + Information about the individual for which the account is being opened and KYC + is being run. + """ + + nature_of_business: Optional[str] = None + """Only present when user_type == "BUSINESS". + + User-submitted description of the business. + """ phone_number: Optional[str] = None - """The newly updated phone_number for the account holder""" + """(Deprecated. + + Use control_person.phone_number when user_type == "BUSINESS". Use + individual.phone_number when user_type == "INDIVIDUAL".) Primary phone of + Account Holder, entered in E.164 format. + """ + + required_documents: Optional[List[RequiredDocument]] = None + """Only present for "KYB_BASIC" and "KYC_ADVANCED" workflows. + + A list of documents required for the account holder to be approved. + """ + + status: Optional[Literal["ACCEPTED", "PENDING_DOCUMENT", "PENDING_RESUBMIT", "REJECTED"]] = None + """(Deprecated. + + Use verification_application.status instead) KYC and KYB evaluation states. + + Note: `PENDING_RESUBMIT` and `PENDING_DOCUMENT` are only applicable for the + `ADVANCED` workflow. + """ + + status_reasons: Optional[ + List[ + Literal[ + "ADDRESS_VERIFICATION_FAILURE", + "AGE_THRESHOLD_FAILURE", + "COMPLETE_VERIFICATION_FAILURE", + "DOB_VERIFICATION_FAILURE", + "ID_VERIFICATION_FAILURE", + "MAX_DOCUMENT_ATTEMPTS", + "MAX_RESUBMISSION_ATTEMPTS", + "NAME_VERIFICATION_FAILURE", + "OTHER_VERIFICATION_FAILURE", + "RISK_THRESHOLD_FAILURE", + "WATCHLIST_ALERT_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_ADDRESS_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_NAME_VERIFICATION_FAILURE", + "PRIMARY_BUSINESS_ENTITY_BUSINESS_OFFICERS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_SOS_FILING_INACTIVE", + "PRIMARY_BUSINESS_ENTITY_SOS_NOT_MATCHED", + "PRIMARY_BUSINESS_ENTITY_CMRA_FAILURE", + "PRIMARY_BUSINESS_ENTITY_WATCHLIST_FAILURE", + "PRIMARY_BUSINESS_ENTITY_REGISTERED_AGENT_FAILURE", + "CONTROL_PERSON_BLOCKLIST_ALERT_FAILURE", + "CONTROL_PERSON_ID_VERIFICATION_FAILURE", + "CONTROL_PERSON_DOB_VERIFICATION_FAILURE", + "CONTROL_PERSON_NAME_VERIFICATION_FAILURE", + ] + ] + ] = None + """(Deprecated. + + Use verification_application.status_reasons) Reason for the evaluation status. + """ + + user_type: Optional[Literal["BUSINESS", "INDIVIDUAL"]] = None + """The type of Account Holder. + + If the type is "INDIVIDUAL", the "individual" attribute will be present. + + If the type is "BUSINESS" then the "business_entity", "control_person", + "beneficial_owner_individuals", "nature_of_business", and "website_url" + attributes will be present. + """ + + verification_application: Optional[KYBKYCPatchResponseVerificationApplication] = None + """Information about the most recent identity verification attempt""" + + website_url: Optional[str] = None + """Only present when user_type == "BUSINESS". Business's primary website.""" + + +class PatchResponseAddress(BaseModel): + """The address for the account holder""" + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class PatchResponse(BaseModel): + token: Optional[str] = None + """The token for the account holder that was updated""" + + address: Optional[PatchResponseAddress] = None + """The address for the account holder""" + + business_account_token: Optional[str] = None + """The token for the business account that the account holder is associated with""" + + email: Optional[str] = None + """The email for the account holder""" + + first_name: Optional[str] = None + """The first name for the account holder""" + + last_name: Optional[str] = None + """The last name for the account holder""" + + legal_business_name: Optional[str] = None + """The legal business name for the account holder""" + + phone_number: Optional[str] = None + """The phone_number for the account holder""" + + +AccountHolderUpdateResponse: TypeAlias = Union[KYBKYCPatchResponse, PatchResponse] diff --git a/src/lithic/types/account_holder_updated_webhook_event.py b/src/lithic/types/account_holder_updated_webhook_event.py new file mode 100644 index 00000000..fcda2c62 --- /dev/null +++ b/src/lithic/types/account_holder_updated_webhook_event.py @@ -0,0 +1,358 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias + +from .._models import BaseModel +from .kyb_business_entity import KYBBusinessEntity + +__all__ = [ + "AccountHolderUpdatedWebhookEvent", + "KYBPayload", + "KYBPayloadUpdateRequest", + "KYBPayloadUpdateRequestBeneficialOwnerIndividual", + "KYBPayloadUpdateRequestBeneficialOwnerIndividualAddress", + "KYBPayloadUpdateRequestControlPerson", + "KYBPayloadUpdateRequestControlPersonAddress", + "KYCPayload", + "KYCPayloadUpdateRequest", + "KYCPayloadUpdateRequestIndividual", + "KYCPayloadUpdateRequestIndividualAddress", + "LegacyPayload", +] + + +class KYBPayloadUpdateRequestBeneficialOwnerIndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYBPayloadUpdateRequestBeneficialOwnerIndividual(BaseModel): + address: Optional[KYBPayloadUpdateRequestBeneficialOwnerIndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYBPayloadUpdateRequestControlPersonAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYBPayloadUpdateRequestControlPerson(BaseModel): + """ + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) (Section II) for more background. + """ + + address: Optional[KYBPayloadUpdateRequestControlPersonAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYBPayloadUpdateRequest(BaseModel): + """Original request to update the account holder.""" + + beneficial_owner_entities: Optional[List[KYBBusinessEntity]] = None + """Deprecated.""" + + beneficial_owner_individuals: Optional[List[KYBPayloadUpdateRequestBeneficialOwnerIndividual]] = None + """ + You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + """ + + business_entity: Optional[KYBBusinessEntity] = None + """ + Information for business for which the account is being opened and KYB is being + run. + """ + + control_person: Optional[KYBPayloadUpdateRequestControlPerson] = None + """ + An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + """ + + +class KYBPayload(BaseModel): + """KYB payload for an updated account holder.""" + + token: str + """The token of the account_holder that was created.""" + + update_request: KYBPayloadUpdateRequest + """Original request to update the account holder.""" + + event_type: Optional[Literal["account_holder.updated"]] = None + """The type of event that occurred.""" + + external_id: Optional[str] = None + """ + A user provided id that can be used to link an account holder with an external + system + """ + + nature_of_business: Optional[str] = None + """ + Short description of the company's line of business (i.e., what does the company + do?). + """ + + website_url: Optional[str] = None + """Company website URL.""" + + +class KYCPayloadUpdateRequestIndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYCPayloadUpdateRequestIndividual(BaseModel): + """ + Information on the individual for whom the account is being opened and KYC is being run. + """ + + address: Optional[KYCPayloadUpdateRequestIndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYCPayloadUpdateRequest(BaseModel): + """Original request to update the account holder.""" + + individual: Optional[KYCPayloadUpdateRequestIndividual] = None + """ + Information on the individual for whom the account is being opened and KYC is + being run. + """ + + +class KYCPayload(BaseModel): + """KYC payload for an updated account holder.""" + + token: str + """The token of the account_holder that was created.""" + + update_request: KYCPayloadUpdateRequest + """Original request to update the account holder.""" + + event_type: Optional[Literal["account_holder.updated"]] = None + """The type of event that occurred.""" + + external_id: Optional[str] = None + """ + A user provided id that can be used to link an account holder with an external + system + """ + + +class LegacyPayload(BaseModel): + """Legacy payload for an updated account holder.""" + + token: str + """The token of the account_holder that was created.""" + + business_account_token: Optional[str] = None + """ + If applicable, represents the business account token associated with the + account_holder. + """ + + created: Optional[datetime] = None + """When the account_holder updated event was created""" + + email: Optional[str] = None + """ + If updated, the newly updated email associated with the account_holder otherwise + the existing email is provided. + """ + + event_type: Optional[Literal["account_holder.updated"]] = None + """The type of event that occurred.""" + + external_id: Optional[str] = None + """If applicable, represents the external_id associated with the account_holder.""" + + first_name: Optional[str] = None + """If applicable, represents the account_holder's first name.""" + + last_name: Optional[str] = None + """If applicable, represents the account_holder's last name.""" + + legal_business_name: Optional[str] = None + """If applicable, represents the account_holder's business name.""" + + phone_number: Optional[str] = None + """ + If updated, the newly updated phone_number associated with the account_holder + otherwise the existing phone_number is provided. + """ + + +AccountHolderUpdatedWebhookEvent: TypeAlias = Union[KYBPayload, KYCPayload, LegacyPayload] diff --git a/src/lithic/types/account_holder_upload_document_params.py b/src/lithic/types/account_holder_upload_document_params.py index 380e8804..21c30873 100644 --- a/src/lithic/types/account_holder_upload_document_params.py +++ b/src/lithic/types/account_holder_upload_document_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -8,5 +8,30 @@ class AccountHolderUploadDocumentParams(TypedDict, total=False): - document_type: Required[Literal["commercial_license", "drivers_license", "passport", "passport_card", "visa"]] - """Type of the document to upload.""" + document_type: Required[ + Literal[ + "EIN_LETTER", + "TAX_RETURN", + "OPERATING_AGREEMENT", + "CERTIFICATE_OF_FORMATION", + "DRIVERS_LICENSE", + "PASSPORT", + "PASSPORT_CARD", + "CERTIFICATE_OF_GOOD_STANDING", + "ARTICLES_OF_INCORPORATION", + "ARTICLES_OF_ORGANIZATION", + "BYLAWS", + "GOVERNMENT_BUSINESS_LICENSE", + "PARTNERSHIP_AGREEMENT", + "SS4_FORM", + "BANK_STATEMENT", + "UTILITY_BILL_STATEMENT", + "SSN_CARD", + "ITIN_LETTER", + "FINCEN_BOI_REPORT", + ] + ] + """The type of document to upload""" + + entity_token: Required[str] + """Globally unique identifier for the entity.""" diff --git a/src/lithic/types/account_holder_verification_webhook_event.py b/src/lithic/types/account_holder_verification_webhook_event.py new file mode 100644 index 00000000..cac64adb --- /dev/null +++ b/src/lithic/types/account_holder_verification_webhook_event.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AccountHolderVerificationWebhookEvent"] + + +class AccountHolderVerificationWebhookEvent(BaseModel): + event_type: Literal["account_holder.verification"] + """The type of event that occurred.""" + + token: Optional[str] = None + """The token of the account_holder being verified.""" + + account_token: Optional[str] = None + """The token of the account being verified.""" + + created: Optional[datetime] = None + """When the account_holder verification status was updated""" + + status: Optional[Literal["ACCEPTED", "PENDING_REVIEW", "REJECTED"]] = None + """The status of the account_holder that was created""" + + status_reasons: Optional[List[str]] = None diff --git a/src/lithic/types/account_list_params.py b/src/lithic/types/account_list_params.py index ff8f3f68..44e75c2c 100644 --- a/src/lithic/types/account_list_params.py +++ b/src/lithic/types/account_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account_spend_limits.py b/src/lithic/types/account_spend_limits.py index 0e74a8f5..8635e927 100644 --- a/src/lithic/types/account_spend_limits.py +++ b/src/lithic/types/account_spend_limits.py @@ -1,30 +1,66 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from .._models import BaseModel -__all__ = ["AccountSpendLimits", "AvailableSpendLimit"] +__all__ = ["AccountSpendLimits", "AvailableSpendLimit", "SpendLimit", "SpendVelocity"] class AvailableSpendLimit(BaseModel): daily: Optional[int] = None """ - The available spend limit relative to the daily limit configured on the Account. + The available spend limit (in cents) relative to the daily limit configured on + the Account (e.g. 100000 would be a $1,000 limit). """ lifetime: Optional[int] = None """ - The available spend limit relative to the lifetime limit configured on the - Account. + The available spend limit (in cents) relative to the lifetime limit configured + on the Account. """ monthly: Optional[int] = None """ - The available spend limit relative to the monthly limit configured on the - Account. + The available spend limit (in cents) relative to the monthly limit configured on + the Account. + """ + + +class SpendLimit(BaseModel): + daily: Optional[int] = None + """The configured daily spend limit (in cents) on the Account.""" + + lifetime: Optional[int] = None + """The configured lifetime spend limit (in cents) on the Account.""" + + monthly: Optional[int] = None + """The configured monthly spend limit (in cents) on the Account.""" + + +class SpendVelocity(BaseModel): + daily: Optional[int] = None + """Current daily spend velocity (in cents) on the Account. + + Present if daily spend limit is set. + """ + + lifetime: Optional[int] = None + """Current lifetime spend velocity (in cents) on the Account. + + Present if lifetime spend limit is set. + """ + + monthly: Optional[int] = None + """Current monthly spend velocity (in cents) on the Account. + + Present if monthly spend limit is set. """ class AccountSpendLimits(BaseModel): available_spend_limit: AvailableSpendLimit + + spend_limit: Optional[SpendLimit] = None + + spend_velocity: Optional[SpendVelocity] = None diff --git a/src/lithic/types/account_update_params.py b/src/lithic/types/account_update_params.py index 3beb9101..954590e8 100644 --- a/src/lithic/types/account_update_params.py +++ b/src/lithic/types/account_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -8,40 +8,90 @@ class AccountUpdateParams(TypedDict, total=False): + comment: str + """Additional context or information related to the account.""" + daily_spend_limit: int - """ - Amount (in cents) for the account's daily spend limit. By default the daily - spend limit is set to $1,250. + """Amount (in cents) for the account's daily spend limit (e.g. + + 100000 would be a $1,000 limit). By default the daily spend limit is set to + $1,250. """ lifetime_spend_limit: int - """Amount (in cents) for the account's lifetime spend limit. - - Once this limit is reached, no transactions will be accepted on any card created - for this account until the limit is updated. Note that a spend limit of 0 is - effectively no limit, and should only be used to reset or remove a prior limit. - Only a limit of 1 or above will result in declined transactions due to checks - against the account limit. This behavior differs from the daily spend limit and - the monthly spend limit. + """Amount (in cents) for the account's lifetime spend limit (e.g. + + 100000 would be a $1,000 limit). Once this limit is reached, no transactions + will be accepted on any card created for this account until the limit is + updated. Note that a spend limit of 0 is effectively no limit, and should only + be used to reset or remove a prior limit. Only a limit of 1 or above will result + in declined transactions due to checks against the account limit. This behavior + differs from the daily spend limit and the monthly spend limit. """ monthly_spend_limit: int - """ - Amount (in cents) for the account's monthly spend limit. By default the monthly - spend limit is set to $5,000. + """Amount (in cents) for the account's monthly spend limit (e.g. + + 100000 would be a $1,000 limit). By default the monthly spend limit is set to + $5,000. """ - state: Literal["ACTIVE", "PAUSED"] + state: Literal["ACTIVE", "PAUSED", "CLOSED"] """Account states.""" + substatus: Literal[ + "FRAUD_IDENTIFIED", + "SUSPICIOUS_ACTIVITY", + "RISK_VIOLATION", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "INTERNAL_REVIEW", + "OTHER", + ] + """Account state substatus values: + + - `FRAUD_IDENTIFIED` - The account has been recognized as being created or used + with stolen or fabricated identity information, encompassing both true + identity theft and synthetic identities. + - `SUSPICIOUS_ACTIVITY` - The account has exhibited suspicious behavior, such as + unauthorized access or fraudulent transactions, necessitating further + investigation. + - `RISK_VIOLATION` - The account has been involved in deliberate misuse by the + legitimate account holder. Examples include disputing valid transactions + without cause, falsely claiming non-receipt of goods, or engaging in + intentional bust-out schemes to exploit account services. + - `END_USER_REQUEST` - The account holder has voluntarily requested the closure + of the account for personal reasons. This encompasses situations such as + bankruptcy, other financial considerations, or the account holder's death. + - `ISSUER_REQUEST` - The issuer has initiated the closure of the account due to + business strategy, risk management, inactivity, product changes, regulatory + concerns, or violations of terms and conditions. + - `NOT_ACTIVE` - The account has not had any transactions or payment activity + within a specified period. This status applies to accounts that are paused or + closed due to inactivity. + - `INTERNAL_REVIEW` - The account is temporarily paused pending further internal + review. In future implementations, this status may prevent clients from + activating the account via APIs until the review is completed. + - `OTHER` - The reason for the account's current status does not fall into any + of the above categories. A comment should be provided to specify the + particular reason. + """ + verification_address: VerificationAddress """ Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + transactions if enabled via Auth Rules. This field is deprecated as AVS checks + are no longer supported by Auth Rules. The field will be removed from the schema + in a future release. """ class VerificationAddress(TypedDict, total=False): + """ + Address used during Address Verification Service (AVS) checks during transactions if enabled via Auth Rules. This field is deprecated as AVS checks are no longer supported by Auth Rules. The field will be removed from the schema in a future release. + """ + address1: str address2: str diff --git a/src/lithic/types/accounts/__init__.py b/src/lithic/types/accounts/__init__.py deleted file mode 100644 index 0eb7dec1..00000000 --- a/src/lithic/types/accounts/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from .credit_configuration_update_params import CreditConfigurationUpdateParams as CreditConfigurationUpdateParams diff --git a/src/lithic/types/accounts/credit_configuration_update_params.py b/src/lithic/types/accounts/credit_configuration_update_params.py deleted file mode 100644 index f5d74b47..00000000 --- a/src/lithic/types/accounts/credit_configuration_update_params.py +++ /dev/null @@ -1,21 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing_extensions import TypedDict - -__all__ = ["CreditConfigurationUpdateParams"] - - -class CreditConfigurationUpdateParams(TypedDict, total=False): - billing_period: int - """Number of days within the billing period""" - - credit_limit: int - """Credit limit extended to the Business Account""" - - external_bank_account_token: str - """The external bank account token to use for auto-collections""" - - payment_period: int - """Number of days after the billing period ends that a payment is required""" diff --git a/src/lithic/types/address_update_param.py b/src/lithic/types/address_update_param.py new file mode 100644 index 00000000..221e9050 --- /dev/null +++ b/src/lithic/types/address_update_param.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["AddressUpdateParam"] + + +class AddressUpdateParam(TypedDict, total=False): + address1: str + """Valid deliverable address (no PO boxes).""" + + address2: str + """Unit or apartment number (if applicable).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ diff --git a/src/lithic/types/aggregate_balance.py b/src/lithic/types/aggregate_balance.py deleted file mode 100644 index 32829ace..00000000 --- a/src/lithic/types/aggregate_balance.py +++ /dev/null @@ -1,52 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from datetime import datetime -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["AggregateBalance"] - - -class AggregateBalance(BaseModel): - available_amount: int - """Funds available for spend in the currency's smallest unit (e.g., cents for USD)""" - - created: datetime - """Date and time for when the balance was first created.""" - - currency: str - """3-digit alphabetic ISO 4217 code for the local currency of the balance.""" - - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] - """Type of financial account""" - - last_financial_account_token: str - """ - Globally unique identifier for the financial account that had its balance - updated most recently - """ - - last_transaction_event_token: str - """ - Globally unique identifier for the last transaction event that impacted this - balance - """ - - last_transaction_token: str - """Globally unique identifier for the last transaction that impacted this balance""" - - pending_amount: int - """Funds not available for spend due to card authorizations or pending ACH release. - - Shown in the currency's smallest unit (e.g., cents for USD) - """ - - total_amount: int - """ - The sum of available and pending balance in the currency's smallest unit (e.g., - cents for USD) - """ - - updated: datetime - """Date and time for when the balance was last updated.""" diff --git a/src/lithic/types/aggregate_balance_list_params.py b/src/lithic/types/aggregate_balance_list_params.py deleted file mode 100644 index c093df18..00000000 --- a/src/lithic/types/aggregate_balance_list_params.py +++ /dev/null @@ -1,12 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing_extensions import Literal, TypedDict - -__all__ = ["AggregateBalanceListParams"] - - -class AggregateBalanceListParams(TypedDict, total=False): - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] - """Get the aggregate balance for a given Financial Account type.""" diff --git a/src/lithic/types/api_status.py b/src/lithic/types/api_status.py index 7c7a3750..180f1470 100644 --- a/src/lithic/types/api_status.py +++ b/src/lithic/types/api_status.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/asa_request_webhook_event.py b/src/lithic/types/asa_request_webhook_event.py new file mode 100644 index 00000000..2a338e32 --- /dev/null +++ b/src/lithic/types/asa_request_webhook_event.py @@ -0,0 +1,440 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .token_info import TokenInfo +from .shared.merchant import Merchant +from .cardholder_authentication import CardholderAuthentication + +__all__ = [ + "AsaRequestWebhookEvent", + "Avs", + "Card", + "FleetInfo", + "LatestChallenge", + "NetworkSpecificData", + "NetworkSpecificDataMastercard", + "NetworkSpecificDataMastercardOnBehalfServiceResult", + "NetworkSpecificDataVisa", + "Pos", + "PosEntryMode", + "PosTerminal", +] + + +class Avs(BaseModel): + address: str + """Cardholder address""" + + address_on_file_match: Literal["MATCH", "MATCH_ADDRESS_ONLY", "MATCH_ZIP_ONLY", "MISMATCH", "NOT_PRESENT"] + """ + Lithic's evaluation result comparing the transaction's address data with the + cardholder KYC data if it exists. In the event Lithic does not have any + Cardholder KYC data, or the transaction does not contain any address data, + NOT_PRESENT will be returned + """ + + zipcode: str + """Cardholder ZIP code""" + + +class Card(BaseModel): + """Card object in ASA""" + + token: Optional[str] = None + """Globally unique identifier for the card.""" + + hostname: Optional[str] = None + """Hostname of card’s locked merchant (will be empty if not applicable)""" + + last_four: Optional[str] = None + """Last four digits of the card number""" + + memo: Optional[str] = None + """Customizable name to identify the card. + + We recommend against using this field to store JSON data as it can cause + unexpected behavior. + """ + + spend_limit: Optional[int] = None + """Amount (in cents) to limit approved authorizations. + + Purchase requests above the spend limit will be declined (refunds and credits + will be approved). + + Note that while spend limits are enforced based on authorized and settled volume + on a card, they are not recommended to be used for balance or + reconciliation-level accuracy. Spend limits also cannot block force posted + charges (i.e., when a merchant sends a clearing message without a prior + authorization). + """ + + spend_limit_duration: Optional[Literal["ANNUALLY", "FOREVER", "MONTHLY", "TRANSACTION"]] = None + """ + Note that to support recurring monthly payments, which can occur on different + day every month, the time window we consider for MONTHLY velocity starts 6 days + after the current calendar date one month prior. + """ + + state: Optional[Literal["CLOSED", "OPEN", "PAUSED", "PENDING_ACTIVATION", "PENDING_FULFILLMENT"]] = None + + type: Optional[Literal["SINGLE_USE", "MERCHANT_LOCKED", "UNLOCKED", "PHYSICAL", "DIGITAL_WALLET", "VIRTUAL"]] = None + + +class FleetInfo(BaseModel): + """ + Optional Object containing information if the Card is a part of a Fleet managed program + """ + + fleet_prompt_code: Literal["NO_PROMPT", "VEHICLE_NUMBER", "DRIVER_NUMBER"] + """Code indicating what the driver was prompted to enter at time of purchase. + + This is configured at a program level and is a static configuration, and does + not change on a request to request basis + """ + + fleet_restriction_code: Literal["NO_RESTRICTIONS", "FUEL_ONLY"] + """Code indicating which restrictions, if any, there are on purchase. + + This is configured at a program level and is a static configuration, and does + not change on a request to request basis + """ + + driver_number: Optional[str] = None + """Number representing the driver""" + + vehicle_number: Optional[str] = None + """Number associated with the vehicle""" + + +class LatestChallenge(BaseModel): + """ + The latest Authorization Challenge that was issued to the cardholder for this merchant. + """ + + phone_number: str + """The phone number used for sending Authorization Challenge SMS.""" + + status: Literal["COMPLETED", "PENDING", "EXPIRED", "ERROR"] + """The status of the Authorization Challenge + + - `COMPLETED` - Challenge was successfully completed by the cardholder + - `PENDING` - Challenge is still open + - `EXPIRED` - Challenge has expired without being completed + - `ERROR` - There was an error processing the challenge + """ + + completed_at: Optional[datetime] = None + """The date and time when the Authorization Challenge was completed in UTC. + + Present only if the status is `COMPLETED`. + """ + + +class NetworkSpecificDataMastercardOnBehalfServiceResult(BaseModel): + result_1: str + """Indicates the results of the service processing.""" + + result_2: str + """Identifies the results of the service processing.""" + + service: str + """Indicates the service performed on the transaction.""" + + +class NetworkSpecificDataMastercard(BaseModel): + ecommerce_security_level_indicator: Optional[str] = None + """Indicates the electronic commerce security level and UCAF collection.""" + + on_behalf_service_result: Optional[List[NetworkSpecificDataMastercardOnBehalfServiceResult]] = None + """The On-behalf Service performed on the transaction and the results. + + Contains all applicable, on-behalf service results that were performed on a + given transaction. + """ + + transaction_type_identifier: Optional[str] = None + """Indicates the type of additional transaction purpose.""" + + +class NetworkSpecificDataVisa(BaseModel): + business_application_identifier: Optional[str] = None + """ + Identifies the purpose or category of a transaction, used to classify and + process transactions according to Visa’s rules. + """ + + +class NetworkSpecificData(BaseModel): + """ + Contains raw data provided by the card network, including attributes that provide further context about the authorization. If populated by the network, data is organized by Lithic and passed through without further modification. Please consult the official network documentation for more details about these values and how to use them. This object is only available to certain programs- contact your Customer Success Manager to discuss enabling access. + """ + + mastercard: Optional[NetworkSpecificDataMastercard] = None + + visa: Optional[NetworkSpecificDataVisa] = None + + +class PosEntryMode(BaseModel): + """POS > Entry Mode object in ASA""" + + card: Optional[Literal["PRESENT", "NOT_PRESENT", "UNKNOWN"]] = None + """Card Presence Indicator""" + + cardholder: Optional[ + Literal[ + "DEFERRED_BILLING", + "ELECTRONIC_ORDER", + "INSTALLMENT", + "MAIL_ORDER", + "NOT_PRESENT", + "PRESENT", + "REOCCURRING", + "TELEPHONE_ORDER", + "UNKNOWN", + ] + ] = None + """Cardholder Presence Indicator""" + + pan: Optional[ + Literal[ + "AUTO_ENTRY", + "BAR_CODE", + "CONTACTLESS", + "ECOMMERCE", + "ERROR_KEYED", + "ERROR_MAGNETIC_STRIPE", + "ICC", + "KEY_ENTERED", + "MAGNETIC_STRIPE", + "MANUAL", + "OCR", + "SECURE_CARDLESS", + "UNSPECIFIED", + "UNKNOWN", + "CREDENTIAL_ON_FILE", + ] + ] = None + """Method of entry for the PAN""" + + pin_entered: Optional[bool] = None + """Indicates whether the cardholder entered the PIN. True if the PIN was entered.""" + + +class PosTerminal(BaseModel): + attended: bool + """True if a clerk is present at the sale.""" + + card_retention_capable: bool + """True if the terminal is capable of retaining the card.""" + + on_premise: bool + """True if the sale was made at the place of business (vs. mobile).""" + + operator: Literal["ADMINISTRATIVE", "CARDHOLDER", "CARD_ACCEPTOR", "UNKNOWN"] + """The person that is designated to swipe the card""" + + partial_approval_capable: bool + """True if the terminal is capable of partial approval. + + Partial approval is when part of a transaction is approved and another payment + must be used for the remainder. Example scenario: A $40 transaction is attempted + on a prepaid card with a $25 balance. If partial approval is enabled, $25 can be + authorized, at which point the POS will prompt the user for an additional + payment of $15. + """ + + pin_capability: Literal["CAPABLE", "INOPERATIVE", "NOT_CAPABLE", "UNSPECIFIED"] + """Status of whether the POS is able to accept PINs""" + + type: Literal[ + "ADMINISTRATIVE", + "ATM", + "AUTHORIZATION", + "COUPON_MACHINE", + "DIAL_TERMINAL", + "ECOMMERCE", + "ECR", + "FUEL_MACHINE", + "HOME_TERMINAL", + "MICR", + "OFF_PREMISE", + "PAYMENT", + "PDA", + "PHONE", + "POINT", + "POS_TERMINAL", + "PUBLIC_UTILITY", + "SELF_SERVICE", + "TELEVISION", + "TELLER", + "TRAVELERS_CHECK_MACHINE", + "VENDING", + "VOICE", + "UNKNOWN", + ] + """POS Type""" + + acceptor_terminal_id: Optional[str] = None + """ + Uniquely identifies a terminal at the card acceptor location of acquiring + institutions or merchant POS Systems. Left justified with trailing spaces. + """ + + +class Pos(BaseModel): + entry_mode: Optional[PosEntryMode] = None + """POS > Entry Mode object in ASA""" + + terminal: Optional[PosTerminal] = None + + +class AsaRequestWebhookEvent(BaseModel): + """The Auth Stream Access request payload that was sent to the ASA responder.""" + + token: str + """The provisional transaction group uuid associated with the authorization""" + + acquirer_fee: int + """Fee (in cents) assessed by the merchant and paid for by the cardholder. + + Will be zero if no fee is assessed. Rebates may be transmitted as a negative + value to indicate credited fees. + """ + + amount: int + """Authorization amount of the transaction (in cents), including any acquirer fees. + + The contents of this field are identical to `authorization_amount`. + """ + + authorization_amount: int + """The base transaction amount (in cents) plus the acquirer fee field. + + This is the amount the issuer should authorize against unless the issuer is + paying the acquirer fee on behalf of the cardholder. + """ + + avs: Avs + + card: Card + """Card object in ASA""" + + cardholder_currency: str + """3-character alphabetic ISO 4217 code for cardholder's billing currency.""" + + cash_amount: int + """ + The portion of the transaction requested as cash back by the cardholder, and + does not include any acquirer fees. The amount field includes the purchase + amount, the requested cash back amount, and any acquirer fees. + + If no cash back was requested, the value of this field will be 0, and the field + will always be present. + """ + + created: datetime + """Date and time when the transaction first occurred in UTC.""" + + merchant: Merchant + + merchant_amount: int + """ + The amount that the merchant will receive, denominated in `merchant_currency` + and in the smallest currency unit. Note the amount includes `acquirer_fee`, + similar to `authorization_amount`. It will be different from + `authorization_amount` if the merchant is taking payment in a different + currency. + """ + + merchant_currency: str + """3-character alphabetic ISO 4217 code for the local currency of the transaction.""" + + settled_amount: int + """ + Amount (in cents) of the transaction that has been settled, including any + acquirer fees + """ + + status: Literal[ + "AUTHORIZATION", + "CREDIT_AUTHORIZATION", + "FINANCIAL_AUTHORIZATION", + "FINANCIAL_CREDIT_AUTHORIZATION", + "BALANCE_INQUIRY", + ] + """The type of authorization request that this request is for. + + Note that `CREDIT_AUTHORIZATION` and `FINANCIAL_CREDIT_AUTHORIZATION` is only + available to users with credit decisioning via ASA enabled. + """ + + transaction_initiator: Literal["CARDHOLDER", "MERCHANT", "UNKNOWN"] + """The entity that initiated the transaction.""" + + account_type: Optional[Literal["CHECKING", "SAVINGS"]] = None + + cardholder_authentication: Optional[CardholderAuthentication] = None + + cashback: Optional[int] = None + """Deprecated, use `cash_amount`.""" + + conversion_rate: Optional[float] = None + """ + If the transaction was requested in a currency other than the settlement + currency, this field will be populated to indicate the rate used to translate + the merchant_amount to the amount (i.e., `merchant_amount` x `conversion_rate` = + `amount`). Note that the `merchant_amount` is in the local currency and the + amount is in the settlement currency. + """ + + event_token: Optional[str] = None + """The event token associated with the authorization. + + This field is only set for programs enrolled into the beta. + """ + + fleet_info: Optional[FleetInfo] = None + """ + Optional Object containing information if the Card is a part of a Fleet managed + program + """ + + latest_challenge: Optional[LatestChallenge] = None + """ + The latest Authorization Challenge that was issued to the cardholder for this + merchant. + """ + + network: Optional[Literal["AMEX", "INTERLINK", "MAESTRO", "MASTERCARD", "UNKNOWN", "VISA"]] = None + """Card network of the authorization.""" + + network_risk_score: Optional[int] = None + """ + Network-provided score assessing risk level associated with a given + authorization. Scores are on a range of 0-999, with 0 representing the lowest + risk and 999 representing the highest risk. For Visa transactions, where the raw + score has a range of 0-99, Lithic will normalize the score by multiplying the + raw score by 10x. + """ + + network_specific_data: Optional[NetworkSpecificData] = None + """ + Contains raw data provided by the card network, including attributes that + provide further context about the authorization. If populated by the network, + data is organized by Lithic and passed through without further modification. + Please consult the official network documentation for more details about these + values and how to use them. This object is only available to certain programs- + contact your Customer Success Manager to discuss enabling access. + """ + + pos: Optional[Pos] = None + + token_info: Optional[TokenInfo] = None + + ttl: Optional[datetime] = None + """Deprecated: approximate time-to-live for the authorization.""" diff --git a/src/lithic/types/auth_rule.py b/src/lithic/types/auth_rule.py deleted file mode 100644 index 2017f101..00000000 --- a/src/lithic/types/auth_rule.py +++ /dev/null @@ -1,54 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import List, Optional -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["AuthRule"] - - -class AuthRule(BaseModel): - token: str - """Globally unique identifier.""" - - state: Literal["ACTIVE", "INACTIVE"] - """Indicates whether the Auth Rule is ACTIVE or INACTIVE""" - - account_tokens: Optional[List[str]] = None - """Array of account_token(s) identifying the accounts that the Auth Rule applies - to. - - Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - """ - - allowed_countries: Optional[List[str]] = None - """Countries in which the Auth Rule permits transactions. - - Note that Lithic maintains a list of countries in which all transactions are - blocked; "allowing" those countries in an Auth Rule does not override the - Lithic-wide restrictions. - """ - - allowed_mcc: Optional[List[str]] = None - """Merchant category codes for which the Auth Rule permits transactions.""" - - blocked_countries: Optional[List[str]] = None - """Countries in which the Auth Rule automatically declines transactions.""" - - blocked_mcc: Optional[List[str]] = None - """ - Merchant category codes for which the Auth Rule automatically declines - transactions. - """ - - card_tokens: Optional[List[str]] = None - """Array of card_token(s) identifying the cards that the Auth Rule applies to. - - Note that only this field or `account_tokens` can be provided for a given Auth - Rule. - """ - - program_level: Optional[bool] = None - """Boolean indicating whether the Auth Rule is applied at the program level.""" diff --git a/src/lithic/types/auth_rule_apply_params.py b/src/lithic/types/auth_rule_apply_params.py deleted file mode 100644 index 9cb97db5..00000000 --- a/src/lithic/types/auth_rule_apply_params.py +++ /dev/null @@ -1,26 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import List -from typing_extensions import TypedDict - -__all__ = ["AuthRuleApplyParams"] - - -class AuthRuleApplyParams(TypedDict, total=False): - account_tokens: List[str] - """ - Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - """ - - card_tokens: List[str] - """ - Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - """ - - program_level: bool - """Boolean indicating whether the Auth Rule is applied at the program level.""" diff --git a/src/lithic/types/auth_rule_create_params.py b/src/lithic/types/auth_rule_create_params.py deleted file mode 100644 index 308a1eab..00000000 --- a/src/lithic/types/auth_rule_create_params.py +++ /dev/null @@ -1,48 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import List -from typing_extensions import TypedDict - -__all__ = ["AuthRuleCreateParams"] - - -class AuthRuleCreateParams(TypedDict, total=False): - account_tokens: List[str] - """Array of account_token(s) identifying the accounts that the Auth Rule applies - to. - - Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - """ - - allowed_countries: List[str] - """Countries in which the Auth Rule permits transactions. - - Note that Lithic maintains a list of countries in which all transactions are - blocked; "allowing" those countries in an Auth Rule does not override the - Lithic-wide restrictions. - """ - - allowed_mcc: List[str] - """Merchant category codes for which the Auth Rule permits transactions.""" - - blocked_countries: List[str] - """Countries in which the Auth Rule automatically declines transactions.""" - - blocked_mcc: List[str] - """ - Merchant category codes for which the Auth Rule automatically declines - transactions. - """ - - card_tokens: List[str] - """Array of card_token(s) identifying the cards that the Auth Rule applies to. - - Note that only this field or `account_tokens` can be provided for a given Auth - Rule. - """ - - program_level: bool - """Boolean indicating whether the Auth Rule is applied at the program level.""" diff --git a/src/lithic/types/auth_rule_remove_params.py b/src/lithic/types/auth_rule_remove_params.py deleted file mode 100644 index 82b23227..00000000 --- a/src/lithic/types/auth_rule_remove_params.py +++ /dev/null @@ -1,26 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import List -from typing_extensions import TypedDict - -__all__ = ["AuthRuleRemoveParams"] - - -class AuthRuleRemoveParams(TypedDict, total=False): - account_tokens: List[str] - """ - Array of account_token(s) identifying the accounts that the Auth Rule applies - to. Note that only this field or `card_tokens` can be provided for a given Auth - Rule. - """ - - card_tokens: List[str] - """ - Array of card_token(s) identifying the cards that the Auth Rule applies to. Note - that only this field or `account_tokens` can be provided for a given Auth Rule. - """ - - program_level: bool - """Boolean indicating whether the Auth Rule is applied at the program level.""" diff --git a/src/lithic/types/auth_rule_remove_response.py b/src/lithic/types/auth_rule_remove_response.py deleted file mode 100644 index 13961e1b..00000000 --- a/src/lithic/types/auth_rule_remove_response.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import List, Optional - -from .._models import BaseModel - -__all__ = ["AuthRuleRemoveResponse"] - - -class AuthRuleRemoveResponse(BaseModel): - account_tokens: Optional[List[str]] = None - - card_tokens: Optional[List[str]] = None - - program_level: Optional[bool] = None diff --git a/src/lithic/types/auth_rule_retrieve_response.py b/src/lithic/types/auth_rule_retrieve_response.py deleted file mode 100644 index 31e16484..00000000 --- a/src/lithic/types/auth_rule_retrieve_response.py +++ /dev/null @@ -1,12 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import List, Optional - -from .._models import BaseModel -from .auth_rule import AuthRule - -__all__ = ["AuthRuleRetrieveResponse"] - - -class AuthRuleRetrieveResponse(BaseModel): - data: Optional[List[AuthRule]] = None diff --git a/src/lithic/types/auth_rule_update_params.py b/src/lithic/types/auth_rule_update_params.py deleted file mode 100644 index 0503c6d9..00000000 --- a/src/lithic/types/auth_rule_update_params.py +++ /dev/null @@ -1,37 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import List -from typing_extensions import TypedDict - -__all__ = ["AuthRuleUpdateParams"] - - -class AuthRuleUpdateParams(TypedDict, total=False): - allowed_countries: List[str] - """ - Array of country codes for which the Auth Rule will permit transactions. Note - that only this field or `blocked_countries` can be used for a given Auth Rule. - """ - - allowed_mcc: List[str] - """ - Array of merchant category codes for which the Auth Rule will permit - transactions. Note that only this field or `blocked_mcc` can be used for a given - Auth Rule. - """ - - blocked_countries: List[str] - """ - Array of country codes for which the Auth Rule will automatically decline - transactions. Note that only this field or `allowed_countries` can be used for a - given Auth Rule. - """ - - blocked_mcc: List[str] - """ - Array of merchant category codes for which the Auth Rule will automatically - decline transactions. Note that only this field or `allowed_mcc` can be used for - a given Auth Rule. - """ diff --git a/src/lithic/types/auth_rules/__init__.py b/src/lithic/types/auth_rules/__init__.py new file mode 100644 index 00000000..7f4cb281 --- /dev/null +++ b/src/lithic/types/auth_rules/__init__.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .auth_rule import AuthRule as AuthRule +from .rule_stats import RuleStats as RuleStats +from .event_stream import EventStream as EventStream +from .v2_list_params import V2ListParams as V2ListParams +from .v2_draft_params import V2DraftParams as V2DraftParams +from .v2_create_params import V2CreateParams as V2CreateParams +from .v2_update_params import V2UpdateParams as V2UpdateParams +from .conditional_value import ConditionalValue as ConditionalValue +from .auth_rule_condition import AuthRuleCondition as AuthRuleCondition +from .conditional_attribute import ConditionalAttribute as ConditionalAttribute +from .conditional_operation import ConditionalOperation as ConditionalOperation +from .velocity_limit_params import VelocityLimitParams as VelocityLimitParams +from .velocity_limit_period import VelocityLimitPeriod as VelocityLimitPeriod +from .conditional_value_param import ConditionalValueParam as ConditionalValueParam +from .merchant_lock_parameters import MerchantLockParameters as MerchantLockParameters +from .auth_rule_condition_param import AuthRuleConditionParam as AuthRuleConditionParam +from .v2_retrieve_report_params import V2RetrieveReportParams as V2RetrieveReportParams +from .v2_retrieve_features_params import V2RetrieveFeaturesParams as V2RetrieveFeaturesParams +from .v2_retrieve_report_response import V2RetrieveReportResponse as V2RetrieveReportResponse +from .velocity_limit_params_param import VelocityLimitParamsParam as VelocityLimitParamsParam +from .velocity_limit_period_param import VelocityLimitPeriodParam as VelocityLimitPeriodParam +from .conditional_block_parameters import ConditionalBlockParameters as ConditionalBlockParameters +from .v2_retrieve_features_response import V2RetrieveFeaturesResponse as V2RetrieveFeaturesResponse +from .merchant_lock_parameters_param import MerchantLockParametersParam as MerchantLockParametersParam +from .conditional_3ds_action_parameters import Conditional3DSActionParameters as Conditional3DSActionParameters +from .conditional_ach_action_parameters import ConditionalACHActionParameters as ConditionalACHActionParameters +from .conditional_block_parameters_param import ConditionalBlockParametersParam as ConditionalBlockParametersParam +from .conditional_3ds_action_parameters_param import ( + Conditional3DSActionParametersParam as Conditional3DSActionParametersParam, +) +from .conditional_ach_action_parameters_param import ( + ConditionalACHActionParametersParam as ConditionalACHActionParametersParam, +) +from .conditional_tokenization_action_parameters import ( + ConditionalTokenizationActionParameters as ConditionalTokenizationActionParameters, +) +from .conditional_authorization_action_parameters import ( + ConditionalAuthorizationActionParameters as ConditionalAuthorizationActionParameters, +) +from .conditional_tokenization_action_parameters_param import ( + ConditionalTokenizationActionParametersParam as ConditionalTokenizationActionParametersParam, +) +from .conditional_authorization_action_parameters_param import ( + ConditionalAuthorizationActionParametersParam as ConditionalAuthorizationActionParametersParam, +) diff --git a/src/lithic/types/auth_rules/auth_rule.py b/src/lithic/types/auth_rules/auth_rule.py new file mode 100644 index 00000000..09f2f172 --- /dev/null +++ b/src/lithic/types/auth_rules/auth_rule.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .event_stream import EventStream +from .velocity_limit_params import VelocityLimitParams +from .merchant_lock_parameters import MerchantLockParameters +from .conditional_block_parameters import ConditionalBlockParameters +from .conditional_3ds_action_parameters import Conditional3DSActionParameters +from .conditional_ach_action_parameters import ConditionalACHActionParameters +from .conditional_tokenization_action_parameters import ConditionalTokenizationActionParameters +from .conditional_authorization_action_parameters import ConditionalAuthorizationActionParameters + +__all__ = ["AuthRule", "CurrentVersion", "CurrentVersionParameters", "DraftVersion", "DraftVersionParameters"] + +CurrentVersionParameters: TypeAlias = Union[ + ConditionalBlockParameters, + VelocityLimitParams, + MerchantLockParameters, + Conditional3DSActionParameters, + ConditionalAuthorizationActionParameters, + ConditionalACHActionParameters, + ConditionalTokenizationActionParameters, +] + + +class CurrentVersion(BaseModel): + parameters: CurrentVersionParameters + """Parameters for the Auth Rule""" + + version: int + """ + The version of the rule, this is incremented whenever the rule's parameters + change. + """ + + +DraftVersionParameters: TypeAlias = Union[ + ConditionalBlockParameters, + VelocityLimitParams, + MerchantLockParameters, + Conditional3DSActionParameters, + ConditionalAuthorizationActionParameters, + ConditionalACHActionParameters, + ConditionalTokenizationActionParameters, +] + + +class DraftVersion(BaseModel): + parameters: DraftVersionParameters + """Parameters for the Auth Rule""" + + version: int + """ + The version of the rule, this is incremented whenever the rule's parameters + change. + """ + + +class AuthRule(BaseModel): + token: str + """Auth Rule Token""" + + account_tokens: List[str] + """Account tokens to which the Auth Rule applies.""" + + business_account_tokens: List[str] + """Business Account tokens to which the Auth Rule applies.""" + + card_tokens: List[str] + """Card tokens to which the Auth Rule applies.""" + + current_version: Optional[CurrentVersion] = None + + draft_version: Optional[DraftVersion] = None + + event_stream: EventStream + """The event stream during which the rule will be evaluated.""" + + lithic_managed: bool + """Indicates whether this auth rule is managed by Lithic. + + If true, the rule cannot be modified or deleted by the user + """ + + name: Optional[str] = None + """Auth Rule Name""" + + program_level: bool + """Whether the Auth Rule applies to all authorizations on the card program.""" + + state: Literal["ACTIVE", "INACTIVE"] + """The state of the Auth Rule""" + + type: Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"] + """The type of Auth Rule. + + For certain rule types, this determines the event stream during which it will be + evaluated. For rules that can be applied to one of several event streams, the + effective one is defined by the separate `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + """ + + excluded_card_tokens: Optional[List[str]] = None + """Card tokens to which the Auth Rule does not apply.""" diff --git a/src/lithic/types/auth_rules/auth_rule_condition.py b/src/lithic/types/auth_rules/auth_rule_condition.py new file mode 100644 index 00000000..5755da4e --- /dev/null +++ b/src/lithic/types/auth_rules/auth_rule_condition.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel +from .conditional_value import ConditionalValue +from .conditional_attribute import ConditionalAttribute +from .conditional_operation import ConditionalOperation + +__all__ = ["AuthRuleCondition"] + + +class AuthRuleCondition(BaseModel): + attribute: ConditionalAttribute + """The attribute to target. + + The following attributes may be targeted: + + - `MCC`: A four-digit number listed in ISO 18245. An MCC is used to classify a + business by the types of goods or services it provides. + - `COUNTRY`: Country of entity of card acceptor. Possible values are: (1) all + ISO 3166-1 alpha-3 country codes, (2) QZZ for Kosovo, and (3) ANT for + Netherlands Antilles. + - `CURRENCY`: 3-character alphabetic ISO 4217 code for the merchant currency of + the transaction. + - `MERCHANT_ID`: Unique alphanumeric identifier for the payment card acceptor + (merchant). + - `DESCRIPTOR`: Short description of card acceptor. + - `LIABILITY_SHIFT`: Indicates whether chargeback liability shift to the issuer + applies to the transaction. Valid values are `NONE`, `3DS_AUTHENTICATED`, or + `TOKEN_AUTHENTICATED`. + - `PAN_ENTRY_MODE`: The method by which the cardholder's primary account number + (PAN) was entered. Valid values are `AUTO_ENTRY`, `BAR_CODE`, `CONTACTLESS`, + `ECOMMERCE`, `ERROR_KEYED`, `ERROR_MAGNETIC_STRIPE`, `ICC`, `KEY_ENTERED`, + `MAGNETIC_STRIPE`, `MANUAL`, `OCR`, `SECURE_CARDLESS`, `UNSPECIFIED`, + `UNKNOWN`, `CREDENTIAL_ON_FILE`, or `ECOMMERCE`. + - `TRANSACTION_AMOUNT`: The base transaction amount (in cents) plus the acquirer + fee field in the settlement/cardholder billing currency. This is the amount + the issuer should authorize against unless the issuer is paying the acquirer + fee on behalf of the cardholder. + - `RISK_SCORE`: Network-provided score assessing risk level associated with a + given authorization. Scores are on a range of 0-999, with 0 representing the + lowest risk and 999 representing the highest risk. For Visa transactions, + where the raw score has a range of 0-99, Lithic will normalize the score by + multiplying the raw score by 10x. + - `CARD_TRANSACTION_COUNT_15M`: The number of transactions on the card in the + trailing 15 minutes before the authorization. + - `CARD_TRANSACTION_COUNT_1H`: The number of transactions on the card in the + trailing hour up and until the authorization. + - `CARD_TRANSACTION_COUNT_24H`: The number of transactions on the card in the + trailing 24 hours up and until the authorization. + - `CARD_STATE`: The current state of the card associated with the transaction. + Valid values are `CLOSED`, `OPEN`, `PAUSED`, `PENDING_ACTIVATION`, + `PENDING_FULFILLMENT`. + - `PIN_ENTERED`: Indicates whether a PIN was entered during the transaction. + Valid values are `TRUE`, `FALSE`. + - `PIN_STATUS`: The current state of card's PIN. Valid values are `NOT_SET`, + `OK`, `BLOCKED`. + - `WALLET_TYPE`: For transactions using a digital wallet token, indicates the + source of the token. Valid values are `APPLE_PAY`, `GOOGLE_PAY`, + `SAMSUNG_PAY`, `MASTERPASS`, `MERCHANT`, `OTHER`, `NONE`. + - `ADDRESS_MATCH`: Lithic's evaluation result comparing transaction's address + data with the cardholder KYC data if it exists. Valid values are `MATCH`, + `MATCH_ADDRESS_ONLY`, `MATCH_ZIP_ONLY`,`MISMATCH`,`NOT_PRESENT`. + """ + + operation: ConditionalOperation + """The operation to apply to the attribute""" + + value: ConditionalValue + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" diff --git a/src/lithic/types/auth_rules/auth_rule_condition_param.py b/src/lithic/types/auth_rules/auth_rule_condition_param.py new file mode 100644 index 00000000..02111a53 --- /dev/null +++ b/src/lithic/types/auth_rules/auth_rule_condition_param.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo +from .conditional_attribute import ConditionalAttribute +from .conditional_operation import ConditionalOperation +from .conditional_value_param import ConditionalValueParam + +__all__ = ["AuthRuleConditionParam"] + + +class AuthRuleConditionParam(TypedDict, total=False): + attribute: Required[ConditionalAttribute] + """The attribute to target. + + The following attributes may be targeted: + + - `MCC`: A four-digit number listed in ISO 18245. An MCC is used to classify a + business by the types of goods or services it provides. + - `COUNTRY`: Country of entity of card acceptor. Possible values are: (1) all + ISO 3166-1 alpha-3 country codes, (2) QZZ for Kosovo, and (3) ANT for + Netherlands Antilles. + - `CURRENCY`: 3-character alphabetic ISO 4217 code for the merchant currency of + the transaction. + - `MERCHANT_ID`: Unique alphanumeric identifier for the payment card acceptor + (merchant). + - `DESCRIPTOR`: Short description of card acceptor. + - `LIABILITY_SHIFT`: Indicates whether chargeback liability shift to the issuer + applies to the transaction. Valid values are `NONE`, `3DS_AUTHENTICATED`, or + `TOKEN_AUTHENTICATED`. + - `PAN_ENTRY_MODE`: The method by which the cardholder's primary account number + (PAN) was entered. Valid values are `AUTO_ENTRY`, `BAR_CODE`, `CONTACTLESS`, + `ECOMMERCE`, `ERROR_KEYED`, `ERROR_MAGNETIC_STRIPE`, `ICC`, `KEY_ENTERED`, + `MAGNETIC_STRIPE`, `MANUAL`, `OCR`, `SECURE_CARDLESS`, `UNSPECIFIED`, + `UNKNOWN`, `CREDENTIAL_ON_FILE`, or `ECOMMERCE`. + - `TRANSACTION_AMOUNT`: The base transaction amount (in cents) plus the acquirer + fee field in the settlement/cardholder billing currency. This is the amount + the issuer should authorize against unless the issuer is paying the acquirer + fee on behalf of the cardholder. + - `RISK_SCORE`: Network-provided score assessing risk level associated with a + given authorization. Scores are on a range of 0-999, with 0 representing the + lowest risk and 999 representing the highest risk. For Visa transactions, + where the raw score has a range of 0-99, Lithic will normalize the score by + multiplying the raw score by 10x. + - `CARD_TRANSACTION_COUNT_15M`: The number of transactions on the card in the + trailing 15 minutes before the authorization. + - `CARD_TRANSACTION_COUNT_1H`: The number of transactions on the card in the + trailing hour up and until the authorization. + - `CARD_TRANSACTION_COUNT_24H`: The number of transactions on the card in the + trailing 24 hours up and until the authorization. + - `CARD_STATE`: The current state of the card associated with the transaction. + Valid values are `CLOSED`, `OPEN`, `PAUSED`, `PENDING_ACTIVATION`, + `PENDING_FULFILLMENT`. + - `PIN_ENTERED`: Indicates whether a PIN was entered during the transaction. + Valid values are `TRUE`, `FALSE`. + - `PIN_STATUS`: The current state of card's PIN. Valid values are `NOT_SET`, + `OK`, `BLOCKED`. + - `WALLET_TYPE`: For transactions using a digital wallet token, indicates the + source of the token. Valid values are `APPLE_PAY`, `GOOGLE_PAY`, + `SAMSUNG_PAY`, `MASTERPASS`, `MERCHANT`, `OTHER`, `NONE`. + - `ADDRESS_MATCH`: Lithic's evaluation result comparing transaction's address + data with the cardholder KYC data if it exists. Valid values are `MATCH`, + `MATCH_ADDRESS_ONLY`, `MATCH_ZIP_ONLY`,`MISMATCH`,`NOT_PRESENT`. + """ + + operation: Required[ConditionalOperation] + """The operation to apply to the attribute""" + + value: Required[Annotated[ConditionalValueParam, PropertyInfo(format="iso8601")]] + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" diff --git a/src/lithic/types/auth_rules/conditional_3ds_action_parameters.py b/src/lithic/types/auth_rules/conditional_3ds_action_parameters.py new file mode 100644 index 00000000..f152eb52 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_3ds_action_parameters.py @@ -0,0 +1,62 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from ..._models import BaseModel +from .conditional_value import ConditionalValue +from .conditional_operation import ConditionalOperation + +__all__ = ["Conditional3DSActionParameters", "Condition"] + + +class Condition(BaseModel): + attribute: Literal[ + "MCC", + "COUNTRY", + "CURRENCY", + "MERCHANT_ID", + "DESCRIPTOR", + "TRANSACTION_AMOUNT", + "RISK_SCORE", + "MESSAGE_CATEGORY", + "ADDRESS_MATCH", + ] + """The attribute to target. + + The following attributes may be targeted: + + - `MCC`: A four-digit number listed in ISO 18245. An MCC is used to classify a + business by the types of goods or services it provides. + - `COUNTRY`: Country of entity of card acceptor. Possible values are: (1) all + ISO 3166-1 alpha-3 country codes, (2) QZZ for Kosovo, and (3) ANT for + Netherlands Antilles. + - `CURRENCY`: 3-character alphabetic ISO 4217 code for the merchant currency of + the transaction. + - `MERCHANT_ID`: Unique alphanumeric identifier for the payment card acceptor + (merchant). + - `DESCRIPTOR`: Short description of card acceptor. + - `TRANSACTION_AMOUNT`: The base transaction amount (in cents) plus the acquirer + fee field in the settlement/cardholder billing currency. This is the amount + the issuer should authorize against unless the issuer is paying the acquirer + fee on behalf of the cardholder. + - `RISK_SCORE`: Mastercard only: Assessment by the network of the authentication + risk level, with a higher value indicating a higher amount of risk. + - `MESSAGE_CATEGORY`: The category of the authentication being processed. + - `ADDRESS_MATCH`: Lithic's evaluation result comparing transaction's address + data with the cardholder KYC data if it exists. Valid values are `MATCH`, + `MATCH_ADDRESS_ONLY`, `MATCH_ZIP_ONLY`,`MISMATCH`,`NOT_PRESENT`. + """ + + operation: ConditionalOperation + """The operation to apply to the attribute""" + + value: ConditionalValue + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class Conditional3DSActionParameters(BaseModel): + action: Literal["DECLINE", "CHALLENGE"] + """The action to take if the conditions are met.""" + + conditions: List[Condition] diff --git a/src/lithic/types/auth_rules/conditional_3ds_action_parameters_param.py b/src/lithic/types/auth_rules/conditional_3ds_action_parameters_param.py new file mode 100644 index 00000000..de980efe --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_3ds_action_parameters_param.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ..._utils import PropertyInfo +from .conditional_operation import ConditionalOperation +from .conditional_value_param import ConditionalValueParam + +__all__ = ["Conditional3DSActionParametersParam", "Condition"] + + +class Condition(TypedDict, total=False): + attribute: Required[ + Literal[ + "MCC", + "COUNTRY", + "CURRENCY", + "MERCHANT_ID", + "DESCRIPTOR", + "TRANSACTION_AMOUNT", + "RISK_SCORE", + "MESSAGE_CATEGORY", + "ADDRESS_MATCH", + ] + ] + """The attribute to target. + + The following attributes may be targeted: + + - `MCC`: A four-digit number listed in ISO 18245. An MCC is used to classify a + business by the types of goods or services it provides. + - `COUNTRY`: Country of entity of card acceptor. Possible values are: (1) all + ISO 3166-1 alpha-3 country codes, (2) QZZ for Kosovo, and (3) ANT for + Netherlands Antilles. + - `CURRENCY`: 3-character alphabetic ISO 4217 code for the merchant currency of + the transaction. + - `MERCHANT_ID`: Unique alphanumeric identifier for the payment card acceptor + (merchant). + - `DESCRIPTOR`: Short description of card acceptor. + - `TRANSACTION_AMOUNT`: The base transaction amount (in cents) plus the acquirer + fee field in the settlement/cardholder billing currency. This is the amount + the issuer should authorize against unless the issuer is paying the acquirer + fee on behalf of the cardholder. + - `RISK_SCORE`: Mastercard only: Assessment by the network of the authentication + risk level, with a higher value indicating a higher amount of risk. + - `MESSAGE_CATEGORY`: The category of the authentication being processed. + - `ADDRESS_MATCH`: Lithic's evaluation result comparing transaction's address + data with the cardholder KYC data if it exists. Valid values are `MATCH`, + `MATCH_ADDRESS_ONLY`, `MATCH_ZIP_ONLY`,`MISMATCH`,`NOT_PRESENT`. + """ + + operation: Required[ConditionalOperation] + """The operation to apply to the attribute""" + + value: Required[Annotated[ConditionalValueParam, PropertyInfo(format="iso8601")]] + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class Conditional3DSActionParametersParam(TypedDict, total=False): + action: Required[Literal["DECLINE", "CHALLENGE"]] + """The action to take if the conditions are met.""" + + conditions: Required[Iterable[Condition]] diff --git a/src/lithic/types/auth_rules/conditional_ach_action_parameters.py b/src/lithic/types/auth_rules/conditional_ach_action_parameters.py new file mode 100644 index 00000000..076a4dfc --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_ach_action_parameters.py @@ -0,0 +1,134 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .conditional_value import ConditionalValue +from .conditional_operation import ConditionalOperation + +__all__ = ["ConditionalACHActionParameters", "Action", "ActionApproveAction", "ActionReturnAction", "Condition"] + + +class ActionApproveAction(BaseModel): + type: Literal["APPROVE"] + """Approve the ACH transaction""" + + +class ActionReturnAction(BaseModel): + code: Literal[ + "R01", + "R02", + "R03", + "R04", + "R05", + "R06", + "R07", + "R08", + "R09", + "R10", + "R11", + "R12", + "R13", + "R14", + "R15", + "R16", + "R17", + "R18", + "R19", + "R20", + "R21", + "R22", + "R23", + "R24", + "R25", + "R26", + "R27", + "R28", + "R29", + "R30", + "R31", + "R32", + "R33", + "R34", + "R35", + "R36", + "R37", + "R38", + "R39", + "R40", + "R41", + "R42", + "R43", + "R44", + "R45", + "R46", + "R47", + "R50", + "R51", + "R52", + "R53", + "R61", + "R62", + "R67", + "R68", + "R69", + "R70", + "R71", + "R72", + "R73", + "R74", + "R75", + "R76", + "R77", + "R80", + "R81", + "R82", + "R83", + "R84", + "R85", + ] + """NACHA return code to use when returning the transaction. + + Note that the list of available return codes is subject to an allowlist + configured at the program level + """ + + type: Literal["RETURN"] + """Return the ACH transaction""" + + +Action: TypeAlias = Union[ActionApproveAction, ActionReturnAction] + + +class Condition(BaseModel): + attribute: Literal["COMPANY_NAME", "COMPANY_ID", "TIMESTAMP", "TRANSACTION_AMOUNT", "SEC_CODE", "MEMO"] + """The attribute to target. + + The following attributes may be targeted: + + - `COMPANY_NAME`: The name of the company initiating the ACH transaction. + - `COMPANY_ID`: The company ID (also known as Standard Entry Class (SEC) Company + ID) of the entity initiating the ACH transaction. + - `TIMESTAMP`: The timestamp of the ACH transaction in ISO 8601 format. + - `TRANSACTION_AMOUNT`: The amount of the ACH transaction in minor units + (cents). + - `SEC_CODE`: Standard Entry Class code indicating the type of ACH transaction. + Valid values include PPD (Prearranged Payment and Deposit Entry), CCD + (Corporate Credit or Debit Entry), WEB (Internet-Initiated/Mobile Entry), TEL + (Telephone-Initiated Entry), and others. + - `MEMO`: Optional memo or description field included with the ACH transaction. + """ + + operation: ConditionalOperation + """The operation to apply to the attribute""" + + value: ConditionalValue + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class ConditionalACHActionParameters(BaseModel): + action: Action + """The action to take if the conditions are met""" + + conditions: List[Condition] diff --git a/src/lithic/types/auth_rules/conditional_ach_action_parameters_param.py b/src/lithic/types/auth_rules/conditional_ach_action_parameters_param.py new file mode 100644 index 00000000..5c29f6e6 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_ach_action_parameters_param.py @@ -0,0 +1,138 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict + +from ..._utils import PropertyInfo +from .conditional_operation import ConditionalOperation +from .conditional_value_param import ConditionalValueParam + +__all__ = ["ConditionalACHActionParametersParam", "Action", "ActionApproveAction", "ActionReturnAction", "Condition"] + + +class ActionApproveAction(TypedDict, total=False): + type: Required[Literal["APPROVE"]] + """Approve the ACH transaction""" + + +class ActionReturnAction(TypedDict, total=False): + code: Required[ + Literal[ + "R01", + "R02", + "R03", + "R04", + "R05", + "R06", + "R07", + "R08", + "R09", + "R10", + "R11", + "R12", + "R13", + "R14", + "R15", + "R16", + "R17", + "R18", + "R19", + "R20", + "R21", + "R22", + "R23", + "R24", + "R25", + "R26", + "R27", + "R28", + "R29", + "R30", + "R31", + "R32", + "R33", + "R34", + "R35", + "R36", + "R37", + "R38", + "R39", + "R40", + "R41", + "R42", + "R43", + "R44", + "R45", + "R46", + "R47", + "R50", + "R51", + "R52", + "R53", + "R61", + "R62", + "R67", + "R68", + "R69", + "R70", + "R71", + "R72", + "R73", + "R74", + "R75", + "R76", + "R77", + "R80", + "R81", + "R82", + "R83", + "R84", + "R85", + ] + ] + """NACHA return code to use when returning the transaction. + + Note that the list of available return codes is subject to an allowlist + configured at the program level + """ + + type: Required[Literal["RETURN"]] + """Return the ACH transaction""" + + +Action: TypeAlias = Union[ActionApproveAction, ActionReturnAction] + + +class Condition(TypedDict, total=False): + attribute: Required[Literal["COMPANY_NAME", "COMPANY_ID", "TIMESTAMP", "TRANSACTION_AMOUNT", "SEC_CODE", "MEMO"]] + """The attribute to target. + + The following attributes may be targeted: + + - `COMPANY_NAME`: The name of the company initiating the ACH transaction. + - `COMPANY_ID`: The company ID (also known as Standard Entry Class (SEC) Company + ID) of the entity initiating the ACH transaction. + - `TIMESTAMP`: The timestamp of the ACH transaction in ISO 8601 format. + - `TRANSACTION_AMOUNT`: The amount of the ACH transaction in minor units + (cents). + - `SEC_CODE`: Standard Entry Class code indicating the type of ACH transaction. + Valid values include PPD (Prearranged Payment and Deposit Entry), CCD + (Corporate Credit or Debit Entry), WEB (Internet-Initiated/Mobile Entry), TEL + (Telephone-Initiated Entry), and others. + - `MEMO`: Optional memo or description field included with the ACH transaction. + """ + + operation: Required[ConditionalOperation] + """The operation to apply to the attribute""" + + value: Required[Annotated[ConditionalValueParam, PropertyInfo(format="iso8601")]] + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class ConditionalACHActionParametersParam(TypedDict, total=False): + action: Required[Action] + """The action to take if the conditions are met""" + + conditions: Required[Iterable[Condition]] diff --git a/src/lithic/types/auth_rules/conditional_attribute.py b/src/lithic/types/auth_rules/conditional_attribute.py new file mode 100644 index 00000000..fbf3231a --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_attribute.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ConditionalAttribute"] + +ConditionalAttribute: TypeAlias = Literal[ + "MCC", + "COUNTRY", + "CURRENCY", + "MERCHANT_ID", + "DESCRIPTOR", + "LIABILITY_SHIFT", + "PAN_ENTRY_MODE", + "TRANSACTION_AMOUNT", + "RISK_SCORE", + "CARD_TRANSACTION_COUNT_15M", + "CARD_TRANSACTION_COUNT_1H", + "CARD_TRANSACTION_COUNT_24H", + "CARD_STATE", + "PIN_ENTERED", + "PIN_STATUS", + "WALLET_TYPE", + "ADDRESS_MATCH", +] diff --git a/src/lithic/types/auth_rules/conditional_authorization_action_parameters.py b/src/lithic/types/auth_rules/conditional_authorization_action_parameters.py new file mode 100644 index 00000000..a322a14a --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_authorization_action_parameters.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from ..._models import BaseModel +from .conditional_value import ConditionalValue +from .conditional_operation import ConditionalOperation + +__all__ = ["ConditionalAuthorizationActionParameters", "Condition"] + + +class Condition(BaseModel): + attribute: Literal[ + "MCC", + "COUNTRY", + "CURRENCY", + "MERCHANT_ID", + "DESCRIPTOR", + "LIABILITY_SHIFT", + "PAN_ENTRY_MODE", + "TRANSACTION_AMOUNT", + "CASH_AMOUNT", + "RISK_SCORE", + "CARD_TRANSACTION_COUNT_15M", + "CARD_TRANSACTION_COUNT_1H", + "CARD_TRANSACTION_COUNT_24H", + "CARD_STATE", + "PIN_ENTERED", + "PIN_STATUS", + "WALLET_TYPE", + "TRANSACTION_INITIATOR", + "ADDRESS_MATCH", + ] + """The attribute to target. + + The following attributes may be targeted: + + - `MCC`: A four-digit number listed in ISO 18245. An MCC is used to classify a + business by the types of goods or services it provides. + - `COUNTRY`: Country of entity of card acceptor. Possible values are: (1) all + ISO 3166-1 alpha-3 country codes, (2) QZZ for Kosovo, and (3) ANT for + Netherlands Antilles. + - `CURRENCY`: 3-character alphabetic ISO 4217 code for the merchant currency of + the transaction. + - `MERCHANT_ID`: Unique alphanumeric identifier for the payment card acceptor + (merchant). + - `DESCRIPTOR`: Short description of card acceptor. + - `LIABILITY_SHIFT`: Indicates whether chargeback liability shift to the issuer + applies to the transaction. Valid values are `NONE`, `3DS_AUTHENTICATED`, or + `TOKEN_AUTHENTICATED`. + - `PAN_ENTRY_MODE`: The method by which the cardholder's primary account number + (PAN) was entered. Valid values are `AUTO_ENTRY`, `BAR_CODE`, `CONTACTLESS`, + `ECOMMERCE`, `ERROR_KEYED`, `ERROR_MAGNETIC_STRIPE`, `ICC`, `KEY_ENTERED`, + `MAGNETIC_STRIPE`, `MANUAL`, `OCR`, `SECURE_CARDLESS`, `UNSPECIFIED`, + `UNKNOWN`, `CREDENTIAL_ON_FILE`, or `ECOMMERCE`. + - `TRANSACTION_AMOUNT`: The base transaction amount (in cents) plus the acquirer + fee field in the settlement/cardholder billing currency. This is the amount + the issuer should authorize against unless the issuer is paying the acquirer + fee on behalf of the cardholder. + - `CASH_AMOUNT`: The cash amount of the transaction in minor units (cents). This + represents the amount of cash being withdrawn or advanced. + - `RISK_SCORE`: Network-provided score assessing risk level associated with a + given authorization. Scores are on a range of 0-999, with 0 representing the + lowest risk and 999 representing the highest risk. For Visa transactions, + where the raw score has a range of 0-99, Lithic will normalize the score by + multiplying the raw score by 10x. + - `CARD_TRANSACTION_COUNT_15M`: The number of transactions on the card in the + trailing 15 minutes before the authorization. + - `CARD_TRANSACTION_COUNT_1H`: The number of transactions on the card in the + trailing hour up and until the authorization. + - `CARD_TRANSACTION_COUNT_24H`: The number of transactions on the card in the + trailing 24 hours up and until the authorization. + - `CARD_STATE`: The current state of the card associated with the transaction. + Valid values are `CLOSED`, `OPEN`, `PAUSED`, `PENDING_ACTIVATION`, + `PENDING_FULFILLMENT`. + - `PIN_ENTERED`: Indicates whether a PIN was entered during the transaction. + Valid values are `TRUE`, `FALSE`. + - `PIN_STATUS`: The current state of card's PIN. Valid values are `NOT_SET`, + `OK`, `BLOCKED`. + - `WALLET_TYPE`: For transactions using a digital wallet token, indicates the + source of the token. Valid values are `APPLE_PAY`, `GOOGLE_PAY`, + `SAMSUNG_PAY`, `MASTERPASS`, `MERCHANT`, `OTHER`, `NONE`. + - `TRANSACTION_INITIATOR`: The entity that initiated the transaction indicates + the source of the token. Valid values are `CARDHOLDER`, `MERCHANT`, `UNKNOWN`. + - `ADDRESS_MATCH`: Lithic's evaluation result comparing transaction's address + data with the cardholder KYC data if it exists. Valid values are `MATCH`, + `MATCH_ADDRESS_ONLY`, `MATCH_ZIP_ONLY`,`MISMATCH`,`NOT_PRESENT`. + """ + + operation: ConditionalOperation + """The operation to apply to the attribute""" + + value: ConditionalValue + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class ConditionalAuthorizationActionParameters(BaseModel): + action: Literal["DECLINE", "CHALLENGE"] + """The action to take if the conditions are met.""" + + conditions: List[Condition] diff --git a/src/lithic/types/auth_rules/conditional_authorization_action_parameters_param.py b/src/lithic/types/auth_rules/conditional_authorization_action_parameters_param.py new file mode 100644 index 00000000..04eee746 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_authorization_action_parameters_param.py @@ -0,0 +1,106 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ..._utils import PropertyInfo +from .conditional_operation import ConditionalOperation +from .conditional_value_param import ConditionalValueParam + +__all__ = ["ConditionalAuthorizationActionParametersParam", "Condition"] + + +class Condition(TypedDict, total=False): + attribute: Required[ + Literal[ + "MCC", + "COUNTRY", + "CURRENCY", + "MERCHANT_ID", + "DESCRIPTOR", + "LIABILITY_SHIFT", + "PAN_ENTRY_MODE", + "TRANSACTION_AMOUNT", + "CASH_AMOUNT", + "RISK_SCORE", + "CARD_TRANSACTION_COUNT_15M", + "CARD_TRANSACTION_COUNT_1H", + "CARD_TRANSACTION_COUNT_24H", + "CARD_STATE", + "PIN_ENTERED", + "PIN_STATUS", + "WALLET_TYPE", + "TRANSACTION_INITIATOR", + "ADDRESS_MATCH", + ] + ] + """The attribute to target. + + The following attributes may be targeted: + + - `MCC`: A four-digit number listed in ISO 18245. An MCC is used to classify a + business by the types of goods or services it provides. + - `COUNTRY`: Country of entity of card acceptor. Possible values are: (1) all + ISO 3166-1 alpha-3 country codes, (2) QZZ for Kosovo, and (3) ANT for + Netherlands Antilles. + - `CURRENCY`: 3-character alphabetic ISO 4217 code for the merchant currency of + the transaction. + - `MERCHANT_ID`: Unique alphanumeric identifier for the payment card acceptor + (merchant). + - `DESCRIPTOR`: Short description of card acceptor. + - `LIABILITY_SHIFT`: Indicates whether chargeback liability shift to the issuer + applies to the transaction. Valid values are `NONE`, `3DS_AUTHENTICATED`, or + `TOKEN_AUTHENTICATED`. + - `PAN_ENTRY_MODE`: The method by which the cardholder's primary account number + (PAN) was entered. Valid values are `AUTO_ENTRY`, `BAR_CODE`, `CONTACTLESS`, + `ECOMMERCE`, `ERROR_KEYED`, `ERROR_MAGNETIC_STRIPE`, `ICC`, `KEY_ENTERED`, + `MAGNETIC_STRIPE`, `MANUAL`, `OCR`, `SECURE_CARDLESS`, `UNSPECIFIED`, + `UNKNOWN`, `CREDENTIAL_ON_FILE`, or `ECOMMERCE`. + - `TRANSACTION_AMOUNT`: The base transaction amount (in cents) plus the acquirer + fee field in the settlement/cardholder billing currency. This is the amount + the issuer should authorize against unless the issuer is paying the acquirer + fee on behalf of the cardholder. + - `CASH_AMOUNT`: The cash amount of the transaction in minor units (cents). This + represents the amount of cash being withdrawn or advanced. + - `RISK_SCORE`: Network-provided score assessing risk level associated with a + given authorization. Scores are on a range of 0-999, with 0 representing the + lowest risk and 999 representing the highest risk. For Visa transactions, + where the raw score has a range of 0-99, Lithic will normalize the score by + multiplying the raw score by 10x. + - `CARD_TRANSACTION_COUNT_15M`: The number of transactions on the card in the + trailing 15 minutes before the authorization. + - `CARD_TRANSACTION_COUNT_1H`: The number of transactions on the card in the + trailing hour up and until the authorization. + - `CARD_TRANSACTION_COUNT_24H`: The number of transactions on the card in the + trailing 24 hours up and until the authorization. + - `CARD_STATE`: The current state of the card associated with the transaction. + Valid values are `CLOSED`, `OPEN`, `PAUSED`, `PENDING_ACTIVATION`, + `PENDING_FULFILLMENT`. + - `PIN_ENTERED`: Indicates whether a PIN was entered during the transaction. + Valid values are `TRUE`, `FALSE`. + - `PIN_STATUS`: The current state of card's PIN. Valid values are `NOT_SET`, + `OK`, `BLOCKED`. + - `WALLET_TYPE`: For transactions using a digital wallet token, indicates the + source of the token. Valid values are `APPLE_PAY`, `GOOGLE_PAY`, + `SAMSUNG_PAY`, `MASTERPASS`, `MERCHANT`, `OTHER`, `NONE`. + - `TRANSACTION_INITIATOR`: The entity that initiated the transaction indicates + the source of the token. Valid values are `CARDHOLDER`, `MERCHANT`, `UNKNOWN`. + - `ADDRESS_MATCH`: Lithic's evaluation result comparing transaction's address + data with the cardholder KYC data if it exists. Valid values are `MATCH`, + `MATCH_ADDRESS_ONLY`, `MATCH_ZIP_ONLY`,`MISMATCH`,`NOT_PRESENT`. + """ + + operation: Required[ConditionalOperation] + """The operation to apply to the attribute""" + + value: Required[Annotated[ConditionalValueParam, PropertyInfo(format="iso8601")]] + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class ConditionalAuthorizationActionParametersParam(TypedDict, total=False): + action: Required[Literal["DECLINE", "CHALLENGE"]] + """The action to take if the conditions are met.""" + + conditions: Required[Iterable[Condition]] diff --git a/src/lithic/types/auth_rules/conditional_block_parameters.py b/src/lithic/types/auth_rules/conditional_block_parameters.py new file mode 100644 index 00000000..b615fbe2 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_block_parameters.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel +from .auth_rule_condition import AuthRuleCondition + +__all__ = ["ConditionalBlockParameters"] + + +class ConditionalBlockParameters(BaseModel): + conditions: List[AuthRuleCondition] diff --git a/src/lithic/types/auth_rules/conditional_block_parameters_param.py b/src/lithic/types/auth_rules/conditional_block_parameters_param.py new file mode 100644 index 00000000..b9a456a3 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_block_parameters_param.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict + +from .auth_rule_condition_param import AuthRuleConditionParam + +__all__ = ["ConditionalBlockParametersParam"] + + +class ConditionalBlockParametersParam(TypedDict, total=False): + conditions: Required[Iterable[AuthRuleConditionParam]] diff --git a/src/lithic/types/auth_rules/conditional_operation.py b/src/lithic/types/auth_rules/conditional_operation.py new file mode 100644 index 00000000..42caeaa6 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_operation.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ConditionalOperation"] + +ConditionalOperation: TypeAlias = Literal[ + "IS_ONE_OF", + "IS_NOT_ONE_OF", + "MATCHES", + "DOES_NOT_MATCH", + "IS_EQUAL_TO", + "IS_NOT_EQUAL_TO", + "IS_GREATER_THAN", + "IS_GREATER_THAN_OR_EQUAL_TO", + "IS_LESS_THAN", + "IS_LESS_THAN_OR_EQUAL_TO", + "IS_AFTER", + "IS_BEFORE", + "CONTAINS_ANY", + "CONTAINS_ALL", + "CONTAINS_NONE", +] diff --git a/src/lithic/types/auth_rules/conditional_tokenization_action_parameters.py b/src/lithic/types/auth_rules/conditional_tokenization_action_parameters.py new file mode 100644 index 00000000..4e3a645a --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_tokenization_action_parameters.py @@ -0,0 +1,130 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel +from .conditional_value import ConditionalValue +from .conditional_operation import ConditionalOperation + +__all__ = [ + "ConditionalTokenizationActionParameters", + "Action", + "ActionDeclineAction", + "ActionRequireTfaAction", + "Condition", +] + + +class ActionDeclineAction(BaseModel): + type: Literal["DECLINE"] + """Decline the tokenization request""" + + reason: Optional[ + Literal[ + "ACCOUNT_SCORE_1", + "DEVICE_SCORE_1", + "ALL_WALLET_DECLINE_REASONS_PRESENT", + "WALLET_RECOMMENDED_DECISION_RED", + "CVC_MISMATCH", + "CARD_EXPIRY_MONTH_MISMATCH", + "CARD_EXPIRY_YEAR_MISMATCH", + "CARD_INVALID_STATE", + "CUSTOMER_RED_PATH", + "INVALID_CUSTOMER_RESPONSE", + "NETWORK_FAILURE", + "GENERIC_DECLINE", + "DIGITAL_CARD_ART_REQUIRED", + ] + ] = None + """Reason code for declining the tokenization request""" + + +class ActionRequireTfaAction(BaseModel): + type: Literal["REQUIRE_TFA"] + """Require two-factor authentication for the tokenization request""" + + reason: Optional[ + Literal[ + "WALLET_RECOMMENDED_TFA", + "SUSPICIOUS_ACTIVITY", + "DEVICE_RECENTLY_LOST", + "TOO_MANY_RECENT_ATTEMPTS", + "TOO_MANY_RECENT_TOKENS", + "TOO_MANY_DIFFERENT_CARDHOLDERS", + "OUTSIDE_HOME_TERRITORY", + "HAS_SUSPENDED_TOKENS", + "HIGH_RISK", + "ACCOUNT_SCORE_LOW", + "DEVICE_SCORE_LOW", + "CARD_STATE_TFA", + "HARDCODED_TFA", + "CUSTOMER_RULE_TFA", + "DEVICE_HOST_CARD_EMULATION", + ] + ] = None + """Reason code for requiring two-factor authentication""" + + +Action: TypeAlias = Union[ActionDeclineAction, ActionRequireTfaAction] + + +class Condition(BaseModel): + attribute: Literal[ + "TIMESTAMP", + "TOKENIZATION_CHANNEL", + "TOKENIZATION_SOURCE", + "TOKEN_REQUESTOR_NAME", + "WALLET_ACCOUNT_SCORE", + "WALLET_DEVICE_SCORE", + "WALLET_RECOMMENDED_DECISION", + "WALLET_RECOMMENDATION_REASONS", + "TOKEN_REQUESTOR_ID", + "WALLET_TOKEN_STATUS", + ] + """The attribute to target. + + The following attributes may be targeted: + + - `TIMESTAMP`: The timestamp of the tokenization request in ISO 8601 format. + - `TOKENIZATION_CHANNEL`: The channel through which the tokenization request was + initiated (e.g., DIGITAL_WALLET, ECOMMERCE). + - `TOKENIZATION_SOURCE`: The source of the tokenization request. + - `TOKEN_REQUESTOR_NAME`: The name of the entity requesting the token. Valid + values are `ALT_ID`, `AMAZON_ONE`, `AMERICAN_EXPRESS_TOKEN_SERVICE`, + `ANDROID_PAY`, `APPLE_PAY`, `FACEBOOK`, `FITBIT_PAY`, `GARMIN_PAY`, + `GOOGLE_PAY`, `GOOGLE_VCN`, `ISSUER_HCE`, `MICROSOFT_PAY`, `NETFLIX`, + `SAMSUNG_PAY`, `UNKNOWN`, `VISA_CHECKOUT`. + - `WALLET_ACCOUNT_SCORE`: Risk score for the account in the digital wallet. + Numeric value where lower numbers indicate higher risk (e.g., 1 = high risk, 2 + = medium risk). + - `WALLET_DEVICE_SCORE`: Risk score for the device in the digital wallet. + Numeric value where lower numbers indicate higher risk (e.g., 1 = high risk, 2 + = medium risk). + - `WALLET_RECOMMENDED_DECISION`: The decision recommended by the digital wallet + provider. Valid values include APPROVE, DECLINE, + REQUIRE_ADDITIONAL_AUTHENTICATION. + - `WALLET_RECOMMENDATION_REASONS`: List of reasons provided by the digital + wallet provider for the recommended decision. Valid values are + `ACCOUNT_CARD_TOO_NEW`, `ACCOUNT_RECENTLY_CHANGED`, `ACCOUNT_TOO_NEW`, + `ACCOUNT_TOO_NEW_SINCE_LAUNCH`, `DEVICE_RECENTLY_LOST`, + `HAS_SUSPENDED_TOKENS`, `HIGH_RISK`, `INACTIVE_ACCOUNT`, `LOW_ACCOUNT_SCORE`, + `LOW_DEVICE_SCORE`, `OUTSIDE_HOME_TERRITORY`, `SUSPICIOUS_ACTIVITY`, + `TOO_MANY_DIFFERENT_CARDHOLDERS`, `TOO_MANY_RECENT_ATTEMPTS`, + `TOO_MANY_RECENT_TOKENS`, `UNABLE_TO_ASSESS`. + - `TOKEN_REQUESTOR_ID`: Unique identifier for the entity requesting the token. + - `WALLET_TOKEN_STATUS`: The current status of the wallet token. + """ + + operation: ConditionalOperation + """The operation to apply to the attribute""" + + value: ConditionalValue + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class ConditionalTokenizationActionParameters(BaseModel): + action: Action + """The action to take if the conditions are met""" + + conditions: List[Condition] diff --git a/src/lithic/types/auth_rules/conditional_tokenization_action_parameters_param.py b/src/lithic/types/auth_rules/conditional_tokenization_action_parameters_param.py new file mode 100644 index 00000000..6b733a47 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_tokenization_action_parameters_param.py @@ -0,0 +1,130 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict + +from ..._utils import PropertyInfo +from .conditional_operation import ConditionalOperation +from .conditional_value_param import ConditionalValueParam + +__all__ = [ + "ConditionalTokenizationActionParametersParam", + "Action", + "ActionDeclineAction", + "ActionRequireTfaAction", + "Condition", +] + + +class ActionDeclineAction(TypedDict, total=False): + type: Required[Literal["DECLINE"]] + """Decline the tokenization request""" + + reason: Literal[ + "ACCOUNT_SCORE_1", + "DEVICE_SCORE_1", + "ALL_WALLET_DECLINE_REASONS_PRESENT", + "WALLET_RECOMMENDED_DECISION_RED", + "CVC_MISMATCH", + "CARD_EXPIRY_MONTH_MISMATCH", + "CARD_EXPIRY_YEAR_MISMATCH", + "CARD_INVALID_STATE", + "CUSTOMER_RED_PATH", + "INVALID_CUSTOMER_RESPONSE", + "NETWORK_FAILURE", + "GENERIC_DECLINE", + "DIGITAL_CARD_ART_REQUIRED", + ] + """Reason code for declining the tokenization request""" + + +class ActionRequireTfaAction(TypedDict, total=False): + type: Required[Literal["REQUIRE_TFA"]] + """Require two-factor authentication for the tokenization request""" + + reason: Literal[ + "WALLET_RECOMMENDED_TFA", + "SUSPICIOUS_ACTIVITY", + "DEVICE_RECENTLY_LOST", + "TOO_MANY_RECENT_ATTEMPTS", + "TOO_MANY_RECENT_TOKENS", + "TOO_MANY_DIFFERENT_CARDHOLDERS", + "OUTSIDE_HOME_TERRITORY", + "HAS_SUSPENDED_TOKENS", + "HIGH_RISK", + "ACCOUNT_SCORE_LOW", + "DEVICE_SCORE_LOW", + "CARD_STATE_TFA", + "HARDCODED_TFA", + "CUSTOMER_RULE_TFA", + "DEVICE_HOST_CARD_EMULATION", + ] + """Reason code for requiring two-factor authentication""" + + +Action: TypeAlias = Union[ActionDeclineAction, ActionRequireTfaAction] + + +class Condition(TypedDict, total=False): + attribute: Required[ + Literal[ + "TIMESTAMP", + "TOKENIZATION_CHANNEL", + "TOKENIZATION_SOURCE", + "TOKEN_REQUESTOR_NAME", + "WALLET_ACCOUNT_SCORE", + "WALLET_DEVICE_SCORE", + "WALLET_RECOMMENDED_DECISION", + "WALLET_RECOMMENDATION_REASONS", + "TOKEN_REQUESTOR_ID", + "WALLET_TOKEN_STATUS", + ] + ] + """The attribute to target. + + The following attributes may be targeted: + + - `TIMESTAMP`: The timestamp of the tokenization request in ISO 8601 format. + - `TOKENIZATION_CHANNEL`: The channel through which the tokenization request was + initiated (e.g., DIGITAL_WALLET, ECOMMERCE). + - `TOKENIZATION_SOURCE`: The source of the tokenization request. + - `TOKEN_REQUESTOR_NAME`: The name of the entity requesting the token. Valid + values are `ALT_ID`, `AMAZON_ONE`, `AMERICAN_EXPRESS_TOKEN_SERVICE`, + `ANDROID_PAY`, `APPLE_PAY`, `FACEBOOK`, `FITBIT_PAY`, `GARMIN_PAY`, + `GOOGLE_PAY`, `GOOGLE_VCN`, `ISSUER_HCE`, `MICROSOFT_PAY`, `NETFLIX`, + `SAMSUNG_PAY`, `UNKNOWN`, `VISA_CHECKOUT`. + - `WALLET_ACCOUNT_SCORE`: Risk score for the account in the digital wallet. + Numeric value where lower numbers indicate higher risk (e.g., 1 = high risk, 2 + = medium risk). + - `WALLET_DEVICE_SCORE`: Risk score for the device in the digital wallet. + Numeric value where lower numbers indicate higher risk (e.g., 1 = high risk, 2 + = medium risk). + - `WALLET_RECOMMENDED_DECISION`: The decision recommended by the digital wallet + provider. Valid values include APPROVE, DECLINE, + REQUIRE_ADDITIONAL_AUTHENTICATION. + - `WALLET_RECOMMENDATION_REASONS`: List of reasons provided by the digital + wallet provider for the recommended decision. Valid values are + `ACCOUNT_CARD_TOO_NEW`, `ACCOUNT_RECENTLY_CHANGED`, `ACCOUNT_TOO_NEW`, + `ACCOUNT_TOO_NEW_SINCE_LAUNCH`, `DEVICE_RECENTLY_LOST`, + `HAS_SUSPENDED_TOKENS`, `HIGH_RISK`, `INACTIVE_ACCOUNT`, `LOW_ACCOUNT_SCORE`, + `LOW_DEVICE_SCORE`, `OUTSIDE_HOME_TERRITORY`, `SUSPICIOUS_ACTIVITY`, + `TOO_MANY_DIFFERENT_CARDHOLDERS`, `TOO_MANY_RECENT_ATTEMPTS`, + `TOO_MANY_RECENT_TOKENS`, `UNABLE_TO_ASSESS`. + - `TOKEN_REQUESTOR_ID`: Unique identifier for the entity requesting the token. + - `WALLET_TOKEN_STATUS`: The current status of the wallet token. + """ + + operation: Required[ConditionalOperation] + """The operation to apply to the attribute""" + + value: Required[Annotated[ConditionalValueParam, PropertyInfo(format="iso8601")]] + """A regex string, to be used with `MATCHES` or `DOES_NOT_MATCH`""" + + +class ConditionalTokenizationActionParametersParam(TypedDict, total=False): + action: Required[Action] + """The action to take if the conditions are met""" + + conditions: Required[Iterable[Condition]] diff --git a/src/lithic/types/auth_rules/conditional_value.py b/src/lithic/types/auth_rules/conditional_value.py new file mode 100644 index 00000000..2f0a8910 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_value.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from datetime import datetime +from typing_extensions import TypeAlias + +__all__ = ["ConditionalValue"] + +ConditionalValue: TypeAlias = Union[str, int, List[str], datetime] diff --git a/src/lithic/types/auth_rules/conditional_value_param.py b/src/lithic/types/auth_rules/conditional_value_param.py new file mode 100644 index 00000000..c1e27c62 --- /dev/null +++ b/src/lithic/types/auth_rules/conditional_value_param.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import TypeAlias + +from ..._types import SequenceNotStr + +__all__ = ["ConditionalValueParam"] + +ConditionalValueParam: TypeAlias = Union[str, int, SequenceNotStr[str], Union[str, datetime]] diff --git a/src/lithic/types/auth_rules/event_stream.py b/src/lithic/types/auth_rules/event_stream.py new file mode 100644 index 00000000..bd270b0f --- /dev/null +++ b/src/lithic/types/auth_rules/event_stream.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["EventStream"] + +EventStream: TypeAlias = Literal[ + "AUTHORIZATION", "THREE_DS_AUTHENTICATION", "TOKENIZATION", "ACH_CREDIT_RECEIPT", "ACH_DEBIT_RECEIPT" +] diff --git a/src/lithic/types/auth_rules/merchant_lock_parameters.py b/src/lithic/types/auth_rules/merchant_lock_parameters.py new file mode 100644 index 00000000..651ea367 --- /dev/null +++ b/src/lithic/types/auth_rules/merchant_lock_parameters.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel + +__all__ = ["MerchantLockParameters", "Merchant"] + + +class Merchant(BaseModel): + """Represents a specific merchant lock based on their ID or descriptor. + + Each merchant object allows transaction rules to work at a granular level and requires at least one of merchant_id or descriptor. + """ + + comment: Optional[str] = None + """ + A comment or explanation about the merchant, used internally for rule management + purposes. + """ + + descriptor: Optional[str] = None + """ + Short description of the merchant, often used to provide more human-readable + context about the transaction merchant. This is typically the name or label + shown on transaction summaries. + """ + + merchant_id: Optional[str] = None + """Unique alphanumeric identifier for the payment card acceptor (merchant). + + This attribute specifies the merchant entity that will be locked or referenced + for authorization rules. + """ + + +class MerchantLockParameters(BaseModel): + merchants: List[Merchant] + """ + A list of merchant locks defining specific merchants or groups of merchants + (based on descriptors or IDs) that the lock applies to. + """ diff --git a/src/lithic/types/auth_rules/merchant_lock_parameters_param.py b/src/lithic/types/auth_rules/merchant_lock_parameters_param.py new file mode 100644 index 00000000..cb0285cf --- /dev/null +++ b/src/lithic/types/auth_rules/merchant_lock_parameters_param.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict + +__all__ = ["MerchantLockParametersParam", "Merchant"] + + +class Merchant(TypedDict, total=False): + """Represents a specific merchant lock based on their ID or descriptor. + + Each merchant object allows transaction rules to work at a granular level and requires at least one of merchant_id or descriptor. + """ + + comment: str + """ + A comment or explanation about the merchant, used internally for rule management + purposes. + """ + + descriptor: str + """ + Short description of the merchant, often used to provide more human-readable + context about the transaction merchant. This is typically the name or label + shown on transaction summaries. + """ + + merchant_id: str + """Unique alphanumeric identifier for the payment card acceptor (merchant). + + This attribute specifies the merchant entity that will be locked or referenced + for authorization rules. + """ + + +class MerchantLockParametersParam(TypedDict, total=False): + merchants: Required[Iterable[Merchant]] + """ + A list of merchant locks defining specific merchants or groups of merchants + (based on descriptors or IDs) that the lock applies to. + """ diff --git a/src/lithic/types/auth_rules/rule_stats.py b/src/lithic/types/auth_rules/rule_stats.py new file mode 100644 index 00000000..b3e621f9 --- /dev/null +++ b/src/lithic/types/auth_rules/rule_stats.py @@ -0,0 +1,56 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["RuleStats", "Example"] + + +class Example(BaseModel): + approved: Optional[bool] = None + """Whether the rule would have approved the request.""" + + decision: Optional[Literal["APPROVED", "DECLINED", "CHALLENGED"]] = None + """The decision made by the rule for this event.""" + + event_token: Optional[str] = None + """The event token.""" + + timestamp: Optional[datetime] = None + """The timestamp of the event.""" + + +class RuleStats(BaseModel): + approved: Optional[int] = None + """ + The total number of historical transactions approved by this rule during the + relevant period, or the number of transactions that would have been approved if + the rule was evaluated in shadow mode. + """ + + challenged: Optional[int] = None + """ + The total number of historical transactions challenged by this rule during the + relevant period, or the number of transactions that would have been challenged + if the rule was evaluated in shadow mode. Currently applicable only for 3DS Auth + Rules. + """ + + declined: Optional[int] = None + """ + The total number of historical transactions declined by this rule during the + relevant period, or the number of transactions that would have been declined if + the rule was evaluated in shadow mode. + """ + + examples: Optional[List[Example]] = None + """Example events and their outcomes.""" + + version: Optional[int] = None + """ + The version of the rule, this is incremented whenever the rule's parameters + change. + """ diff --git a/src/lithic/types/auth_rules/v2/__init__.py b/src/lithic/types/auth_rules/v2/__init__.py new file mode 100644 index 00000000..c665d2de --- /dev/null +++ b/src/lithic/types/auth_rules/v2/__init__.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .backtest_results import BacktestResults as BacktestResults +from .backtest_create_params import BacktestCreateParams as BacktestCreateParams +from .backtest_create_response import BacktestCreateResponse as BacktestCreateResponse diff --git a/src/lithic/types/auth_rules/v2/backtest_create_params.py b/src/lithic/types/auth_rules/v2/backtest_create_params.py new file mode 100644 index 00000000..fec2e270 --- /dev/null +++ b/src/lithic/types/auth_rules/v2/backtest_create_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["BacktestCreateParams"] + + +class BacktestCreateParams(TypedDict, total=False): + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """The end time of the backtest.""" + + start: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """The start time of the backtest.""" diff --git a/src/lithic/types/auth_rules/v2/backtest_create_response.py b/src/lithic/types/auth_rules/v2/backtest_create_response.py new file mode 100644 index 00000000..28ee75ea --- /dev/null +++ b/src/lithic/types/auth_rules/v2/backtest_create_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ...._models import BaseModel + +__all__ = ["BacktestCreateResponse"] + + +class BacktestCreateResponse(BaseModel): + backtest_token: Optional[str] = None + """Auth Rule Backtest Token""" diff --git a/src/lithic/types/auth_rules/v2/backtest_results.py b/src/lithic/types/auth_rules/v2/backtest_results.py new file mode 100644 index 00000000..5496ba32 --- /dev/null +++ b/src/lithic/types/auth_rules/v2/backtest_results.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ...._models import BaseModel +from ..rule_stats import RuleStats + +__all__ = ["BacktestResults", "Results", "SimulationParameters"] + + +class Results(BaseModel): + current_version: Optional[RuleStats] = None + + draft_version: Optional[RuleStats] = None + + +class SimulationParameters(BaseModel): + auth_rule_token: Optional[str] = None + """Auth Rule Token""" + + end: Optional[datetime] = None + """The end time of the simulation.""" + + start: Optional[datetime] = None + """The start time of the simulation.""" + + +class BacktestResults(BaseModel): + backtest_token: str + """Auth Rule Backtest Token""" + + results: Results + + simulation_parameters: SimulationParameters diff --git a/src/lithic/types/auth_rules/v2_create_params.py b/src/lithic/types/auth_rules/v2_create_params.py new file mode 100644 index 00000000..e9f9d2d3 --- /dev/null +++ b/src/lithic/types/auth_rules/v2_create_params.py @@ -0,0 +1,151 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from .event_stream import EventStream +from .velocity_limit_params_param import VelocityLimitParamsParam +from .merchant_lock_parameters_param import MerchantLockParametersParam +from .conditional_block_parameters_param import ConditionalBlockParametersParam +from .conditional_3ds_action_parameters_param import Conditional3DSActionParametersParam +from .conditional_ach_action_parameters_param import ConditionalACHActionParametersParam +from .conditional_tokenization_action_parameters_param import ConditionalTokenizationActionParametersParam +from .conditional_authorization_action_parameters_param import ConditionalAuthorizationActionParametersParam + +__all__ = [ + "V2CreateParams", + "AccountLevelRule", + "AccountLevelRuleParameters", + "CardLevelRule", + "CardLevelRuleParameters", + "ProgramLevelRule", + "ProgramLevelRuleParameters", +] + + +class AccountLevelRule(TypedDict, total=False): + parameters: Required[AccountLevelRuleParameters] + """Parameters for the Auth Rule""" + + type: Required[Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"]] + """The type of Auth Rule. + + For certain rule types, this determines the event stream during which it will be + evaluated. For rules that can be applied to one of several event streams, the + effective one is defined by the separate `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + """ + + account_tokens: SequenceNotStr[str] + """Account tokens to which the Auth Rule applies.""" + + business_account_tokens: SequenceNotStr[str] + """Business Account tokens to which the Auth Rule applies.""" + + event_stream: EventStream + """The event stream during which the rule will be evaluated.""" + + name: Optional[str] + """Auth Rule Name""" + + +AccountLevelRuleParameters: TypeAlias = Union[ + ConditionalBlockParametersParam, + VelocityLimitParamsParam, + MerchantLockParametersParam, + Conditional3DSActionParametersParam, + ConditionalAuthorizationActionParametersParam, + ConditionalACHActionParametersParam, + ConditionalTokenizationActionParametersParam, +] + + +class CardLevelRule(TypedDict, total=False): + card_tokens: Required[SequenceNotStr[str]] + """Card tokens to which the Auth Rule applies.""" + + parameters: Required[CardLevelRuleParameters] + """Parameters for the Auth Rule""" + + type: Required[Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"]] + """The type of Auth Rule. + + For certain rule types, this determines the event stream during which it will be + evaluated. For rules that can be applied to one of several event streams, the + effective one is defined by the separate `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + """ + + event_stream: EventStream + """The event stream during which the rule will be evaluated.""" + + name: Optional[str] + """Auth Rule Name""" + + +CardLevelRuleParameters: TypeAlias = Union[ + ConditionalBlockParametersParam, + VelocityLimitParamsParam, + MerchantLockParametersParam, + Conditional3DSActionParametersParam, + ConditionalAuthorizationActionParametersParam, + ConditionalACHActionParametersParam, + ConditionalTokenizationActionParametersParam, +] + + +class ProgramLevelRule(TypedDict, total=False): + parameters: Required[ProgramLevelRuleParameters] + """Parameters for the Auth Rule""" + + program_level: Required[bool] + """Whether the Auth Rule applies to all authorizations on the card program.""" + + type: Required[Literal["CONDITIONAL_BLOCK", "VELOCITY_LIMIT", "MERCHANT_LOCK", "CONDITIONAL_ACTION"]] + """The type of Auth Rule. + + For certain rule types, this determines the event stream during which it will be + evaluated. For rules that can be applied to one of several event streams, the + effective one is defined by the separate `event_stream` field. + + - `CONDITIONAL_BLOCK`: AUTHORIZATION event stream. + - `VELOCITY_LIMIT`: AUTHORIZATION event stream. + - `MERCHANT_LOCK`: AUTHORIZATION event stream. + - `CONDITIONAL_ACTION`: AUTHORIZATION, THREE_DS_AUTHENTICATION, TOKENIZATION, + ACH_CREDIT_RECEIPT, or ACH_DEBIT_RECEIPT event stream. + """ + + event_stream: EventStream + """The event stream during which the rule will be evaluated.""" + + excluded_card_tokens: SequenceNotStr[str] + """Card tokens to which the Auth Rule does not apply.""" + + name: Optional[str] + """Auth Rule Name""" + + +ProgramLevelRuleParameters: TypeAlias = Union[ + ConditionalBlockParametersParam, + VelocityLimitParamsParam, + MerchantLockParametersParam, + Conditional3DSActionParametersParam, + ConditionalAuthorizationActionParametersParam, + ConditionalACHActionParametersParam, + ConditionalTokenizationActionParametersParam, +] + +V2CreateParams: TypeAlias = Union[AccountLevelRule, CardLevelRule, ProgramLevelRule] diff --git a/src/lithic/types/auth_rules/v2_draft_params.py b/src/lithic/types/auth_rules/v2_draft_params.py new file mode 100644 index 00000000..2352a6b7 --- /dev/null +++ b/src/lithic/types/auth_rules/v2_draft_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import TypeAlias, TypedDict + +from .velocity_limit_params_param import VelocityLimitParamsParam +from .merchant_lock_parameters_param import MerchantLockParametersParam +from .conditional_block_parameters_param import ConditionalBlockParametersParam +from .conditional_3ds_action_parameters_param import Conditional3DSActionParametersParam +from .conditional_ach_action_parameters_param import ConditionalACHActionParametersParam +from .conditional_tokenization_action_parameters_param import ConditionalTokenizationActionParametersParam +from .conditional_authorization_action_parameters_param import ConditionalAuthorizationActionParametersParam + +__all__ = ["V2DraftParams", "Parameters"] + + +class V2DraftParams(TypedDict, total=False): + parameters: Optional[Parameters] + """Parameters for the Auth Rule""" + + +Parameters: TypeAlias = Union[ + ConditionalBlockParametersParam, + VelocityLimitParamsParam, + MerchantLockParametersParam, + Conditional3DSActionParametersParam, + ConditionalAuthorizationActionParametersParam, + ConditionalACHActionParametersParam, + ConditionalTokenizationActionParametersParam, +] diff --git a/src/lithic/types/auth_rules/v2_list_params.py b/src/lithic/types/auth_rules/v2_list_params.py new file mode 100644 index 00000000..af94a849 --- /dev/null +++ b/src/lithic/types/auth_rules/v2_list_params.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +from .event_stream import EventStream + +__all__ = ["V2ListParams"] + + +class V2ListParams(TypedDict, total=False): + account_token: str + """Only return Auth Rules that are bound to the provided account token.""" + + business_account_token: str + """Only return Auth Rules that are bound to the provided business account token.""" + + card_token: str + """Only return Auth Rules that are bound to the provided card token.""" + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + event_stream: EventStream + """Deprecated: Use event_streams instead. + + Only return Auth rules that are executed during the provided event stream. + """ + + event_streams: List[EventStream] + """ + Only return Auth rules that are executed during any of the provided event + streams. If event_streams and event_stream are specified, the values will be + combined. + """ + + page_size: int + """Page size (for pagination).""" + + scope: Literal["PROGRAM", "ACCOUNT", "BUSINESS_ACCOUNT", "CARD", "ANY"] + """Only return Auth Rules that are bound to the provided scope.""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ diff --git a/src/lithic/types/auth_rules/v2_retrieve_features_params.py b/src/lithic/types/auth_rules/v2_retrieve_features_params.py new file mode 100644 index 00000000..a0c4d812 --- /dev/null +++ b/src/lithic/types/auth_rules/v2_retrieve_features_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["V2RetrieveFeaturesParams"] + + +class V2RetrieveFeaturesParams(TypedDict, total=False): + account_token: str + + card_token: str diff --git a/src/lithic/types/auth_rules/v2_retrieve_features_response.py b/src/lithic/types/auth_rules/v2_retrieve_features_response.py new file mode 100644 index 00000000..708a3fdc --- /dev/null +++ b/src/lithic/types/auth_rules/v2_retrieve_features_response.py @@ -0,0 +1,101 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel +from .velocity_limit_period import VelocityLimitPeriod + +__all__ = ["V2RetrieveFeaturesResponse", "Feature", "FeatureFilters", "FeatureValue"] + + +class FeatureFilters(BaseModel): + exclude_countries: Optional[List[str]] = None + """ISO-3166-1 alpha-3 Country Codes to exclude from the velocity calculation. + + Transactions matching any of the provided will be excluded from the calculated + velocity. + """ + + exclude_mccs: Optional[List[str]] = None + """Merchant Category Codes to exclude from the velocity calculation. + + Transactions matching this MCC will be excluded from the calculated velocity. + """ + + include_countries: Optional[List[str]] = None + """ISO-3166-1 alpha-3 Country Codes to include in the velocity calculation. + + Transactions not matching any of the provided will not be included in the + calculated velocity. + """ + + include_mccs: Optional[List[str]] = None + """Merchant Category Codes to include in the velocity calculation. + + Transactions not matching this MCC will not be included in the calculated + velocity. + """ + + include_pan_entry_modes: Optional[ + List[ + Literal[ + "AUTO_ENTRY", + "BAR_CODE", + "CONTACTLESS", + "CREDENTIAL_ON_FILE", + "ECOMMERCE", + "ERROR_KEYED", + "ERROR_MAGNETIC_STRIPE", + "ICC", + "KEY_ENTERED", + "MAGNETIC_STRIPE", + "MANUAL", + "OCR", + "SECURE_CARDLESS", + "UNSPECIFIED", + "UNKNOWN", + ] + ] + ] = None + """PAN entry modes to include in the velocity calculation. + + Transactions not matching any of the provided will not be included in the + calculated velocity. + """ + + +class FeatureValue(BaseModel): + amount: int + """ + Amount (in cents) for the given Auth Rule that is used as input for calculating + the rule. For Velocity Limit rules this would be the calculated Velocity. For + Conditional Rules using CARD*TRANSACTION_COUNT*\\** this will be 0 + """ + + count: int + """ + Number of velocity impacting transactions matching the given scope, period and + filters + """ + + +class Feature(BaseModel): + filters: FeatureFilters + + period: VelocityLimitPeriod + """Velocity over the current day since 00:00 / 12 AM in Eastern Time""" + + scope: Literal["CARD", "ACCOUNT"] + """The scope the velocity is calculated for""" + + value: FeatureValue + + +class V2RetrieveFeaturesResponse(BaseModel): + evaluated: datetime + """Timestamp at which the Features were evaluated""" + + features: List[Feature] + """Calculated Features used for evaluation of the provided Auth Rule""" diff --git a/src/lithic/types/auth_rules/v2_retrieve_report_params.py b/src/lithic/types/auth_rules/v2_retrieve_report_params.py new file mode 100644 index 00000000..79d6b615 --- /dev/null +++ b/src/lithic/types/auth_rules/v2_retrieve_report_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["V2RetrieveReportParams"] + + +class V2RetrieveReportParams(TypedDict, total=False): + begin: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """Start date for the report""" + + end: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """End date for the report""" diff --git a/src/lithic/types/auth_rules/v2_retrieve_report_response.py b/src/lithic/types/auth_rules/v2_retrieve_report_response.py new file mode 100644 index 00000000..27169af6 --- /dev/null +++ b/src/lithic/types/auth_rules/v2_retrieve_report_response.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import datetime +from typing import List, Optional + +from ..._models import BaseModel +from .rule_stats import RuleStats + +__all__ = ["V2RetrieveReportResponse", "DailyStatistic"] + + +class DailyStatistic(BaseModel): + current_version_statistics: Optional[RuleStats] = None + """Detailed statistics for the current version of the rule.""" + + date: datetime.date + """The date (UTC) for which the statistics are reported.""" + + draft_version_statistics: Optional[RuleStats] = None + """Detailed statistics for the draft version of the rule.""" + + +class V2RetrieveReportResponse(BaseModel): + auth_rule_token: str + """Auth Rule Token""" + + begin: datetime.date + """The start date (UTC) of the report.""" + + daily_statistics: List[DailyStatistic] + """Daily evaluation statistics for the Auth Rule.""" + + end: datetime.date + """The end date (UTC) of the report.""" diff --git a/src/lithic/types/auth_rules/v2_update_params.py b/src/lithic/types/auth_rules/v2_update_params.py new file mode 100644 index 00000000..6694505a --- /dev/null +++ b/src/lithic/types/auth_rules/v2_update_params.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, TypeAlias, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["V2UpdateParams", "AccountLevelRule", "CardLevelRule", "ProgramLevelRule"] + + +class AccountLevelRule(TypedDict, total=False): + account_tokens: SequenceNotStr[str] + """Account tokens to which the Auth Rule applies.""" + + business_account_tokens: SequenceNotStr[str] + """Business Account tokens to which the Auth Rule applies.""" + + name: Optional[str] + """Auth Rule Name""" + + state: Literal["INACTIVE"] + """The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + """ + + +class CardLevelRule(TypedDict, total=False): + card_tokens: SequenceNotStr[str] + """Card tokens to which the Auth Rule applies.""" + + name: Optional[str] + """Auth Rule Name""" + + state: Literal["INACTIVE"] + """The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + """ + + +class ProgramLevelRule(TypedDict, total=False): + excluded_card_tokens: SequenceNotStr[str] + """Card tokens to which the Auth Rule does not apply.""" + + name: Optional[str] + """Auth Rule Name""" + + program_level: bool + """Whether the Auth Rule applies to all authorizations on the card program.""" + + state: Literal["INACTIVE"] + """The desired state of the Auth Rule. + + Note that only deactivating an Auth Rule through this endpoint is supported at + this time. If you need to (re-)activate an Auth Rule the /promote endpoint + should be used to promote a draft to the currently active version. + """ + + +V2UpdateParams: TypeAlias = Union[AccountLevelRule, CardLevelRule, ProgramLevelRule] diff --git a/src/lithic/types/auth_rules/velocity_limit_params.py b/src/lithic/types/auth_rules/velocity_limit_params.py new file mode 100644 index 00000000..911fe78b --- /dev/null +++ b/src/lithic/types/auth_rules/velocity_limit_params.py @@ -0,0 +1,91 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .velocity_limit_period import VelocityLimitPeriod + +__all__ = ["VelocityLimitParams", "Filters"] + + +class Filters(BaseModel): + exclude_countries: Optional[List[str]] = None + """ISO-3166-1 alpha-3 Country Codes to exclude from the velocity calculation. + + Transactions matching any of the provided will be excluded from the calculated + velocity. + """ + + exclude_mccs: Optional[List[str]] = None + """Merchant Category Codes to exclude from the velocity calculation. + + Transactions matching this MCC will be excluded from the calculated velocity. + """ + + include_countries: Optional[List[str]] = None + """ISO-3166-1 alpha-3 Country Codes to include in the velocity calculation. + + Transactions not matching any of the provided will not be included in the + calculated velocity. + """ + + include_mccs: Optional[List[str]] = None + """Merchant Category Codes to include in the velocity calculation. + + Transactions not matching this MCC will not be included in the calculated + velocity. + """ + + include_pan_entry_modes: Optional[ + List[ + Literal[ + "AUTO_ENTRY", + "BAR_CODE", + "CONTACTLESS", + "CREDENTIAL_ON_FILE", + "ECOMMERCE", + "ERROR_KEYED", + "ERROR_MAGNETIC_STRIPE", + "ICC", + "KEY_ENTERED", + "MAGNETIC_STRIPE", + "MANUAL", + "OCR", + "SECURE_CARDLESS", + "UNSPECIFIED", + "UNKNOWN", + ] + ] + ] = None + """PAN entry modes to include in the velocity calculation. + + Transactions not matching any of the provided will not be included in the + calculated velocity. + """ + + +class VelocityLimitParams(BaseModel): + filters: Filters + + period: VelocityLimitPeriod + """Velocity over the current day since 00:00 / 12 AM in Eastern Time""" + + scope: Literal["CARD", "ACCOUNT"] + """The scope the velocity is calculated for""" + + limit_amount: Optional[int] = None + """ + The maximum amount of spend velocity allowed in the period in minor units (the + smallest unit of a currency, e.g. cents for USD). Transactions exceeding this + limit will be declined. + """ + + limit_count: Optional[int] = None + """ + The number of spend velocity impacting transactions may not exceed this limit in + the period. Transactions exceeding this limit will be declined. A spend velocity + impacting transaction is a transaction that has been authorized, and optionally + settled, or a force post (a transaction that settled without prior + authorization). + """ diff --git a/src/lithic/types/auth_rules/velocity_limit_params_param.py b/src/lithic/types/auth_rules/velocity_limit_params_param.py new file mode 100644 index 00000000..fc4b6e11 --- /dev/null +++ b/src/lithic/types/auth_rules/velocity_limit_params_param.py @@ -0,0 +1,93 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr +from .velocity_limit_period_param import VelocityLimitPeriodParam + +__all__ = ["VelocityLimitParamsParam", "Filters"] + + +class Filters(TypedDict, total=False): + exclude_countries: Optional[SequenceNotStr[str]] + """ISO-3166-1 alpha-3 Country Codes to exclude from the velocity calculation. + + Transactions matching any of the provided will be excluded from the calculated + velocity. + """ + + exclude_mccs: Optional[SequenceNotStr[str]] + """Merchant Category Codes to exclude from the velocity calculation. + + Transactions matching this MCC will be excluded from the calculated velocity. + """ + + include_countries: Optional[SequenceNotStr[str]] + """ISO-3166-1 alpha-3 Country Codes to include in the velocity calculation. + + Transactions not matching any of the provided will not be included in the + calculated velocity. + """ + + include_mccs: Optional[SequenceNotStr[str]] + """Merchant Category Codes to include in the velocity calculation. + + Transactions not matching this MCC will not be included in the calculated + velocity. + """ + + include_pan_entry_modes: Optional[ + List[ + Literal[ + "AUTO_ENTRY", + "BAR_CODE", + "CONTACTLESS", + "CREDENTIAL_ON_FILE", + "ECOMMERCE", + "ERROR_KEYED", + "ERROR_MAGNETIC_STRIPE", + "ICC", + "KEY_ENTERED", + "MAGNETIC_STRIPE", + "MANUAL", + "OCR", + "SECURE_CARDLESS", + "UNSPECIFIED", + "UNKNOWN", + ] + ] + ] + """PAN entry modes to include in the velocity calculation. + + Transactions not matching any of the provided will not be included in the + calculated velocity. + """ + + +class VelocityLimitParamsParam(TypedDict, total=False): + filters: Required[Filters] + + period: Required[VelocityLimitPeriodParam] + """Velocity over the current day since 00:00 / 12 AM in Eastern Time""" + + scope: Required[Literal["CARD", "ACCOUNT"]] + """The scope the velocity is calculated for""" + + limit_amount: Optional[int] + """ + The maximum amount of spend velocity allowed in the period in minor units (the + smallest unit of a currency, e.g. cents for USD). Transactions exceeding this + limit will be declined. + """ + + limit_count: Optional[int] + """ + The number of spend velocity impacting transactions may not exceed this limit in + the period. Transactions exceeding this limit will be declined. A spend velocity + impacting transaction is a transaction that has been authorized, and optionally + settled, or a force post (a transaction that settled without prior + authorization). + """ diff --git a/src/lithic/types/auth_rules/velocity_limit_period.py b/src/lithic/types/auth_rules/velocity_limit_period.py new file mode 100644 index 00000000..7ac08d9f --- /dev/null +++ b/src/lithic/types/auth_rules/velocity_limit_period.py @@ -0,0 +1,88 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel + +__all__ = [ + "VelocityLimitPeriod", + "TrailingWindowObject", + "FixedWindowDay", + "FixedWindowWeek", + "FixedWindowMonth", + "FixedWindowYear", +] + + +class TrailingWindowObject(BaseModel): + duration: int + """The size of the trailing window to calculate Spend Velocity over in seconds. + + The minimum value is 10 seconds, and the maximum value is 2678400 seconds (31 + days). + """ + + type: Literal["CUSTOM"] + + +class FixedWindowDay(BaseModel): + """Velocity over the current day since 00:00 / 12 AM in Eastern Time""" + + type: Literal["DAY"] + + +class FixedWindowWeek(BaseModel): + """ + Velocity over the current week since 00:00 / 12 AM in Eastern Time on specified `day_of_week` + """ + + type: Literal["WEEK"] + + day_of_week: Optional[int] = None + """The day of the week to start the week from. + + Following ISO-8601, 1 is Monday and 7 is Sunday. Defaults to Monday if not + specified. + """ + + +class FixedWindowMonth(BaseModel): + """ + Velocity over the current month since 00:00 / 12 AM in Eastern Time on specified `day_of_month`. + """ + + type: Literal["MONTH"] + + day_of_month: Optional[int] = None + """The day of the month to start from. + + Accepts values from 1 to 31, and will reset at the end of the month if the day + exceeds the number of days in the month. Defaults to the 1st of the month if not + specified. + """ + + +class FixedWindowYear(BaseModel): + """ + Velocity over the current year since 00:00 / 12 AM in Eastern Time on specified `month` and `day_of_month`. This validates the month and day of the year to start from is a real date. In the event that February 29th is selected, in non-leap years, the window will start from February 28th. + """ + + type: Literal["YEAR"] + + day_of_month: Optional[int] = None + """The day of the month to start from. + + Defaults to the 1st of the month if not specified. + """ + + month: Optional[int] = None + """The month to start from. + + 1 is January and 12 is December. Defaults to January if not specified. + """ + + +VelocityLimitPeriod: TypeAlias = Union[ + TrailingWindowObject, FixedWindowDay, FixedWindowWeek, FixedWindowMonth, FixedWindowYear +] diff --git a/src/lithic/types/auth_rules/velocity_limit_period_param.py b/src/lithic/types/auth_rules/velocity_limit_period_param.py new file mode 100644 index 00000000..d55b61f6 --- /dev/null +++ b/src/lithic/types/auth_rules/velocity_limit_period_param.py @@ -0,0 +1,88 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = [ + "VelocityLimitPeriodParam", + "TrailingWindowObject", + "FixedWindowDay", + "FixedWindowWeek", + "FixedWindowMonth", + "FixedWindowYear", +] + + +class TrailingWindowObject(TypedDict, total=False): + duration: Required[int] + """The size of the trailing window to calculate Spend Velocity over in seconds. + + The minimum value is 10 seconds, and the maximum value is 2678400 seconds (31 + days). + """ + + type: Required[Literal["CUSTOM"]] + + +class FixedWindowDay(TypedDict, total=False): + """Velocity over the current day since 00:00 / 12 AM in Eastern Time""" + + type: Required[Literal["DAY"]] + + +class FixedWindowWeek(TypedDict, total=False): + """ + Velocity over the current week since 00:00 / 12 AM in Eastern Time on specified `day_of_week` + """ + + type: Required[Literal["WEEK"]] + + day_of_week: int + """The day of the week to start the week from. + + Following ISO-8601, 1 is Monday and 7 is Sunday. Defaults to Monday if not + specified. + """ + + +class FixedWindowMonth(TypedDict, total=False): + """ + Velocity over the current month since 00:00 / 12 AM in Eastern Time on specified `day_of_month`. + """ + + type: Required[Literal["MONTH"]] + + day_of_month: int + """The day of the month to start from. + + Accepts values from 1 to 31, and will reset at the end of the month if the day + exceeds the number of days in the month. Defaults to the 1st of the month if not + specified. + """ + + +class FixedWindowYear(TypedDict, total=False): + """ + Velocity over the current year since 00:00 / 12 AM in Eastern Time on specified `month` and `day_of_month`. This validates the month and day of the year to start from is a real date. In the event that February 29th is selected, in non-leap years, the window will start from February 28th. + """ + + type: Required[Literal["YEAR"]] + + day_of_month: int + """The day of the month to start from. + + Defaults to the 1st of the month if not specified. + """ + + month: int + """The month to start from. + + 1 is January and 12 is December. Defaults to January if not specified. + """ + + +VelocityLimitPeriodParam: TypeAlias = Union[ + TrailingWindowObject, FixedWindowDay, FixedWindowWeek, FixedWindowMonth, FixedWindowYear +] diff --git a/src/lithic/types/auth_rules_backtest_report_created_webhook_event.py b/src/lithic/types/auth_rules_backtest_report_created_webhook_event.py new file mode 100644 index 00000000..836e9050 --- /dev/null +++ b/src/lithic/types/auth_rules_backtest_report_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .auth_rules.v2.backtest_results import BacktestResults + +__all__ = ["AuthRulesBacktestReportCreatedWebhookEvent"] + + +class AuthRulesBacktestReportCreatedWebhookEvent(BacktestResults): + event_type: Literal["auth_rules.backtest_report.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/auth_stream_secret.py b/src/lithic/types/auth_stream_secret.py index df85dc69..700172ab 100644 --- a/src/lithic/types/auth_stream_secret.py +++ b/src/lithic/types/auth_stream_secret.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/balance.py b/src/lithic/types/balance.py index edc571e1..1b12f089 100644 --- a/src/lithic/types/balance.py +++ b/src/lithic/types/balance.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime from typing_extensions import Literal @@ -9,6 +9,8 @@ class Balance(BaseModel): + """Balance""" + available_amount: int """Funds available for spend in the currency's smallest unit (e.g., cents for USD)""" @@ -16,12 +18,12 @@ class Balance(BaseModel): """Date and time for when the balance was first created.""" currency: str - """3-digit alphabetic ISO 4217 code for the local currency of the balance.""" + """3-character alphabetic ISO 4217 code for the local currency of the balance.""" financial_account_token: str """Globally unique identifier for the financial account that holds this balance.""" - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] + financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] """Type of financial account.""" last_transaction_event_token: str diff --git a/src/lithic/types/balance_list_params.py b/src/lithic/types/balance_list_params.py index 94a52f3a..087f6e4a 100644 --- a/src/lithic/types/balance_list_params.py +++ b/src/lithic/types/balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -21,5 +21,8 @@ class BalanceListParams(TypedDict, total=False): Defaults to latest available balances """ - financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE"] + business_account_token: str + """List balances for all financial accounts of a given business_account_token.""" + + financial_account_type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] """List balances for a given Financial Account type.""" diff --git a/src/lithic/types/balance_updated_webhook_event.py b/src/lithic/types/balance_updated_webhook_event.py new file mode 100644 index 00000000..522ffaac --- /dev/null +++ b/src/lithic/types/balance_updated_webhook_event.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import Literal + +from .._models import BaseModel +from .financial_account_balance import FinancialAccountBalance + +__all__ = ["BalanceUpdatedWebhookEvent"] + + +class BalanceUpdatedWebhookEvent(BaseModel): + data: List[FinancialAccountBalance] + + event_type: Literal["balance.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/book_transfer_create_params.py b/src/lithic/types/book_transfer_create_params.py new file mode 100644 index 00000000..584734cf --- /dev/null +++ b/src/lithic/types/book_transfer_create_params.py @@ -0,0 +1,100 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["BookTransferCreateParams"] + + +class BookTransferCreateParams(TypedDict, total=False): + amount: Required[int] + """Amount to be transferred in the currency's smallest unit (e.g., cents for USD). + + This should always be a positive value. + """ + + category: Required[ + Literal[ + "ADJUSTMENT", + "BALANCE_OR_FUNDING", + "DERECOGNITION", + "DISPUTE", + "FEE", + "INTERNAL", + "REWARD", + "PROGRAM_FUNDING", + "TRANSFER", + ] + ] + + from_financial_account_token: Required[str] + """ + Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + """ + + subtype: Required[str] + """The program specific subtype code for the specified category/type.""" + + to_financial_account_token: Required[str] + """ + Globally unique identifier for the financial account or card that will receive + the funds. Accepted type dependent on the program's use case. + """ + + type: Required[ + Literal[ + "ATM_BALANCE_INQUIRY", + "ATM_WITHDRAWAL", + "ATM_DECLINE", + "INTERNATIONAL_ATM_WITHDRAWAL", + "INACTIVITY", + "STATEMENT", + "MONTHLY", + "QUARTERLY", + "ANNUAL", + "CUSTOMER_SERVICE", + "ACCOUNT_MAINTENANCE", + "ACCOUNT_ACTIVATION", + "ACCOUNT_CLOSURE", + "CARD_REPLACEMENT", + "CARD_DELIVERY", + "CARD_CREATE", + "CURRENCY_CONVERSION", + "INTEREST", + "LATE_PAYMENT", + "BILL_PAYMENT", + "CASH_BACK", + "ACCOUNT_TO_ACCOUNT", + "CARD_TO_CARD", + "DISBURSE", + "BILLING_ERROR", + "LOSS_WRITE_OFF", + "EXPIRED_CARD", + "EARLY_DERECOGNITION", + "ESCHEATMENT", + "INACTIVITY_FEE_DOWN", + "PROVISIONAL_CREDIT", + "DISPUTE_WON", + "SERVICE", + "TRANSFER", + "COLLECTION", + ] + ] + """Type of the book transfer""" + + token: str + """Customer-provided token that will serve as an idempotency token. + + This token will become the transaction token. + """ + + external_id: str + """External ID defined by the customer""" + + memo: str + """Optional descriptor for the transfer.""" + + on_closed_account: Literal["FAIL", "USE_SUSPENSE"] + """What to do if the financial account is closed when posting an operation""" diff --git a/src/lithic/types/book_transfer_list_params.py b/src/lithic/types/book_transfer_list_params.py new file mode 100644 index 00000000..1c773ca7 --- /dev/null +++ b/src/lithic/types/book_transfer_list_params.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["BookTransferListParams"] + + +class BookTransferListParams(TypedDict, total=False): + account_token: str + + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + business_account_token: str + + category: Literal[ + "ADJUSTMENT", + "BALANCE_OR_FUNDING", + "DERECOGNITION", + "DISPUTE", + "FEE", + "INTERNAL", + "REWARD", + "PROGRAM_FUNDING", + "TRANSFER", + ] + """Book Transfer category to be returned.""" + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + financial_account_token: str + """ + Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + """ + + page_size: int + """Page size (for pagination).""" + + result: Literal["APPROVED", "DECLINED"] + """Book transfer result to be returned.""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ + + status: Literal["DECLINED", "SETTLED"] + """Book transfer status to be returned.""" diff --git a/src/lithic/types/book_transfer_response.py b/src/lithic/types/book_transfer_response.py new file mode 100644 index 00000000..a9648905 --- /dev/null +++ b/src/lithic/types/book_transfer_response.py @@ -0,0 +1,167 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .external_resource import ExternalResource + +__all__ = ["BookTransferResponse", "Event", "TransactionSeries"] + + +class Event(BaseModel): + """Book transfer Event""" + + token: str + """Globally unique identifier.""" + + amount: int + """ + Amount of the financial event that has been settled in the currency's smallest + unit (e.g., cents). + """ + + created: datetime + """Date and time when the financial event occurred. UTC time zone.""" + + detailed_results: List[Literal["APPROVED", "FUNDS_INSUFFICIENT"]] + + memo: str + """Memo for the transfer.""" + + result: Literal["APPROVED", "DECLINED"] + """ + APPROVED financial events were successful while DECLINED financial events were + declined by user, Lithic, or the network. + """ + + subtype: str + """The program specific subtype code for the specified category/type.""" + + type: Literal[ + "ATM_BALANCE_INQUIRY", + "ATM_WITHDRAWAL", + "ATM_DECLINE", + "INTERNATIONAL_ATM_WITHDRAWAL", + "INACTIVITY", + "STATEMENT", + "MONTHLY", + "QUARTERLY", + "ANNUAL", + "CUSTOMER_SERVICE", + "ACCOUNT_MAINTENANCE", + "ACCOUNT_ACTIVATION", + "ACCOUNT_CLOSURE", + "CARD_REPLACEMENT", + "CARD_DELIVERY", + "CARD_CREATE", + "CURRENCY_CONVERSION", + "INTEREST", + "LATE_PAYMENT", + "BILL_PAYMENT", + "CASH_BACK", + "ACCOUNT_TO_ACCOUNT", + "CARD_TO_CARD", + "DISBURSE", + "BILLING_ERROR", + "LOSS_WRITE_OFF", + "EXPIRED_CARD", + "EARLY_DERECOGNITION", + "ESCHEATMENT", + "INACTIVITY_FEE_DOWN", + "PROVISIONAL_CREDIT", + "DISPUTE_WON", + "SERVICE", + "TRANSFER", + "COLLECTION", + ] + """Type of the book transfer""" + + +class TransactionSeries(BaseModel): + """A series of transactions that are grouped together""" + + related_transaction_event_token: Optional[str] = None + + related_transaction_token: Optional[str] = None + + type: str + + +class BookTransferResponse(BaseModel): + """Book transfer transaction""" + + token: str + """Unique identifier for the transaction""" + + category: Literal[ + "ADJUSTMENT", + "BALANCE_OR_FUNDING", + "DERECOGNITION", + "DISPUTE", + "FEE", + "INTERNAL", + "REWARD", + "PROGRAM_FUNDING", + "TRANSFER", + ] + + created: datetime + """ISO 8601 timestamp of when the transaction was created""" + + currency: str + """ + 3-character alphabetic ISO 4217 code for the settling currency of the + transaction + """ + + events: List[Event] + """A list of all financial events that have modified this transfer""" + + family: Literal["TRANSFER"] + """TRANSFER - Book Transfer Transaction""" + + from_financial_account_token: str + """ + Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case + """ + + pending_amount: int + """ + Pending amount of the transaction in the currency's smallest unit (e.g., cents), + including any acquirer fees. + + The value of this field will go to zero over time once the financial transaction + is settled. + """ + + result: Literal["APPROVED", "DECLINED"] + + settled_amount: int + """ + Amount of the transaction that has been settled in the currency's smallest unit + (e.g., cents) + """ + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """The status of the transaction""" + + to_financial_account_token: str + """ + Globally unique identifier for the financial account or card that will receive + the funds. Accepted type dependent on the program's use case + """ + + updated: datetime + """ISO 8601 timestamp of when the transaction was last updated""" + + external_id: Optional[str] = None + """External ID defined by the customer""" + + external_resource: Optional[ExternalResource] = None + """External resource associated with the management operation""" + + transaction_series: Optional[TransactionSeries] = None + """A series of transactions that are grouped together""" diff --git a/src/lithic/types/book_transfer_reverse_params.py b/src/lithic/types/book_transfer_reverse_params.py new file mode 100644 index 00000000..11ec3df6 --- /dev/null +++ b/src/lithic/types/book_transfer_reverse_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["BookTransferReverseParams"] + + +class BookTransferReverseParams(TypedDict, total=False): + memo: str + """Optional descriptor for the reversal.""" diff --git a/src/lithic/types/book_transfer_transaction_created_webhook_event.py b/src/lithic/types/book_transfer_transaction_created_webhook_event.py new file mode 100644 index 00000000..a90acd39 --- /dev/null +++ b/src/lithic/types/book_transfer_transaction_created_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .book_transfer_response import BookTransferResponse + +__all__ = ["BookTransferTransactionCreatedWebhookEvent"] + + +class BookTransferTransactionCreatedWebhookEvent(BookTransferResponse): + """Book transfer transaction""" + + event_type: Literal["book_transfer_transaction.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/book_transfer_transaction_updated_webhook_event.py b/src/lithic/types/book_transfer_transaction_updated_webhook_event.py new file mode 100644 index 00000000..55dc6d8f --- /dev/null +++ b/src/lithic/types/book_transfer_transaction_updated_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .book_transfer_response import BookTransferResponse + +__all__ = ["BookTransferTransactionUpdatedWebhookEvent"] + + +class BookTransferTransactionUpdatedWebhookEvent(BookTransferResponse): + """Book transfer transaction""" + + event_type: Literal["book_transfer_transaction.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/business_account.py b/src/lithic/types/business_account.py deleted file mode 100644 index 2203e0c1..00000000 --- a/src/lithic/types/business_account.py +++ /dev/null @@ -1,28 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import Optional - -from .._models import BaseModel - -__all__ = ["BusinessAccount", "CollectionsConfiguration"] - - -class CollectionsConfiguration(BaseModel): - billing_period: int - """Number of days within the billing period""" - - payment_period: int - """Number of days after the billing period ends that a payment is required""" - - external_bank_account_token: Optional[str] = None - """The external bank account token to use for auto-collections""" - - -class BusinessAccount(BaseModel): - token: str - """Account token""" - - collections_configuration: Optional[CollectionsConfiguration] = None - - credit_limit: Optional[int] = None - """Credit limit extended to the Account""" diff --git a/src/lithic/types/card.py b/src/lithic/types/card.py index 65a45b61..99b56ba7 100644 --- a/src/lithic/types/card.py +++ b/src/lithic/types/card.py @@ -1,176 +1,23 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional -from datetime import datetime -from typing_extensions import Literal +from typing import Optional -from .._models import BaseModel -from .spend_limit_duration import SpendLimitDuration +from .non_pci_card import NonPCICard -__all__ = ["Card", "Funding"] +__all__ = ["Card"] -class Funding(BaseModel): - token: str - """A globally unique identifier for this FundingAccount.""" - - created: datetime - """ - An RFC 3339 string representing when this funding source was added to the Lithic - account. This may be `null`. UTC time zone. - """ - - last_four: str - """The last 4 digits of the account (e.g. - - bank account, debit card) associated with this FundingAccount. This may be null. - """ - - state: Literal["DELETED", "ENABLED", "PENDING"] - """State of funding source. - - Funding source states: - - - `ENABLED` - The funding account is available to use for card creation and - transactions. - - `PENDING` - The funding account is still being verified e.g. bank - micro-deposits verification. - - `DELETED` - The founding account has been deleted. - """ - - type: Literal["DEPOSITORY_CHECKING", "DEPOSITORY_SAVINGS"] - """Types of funding source: - - - `DEPOSITORY_CHECKING` - Bank checking account. - - `DEPOSITORY_SAVINGS` - Bank savings account. - """ - - account_name: Optional[str] = None - """Account name identifying the funding source. This may be `null`.""" - - nickname: Optional[str] = None - """The nickname given to the `FundingAccount` or `null` if it has no nickname.""" - - -class Card(BaseModel): - token: str - """Globally unique identifier.""" - - account_token: str - """Globally unique identifier for the account to which the card belongs.""" - - card_program_token: str - """Globally unique identifier for the card program on which the card exists.""" - - created: datetime - """An RFC 3339 timestamp for when the card was created. UTC time zone.""" - - funding: Funding - - last_four: str - """Last four digits of the card number.""" - - spend_limit: int - """Amount (in cents) to limit approved authorizations. - - Transaction requests above the spend limit will be declined. - """ - - spend_limit_duration: SpendLimitDuration - """Spend limit duration values: - - - `ANNUALLY` - Card will authorize transactions up to spend limit for the - trailing year. - - `FOREVER` - Card will authorize only up to spend limit for the entire lifetime - of the card. - - `MONTHLY` - Card will authorize transactions up to spend limit for the - trailing month. To support recurring monthly payments, which can occur on - different day every month, the time window we consider for monthly velocity - starts 6 days after the current calendar date one month prior. - - `TRANSACTION` - Card will authorize multiple transactions if each individual - transaction is under the spend limit. - """ - - state: Literal["CLOSED", "OPEN", "PAUSED", "PENDING_ACTIVATION", "PENDING_FULFILLMENT"] - """Card state values: - - - `CLOSED` - Card will no longer approve authorizations. Closing a card cannot - be undone. - - `OPEN` - Card will approve authorizations (if they match card and account - parameters). - - `PAUSED` - Card will decline authorizations, but can be resumed at a later - time. - - `PENDING_FULFILLMENT` - The initial state for cards of type `PHYSICAL`. The - card is provisioned pending manufacturing and fulfillment. Cards in this state - can accept authorizations for e-commerce purchases, but not for "Card Present" - purchases where the physical card itself is present. - - `PENDING_ACTIVATION` - Each business day at 2pm Eastern Time Zone (ET), cards - of type `PHYSICAL` in state `PENDING_FULFILLMENT` are sent to the card - production warehouse and updated to state `PENDING_ACTIVATION` . Similar to - `PENDING_FULFILLMENT`, cards in this state can be used for e-commerce - transactions. API clients should update the card's state to `OPEN` only after - the cardholder confirms receipt of the card. - - In sandbox, the same daily batch fulfillment occurs, but no cards are actually - manufactured. +class Card(NonPCICard): """ - - type: Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL"] - """Card types: - - - `VIRTUAL` - Card will authorize at any merchant and can be added to a digital - wallet like Apple Pay or Google Pay (if the card program is digital - wallet-enabled). - - `PHYSICAL` - Manufactured and sent to the cardholder. We offer white label - branding, credit, ATM, PIN debit, chip/EMV, NFC and magstripe functionality. - Reach out at [lithic.com/contact](https://lithic.com/contact) for more - information. - - `SINGLE_USE` - Card is closed upon first successful authorization. - - `MERCHANT_LOCKED` - _[Deprecated]_ Card is locked to the first merchant that - successfully authorizes the card. + Card details with potentially PCI sensitive information for Enterprise customers """ - auth_rule_tokens: Optional[List[str]] = None - """List of identifiers for the Auth Rule(s) that are applied on the card.""" - cvv: Optional[str] = None """Three digit cvv printed on the back of the card.""" - digital_card_art_token: Optional[str] = None - """ - Specifies the digital card art to be displayed in the user’s digital wallet - after tokenization. This artwork must be approved by Mastercard and configured - by Lithic to use. See - [Flexible Card Art Guide](https://docs.lithic.com/docs/about-digital-wallets#flexible-card-art). - """ - - exp_month: Optional[str] = None - """Two digit (MM) expiry month.""" - - exp_year: Optional[str] = None - """Four digit (yyyy) expiry year.""" - - hostname: Optional[str] = None - """Hostname of card’s locked merchant (will be empty if not applicable).""" - - memo: Optional[str] = None - """Friendly name to identify the card. - - We recommend against using this field to store JSON data as it can cause - unexpected behavior. - """ - pan: Optional[str] = None """Primary Account Number (PAN) (i.e. the card number). Customers must be PCI compliant to have PAN returned as a - field in production. Please contact - [support@lithic.com](mailto:support@lithic.com) for questions. - """ - - product_id: Optional[str] = None - """Only applicable to cards of type `PHYSICAL`. - - This must be configured with Lithic before use. Specifies the configuration - (i.e., physical card art) that the card should be manufactured with. + field in production. Please contact support@lithic.com for questions. """ diff --git a/src/lithic/types/card_bulk_order.py b/src/lithic/types/card_bulk_order.py new file mode 100644 index 00000000..c8ad3abc --- /dev/null +++ b/src/lithic/types/card_bulk_order.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardBulkOrder"] + + +class CardBulkOrder(BaseModel): + """Represents a bulk order for physical card shipments""" + + token: str + """Globally unique identifier for the bulk order""" + + card_tokens: List[str] + """List of card tokens associated with this bulk order""" + + created: datetime + """An RFC 3339 timestamp for when the bulk order was created. UTC time zone""" + + customer_product_id: Optional[str] = None + """Customer-specified product configuration for physical card manufacturing. + + This must be configured with Lithic before use + """ + + shipping_address: object + """Shipping address for all cards in this bulk order""" + + shipping_method: Literal["BULK_EXPEDITED"] + """Shipping method for all cards in this bulk order""" + + status: Literal["OPEN", "LOCKED"] + """Status of the bulk order. + + OPEN indicates the order is accepting cards. LOCKED indicates the order is + finalized and no more cards can be added + """ + + updated: datetime + """An RFC 3339 timestamp for when the bulk order was last updated. UTC time zone""" diff --git a/src/lithic/types/card_bulk_order_create_params.py b/src/lithic/types/card_bulk_order_create_params.py new file mode 100644 index 00000000..641747b6 --- /dev/null +++ b/src/lithic/types/card_bulk_order_create_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["CardBulkOrderCreateParams"] + + +class CardBulkOrderCreateParams(TypedDict, total=False): + customer_product_id: Required[str] + """Customer-specified product configuration for physical card manufacturing. + + This must be configured with Lithic before use + """ + + shipping_address: Required[object] + """Shipping address for all cards in this bulk order""" + + shipping_method: Required[Literal["BULK_EXPEDITED"]] + """Shipping method for all cards in this bulk order""" diff --git a/src/lithic/types/card_bulk_order_list_params.py b/src/lithic/types/card_bulk_order_list_params.py new file mode 100644 index 00000000..06bb77fc --- /dev/null +++ b/src/lithic/types/card_bulk_order_list_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["CardBulkOrderListParams"] + + +class CardBulkOrderListParams(TypedDict, total=False): + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + page_size: int + """Page size (for pagination).""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ diff --git a/src/lithic/types/card_bulk_order_update_params.py b/src/lithic/types/card_bulk_order_update_params.py new file mode 100644 index 00000000..20cd8820 --- /dev/null +++ b/src/lithic/types/card_bulk_order_update_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["CardBulkOrderUpdateParams"] + + +class CardBulkOrderUpdateParams(TypedDict, total=False): + status: Required[Literal["LOCKED"]] + """Status to update the bulk order to. Use LOCKED to finalize the order""" diff --git a/src/lithic/types/card_convert_physical_params.py b/src/lithic/types/card_convert_physical_params.py new file mode 100644 index 00000000..03911cf0 --- /dev/null +++ b/src/lithic/types/card_convert_physical_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .shared_params.carrier import Carrier +from .shared_params.shipping_address import ShippingAddress + +__all__ = ["CardConvertPhysicalParams"] + + +class CardConvertPhysicalParams(TypedDict, total=False): + shipping_address: Required[ShippingAddress] + """The shipping address this card will be sent to.""" + + carrier: Carrier + """If omitted, the previous carrier will be used.""" + + product_id: str + """Specifies the configuration (e.g. + + physical card art) that the card should be manufactured with, and only applies + to cards of type `PHYSICAL`. This must be configured with Lithic before use. + """ + + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] + """Shipping method for the card. + + Only applies to cards of type PHYSICAL. Use of options besides `STANDARD` + require additional permissions. + + - `STANDARD` - USPS regular mail or similar international option, with no + tracking + - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, + with tracking + - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with + tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping + """ diff --git a/src/lithic/types/card_converted_webhook_event.py b/src/lithic/types/card_converted_webhook_event.py new file mode 100644 index 00000000..2042a579 --- /dev/null +++ b/src/lithic/types/card_converted_webhook_event.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardConvertedWebhookEvent"] + + +class CardConvertedWebhookEvent(BaseModel): + card_token: str + """The token of the card that was created.""" + + event_type: Literal["card.converted"] + """The type of event that occurred.""" diff --git a/src/lithic/types/card_create_params.py b/src/lithic/types/card_create_params.py index 02ff4c76..648a3dcb 100644 --- a/src/lithic/types/card_create_params.py +++ b/src/lithic/types/card_create_params.py @@ -1,17 +1,18 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing_extensions import Literal, Required, TypedDict -from ..types import shared_params from .spend_limit_duration import SpendLimitDuration +from .shared_params.carrier import Carrier +from .shared_params.shipping_address import ShippingAddress __all__ = ["CardCreateParams"] class CardCreateParams(TypedDict, total=False): - type: Required[Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL"]] + type: Required[Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL", "UNLOCKED", "DIGITAL_WALLET"]] """Card types: - `VIRTUAL` - Card will authorize at any merchant and can be added to a digital @@ -24,6 +25,10 @@ class CardCreateParams(TypedDict, total=False): - `SINGLE_USE` - Card is closed upon first successful authorization. - `MERCHANT_LOCKED` - _[Deprecated]_ Card is locked to the first merchant that successfully authorizes the card. + - `UNLOCKED` - _[Deprecated]_ Similar behavior to VIRTUAL cards, please use + VIRTUAL instead. + - `DIGITAL_WALLET` - _[Deprecated]_ Similar behavior to VIRTUAL cards, please + use VIRTUAL instead. """ account_token: str @@ -35,6 +40,13 @@ class CardCreateParams(TypedDict, total=False): See [Managing Your Program](doc:managing-your-program) for more information. """ + bulk_order_token: str + """ + Globally unique identifier for an existing bulk order to associate this card + with. When specified, the card will be added to the bulk order for batch + shipment. Only applicable to cards of type PHYSICAL + """ + card_program_token: str """For card programs with more than one BIN range. @@ -45,7 +57,7 @@ class CardCreateParams(TypedDict, total=False): test creating cards on specific card programs. """ - carrier: shared_params.Carrier + carrier: Carrier digital_card_art_token: str """ @@ -70,17 +82,13 @@ class CardCreateParams(TypedDict, total=False): """ memo: str - """Friendly name to identify the card. - - We recommend against using this field to store JSON data as it can cause - unexpected behavior. - """ + """Friendly name to identify the card.""" pin: str """Encrypted PIN block (in base64). - Only applies to cards of type `PHYSICAL` and `VIRTUAL`. See - [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block-enterprise). + Applies to cards of type `PHYSICAL` and `VIRTUAL`. See + [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block). """ product_id: str @@ -90,15 +98,76 @@ class CardCreateParams(TypedDict, total=False): (i.e., physical card art) that the card should be manufactured with. """ + replacement_account_token: str + """Restricted field limited to select use cases. + + Lithic will reach out directly if this field should be used. Globally unique + identifier for the replacement card's account. If this field is specified, + `replacement_for` must also be specified. If `replacement_for` is specified and + this field is omitted, the replacement card's account will be inferred from the + card being replaced. + """ + + replacement_comment: str + """ + Additional context or information related to the card that this card will + replace. + """ + replacement_for: str - """Only applicable to cards of type `PHYSICAL`. + """Globally unique identifier for the card that this card will replace. + + If the card type is `PHYSICAL` it will be replaced by a `PHYSICAL` card. If the + card type is `VIRTUAL` it will be replaced by a `VIRTUAL` card. + """ - Globally unique identifier for the card that this physical card will replace. + replacement_substatus: Literal[ + "LOST", + "COMPROMISED", + "DAMAGED", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "SUSPICIOUS_ACTIVITY", + "INTERNAL_REVIEW", + "EXPIRED", + "UNDELIVERABLE", + "OTHER", + ] + """Card state substatus values for the card that this card will replace: + + - `LOST` - The physical card is no longer in the cardholder's possession due to + being lost or never received by the cardholder. + - `COMPROMISED` - Card information has been exposed, potentially leading to + unauthorized access. This may involve physical card theft, cloning, or online + data breaches. + - `DAMAGED` - The physical card is not functioning properly, such as having chip + failures or a demagnetized magnetic stripe. + - `END_USER_REQUEST` - The cardholder requested the closure of the card for + reasons unrelated to fraud or damage, such as switching to a different product + or closing the account. + - `ISSUER_REQUEST` - The issuer closed the card for reasons unrelated to fraud + or damage, such as account inactivity, product or policy changes, or + technology upgrades. + - `NOT_ACTIVE` - The card hasn’t had any transaction activity for a specified + period, applicable to statuses like `PAUSED` or `CLOSED`. + - `SUSPICIOUS_ACTIVITY` - The card has one or more suspicious transactions or + activities that require review. This can involve prompting the cardholder to + confirm legitimate use or report confirmed fraud. + - `INTERNAL_REVIEW` - The card is temporarily paused pending further internal + review. + - `EXPIRED` - The card has expired and has been closed without being reissued. + - `UNDELIVERABLE` - The card cannot be delivered to the cardholder and has been + returned. + - `OTHER` - The reason for the status does not fall into any of the above + categories. A comment should be provided to specify the reason. """ - shipping_address: shared_params.ShippingAddress + shipping_address: ShippingAddress - shipping_method: Literal["2_DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] """Shipping method for the card. Only applies to cards of type PHYSICAL. Use of options besides `STANDARD` @@ -109,19 +178,22 @@ class CardCreateParams(TypedDict, total=False): - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping """ spend_limit: int - """Amount (in cents) to limit approved authorizations. + """Amount (in cents) to limit approved authorizations (e.g. - Transaction requests above the spend limit will be declined. Note that a spend - limit of 0 is effectively no limit, and should only be used to reset or remove a - prior limit. Only a limit of 1 or above will result in declined transactions due - to checks against the card limit. + 100000 would be a $1,000 limit). Transaction requests above the spend limit will + be declined. Note that a spend limit of 0 is effectively no limit, and should + only be used to reset or remove a prior limit. Only a limit of 1 or above will + result in declined transactions due to checks against the card limit. """ spend_limit_duration: SpendLimitDuration diff --git a/src/lithic/types/card_created_webhook_event.py b/src/lithic/types/card_created_webhook_event.py new file mode 100644 index 00000000..8b1e8550 --- /dev/null +++ b/src/lithic/types/card_created_webhook_event.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardCreatedWebhookEvent"] + + +class CardCreatedWebhookEvent(BaseModel): + card_token: str + """The token of the card that was created.""" + + event_type: Literal["card.created"] + """The type of event that occurred.""" + + replacement_for: Optional[str] = None + """The token of the card that was replaced, if the new card is a replacement card.""" diff --git a/src/lithic/types/card_embed_params.py b/src/lithic/types/card_embed_params.py index 97b9c764..7e21ab6d 100644 --- a/src/lithic/types/card_embed_params.py +++ b/src/lithic/types/card_embed_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_embed_response.py b/src/lithic/types/card_embed_response.py index 5e480f33..7336c3be 100644 --- a/src/lithic/types/card_embed_response.py +++ b/src/lithic/types/card_embed_response.py @@ -1,6 +1,7 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing_extensions import TypeAlias __all__ = ["CardEmbedResponse"] -CardEmbedResponse = str +CardEmbedResponse: TypeAlias = str diff --git a/src/lithic/types/card_get_embed_html_params.py b/src/lithic/types/card_get_embed_html_params.py deleted file mode 100644 index 86bb86d3..00000000 --- a/src/lithic/types/card_get_embed_html_params.py +++ /dev/null @@ -1,41 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["CardGetEmbedHTMLParams"] - - -class CardGetEmbedHTMLParams(TypedDict, total=False): - token: Required[str] - """Globally unique identifier for the card to be displayed.""" - - css: str - """ - A publicly available URI, so the white-labeled card element can be styled with - the client's branding. - """ - - expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """An RFC 3339 timestamp for when the request should expire. UTC time zone. - - If no timezone is specified, UTC will be used. If payload does not contain an - expiration, the request will never expire. - - Using an `expiration` reduces the risk of a - [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying - the `expiration`, in the event that a malicious user gets a copy of your request - in transit, they will be able to obtain the response data indefinitely. - """ - - target_origin: str - """Required if you want to post the element clicked to the parent iframe. - - If you supply this param, you can also capture click events in the parent iframe - by adding an event listener. - """ diff --git a/src/lithic/types/card_get_embed_url_params.py b/src/lithic/types/card_get_embed_url_params.py deleted file mode 100644 index df77c553..00000000 --- a/src/lithic/types/card_get_embed_url_params.py +++ /dev/null @@ -1,41 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["CardGetEmbedURLParams"] - - -class CardGetEmbedURLParams(TypedDict, total=False): - token: Required[str] - """Globally unique identifier for the card to be displayed.""" - - css: str - """ - A publicly available URI, so the white-labeled card element can be styled with - the client's branding. - """ - - expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """An RFC 3339 timestamp for when the request should expire. UTC time zone. - - If no timezone is specified, UTC will be used. If payload does not contain an - expiration, the request will never expire. - - Using an `expiration` reduces the risk of a - [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying - the `expiration`, in the event that a malicious user gets a copy of your request - in transit, they will be able to obtain the response data indefinitely. - """ - - target_origin: str - """Required if you want to post the element clicked to the parent iframe. - - If you supply this param, you can also capture click events in the parent iframe - by adding an event listener. - """ diff --git a/src/lithic/types/card_list_params.py b/src/lithic/types/card_list_params.py index df74475a..456a8bea 100644 --- a/src/lithic/types/card_list_params.py +++ b/src/lithic/types/card_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -33,6 +33,9 @@ class CardListParams(TypedDict, total=False): Used to retrieve the previous page of results before this item. """ + memo: str + """Returns cards containing the specified partial or full memo text.""" + page_size: int """Page size (for pagination).""" diff --git a/src/lithic/types/card_product_credit_detail_response.py b/src/lithic/types/card_product_credit_detail_response.py deleted file mode 100644 index 025a73b5..00000000 --- a/src/lithic/types/card_product_credit_detail_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from .._models import BaseModel - -__all__ = ["CardProductCreditDetailResponse"] - - -class CardProductCreditDetailResponse(BaseModel): - credit_extended: int - """The amount of credit extended within the program""" - - credit_limit: int - """The total credit limit of the program""" diff --git a/src/lithic/types/card_program.py b/src/lithic/types/card_program.py index 858502a1..a00fad51 100644 --- a/src/lithic/types/card_program.py +++ b/src/lithic/types/card_program.py @@ -1,5 +1,6 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import List, Optional from datetime import datetime from .._models import BaseModel @@ -11,6 +12,12 @@ class CardProgram(BaseModel): token: str """Globally unique identifier.""" + account_level_management_enabled: bool + """Whether the card program is participating in Account Level Management. + + Currently applicable to Visa card programs only. + """ + created: datetime """Timestamp of when the card program was created.""" @@ -22,3 +29,12 @@ class CardProgram(BaseModel): pan_range_start: str """The first digits of the card number that this card program starts with.""" + + cardholder_currency: Optional[str] = None + """3-character alphabetic ISO 4217 code for the currency of the cardholder.""" + + settlement_currencies: Optional[List[str]] = None + """ + List of 3-character alphabetic ISO 4217 codes for the currencies that the card + program supports for settlement. + """ diff --git a/src/lithic/types/card_program_list_params.py b/src/lithic/types/card_program_list_params.py index 15257297..cfaa30b8 100644 --- a/src/lithic/types/card_program_list_params.py +++ b/src/lithic/types/card_program_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_provision_params.py b/src/lithic/types/card_provision_params.py index bde462a9..48ba1e86 100644 --- a/src/lithic/types/card_provision_params.py +++ b/src/lithic/types/card_provision_params.py @@ -1,14 +1,18 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing_extensions import Literal, TypedDict +from typing import Union +from typing_extensions import Literal, Annotated, TypedDict + +from .._types import Base64FileInput +from .._utils import PropertyInfo __all__ = ["CardProvisionParams"] class CardProvisionParams(TypedDict, total=False): - certificate: str + certificate: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] """Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only `activationData` in the response. Apple's public leaf @@ -17,17 +21,31 @@ class CardProvisionParams(TypedDict, total=False): wallet. """ + client_device_id: str + """ + Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Stable device identification set by the wallet + provider. + """ + + client_wallet_account_id: str + """ + Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Consumer ID that identifies the wallet account + holder entity. + """ + digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] """Name of digital wallet provider.""" - nonce: str + nonce: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] """Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only `activationData` in the response. Base64 cryptographic nonce provided by the device's wallet. """ - nonce_signature: str + nonce_signature: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] """Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only `activationData` in the response. Base64 cryptographic diff --git a/src/lithic/types/card_provision_response.py b/src/lithic/types/card_provision_response.py index 24c9fa70..23069751 100644 --- a/src/lithic/types/card_provision_response.py +++ b/src/lithic/types/card_provision_response.py @@ -1,11 +1,19 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Union, Optional +from typing_extensions import TypeAlias from .._models import BaseModel +from .provision_response import ProvisionResponse -__all__ = ["CardProvisionResponse"] +__all__ = ["CardProvisionResponse", "ProvisioningPayload"] + +ProvisioningPayload: TypeAlias = Union[str, ProvisionResponse] class CardProvisionResponse(BaseModel): - provisioning_payload: Optional[str] = None + provisioning_payload: Optional[ProvisioningPayload] = None + """ + Base64 encoded JSON payload representing a payment card that can be passed to a + device's digital wallet. Applies to Google and Samsung Pay wallets. + """ diff --git a/src/lithic/types/card_reissue_params.py b/src/lithic/types/card_reissue_params.py index db5da95c..828ff228 100644 --- a/src/lithic/types/card_reissue_params.py +++ b/src/lithic/types/card_reissue_params.py @@ -1,16 +1,17 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing_extensions import Literal, TypedDict -from ..types import shared_params +from .shared_params.carrier import Carrier +from .shared_params.shipping_address import ShippingAddress __all__ = ["CardReissueParams"] class CardReissueParams(TypedDict, total=False): - carrier: shared_params.Carrier + carrier: Carrier """If omitted, the previous carrier will be used.""" product_id: str @@ -20,21 +21,27 @@ class CardReissueParams(TypedDict, total=False): to cards of type `PHYSICAL`. This must be configured with Lithic before use. """ - shipping_address: shared_params.ShippingAddress + shipping_address: ShippingAddress """If omitted, the previous shipping address will be used.""" - shipping_method: Literal["2-DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] """Shipping method for the card. - Use of options besides `STANDARD` require additional permissions. + Only applies to cards of type PHYSICAL. Use of options besides `STANDARD` + require additional permissions. - `STANDARD` - USPS regular mail or similar international option, with no tracking - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping """ diff --git a/src/lithic/types/card_reissued_webhook_event.py b/src/lithic/types/card_reissued_webhook_event.py new file mode 100644 index 00000000..5a31af38 --- /dev/null +++ b/src/lithic/types/card_reissued_webhook_event.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardReissuedWebhookEvent"] + + +class CardReissuedWebhookEvent(BaseModel): + event_type: Literal["card.reissued"] + """The type of event that occurred.""" + + card_token: Optional[str] = None + """The token of the card that was reissued.""" diff --git a/src/lithic/types/card_renew_params.py b/src/lithic/types/card_renew_params.py index a3d6fd9a..e6fcffe2 100644 --- a/src/lithic/types/card_renew_params.py +++ b/src/lithic/types/card_renew_params.py @@ -1,19 +1,20 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing_extensions import Literal, Required, TypedDict -from ..types import shared_params +from .shared_params.carrier import Carrier +from .shared_params.shipping_address import ShippingAddress __all__ = ["CardRenewParams"] class CardRenewParams(TypedDict, total=False): - shipping_address: Required[shared_params.ShippingAddress] + shipping_address: Required[ShippingAddress] """The shipping address this card will be sent to.""" - carrier: shared_params.Carrier + carrier: Carrier """If omitted, the previous carrier will be used.""" exp_month: str @@ -37,18 +38,24 @@ class CardRenewParams(TypedDict, total=False): to cards of type `PHYSICAL`. This must be configured with Lithic before use. """ - shipping_method: Literal["2-DAY", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING"] + shipping_method: Literal[ + "2_DAY", "BULK_EXPEDITED", "EXPEDITED", "EXPRESS", "PRIORITY", "STANDARD", "STANDARD_WITH_TRACKING" + ] """Shipping method for the card. - Use of options besides `STANDARD` require additional permissions. + Only applies to cards of type PHYSICAL. Use of options besides `STANDARD` + require additional permissions. - `STANDARD` - USPS regular mail or similar international option, with no tracking - `STANDARD_WITH_TRACKING` - USPS regular mail or similar international option, with tracking - `PRIORITY` - USPS Priority, 1-3 day shipping, with tracking - - `EXPRESS` - FedEx Express, 3-day shipping, with tracking - - `2_DAY` - FedEx 2-day shipping, with tracking - - `EXPEDITED` - FedEx Standard Overnight or similar international option, with + - `EXPRESS` - FedEx or UPS depending on card manufacturer, Express, 3-day + shipping, with tracking + - `2_DAY` - FedEx or UPS depending on card manufacturer, 2-day shipping, with tracking + - `EXPEDITED` - FedEx or UPS depending on card manufacturer, Standard Overnight + or similar international option, with tracking + - `BULK_EXPEDITED` - Bulk shipment with Expedited shipping """ diff --git a/src/lithic/types/card_renewed_webhook_event.py b/src/lithic/types/card_renewed_webhook_event.py new file mode 100644 index 00000000..22c07ad3 --- /dev/null +++ b/src/lithic/types/card_renewed_webhook_event.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardRenewedWebhookEvent"] + + +class CardRenewedWebhookEvent(BaseModel): + event_type: Literal["card.renewed"] + """The type of event that occurred.""" + + card_token: Optional[str] = None + """The token of the card that was renewed.""" + + exp_month: Optional[str] = None + """The new expiration month of the card.""" + + exp_year: Optional[str] = None + """The new expiration year of the card.""" + + previous_exp_month: Optional[str] = None + """The previous expiration month of the card.""" + + previous_exp_year: Optional[str] = None + """The previous expiration year of the card.""" diff --git a/src/lithic/types/card_search_by_pan_params.py b/src/lithic/types/card_search_by_pan_params.py index c9ec8935..2a0504ef 100644 --- a/src/lithic/types/card_search_by_pan_params.py +++ b/src/lithic/types/card_search_by_pan_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_shipped_webhook_event.py b/src/lithic/types/card_shipped_webhook_event.py new file mode 100644 index 00000000..e6dbe96f --- /dev/null +++ b/src/lithic/types/card_shipped_webhook_event.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardShippedWebhookEvent"] + + +class CardShippedWebhookEvent(BaseModel): + bulk_order_token: Optional[str] = None + """The token of the bulk order associated with this card shipment, if applicable.""" + + card_token: str + """The token of the card that was shipped.""" + + event_type: Literal["card.shipped"] + """The type of event that occurred.""" + + shipping_method: Literal[ + "Ex-US expedited with tracking", + "Ex-US standard with tracking", + "Ex-US standard without tracking", + "FedEx 2 days", + "FedEx express", + "FedEx overnight", + "USPS priority", + "USPS with tracking", + "USPS without tracking envelope", + "USPS without tracking envelope non-machine", + "USPS without tracking flat", + ] + """The specific shipping method used to ship the card.""" + + tracking_number: Optional[str] = None + """The tracking number of the shipment.""" diff --git a/src/lithic/types/card_spend_limits.py b/src/lithic/types/card_spend_limits.py index 782d2a7e..4517050e 100644 --- a/src/lithic/types/card_spend_limits.py +++ b/src/lithic/types/card_spend_limits.py @@ -1,22 +1,66 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from .._models import BaseModel -__all__ = ["CardSpendLimits", "AvailableSpendLimit"] +__all__ = ["CardSpendLimits", "AvailableSpendLimit", "SpendLimit", "SpendVelocity"] class AvailableSpendLimit(BaseModel): annually: Optional[int] = None - """The available spend limit relative to the annual limit configured on the Card.""" + """ + The available spend limit (in cents) relative to the annual limit configured on + the Card (e.g. 100000 would be a $1,000 limit). + """ forever: Optional[int] = None - """The available spend limit relative to the forever limit configured on the Card.""" + """ + The available spend limit (in cents) relative to the forever limit configured on + the Card. + """ monthly: Optional[int] = None - """The available spend limit relative to the monthly limit configured on the Card.""" + """ + The available spend limit (in cents) relative to the monthly limit configured on + the Card. + """ + + +class SpendLimit(BaseModel): + annually: Optional[int] = None + """The configured annual spend limit (in cents) on the Card.""" + + forever: Optional[int] = None + """The configured forever spend limit (in cents) on the Card.""" + + monthly: Optional[int] = None + """The configured monthly spend limit (in cents) on the Card.""" + + +class SpendVelocity(BaseModel): + annually: Optional[int] = None + """Current annual spend velocity (in cents) on the Card. + + Present if annual spend limit is set. + """ + + forever: Optional[int] = None + """Current forever spend velocity (in cents) on the Card. + + Present if forever spend limit is set. + """ + + monthly: Optional[int] = None + """Current monthly spend velocity (in cents) on the Card. + + Present if monthly spend limit is set. + """ class CardSpendLimits(BaseModel): available_spend_limit: AvailableSpendLimit + + spend_limit: Optional[SpendLimit] = None + + spend_velocity: Optional[SpendVelocity] = None diff --git a/src/lithic/types/card_transaction_enhanced_data_created_webhook_event.py b/src/lithic/types/card_transaction_enhanced_data_created_webhook_event.py new file mode 100644 index 00000000..708f2082 --- /dev/null +++ b/src/lithic/types/card_transaction_enhanced_data_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .transactions.events.enhanced_data import EnhancedData + +__all__ = ["CardTransactionEnhancedDataCreatedWebhookEvent"] + + +class CardTransactionEnhancedDataCreatedWebhookEvent(EnhancedData): + event_type: Literal["card_transaction.enhanced_data.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/card_transaction_enhanced_data_updated_webhook_event.py b/src/lithic/types/card_transaction_enhanced_data_updated_webhook_event.py new file mode 100644 index 00000000..d58b3eb0 --- /dev/null +++ b/src/lithic/types/card_transaction_enhanced_data_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .transactions.events.enhanced_data import EnhancedData + +__all__ = ["CardTransactionEnhancedDataUpdatedWebhookEvent"] + + +class CardTransactionEnhancedDataUpdatedWebhookEvent(EnhancedData): + event_type: Literal["card_transaction.enhanced_data.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/card_transaction_updated_webhook_event.py b/src/lithic/types/card_transaction_updated_webhook_event.py new file mode 100644 index 00000000..09ad978e --- /dev/null +++ b/src/lithic/types/card_transaction_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .transaction import Transaction + +__all__ = ["CardTransactionUpdatedWebhookEvent"] + + +class CardTransactionUpdatedWebhookEvent(Transaction): + event_type: Literal["card_transaction.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/card_update_params.py b/src/lithic/types/card_update_params.py index e5d52a7c..b658579c 100644 --- a/src/lithic/types/card_update_params.py +++ b/src/lithic/types/card_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -10,11 +10,8 @@ class CardUpdateParams(TypedDict, total=False): - auth_rule_token: str - """ - Identifier for any Auth Rules that will be applied to transactions taking place - with the card. - """ + comment: str + """Additional context or information related to the card.""" digital_card_art_token: str """ @@ -25,26 +22,36 @@ class CardUpdateParams(TypedDict, total=False): """ memo: str - """Friendly name to identify the card. + """Friendly name to identify the card.""" - We recommend against using this field to store JSON data as it can cause - unexpected behavior. + network_program_token: str + """Globally unique identifier for the card's network program. + + Currently applicable to Visa cards participating in Account Level Management + only. """ pin: str """Encrypted PIN block (in base64). - Only applies to cards of type `PHYSICAL` and `VIRTUAL`. See - [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block-enterprise). + Only applies to cards of type `PHYSICAL` and `VIRTUAL`. Changing PIN also resets + PIN status to `OK`. See + [Encrypted PIN Block](https://docs.lithic.com/docs/cards#encrypted-pin-block). + """ + + pin_status: Literal["OK"] + """Indicates if a card is blocked due a PIN status issue (e.g. + + excessive incorrect attempts). Can only be set to `OK` to unblock a card. """ spend_limit: int - """Amount (in cents) to limit approved authorizations. + """Amount (in cents) to limit approved authorizations (e.g. - Transaction requests above the spend limit will be declined. Note that a spend - limit of 0 is effectively no limit, and should only be used to reset or remove a - prior limit. Only a limit of 1 or above will result in declined transactions due - to checks against the card limit. + 100000 would be a $1,000 limit). Transaction requests above the spend limit will + be declined. Note that a spend limit of 0 is effectively no limit, and should + only be used to reset or remove a prior limit. Only a limit of 1 or above will + result in declined transactions due to checks against the card limit. """ spend_limit_duration: SpendLimitDuration @@ -72,3 +79,45 @@ class CardUpdateParams(TypedDict, total=False): - `PAUSED` - Card will decline authorizations, but can be resumed at a later time. """ + + substatus: Literal[ + "LOST", + "COMPROMISED", + "DAMAGED", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "SUSPICIOUS_ACTIVITY", + "INTERNAL_REVIEW", + "EXPIRED", + "UNDELIVERABLE", + "OTHER", + ] + """Card state substatus values: + + - `LOST` - The physical card is no longer in the cardholder's possession due to + being lost or never received by the cardholder. + - `COMPROMISED` - Card information has been exposed, potentially leading to + unauthorized access. This may involve physical card theft, cloning, or online + data breaches. + - `DAMAGED` - The physical card is not functioning properly, such as having chip + failures or a demagnetized magnetic stripe. + - `END_USER_REQUEST` - The cardholder requested the closure of the card for + reasons unrelated to fraud or damage, such as switching to a different product + or closing the account. + - `ISSUER_REQUEST` - The issuer closed the card for reasons unrelated to fraud + or damage, such as account inactivity, product or policy changes, or + technology upgrades. + - `NOT_ACTIVE` - The card hasn’t had any transaction activity for a specified + period, applicable to statuses like `PAUSED` or `CLOSED`. + - `SUSPICIOUS_ACTIVITY` - The card has one or more suspicious transactions or + activities that require review. This can involve prompting the cardholder to + confirm legitimate use or report confirmed fraud. + - `INTERNAL_REVIEW` - The card is temporarily paused pending further internal + review. + - `EXPIRED` - The card has expired and has been closed without being reissued. + - `UNDELIVERABLE` - The card cannot be delivered to the cardholder and has been + returned. + - `OTHER` - The reason for the status does not fall into any of the above + categories. A comment should be provided to specify the reason. + """ diff --git a/src/lithic/types/card_web_provision_params.py b/src/lithic/types/card_web_provision_params.py new file mode 100644 index 00000000..bc24e45c --- /dev/null +++ b/src/lithic/types/card_web_provision_params.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["CardWebProvisionParams"] + + +class CardWebProvisionParams(TypedDict, total=False): + client_device_id: str + """Only applicable if `digital_wallet` is GOOGLE_PAY. + + Google Pay Web Push Provisioning device identifier required for the tokenization + flow + """ + + client_wallet_account_id: str + """Only applicable if `digital_wallet` is GOOGLE_PAY. + + Google Pay Web Push Provisioning wallet account identifier required for the + tokenization flow + """ + + digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY"] + """Name of digital wallet provider.""" + + server_session_id: str + """Only applicable if `digital_wallet` is GOOGLE_PAY. + + Google Pay Web Push Provisioning session identifier required for the FPAN flow. + """ diff --git a/src/lithic/types/card_web_provision_response.py b/src/lithic/types/card_web_provision_response.py new file mode 100644 index 00000000..0f8f2de0 --- /dev/null +++ b/src/lithic/types/card_web_provision_response.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import TypeAlias + +from .._models import BaseModel + +__all__ = [ + "CardWebProvisionResponse", + "AppleWebPushProvisioningResponse", + "AppleWebPushProvisioningResponseJws", + "AppleWebPushProvisioningResponseJwsHeader", + "GoogleWebPushProvisioningResponse", +] + + +class AppleWebPushProvisioningResponseJwsHeader(BaseModel): + """ + JWS unprotected headers containing header parameters that aren't integrity-protected by the JWS signature. + """ + + kid: Optional[str] = None + """The ID for the JWS Public Key of the key pair used to generate the signature.""" + + +class AppleWebPushProvisioningResponseJws(BaseModel): + """JWS object required for handoff to Apple's script.""" + + header: Optional[AppleWebPushProvisioningResponseJwsHeader] = None + """ + JWS unprotected headers containing header parameters that aren't + integrity-protected by the JWS signature. + """ + + payload: Optional[str] = None + """Base64url encoded JSON object containing the provisioning payload.""" + + protected: Optional[str] = None + """ + Base64url encoded JWS protected headers containing the header parameters that + are integrity-protected by the JWS signature. + """ + + signature: Optional[str] = None + """Base64url encoded signature of the JWS object.""" + + +class AppleWebPushProvisioningResponse(BaseModel): + jws: Optional[AppleWebPushProvisioningResponseJws] = None + """JWS object required for handoff to Apple's script.""" + + state: Optional[str] = None + """A unique identifier for the JWS object.""" + + +class GoogleWebPushProvisioningResponse(BaseModel): + google_opc: Optional[str] = None + """ + A base64 encoded and encrypted payload representing card data for the Google Pay + UWPP FPAN flow. + """ + + tsp_opc: Optional[str] = None + """ + A base64 encoded and encrypted payload representing card data for the Google Pay + UWPP tokenization flow. + """ + + +CardWebProvisionResponse: TypeAlias = Union[AppleWebPushProvisioningResponse, GoogleWebPushProvisioningResponse] diff --git a/src/lithic/types/cardholder_authentication.py b/src/lithic/types/cardholder_authentication.py new file mode 100644 index 00000000..e0c2aa36 --- /dev/null +++ b/src/lithic/types/cardholder_authentication.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["CardholderAuthentication"] + + +class CardholderAuthentication(BaseModel): + authentication_method: Literal["FRICTIONLESS", "CHALLENGE", "NONE"] + """Indicates the method used to authenticate the cardholder.""" + + authentication_result: Literal["ATTEMPTS", "DECLINE", "NONE", "SUCCESS"] + """Indicates the outcome of the 3DS authentication process.""" + + decision_made_by: Literal[ + "CUSTOMER_RULES", "CUSTOMER_ENDPOINT", "LITHIC_DEFAULT", "LITHIC_RULES", "NETWORK", "UNKNOWN" + ] + """Indicates which party made the 3DS authentication decision.""" + + liability_shift: Literal["3DS_AUTHENTICATED", "TOKEN_AUTHENTICATED", "NONE"] + """Indicates whether chargeback liability shift applies to the transaction. + + Possible enum values: + + - `3DS_AUTHENTICATED`: The transaction was fully authenticated through a 3-D + Secure flow, chargeback liability shift applies. + - `NONE`: Chargeback liability shift has not shifted to the issuer, i.e. the + merchant is liable. + - `TOKEN_AUTHENTICATED`: The transaction was a tokenized payment with validated + cryptography, possibly recurring. Chargeback liability shift to the issuer + applies. + """ + + three_ds_authentication_token: Optional[str] = None + """ + Unique identifier you can use to match a given 3DS authentication (available via + the three_ds_authentication.created event webhook) and the transaction. Note + that in cases where liability shift does not occur, this token is matched to the + transaction on a best-effort basis. + """ diff --git a/src/lithic/types/cards/__init__.py b/src/lithic/types/cards/__init__.py index be61455b..3f28d3d0 100644 --- a/src/lithic/types/cards/__init__.py +++ b/src/lithic/types/cards/__init__.py @@ -1,8 +1,6 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from .balance_list_params import BalanceListParams as BalanceListParams -from .aggregate_balance_list_params import AggregateBalanceListParams as AggregateBalanceListParams -from .aggregate_balance_list_response import AggregateBalanceListResponse as AggregateBalanceListResponse from .financial_transaction_list_params import FinancialTransactionListParams as FinancialTransactionListParams diff --git a/src/lithic/types/cards/aggregate_balance_list_params.py b/src/lithic/types/cards/aggregate_balance_list_params.py deleted file mode 100644 index 240c51a4..00000000 --- a/src/lithic/types/cards/aggregate_balance_list_params.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing_extensions import TypedDict - -__all__ = ["AggregateBalanceListParams"] - - -class AggregateBalanceListParams(TypedDict, total=False): - account_token: str - """Cardholder to retrieve aggregate balances for.""" - - business_account_token: str - """Business to retrieve aggregate balances for.""" diff --git a/src/lithic/types/cards/aggregate_balance_list_response.py b/src/lithic/types/cards/aggregate_balance_list_response.py deleted file mode 100644 index 849e15ab..00000000 --- a/src/lithic/types/cards/aggregate_balance_list_response.py +++ /dev/null @@ -1,48 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from datetime import datetime - -from ..._models import BaseModel - -__all__ = ["AggregateBalanceListResponse"] - - -class AggregateBalanceListResponse(BaseModel): - available_amount: int - """Funds available for spend in the currency's smallest unit (e.g., cents for USD)""" - - created: datetime - """Date and time for when the balance was first created.""" - - currency: str - """3-digit alphabetic ISO 4217 code for the local currency of the balance.""" - - last_card_token: str - """ - Globally unique identifier for the card that had its balance updated most - recently - """ - - last_transaction_event_token: str - """ - Globally unique identifier for the last transaction event that impacted this - balance - """ - - last_transaction_token: str - """Globally unique identifier for the last transaction that impacted this balance""" - - pending_amount: int - """Funds not available for spend due to card authorizations or pending ACH release. - - Shown in the currency's smallest unit (e.g., cents for USD) - """ - - total_amount: int - """ - The sum of available and pending balance in the currency's smallest unit (e.g., - cents for USD) - """ - - updated: datetime - """Date and time for when the balance was last updated.""" diff --git a/src/lithic/types/cards/balance_list_params.py b/src/lithic/types/cards/balance_list_params.py index b80030a0..be24dded 100644 --- a/src/lithic/types/cards/balance_list_params.py +++ b/src/lithic/types/cards/balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/cards/financial_transaction_list_params.py b/src/lithic/types/cards/financial_transaction_list_params.py index 8c237520..04bdf49f 100644 --- a/src/lithic/types/cards/financial_transaction_list_params.py +++ b/src/lithic/types/cards/financial_transaction_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/category_details.py b/src/lithic/types/category_details.py new file mode 100644 index 00000000..81d56c66 --- /dev/null +++ b/src/lithic/types/category_details.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["CategoryDetails"] + + +class CategoryDetails(BaseModel): + balance_transfers: str + + cash_advances: str + + purchases: str diff --git a/src/lithic/types/credit_products/__init__.py b/src/lithic/types/credit_products/__init__.py new file mode 100644 index 00000000..adbd039a --- /dev/null +++ b/src/lithic/types/credit_products/__init__.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .extended_credit import ExtendedCredit as ExtendedCredit +from .prime_rate_create_params import PrimeRateCreateParams as PrimeRateCreateParams +from .prime_rate_retrieve_params import PrimeRateRetrieveParams as PrimeRateRetrieveParams +from .prime_rate_retrieve_response import PrimeRateRetrieveResponse as PrimeRateRetrieveResponse diff --git a/src/lithic/types/credit_products/extended_credit.py b/src/lithic/types/credit_products/extended_credit.py new file mode 100644 index 00000000..9de5c563 --- /dev/null +++ b/src/lithic/types/credit_products/extended_credit.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["ExtendedCredit"] + + +class ExtendedCredit(BaseModel): + credit_extended: int diff --git a/src/lithic/types/credit_products/prime_rate_create_params.py b/src/lithic/types/credit_products/prime_rate_create_params.py new file mode 100644 index 00000000..99ba37c6 --- /dev/null +++ b/src/lithic/types/credit_products/prime_rate_create_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["PrimeRateCreateParams"] + + +class PrimeRateCreateParams(TypedDict, total=False): + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """Date the rate goes into effect""" + + rate: Required[str] + """The rate in decimal format""" diff --git a/src/lithic/types/credit_products/prime_rate_retrieve_params.py b/src/lithic/types/credit_products/prime_rate_retrieve_params.py new file mode 100644 index 00000000..2b61243a --- /dev/null +++ b/src/lithic/types/credit_products/prime_rate_retrieve_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["PrimeRateRetrieveParams"] + + +class PrimeRateRetrieveParams(TypedDict, total=False): + ending_before: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """The effective date that the prime rates ends before""" + + starting_after: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """The effective date that the prime rate starts after""" diff --git a/src/lithic/types/credit_products/prime_rate_retrieve_response.py b/src/lithic/types/credit_products/prime_rate_retrieve_response.py new file mode 100644 index 00000000..5d4e3d92 --- /dev/null +++ b/src/lithic/types/credit_products/prime_rate_retrieve_response.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import date + +from ..._models import BaseModel + +__all__ = ["PrimeRateRetrieveResponse", "Data"] + + +class Data(BaseModel): + effective_date: date + """Date the rate goes into effect""" + + rate: str + """The rate in decimal format""" + + +class PrimeRateRetrieveResponse(BaseModel): + data: List[Data] + """List of prime rates""" + + has_more: bool + """Whether there are more prime rates""" diff --git a/src/lithic/types/device.py b/src/lithic/types/device.py new file mode 100644 index 00000000..ceaeacfb --- /dev/null +++ b/src/lithic/types/device.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["Device"] + + +class Device(BaseModel): + imei: Optional[str] = None + """The IMEI number of the device being provisioned. + + For Amex, this field contains device ID instead as IMEI is not provided + """ + + ip_address: Optional[str] = None + """The IP address of the device initiating the request""" + + location: Optional[str] = None + """ + Latitude and longitude where the device is located during the authorization + attempt + """ diff --git a/src/lithic/types/digital_card_art.py b/src/lithic/types/digital_card_art.py index a414fa9f..3df0a6d2 100644 --- a/src/lithic/types/digital_card_art.py +++ b/src/lithic/types/digital_card_art.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime diff --git a/src/lithic/types/digital_card_art_list_params.py b/src/lithic/types/digital_card_art_list_params.py index eb3ed626..5233633f 100644 --- a/src/lithic/types/digital_card_art_list_params.py +++ b/src/lithic/types/digital_card_art_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/digital_wallet_token_metadata.py b/src/lithic/types/digital_wallet_token_metadata.py new file mode 100644 index 00000000..2e26e00c --- /dev/null +++ b/src/lithic/types/digital_wallet_token_metadata.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DigitalWalletTokenMetadata", "PaymentAccountInfo", "PaymentAccountInfoAccountHolderData"] + + +class PaymentAccountInfoAccountHolderData(BaseModel): + """ + Additional information that can be used to identify the account holder, such as name, address, etc + """ + + phone_number: Optional[str] = None + """ + The phone number, may contain country code along with phone number when + countryDialInCode is not present + """ + + +class PaymentAccountInfo(BaseModel): + """Contains the information of the account responsible for the payment.""" + + account_holder_data: PaymentAccountInfoAccountHolderData + """ + Additional information that can be used to identify the account holder, such as + name, address, etc + """ + + pan_unique_reference: Optional[str] = None + """Reference to the PAN that is unique per Wallet Provider""" + + payment_account_reference: Optional[str] = None + """The unique account reference assigned to the PAN""" + + token_unique_reference: Optional[str] = None + """ + A unique reference assigned following the allocation of a token used to identify + the token for the duration of its lifetime. + """ + + +class DigitalWalletTokenMetadata(BaseModel): + """Contains the metadata for the digital wallet being tokenized.""" + + payment_account_info: PaymentAccountInfo + """Contains the information of the account responsible for the payment.""" + + status: str + """The current status of the digital wallet token. Pending or declined.""" + + payment_app_instance_id: Optional[str] = None + """ + The identifier of the Payment App instance within a device that will be + provisioned with a token + """ + + token_requestor_id: Optional[str] = None + """The party that requested the digitization""" + + token_requestor_name: Optional[ + Literal[ + "AMAZON_ONE", + "ANDROID_PAY", + "APPLE_PAY", + "FACEBOOK", + "FITBIT_PAY", + "GARMIN_PAY", + "MICROSOFT_PAY", + "NETFLIX", + "SAMSUNG_PAY", + "UNKNOWN", + "VISA_CHECKOUT", + ] + ] = None + """Human-readable name of the wallet that the token_requestor_id maps to.""" diff --git a/src/lithic/types/digital_wallet_tokenization_approval_request_webhook_event.py b/src/lithic/types/digital_wallet_tokenization_approval_request_webhook_event.py new file mode 100644 index 00000000..ab01de75 --- /dev/null +++ b/src/lithic/types/digital_wallet_tokenization_approval_request_webhook_event.py @@ -0,0 +1,83 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .device import Device +from .._models import BaseModel +from .tokenization_tfa_reason import TokenizationTfaReason +from .wallet_decisioning_info import WalletDecisioningInfo +from .tokenization_rule_result import TokenizationRuleResult +from .tokenization_decline_reason import TokenizationDeclineReason +from .digital_wallet_token_metadata import DigitalWalletTokenMetadata + +__all__ = ["DigitalWalletTokenizationApprovalRequestWebhookEvent", "CustomerTokenizationDecision"] + + +class CustomerTokenizationDecision(BaseModel): + """Contains the metadata for the customer tokenization decision.""" + + outcome: Literal[ + "APPROVED", "DECLINED", "ERROR", "INVALID_RESPONSE", "REQUIRE_ADDITIONAL_AUTHENTICATION", "TIMEOUT" + ] + """The outcome of the customer's decision""" + + responder_url: str + """The customer's subscribed URL""" + + latency: Optional[str] = None + """Time in ms it took for the customer's URL to respond""" + + response_code: Optional[str] = None + """The response code that the customer provided""" + + +class DigitalWalletTokenizationApprovalRequestWebhookEvent(BaseModel): + account_token: str + """Unique identifier for the user tokenizing a card""" + + card_token: str + """Unique identifier for the card being tokenized""" + + created: datetime + """Indicate when the request was received from Mastercard or Visa""" + + customer_tokenization_decision: Optional[CustomerTokenizationDecision] = None + """Contains the metadata for the customer tokenization decision.""" + + event_type: Literal["digital_wallet.tokenization_approval_request"] + """The name of this event""" + + issuer_decision: Literal["APPROVED", "DENIED", "VERIFICATION_REQUIRED"] + """Whether Lithic decisioned on the token, and if so, what the decision was. + + APPROVED/VERIFICATION_REQUIRED/DENIED. + """ + + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT"] + """The channel through which the tokenization was made.""" + + tokenization_token: str + """Unique identifier for the digital wallet token attempt""" + + wallet_decisioning_info: WalletDecisioningInfo + + device: Optional[Device] = None + + digital_wallet_token_metadata: Optional[DigitalWalletTokenMetadata] = None + """Contains the metadata for the digital wallet being tokenized.""" + + rule_results: Optional[List[TokenizationRuleResult]] = None + """Results from rules that were evaluated for this tokenization""" + + tokenization_decline_reasons: Optional[List[TokenizationDeclineReason]] = None + """List of reasons why the tokenization was declined""" + + tokenization_source: Optional[ + Literal["ACCOUNT_ON_FILE", "CONTACTLESS_TAP", "MANUAL_PROVISION", "PUSH_PROVISION", "TOKEN", "UNKNOWN"] + ] = None + """The source of the tokenization.""" + + tokenization_tfa_reasons: Optional[List[TokenizationTfaReason]] = None + """List of reasons why two-factor authentication was required""" diff --git a/src/lithic/types/digital_wallet_tokenization_result_webhook_event.py b/src/lithic/types/digital_wallet_tokenization_result_webhook_event.py new file mode 100644 index 00000000..7e4a4328 --- /dev/null +++ b/src/lithic/types/digital_wallet_tokenization_result_webhook_event.py @@ -0,0 +1,71 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .tokenization_tfa_reason import TokenizationTfaReason +from .tokenization_rule_result import TokenizationRuleResult + +__all__ = ["DigitalWalletTokenizationResultWebhookEvent", "TokenizationResultDetails"] + + +class TokenizationResultDetails(BaseModel): + """The result of the tokenization request.""" + + issuer_decision: str + """Lithic's tokenization decision.""" + + tokenization_decline_reasons: List[ + Literal[ + "ACCOUNT_SCORE_1", + "ALL_WALLET_DECLINE_REASONS_PRESENT", + "CARD_EXPIRY_MONTH_MISMATCH", + "CARD_EXPIRY_YEAR_MISMATCH", + "CARD_INVALID_STATE", + "CUSTOMER_RED_PATH", + "CVC_MISMATCH", + "DEVICE_SCORE_1", + "GENERIC_DECLINE", + "INVALID_CUSTOMER_RESPONSE", + "NETWORK_FAILURE", + "WALLET_RECOMMENDED_DECISION_RED", + ] + ] + """List of reasons why the tokenization was declined""" + + customer_decision: Optional[str] = None + """The customer's tokenization decision if applicable.""" + + rule_results: Optional[List[TokenizationRuleResult]] = None + """Results from rules that were evaluated for this tokenization""" + + token_activated_date_time: Optional[datetime] = None + """An RFC 3339 timestamp indicating when the tokenization succeeded.""" + + tokenization_tfa_reasons: Optional[List[TokenizationTfaReason]] = None + """List of reasons why two-factor authentication was required""" + + wallet_decision: Optional[str] = None + """The wallet's recommended decision.""" + + +class DigitalWalletTokenizationResultWebhookEvent(BaseModel): + account_token: str + """Account token""" + + card_token: str + """Card token""" + + created: datetime + """Created date""" + + event_type: Literal["digital_wallet.tokenization_result"] + """The type of event that occurred.""" + + tokenization_result_details: TokenizationResultDetails + """The result of the tokenization request.""" + + tokenization_token: str + """Tokenization token""" diff --git a/src/lithic/types/digital_wallet_tokenization_two_factor_authentication_code_sent_webhook_event.py b/src/lithic/types/digital_wallet_tokenization_two_factor_authentication_code_sent_webhook_event.py new file mode 100644 index 00000000..f177dd05 --- /dev/null +++ b/src/lithic/types/digital_wallet_tokenization_two_factor_authentication_code_sent_webhook_event.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent", "ActivationMethod"] + + +class ActivationMethod(BaseModel): + type: Literal["EMAIL_TO_CARDHOLDER_ADDRESS", "TEXT_TO_CARDHOLDER_NUMBER"] + """ + The communication method that the user has selected to use to receive the + authentication code. Supported Values: Sms = "TEXT_TO_CARDHOLDER_NUMBER". Email + = "EMAIL_TO_CARDHOLDER_ADDRESS" + """ + + value: str + """ + The location to which the authentication code was sent. The format depends on + the ActivationMethod.Type field. If Type is Email, the Value will be the email + address. If the Type is Sms, the Value will be the phone number. + """ + + +class DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent(BaseModel): + account_token: str + """Unique identifier for the user tokenizing a card""" + + activation_method: ActivationMethod + + card_token: str + """Unique identifier for the card being tokenized""" + + created: datetime + """Indicate when the request was received from Mastercard or Visa""" + + event_type: Literal["digital_wallet.tokenization_two_factor_authentication_code_sent"] + """The type of event that occurred.""" + + tokenization_token: str + """Unique identifier for the tokenization""" diff --git a/src/lithic/types/digital_wallet_tokenization_two_factor_authentication_code_webhook_event.py b/src/lithic/types/digital_wallet_tokenization_two_factor_authentication_code_webhook_event.py new file mode 100644 index 00000000..54bfb368 --- /dev/null +++ b/src/lithic/types/digital_wallet_tokenization_two_factor_authentication_code_webhook_event.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent", "ActivationMethod"] + + +class ActivationMethod(BaseModel): + type: Literal["EMAIL_TO_CARDHOLDER_ADDRESS", "TEXT_TO_CARDHOLDER_NUMBER"] + """ + The communication method that the user has selected to use to receive the + authentication code. Supported Values: Sms = "TEXT_TO_CARDHOLDER_NUMBER". Email + = "EMAIL_TO_CARDHOLDER_ADDRESS" + """ + + value: str + """ + The location where the user wants to receive the authentication code. The format + depends on the ActivationMethod.Type field. If Type is Email, the Value will be + the email address. If the Type is Sms, the Value will be the phone number. + """ + + +class DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent(BaseModel): + account_token: str + """Unique identifier for the user tokenizing a card""" + + activation_method: ActivationMethod + + authentication_code: str + """Authentication code to provide to the user tokenizing a card.""" + + card_token: str + """Unique identifier for the card being tokenized""" + + created: datetime + """Indicate when the request was received from Mastercard or Visa""" + + event_type: Literal["digital_wallet.tokenization_two_factor_authentication_code"] + """The type of event that occurred.""" + + tokenization_token: str + """Unique identifier for the tokenization""" diff --git a/src/lithic/types/digital_wallet_tokenization_updated_webhook_event.py b/src/lithic/types/digital_wallet_tokenization_updated_webhook_event.py new file mode 100644 index 00000000..608370e6 --- /dev/null +++ b/src/lithic/types/digital_wallet_tokenization_updated_webhook_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .tokenization import Tokenization + +__all__ = ["DigitalWalletTokenizationUpdatedWebhookEvent"] + + +class DigitalWalletTokenizationUpdatedWebhookEvent(BaseModel): + account_token: str + """Account token""" + + card_token: str + """Card token""" + + created: datetime + """Created date""" + + event_type: Literal["digital_wallet.tokenization_updated"] + """The type of event that occurred.""" + + tokenization: Tokenization diff --git a/src/lithic/types/dispute.py b/src/lithic/types/dispute.py index d9e5a863..6a827ee4 100644 --- a/src/lithic/types/dispute.py +++ b/src/lithic/types/dispute.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime @@ -10,6 +10,8 @@ class Dispute(BaseModel): + """Dispute.""" + token: str """Globally unique identifier.""" @@ -86,9 +88,6 @@ class Dispute(BaseModel): representment_date: Optional[datetime] = None """Date the representment was received.""" - resolution_amount: Optional[int] = None - """Resolution amount net of network fees.""" - resolution_date: Optional[datetime] = None """Date that the dispute was resolved.""" diff --git a/src/lithic/types/dispute_create_params.py b/src/lithic/types/dispute_create_params.py index 4d981410..d515813b 100644 --- a/src/lithic/types/dispute_create_params.py +++ b/src/lithic/types/dispute_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_evidence.py b/src/lithic/types/dispute_evidence.py index a850e9b9..02dd0c1f 100644 --- a/src/lithic/types/dispute_evidence.py +++ b/src/lithic/types/dispute_evidence.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime @@ -10,6 +10,8 @@ class DisputeEvidence(BaseModel): + """Dispute evidence.""" + token: str """Globally unique identifier.""" diff --git a/src/lithic/types/dispute_evidence_upload_failed_webhook_event.py b/src/lithic/types/dispute_evidence_upload_failed_webhook_event.py new file mode 100644 index 00000000..881449dd --- /dev/null +++ b/src/lithic/types/dispute_evidence_upload_failed_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .dispute_evidence import DisputeEvidence + +__all__ = ["DisputeEvidenceUploadFailedWebhookEvent"] + + +class DisputeEvidenceUploadFailedWebhookEvent(DisputeEvidence): + """Dispute evidence.""" + + event_type: Literal["dispute_evidence.upload_failed"] + """The type of event that occurred.""" diff --git a/src/lithic/types/dispute_initiate_evidence_upload_params.py b/src/lithic/types/dispute_initiate_evidence_upload_params.py index 5ed6c9e0..29e610e7 100644 --- a/src/lithic/types/dispute_initiate_evidence_upload_params.py +++ b/src/lithic/types/dispute_initiate_evidence_upload_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_list_evidences_params.py b/src/lithic/types/dispute_list_evidences_params.py index c9b3f360..9c84266d 100644 --- a/src/lithic/types/dispute_list_evidences_params.py +++ b/src/lithic/types/dispute_list_evidences_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_list_params.py b/src/lithic/types/dispute_list_params.py index 6ad90569..f83e42ab 100644 --- a/src/lithic/types/dispute_list_params.py +++ b/src/lithic/types/dispute_list_params.py @@ -1,11 +1,12 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing import List, Union +from typing import Union from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo __all__ = ["DisputeListParams"] @@ -52,5 +53,5 @@ class DisputeListParams(TypedDict, total=False): ] """List disputes of a specific status.""" - transaction_tokens: List[str] + transaction_tokens: SequenceNotStr[str] """Transaction tokens to filter by.""" diff --git a/src/lithic/types/dispute_transaction_created_webhook_event.py b/src/lithic/types/dispute_transaction_created_webhook_event.py new file mode 100644 index 00000000..ad9e8fe1 --- /dev/null +++ b/src/lithic/types/dispute_transaction_created_webhook_event.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .dispute_v2 import DisputeV2 + +__all__ = ["DisputeTransactionCreatedWebhookEvent"] + + +class DisputeTransactionCreatedWebhookEvent(DisputeV2): + """ + The Dispute object tracks the progression of a dispute throughout its lifecycle. + """ + + event_type: Literal["dispute_transaction.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/dispute_transaction_updated_webhook_event.py b/src/lithic/types/dispute_transaction_updated_webhook_event.py new file mode 100644 index 00000000..4c9d4b53 --- /dev/null +++ b/src/lithic/types/dispute_transaction_updated_webhook_event.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .dispute_v2 import DisputeV2 + +__all__ = ["DisputeTransactionUpdatedWebhookEvent"] + + +class DisputeTransactionUpdatedWebhookEvent(DisputeV2): + """ + The Dispute object tracks the progression of a dispute throughout its lifecycle. + """ + + event_type: Literal["dispute_transaction.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/dispute_update_params.py b/src/lithic/types/dispute_update_params.py index 52b8ba74..de0b45aa 100644 --- a/src/lithic/types/dispute_update_params.py +++ b/src/lithic/types/dispute_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_updated_webhook_event.py b/src/lithic/types/dispute_updated_webhook_event.py new file mode 100644 index 00000000..5a26bc15 --- /dev/null +++ b/src/lithic/types/dispute_updated_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .dispute import Dispute + +__all__ = ["DisputeUpdatedWebhookEvent"] + + +class DisputeUpdatedWebhookEvent(Dispute): + """Dispute.""" + + event_type: Literal["dispute.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/dispute_v2.py b/src/lithic/types/dispute_v2.py new file mode 100644 index 00000000..7b09d20d --- /dev/null +++ b/src/lithic/types/dispute_v2.py @@ -0,0 +1,190 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .shared.merchant import Merchant + +__all__ = [ + "DisputeV2", + "Event", + "EventData", + "EventDataWorkflow", + "EventDataFinancial", + "EventDataCardholderLiability", + "LiabilityAllocation", + "TransactionSeries", +] + + +class EventDataWorkflow(BaseModel): + """Details specific to workflow events""" + + action: Literal["OPENED", "CLOSED", "REOPENED"] + """Action taken in this stage""" + + amount: Optional[int] = None + """Amount in minor units""" + + disposition: Optional[Literal["WON", "LOST", "PARTIALLY_WON", "WITHDRAWN", "DENIED"]] = None + """Dispute resolution outcome""" + + reason: Optional[str] = None + """Reason for the action""" + + stage: Literal["CLAIM"] + """Current stage of the dispute workflow""" + + type: Literal["WORKFLOW"] + """Event type discriminator""" + + +class EventDataFinancial(BaseModel): + """Details specific to financial events""" + + amount: int + """Amount in minor units""" + + polarity: Literal["CREDIT", "DEBIT"] + """Direction of funds flow""" + + stage: Literal["CHARGEBACK", "REPRESENTMENT", "PREARBITRATION", "ARBITRATION", "COLLABORATION"] + """Stage at which the financial event occurred""" + + type: Literal["FINANCIAL"] + """Event type discriminator""" + + +class EventDataCardholderLiability(BaseModel): + """Details specific to cardholder liability events""" + + action: Literal["PROVISIONAL_CREDIT_GRANTED", "PROVISIONAL_CREDIT_REVERSED", "WRITTEN_OFF"] + """Action taken regarding cardholder liability""" + + amount: int + """Amount in minor units""" + + reason: str + """Reason for the action""" + + type: Literal["CARDHOLDER_LIABILITY"] + """Event type discriminator""" + + +EventData: TypeAlias = Annotated[ + Union[EventDataWorkflow, EventDataFinancial, EventDataCardholderLiability], PropertyInfo(discriminator="type") +] + + +class Event(BaseModel): + """Event that occurred in the dispute lifecycle""" + + token: str + """Unique identifier for the event, in UUID format""" + + created: datetime + """When the event occurred""" + + data: EventData + """Details specific to the event type""" + + type: Literal["WORKFLOW", "FINANCIAL", "CARDHOLDER_LIABILITY"] + """Type of event""" + + +class LiabilityAllocation(BaseModel): + """Current breakdown of how liability is allocated for the disputed amount""" + + denied_amount: int + """The amount that has been denied to the cardholder""" + + original_amount: int + """The initial amount disputed""" + + recovered_amount: int + """ + The amount that has been recovered from the merchant through the dispute process + """ + + remaining_amount: int + """Any disputed amount that is still outstanding, i.e. + + has not been recovered, written off, or denied + """ + + written_off_amount: int + """The amount the issuer has chosen to write off""" + + +class TransactionSeries(BaseModel): + """ + Contains identifiers for the transaction and specific event within being disputed; null if no transaction can be identified + """ + + related_transaction_event_token: Optional[str] = None + """ + Token of the specific event in the original transaction being disputed, in UUID + format; null if no event can be identified + """ + + related_transaction_token: str + """Token of the original transaction being disputed, in UUID format""" + + type: Literal["DISPUTE"] + """ + The type of transaction series associating the dispute and the original + transaction. Always set to DISPUTE + """ + + +class DisputeV2(BaseModel): + """ + The Dispute object tracks the progression of a dispute throughout its lifecycle. + """ + + token: str + """Token assigned by Lithic for the dispute, in UUID format.""" + + account_token: str + """Token for the account associated with the dispute, in UUID format.""" + + card_token: str + """Token for the card used in the dispute, in UUID format.""" + + case_id: Optional[str] = None + """Identifier assigned by the network for this dispute.""" + + created: datetime + """When the dispute was created.""" + + currency: str + """Three-letter ISO 4217 currency code.""" + + disposition: Optional[Literal["WON", "LOST", "PARTIALLY_WON", "WITHDRAWN", "DENIED"]] = None + """Dispute resolution outcome""" + + events: List[Event] + """Chronological list of events that have occurred in the dispute lifecycle""" + + liability_allocation: LiabilityAllocation + """Current breakdown of how liability is allocated for the disputed amount""" + + merchant: Merchant + + network: Literal["VISA", "MASTERCARD"] + """Card network handling the dispute.""" + + status: Optional[Literal["OPEN", "CLOSED"]] = None + """Current status of the dispute.""" + + transaction_series: Optional[TransactionSeries] = None + """ + Contains identifiers for the transaction and specific event within being + disputed; null if no transaction can be identified + """ + + updated: datetime + """When the dispute was last updated.""" diff --git a/src/lithic/types/disputes_v2_list_params.py b/src/lithic/types/disputes_v2_list_params.py new file mode 100644 index 00000000..5f061147 --- /dev/null +++ b/src/lithic/types/disputes_v2_list_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["DisputesV2ListParams"] + + +class DisputesV2ListParams(TypedDict, total=False): + account_token: str + """Filter by account token.""" + + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """RFC 3339 timestamp for filtering by created date, inclusive.""" + + card_token: str + """Filter by card token.""" + + disputed_transaction_token: str + """Filter by the token of the transaction being disputed. + + Corresponds with transaction_series.related_transaction_token in the Dispute. + """ + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """RFC 3339 timestamp for filtering by created date, inclusive.""" + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + page_size: int + """Number of items to return.""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ diff --git a/src/lithic/types/event.py b/src/lithic/types/event.py index 035a2a26..9ac810f3 100644 --- a/src/lithic/types/event.py +++ b/src/lithic/types/event.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict from datetime import datetime @@ -10,6 +10,8 @@ class Event(BaseModel): + """A single event that affects the transaction state and lifecycle.""" + token: str """Globally unique identifier.""" @@ -20,45 +22,159 @@ class Event(BaseModel): """ event_type: Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] - """Event types: - - - `account_holder.created` - Notification that a new account holder has been - created and was not rejected. - - `account_holder.updated` - Notification that an account holder was updated. - - `account_holder.verification` - Notification than an account holder's identity - verification is complete. - - `card.created` - Notification that a card has been created. - - `card.renewed` - Notification that a card has been renewed. - - `card.shipped` - Physical card shipment notification. See - https://docs.lithic.com/docs/cards#physical-card-shipped-webhook. - - `card_transaction.updated` - Transaction Lifecycle webhook. See - https://docs.lithic.com/docs/transaction-webhooks. - - `dispute.updated` - A dispute has been updated. - - `digital_wallet.tokenization_approval_request` - Card network's request to - Lithic to activate a digital wallet token. - - `digital_wallet.tokenization_result` - Notification of the end result of a - tokenization, whether successful or failed. - - `digital_wallet.tokenization_two_factor_authentication_code` - A code to be - passed to an end user to complete digital wallet authentication. See - https://docs.lithic.com/docs/tokenization-control#digital-wallet-tokenization-auth-code. + """The type of event that occurred. Possible values: + + - account_holder_document.updated: Occurs when an account holder's document + upload status has been updated + - account_holder.created: Occurs when a new account_holder is created. + - account_holder.updated: Occurs when an account_holder is updated. + - account_holder.verification: Occurs when an asynchronous account_holder's + verification is completed. + - auth_rules.backtest_report.created: Auth Rules backtest report created. + - balance.updated: Financial Account Balance Update + - book_transfer_transaction.created: Occurs when a book transfer transaction is + created. + - book_transfer_transaction.updated: Occurs when a book transfer transaction is + updated. + - card_transaction.enhanced_data.created: Occurs when L2/L3 enhanced commercial + data is processed for a transaction event. + - card_transaction.enhanced_data.updated: Occurs when L2/L3 enhanced commercial + data is reprocessed for a transaction event. + - card_transaction.updated: Occurs when a card transaction happens. + - card.converted: Occurs when a card is converted from virtual to physical + cards. + - card.created: Occurs when a new card is created. + - card.reissued: Occurs when a card is reissued. + - card.renewed: Occurs when a card is renewed. + - card.shipped: Occurs when a card is shipped. + - digital_wallet.tokenization_approval_request: Occurs when a tokenization + approval request is made. This event will be deprecated in the future. We + recommend using `tokenization.approval_request` instead. + - digital_wallet.tokenization_result: Occurs when a tokenization request + succeeded or failed. + + This event will be deprecated in the future. We recommend using + `tokenization.result` instead. + + - digital_wallet.tokenization_two_factor_authentication_code: Occurs when a + tokenization request 2FA code is sent to the Lithic customer for self serve + delivery. + + This event will be deprecated in the future. We recommend using + `tokenization.two_factor_authentication_code` instead. + + - digital_wallet.tokenization_two_factor_authentication_code_sent: Occurs when a + tokenization request 2FA code is sent to our downstream messaging providers + for delivery. + + This event will be deprecated in the future. We recommend using + `tokenization.two_factor_authentication_code_sent` instead. + + - digital_wallet.tokenization_updated: Occurs when a tokenization's status has + changed. + + This event will be deprecated in the future. We recommend using + `tokenization.updated` instead. + + - dispute_evidence.upload_failed: Occurs when a dispute evidence upload fails. + - dispute_transaction.created: Occurs when a new dispute transaction is created + - dispute_transaction.updated: Occurs when a dispute transaction is updated + - dispute.updated: Occurs when a dispute is updated. + - external_bank_account.created: Occurs when an external bank account is + created. + - external_bank_account.updated: Occurs when an external bank account is + updated. + - external_payment.created: Occurs when an external payment is created. + - external_payment.updated: Occurs when an external payment is updated. + - financial_account.created: Occurs when a financial account is created. + - financial_account.updated: Occurs when a financial account is updated. + - funding_event.created: Occurs when a funding event is created. + - internal_transaction.created: Occurs when an internal adjustment is created. + - internal_transaction.updated: Occurs when an internal adjustment is updated. + - loan_tape.created: Occurs when a loan tape is created. + - loan_tape.updated: Occurs when a loan tape is updated. + - management_operation.created: Occurs when an management operation is created. + - management_operation.updated: Occurs when an management operation is updated. + - network_total.created: Occurs when a network total is created. + - network_total.updated: Occurs when a network total is updated. + - payment_transaction.created: Occurs when a payment transaction is created. + - payment_transaction.updated: Occurs when a payment transaction is updated. + - settlement_report.updated: Occurs when a settlement report is created or + updated. + - statements.created: Occurs when a statement has been created + - three_ds_authentication.challenge: The `three_ds_authentication.challenge` + event. Upon receiving this request, the Card Program should issue its own + challenge to the cardholder. After a cardholder challenge is successfully + completed, the Card Program needs to respond back to Lithic by call to + [/v1/three_ds_decisioning/challenge_response](https://docs.lithic.com/reference/post_v1-three-ds-decisioning-challenge-response). + Then the cardholder must navigate back to the merchant checkout flow to + complete the transaction. Some merchants will include an `app_requestor_url` + for app-based purchases; Lithic recommends triggering a redirect to that URL + after the cardholder completes an app-based challenge. + - three_ds_authentication.created: Occurs when a 3DS authentication is created. + - three_ds_authentication.updated: Occurs when a 3DS authentication is updated + (eg. challenge is completed). + - tokenization.approval_request: Occurs when a tokenization approval request is + made. + - tokenization.result: Occurs when a tokenization request succeeded or failed. + - tokenization.two_factor_authentication_code: Occurs when a tokenization + request 2FA code is sent to the Lithic customer for self serve delivery. + - tokenization.two_factor_authentication_code_sent: Occurs when a tokenization + request 2FA code is sent to our downstream messaging providers for delivery. + - tokenization.updated: Occurs when a tokenization's status has changed. """ payload: Dict[str, object] diff --git a/src/lithic/types/event_list_attempts_params.py b/src/lithic/types/event_list_attempts_params.py index d2f594f6..cd087c03 100644 --- a/src/lithic/types/event_list_attempts_params.py +++ b/src/lithic/types/event_list_attempts_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/event_list_params.py b/src/lithic/types/event_list_params.py index e5eadac5..3092590a 100644 --- a/src/lithic/types/event_list_params.py +++ b/src/lithic/types/event_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -32,23 +32,58 @@ class EventListParams(TypedDict, total=False): event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] """Event types to filter events by.""" diff --git a/src/lithic/types/event_resend_params.py b/src/lithic/types/event_resend_params.py deleted file mode 100644 index e381da50..00000000 --- a/src/lithic/types/event_resend_params.py +++ /dev/null @@ -1,41 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["EventResendParams"] - - -class EventResendParams(TypedDict, total=False): - token: Required[str] - """Globally unique identifier for the card to be displayed.""" - - css: str - """ - A publicly available URI, so the white-labeled card element can be styled with - the client's branding. - """ - - expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """An RFC 3339 timestamp for when the request should expire. UTC time zone. - - If no timezone is specified, UTC will be used. If payload does not contain an - expiration, the request will never expire. - - Using an `expiration` reduces the risk of a - [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying - the `expiration`, in the event that a malicious user gets a copy of your request - in transit, they will be able to obtain the response data indefinitely. - """ - - target_origin: str - """Required if you want to post the element clicked to the parent iframe. - - If you supply this param, you can also capture click events in the parent iframe - by adding an event listener. - """ diff --git a/src/lithic/types/event_subscription.py b/src/lithic/types/event_subscription.py index 28f8802f..2ff52a1f 100644 --- a/src/lithic/types/event_subscription.py +++ b/src/lithic/types/event_subscription.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from typing_extensions import Literal @@ -9,6 +9,8 @@ class EventSubscription(BaseModel): + """A subscription to specific event types.""" + token: str """Globally unique identifier.""" @@ -23,23 +25,58 @@ class EventSubscription(BaseModel): event_types: Optional[ List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] ] = None diff --git a/src/lithic/types/events/__init__.py b/src/lithic/types/events/__init__.py index aea612e2..afe8a307 100644 --- a/src/lithic/types/events/__init__.py +++ b/src/lithic/types/events/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_create_params.py b/src/lithic/types/events/subscription_create_params.py index acd89ebb..43b7e5f2 100644 --- a/src/lithic/types/events/subscription_create_params.py +++ b/src/lithic/types/events/subscription_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -20,23 +20,58 @@ class SubscriptionCreateParams(TypedDict, total=False): event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] """Indicates types of events that will be sent to this subscription. diff --git a/src/lithic/types/events/subscription_list_attempts_params.py b/src/lithic/types/events/subscription_list_attempts_params.py index 00523a7d..ce3f2678 100644 --- a/src/lithic/types/events/subscription_list_attempts_params.py +++ b/src/lithic/types/events/subscription_list_attempts_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_list_params.py b/src/lithic/types/events/subscription_list_params.py index feb66ed8..bc25fa49 100644 --- a/src/lithic/types/events/subscription_list_params.py +++ b/src/lithic/types/events/subscription_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_recover_params.py b/src/lithic/types/events/subscription_recover_params.py index 46953dab..dcc4fe80 100644 --- a/src/lithic/types/events/subscription_recover_params.py +++ b/src/lithic/types/events/subscription_recover_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_replay_missing_params.py b/src/lithic/types/events/subscription_replay_missing_params.py index 06fd3b3d..ebb1a04a 100644 --- a/src/lithic/types/events/subscription_replay_missing_params.py +++ b/src/lithic/types/events/subscription_replay_missing_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_retrieve_secret_response.py b/src/lithic/types/events/subscription_retrieve_secret_response.py index 607372ad..467c4f8a 100644 --- a/src/lithic/types/events/subscription_retrieve_secret_response.py +++ b/src/lithic/types/events/subscription_retrieve_secret_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/events/subscription_send_simulated_example_params.py b/src/lithic/types/events/subscription_send_simulated_example_params.py index 6e62a6e8..dc39c63a 100644 --- a/src/lithic/types/events/subscription_send_simulated_example_params.py +++ b/src/lithic/types/events/subscription_send_simulated_example_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,22 +9,57 @@ class SubscriptionSendSimulatedExampleParams(TypedDict, total=False): event_type: Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] """Event type to send example message for.""" diff --git a/src/lithic/types/events/subscription_update_params.py b/src/lithic/types/events/subscription_update_params.py index c6143566..834d78fc 100644 --- a/src/lithic/types/events/subscription_update_params.py +++ b/src/lithic/types/events/subscription_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -20,23 +20,58 @@ class SubscriptionUpdateParams(TypedDict, total=False): event_types: List[ Literal[ + "account_holder_document.updated", "account_holder.created", "account_holder.updated", "account_holder.verification", + "auth_rules.backtest_report.created", "balance.updated", + "book_transfer_transaction.created", + "book_transfer_transaction.updated", + "card_transaction.enhanced_data.created", + "card_transaction.enhanced_data.updated", + "card_transaction.updated", + "card.converted", "card.created", + "card.reissued", "card.renewed", "card.shipped", - "card_transaction.updated", "digital_wallet.tokenization_approval_request", "digital_wallet.tokenization_result", "digital_wallet.tokenization_two_factor_authentication_code", - "dispute.updated", + "digital_wallet.tokenization_two_factor_authentication_code_sent", + "digital_wallet.tokenization_updated", "dispute_evidence.upload_failed", + "dispute_transaction.created", + "dispute_transaction.updated", + "dispute.updated", + "external_bank_account.created", + "external_bank_account.updated", + "external_payment.created", + "external_payment.updated", + "financial_account.created", + "financial_account.updated", + "funding_event.created", + "internal_transaction.created", + "internal_transaction.updated", + "loan_tape.created", + "loan_tape.updated", + "management_operation.created", + "management_operation.updated", + "network_total.created", + "network_total.updated", "payment_transaction.created", "payment_transaction.updated", + "settlement_report.updated", + "statements.created", + "three_ds_authentication.challenge", "three_ds_authentication.created", - "transfer_transaction.created", + "three_ds_authentication.updated", + "tokenization.approval_request", + "tokenization.result", + "tokenization.two_factor_authentication_code", + "tokenization.two_factor_authentication_code_sent", + "tokenization.updated", ] ] """Indicates types of events that will be sent to this subscription. diff --git a/src/lithic/types/external_bank_account.py b/src/lithic/types/external_bank_account.py new file mode 100644 index 00000000..eb165522 --- /dev/null +++ b/src/lithic/types/external_bank_account.py @@ -0,0 +1,105 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import date, datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .owner_type import OwnerType +from .verification_method import VerificationMethod +from .external_bank_account_address import ExternalBankAccountAddress + +__all__ = ["ExternalBankAccount"] + + +class ExternalBankAccount(BaseModel): + token: str + """ + A globally unique identifier for this record of an external bank account + association. If a program links an external bank account to more than one + end-user or to both the program and the end-user, then Lithic will return each + record of the association + """ + + country: str + """The country that the bank account is located in using ISO 3166-1. + + We will only accept USA bank accounts e.g., USA + """ + + created: datetime + """ + An ISO 8601 string representing when this funding source was added to the Lithic + account. + """ + + currency: str + """currency of the external account 3-character alphabetic ISO 4217 code""" + + last_four: str + """The last 4 digits of the bank account. + + Derived by Lithic from the account number passed + """ + + owner: str + """Legal Name of the business or individual who owns the external account. + + This will appear in statements + """ + + owner_type: OwnerType + """Owner Type""" + + routing_number: str + """Routing Number""" + + state: Literal["ENABLED", "CLOSED", "PAUSED"] + """Account State""" + + type: Literal["CHECKING", "SAVINGS"] + """Account Type""" + + verification_attempts: int + """The number of attempts at verification""" + + verification_method: VerificationMethod + """Verification Method""" + + verification_state: Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"] + """Verification State""" + + account_token: Optional[str] = None + """Indicates which Lithic account the external account is associated with. + + For external accounts that are associated with the program, account_token field + returned will be null + """ + + address: Optional[ExternalBankAccountAddress] = None + """Address""" + + company_id: Optional[str] = None + """Optional field that helps identify bank accounts in receipts""" + + dob: Optional[date] = None + """Date of Birth of the Individual that owns the external bank account""" + + doing_business_as: Optional[str] = None + """Doing Business As""" + + financial_account_token: Optional[str] = None + """The financial account token of the operating account to fund the micro deposits""" + + name: Optional[str] = None + """The nickname for this External Bank Account""" + + user_defined_id: Optional[str] = None + """User Defined ID""" + + verification_failed_reason: Optional[str] = None + """Optional free text description of the reason for the failed verification. + + For ACH micro-deposits returned, this field will display the reason return code + sent by the ACH network + """ diff --git a/src/lithic/types/external_bank_account_address.py b/src/lithic/types/external_bank_account_address.py index 946a9e98..e755b393 100644 --- a/src/lithic/types/external_bank_account_address.py +++ b/src/lithic/types/external_bank_account_address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/external_bank_account_address_param.py b/src/lithic/types/external_bank_account_address_param.py index d057e56c..ff178c71 100644 --- a/src/lithic/types/external_bank_account_address_param.py +++ b/src/lithic/types/external_bank_account_address_param.py @@ -1,7 +1,8 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations +from typing import Optional from typing_extensions import Required, TypedDict __all__ = ["ExternalBankAccountAddressParam"] @@ -18,4 +19,4 @@ class ExternalBankAccountAddressParam(TypedDict, total=False): state: Required[str] - address2: str + address2: Optional[str] diff --git a/src/lithic/types/external_bank_account_create_params.py b/src/lithic/types/external_bank_account_create_params.py index 78168666..d0660aae 100644 --- a/src/lithic/types/external_bank_account_create_params.py +++ b/src/lithic/types/external_bank_account_create_params.py @@ -1,10 +1,10 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing import Union from datetime import date -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict from .._utils import PropertyInfo from .owner_type import OwnerType @@ -14,77 +14,189 @@ __all__ = [ "ExternalBankAccountCreateParams", "BankVerifiedCreateBankAccountAPIRequest", - "PlaidCreateBankAccountAPIRequest", + "ExternallyVerifiedCreateBankAccountAPIRequest", + "UnverifiedCreateBankAccountAPIRequest", ] class BankVerifiedCreateBankAccountAPIRequest(TypedDict, total=False): account_number: Required[str] + """Account Number""" country: Required[str] + """The country that the bank account is located in using ISO 3166-1. + + We will only accept USA bank accounts e.g., USA + """ currency: Required[str] + """currency of the external account 3-character alphabetic ISO 4217 code""" + + financial_account_token: Required[str] + """The financial account token of the operating account to fund the micro deposits""" owner: Required[str] + """Legal Name of the business or individual who owns the external account. + + This will appear in statements + """ owner_type: Required[OwnerType] + """Owner Type""" routing_number: Required[str] + """Routing Number""" type: Required[Literal["CHECKING", "SAVINGS"]] + """Account Type""" verification_method: Required[VerificationMethod] + """Verification Method""" account_token: str + """Indicates which Lithic account the external account is associated with. - address: ExternalBankAccountAddressParam - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. + For external accounts that are associated with the program, account_token field + returned will be null """ + address: ExternalBankAccountAddressParam + """Address""" + company_id: str + """Optional field that helps identify bank accounts in receipts""" dob: Annotated[Union[str, date], PropertyInfo(format="iso8601")] """Date of Birth of the Individual that owns the external bank account""" doing_business_as: str + """Doing Business As""" name: str + """The nickname for this External Bank Account""" user_defined_id: str + """User Defined ID""" verification_enforcement: bool - """Indicates whether verification was enforced for a given association record. - For MICRO_DEPOSIT, option to disable verification if the external bank account - has already been verified before. By default, verification will be required - unless users pass in a value of false + +class ExternallyVerifiedCreateBankAccountAPIRequest(TypedDict, total=False): + account_number: Required[str] + """Account Number""" + + country: Required[str] + """The country that the bank account is located in using ISO 3166-1. + + We will only accept USA bank accounts e.g., USA """ + currency: Required[str] + """currency of the external account 3-character alphabetic ISO 4217 code""" -class PlaidCreateBankAccountAPIRequest(TypedDict, total=False): owner: Required[str] + """Legal Name of the business or individual who owns the external account. + + This will appear in statements + """ owner_type: Required[OwnerType] + """Owner Type""" - processor_token: Required[str] + routing_number: Required[str] + """Routing Number""" - verification_method: Required[VerificationMethod] + type: Required[Literal["CHECKING", "SAVINGS"]] + """Account Type""" + + verification_method: Required[Literal["EXTERNALLY_VERIFIED"]] + """Verification Method""" account_token: str + """Indicates which Lithic account the external account is associated with. + + For external accounts that are associated with the program, account_token field + returned will be null + """ + + address: ExternalBankAccountAddressParam + """Address""" company_id: str + """Optional field that helps identify bank accounts in receipts""" dob: Annotated[Union[str, date], PropertyInfo(format="iso8601")] """Date of Birth of the Individual that owns the external bank account""" doing_business_as: str + """Doing Business As""" + + name: str + """The nickname for this External Bank Account""" user_defined_id: str + """User Defined ID""" + + +class UnverifiedCreateBankAccountAPIRequest(TypedDict, total=False): + account_number: Required[str] + """Account Number""" + + country: Required[str] + """The country that the bank account is located in using ISO 3166-1. + We will only accept USA bank accounts e.g., USA + """ + + currency: Required[str] + """currency of the external account 3-character alphabetic ISO 4217 code""" + + owner: Required[str] + """Legal Name of the business or individual who owns the external account. + + This will appear in statements + """ + + owner_type: Required[OwnerType] + """Owner Type""" + + routing_number: Required[str] + """Routing Number""" + + type: Required[Literal["CHECKING", "SAVINGS"]] + """Account Type""" + + verification_method: Required[Literal["UNVERIFIED"]] + """Verification Method""" + + account_token: str + """Indicates which Lithic account the external account is associated with. -ExternalBankAccountAddress = ExternalBankAccountAddressParam -"""This type is deprecated, please use ExternalBankAccountAddressParam instead""" + For external accounts that are associated with the program, account_token field + returned will be null + """ + + address: ExternalBankAccountAddressParam + """Address""" + + company_id: str + """Optional field that helps identify bank accounts in receipts""" + + dob: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Date of Birth of the Individual that owns the external bank account""" -ExternalBankAccountCreateParams = Union[BankVerifiedCreateBankAccountAPIRequest, PlaidCreateBankAccountAPIRequest] + doing_business_as: str + """Doing Business As""" + + name: str + """The nickname for this External Bank Account""" + + user_defined_id: str + """User Defined ID""" + + +ExternalBankAccountCreateParams: TypeAlias = Union[ + BankVerifiedCreateBankAccountAPIRequest, + ExternallyVerifiedCreateBankAccountAPIRequest, + UnverifiedCreateBankAccountAPIRequest, +] diff --git a/src/lithic/types/external_bank_account_create_response.py b/src/lithic/types/external_bank_account_create_response.py index 593fd169..05a5eb46 100644 --- a/src/lithic/types/external_bank_account_create_response.py +++ b/src/lithic/types/external_bank_account_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime @@ -32,7 +32,7 @@ class ExternalBankAccountCreateResponse(BaseModel): """ currency: str - """currency of the external account 3-digit alphabetic ISO 4217 code""" + """currency of the external account 3-character alphabetic ISO 4217 code""" last_four: str """The last 4 digits of the bank account. @@ -47,19 +47,25 @@ class ExternalBankAccountCreateResponse(BaseModel): """ owner_type: Literal["BUSINESS", "INDIVIDUAL"] + """Owner Type""" routing_number: str + """Routing Number""" - state: Literal["CLOSED", "ENABLED", "PAUSED"] + state: Literal["ENABLED", "CLOSED", "PAUSED"] + """Account State""" type: Literal["CHECKING", "SAVINGS"] + """Account Type""" verification_attempts: int """The number of attempts at verification""" verification_method: Literal["MANUAL", "MICRO_DEPOSIT", "PLAID", "PRENOTE"] + """Verification Method""" - verification_state: Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"] + verification_state: Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"] + """Verification State""" account_token: Optional[str] = None """Indicates which Lithic account the external account is associated with. @@ -69,10 +75,7 @@ class ExternalBankAccountCreateResponse(BaseModel): """ address: Optional[ExternalBankAccountAddress] = None - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. - """ + """Address""" company_id: Optional[str] = None """Optional field that helps identify bank accounts in receipts""" @@ -81,11 +84,16 @@ class ExternalBankAccountCreateResponse(BaseModel): """Date of Birth of the Individual that owns the external bank account""" doing_business_as: Optional[str] = None + """Doing Business As""" + + financial_account_token: Optional[str] = None + """The financial account token of the operating account to fund the micro deposits""" name: Optional[str] = None - """The nickname given to this record of External Bank Account""" + """The nickname for this External Bank Account""" user_defined_id: Optional[str] = None + """User Defined ID""" verification_failed_reason: Optional[str] = None """Optional free text description of the reason for the failed verification. diff --git a/src/lithic/types/external_bank_account_created_webhook_event.py b/src/lithic/types/external_bank_account_created_webhook_event.py new file mode 100644 index 00000000..a47d282c --- /dev/null +++ b/src/lithic/types/external_bank_account_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .external_bank_account import ExternalBankAccount + +__all__ = ["ExternalBankAccountCreatedWebhookEvent"] + + +class ExternalBankAccountCreatedWebhookEvent(ExternalBankAccount): + event_type: Literal["external_bank_account.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/external_bank_account_list_params.py b/src/lithic/types/external_bank_account_list_params.py index 561b6808..3d703893 100644 --- a/src/lithic/types/external_bank_account_list_params.py +++ b/src/lithic/types/external_bank_account_list_params.py @@ -1,10 +1,11 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing import List from typing_extensions import Literal, TypedDict +from .._types import SequenceNotStr from .owner_type import OwnerType __all__ = ["ExternalBankAccountListParams"] @@ -15,7 +16,7 @@ class ExternalBankAccountListParams(TypedDict, total=False): account_types: List[Literal["CHECKING", "SAVINGS"]] - countries: List[str] + countries: SequenceNotStr[str] ending_before: str """A cursor representing an item's token before which a page of results should end. @@ -35,6 +36,6 @@ class ExternalBankAccountListParams(TypedDict, total=False): Used to retrieve the next page of results after this item. """ - states: List[Literal["CLOSED", "ENABLED", "PAUSED"]] + states: List[Literal["ENABLED", "CLOSED", "PAUSED"]] - verification_states: List[Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"]] + verification_states: List[Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"]] diff --git a/src/lithic/types/external_bank_account_list_response.py b/src/lithic/types/external_bank_account_list_response.py index 867b7937..d83bbb6e 100644 --- a/src/lithic/types/external_bank_account_list_response.py +++ b/src/lithic/types/external_bank_account_list_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime @@ -32,7 +32,7 @@ class ExternalBankAccountListResponse(BaseModel): """ currency: str - """currency of the external account 3-digit alphabetic ISO 4217 code""" + """currency of the external account 3-character alphabetic ISO 4217 code""" last_four: str """The last 4 digits of the bank account. @@ -47,19 +47,25 @@ class ExternalBankAccountListResponse(BaseModel): """ owner_type: Literal["BUSINESS", "INDIVIDUAL"] + """Owner Type""" routing_number: str + """Routing Number""" - state: Literal["CLOSED", "ENABLED", "PAUSED"] + state: Literal["ENABLED", "CLOSED", "PAUSED"] + """Account State""" type: Literal["CHECKING", "SAVINGS"] + """Account Type""" verification_attempts: int """The number of attempts at verification""" verification_method: Literal["MANUAL", "MICRO_DEPOSIT", "PLAID", "PRENOTE"] + """Verification Method""" - verification_state: Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"] + verification_state: Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"] + """Verification State""" account_token: Optional[str] = None """Indicates which Lithic account the external account is associated with. @@ -69,10 +75,7 @@ class ExternalBankAccountListResponse(BaseModel): """ address: Optional[ExternalBankAccountAddress] = None - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. - """ + """Address""" company_id: Optional[str] = None """Optional field that helps identify bank accounts in receipts""" @@ -81,11 +84,16 @@ class ExternalBankAccountListResponse(BaseModel): """Date of Birth of the Individual that owns the external bank account""" doing_business_as: Optional[str] = None + """Doing Business As""" + + financial_account_token: Optional[str] = None + """The financial account token of the operating account to fund the micro deposits""" name: Optional[str] = None - """The nickname given to this record of External Bank Account""" + """The nickname for this External Bank Account""" user_defined_id: Optional[str] = None + """User Defined ID""" verification_failed_reason: Optional[str] = None """Optional free text description of the reason for the failed verification. diff --git a/src/lithic/types/external_bank_account_retrieve_response.py b/src/lithic/types/external_bank_account_retrieve_response.py index 7bb09881..89d46fb3 100644 --- a/src/lithic/types/external_bank_account_retrieve_response.py +++ b/src/lithic/types/external_bank_account_retrieve_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime @@ -32,7 +32,7 @@ class ExternalBankAccountRetrieveResponse(BaseModel): """ currency: str - """currency of the external account 3-digit alphabetic ISO 4217 code""" + """currency of the external account 3-character alphabetic ISO 4217 code""" last_four: str """The last 4 digits of the bank account. @@ -47,19 +47,25 @@ class ExternalBankAccountRetrieveResponse(BaseModel): """ owner_type: Literal["BUSINESS", "INDIVIDUAL"] + """Owner Type""" routing_number: str + """Routing Number""" - state: Literal["CLOSED", "ENABLED", "PAUSED"] + state: Literal["ENABLED", "CLOSED", "PAUSED"] + """Account State""" type: Literal["CHECKING", "SAVINGS"] + """Account Type""" verification_attempts: int """The number of attempts at verification""" verification_method: Literal["MANUAL", "MICRO_DEPOSIT", "PLAID", "PRENOTE"] + """Verification Method""" - verification_state: Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"] + verification_state: Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"] + """Verification State""" account_token: Optional[str] = None """Indicates which Lithic account the external account is associated with. @@ -69,10 +75,7 @@ class ExternalBankAccountRetrieveResponse(BaseModel): """ address: Optional[ExternalBankAccountAddress] = None - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. - """ + """Address""" company_id: Optional[str] = None """Optional field that helps identify bank accounts in receipts""" @@ -81,11 +84,16 @@ class ExternalBankAccountRetrieveResponse(BaseModel): """Date of Birth of the Individual that owns the external bank account""" doing_business_as: Optional[str] = None + """Doing Business As""" + + financial_account_token: Optional[str] = None + """The financial account token of the operating account to fund the micro deposits""" name: Optional[str] = None - """The nickname given to this record of External Bank Account""" + """The nickname for this External Bank Account""" user_defined_id: Optional[str] = None + """User Defined ID""" verification_failed_reason: Optional[str] = None """Optional free text description of the reason for the failed verification. diff --git a/src/lithic/types/external_bank_account_retry_micro_deposits_params.py b/src/lithic/types/external_bank_account_retry_micro_deposits_params.py new file mode 100644 index 00000000..97bfa30b --- /dev/null +++ b/src/lithic/types/external_bank_account_retry_micro_deposits_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ExternalBankAccountRetryMicroDepositsParams"] + + +class ExternalBankAccountRetryMicroDepositsParams(TypedDict, total=False): + financial_account_token: str diff --git a/src/lithic/types/external_bank_account_retry_micro_deposits_response.py b/src/lithic/types/external_bank_account_retry_micro_deposits_response.py index be343ea7..3abe8d31 100644 --- a/src/lithic/types/external_bank_account_retry_micro_deposits_response.py +++ b/src/lithic/types/external_bank_account_retry_micro_deposits_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime @@ -32,7 +32,7 @@ class ExternalBankAccountRetryMicroDepositsResponse(BaseModel): """ currency: str - """currency of the external account 3-digit alphabetic ISO 4217 code""" + """currency of the external account 3-character alphabetic ISO 4217 code""" last_four: str """The last 4 digits of the bank account. @@ -47,19 +47,25 @@ class ExternalBankAccountRetryMicroDepositsResponse(BaseModel): """ owner_type: Literal["BUSINESS", "INDIVIDUAL"] + """Owner Type""" routing_number: str + """Routing Number""" - state: Literal["CLOSED", "ENABLED", "PAUSED"] + state: Literal["ENABLED", "CLOSED", "PAUSED"] + """Account State""" type: Literal["CHECKING", "SAVINGS"] + """Account Type""" verification_attempts: int """The number of attempts at verification""" verification_method: Literal["MANUAL", "MICRO_DEPOSIT", "PLAID", "PRENOTE"] + """Verification Method""" - verification_state: Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"] + verification_state: Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"] + """Verification State""" account_token: Optional[str] = None """Indicates which Lithic account the external account is associated with. @@ -69,10 +75,7 @@ class ExternalBankAccountRetryMicroDepositsResponse(BaseModel): """ address: Optional[ExternalBankAccountAddress] = None - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. - """ + """Address""" company_id: Optional[str] = None """Optional field that helps identify bank accounts in receipts""" @@ -81,11 +84,16 @@ class ExternalBankAccountRetryMicroDepositsResponse(BaseModel): """Date of Birth of the Individual that owns the external bank account""" doing_business_as: Optional[str] = None + """Doing Business As""" + + financial_account_token: Optional[str] = None + """The financial account token of the operating account to fund the micro deposits""" name: Optional[str] = None - """The nickname given to this record of External Bank Account""" + """The nickname for this External Bank Account""" user_defined_id: Optional[str] = None + """User Defined ID""" verification_failed_reason: Optional[str] = None """Optional free text description of the reason for the failed verification. diff --git a/src/lithic/types/external_bank_account_retry_prenote_params.py b/src/lithic/types/external_bank_account_retry_prenote_params.py new file mode 100644 index 00000000..b86f2b44 --- /dev/null +++ b/src/lithic/types/external_bank_account_retry_prenote_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ExternalBankAccountRetryPrenoteParams"] + + +class ExternalBankAccountRetryPrenoteParams(TypedDict, total=False): + financial_account_token: str diff --git a/src/lithic/types/external_bank_account_update_params.py b/src/lithic/types/external_bank_account_update_params.py index 68ac4e2d..d20a51e4 100644 --- a/src/lithic/types/external_bank_account_update_params.py +++ b/src/lithic/types/external_bank_account_update_params.py @@ -1,10 +1,10 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing import Union from datetime import date -from typing_extensions import Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._utils import PropertyInfo from .owner_type import OwnerType @@ -15,25 +15,33 @@ class ExternalBankAccountUpdateParams(TypedDict, total=False): address: ExternalBankAccountAddressParam - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. - """ + """Address""" company_id: str + """Optional field that helps identify bank accounts in receipts""" dob: Annotated[Union[str, date], PropertyInfo(format="iso8601")] """Date of Birth of the Individual that owns the external bank account""" doing_business_as: str + """Doing Business As""" name: str + """The nickname for this External Bank Account""" owner: str + """Legal Name of the business or individual who owns the external account. + + This will appear in statements + """ owner_type: OwnerType + """Owner Type""" + + type: Literal["CHECKING", "SAVINGS"] user_defined_id: str + """User Defined ID""" ExternalBankAccountAddress = ExternalBankAccountAddressParam diff --git a/src/lithic/types/external_bank_account_update_response.py b/src/lithic/types/external_bank_account_update_response.py index 85d617fe..cda7ab58 100644 --- a/src/lithic/types/external_bank_account_update_response.py +++ b/src/lithic/types/external_bank_account_update_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime @@ -32,7 +32,7 @@ class ExternalBankAccountUpdateResponse(BaseModel): """ currency: str - """currency of the external account 3-digit alphabetic ISO 4217 code""" + """currency of the external account 3-character alphabetic ISO 4217 code""" last_four: str """The last 4 digits of the bank account. @@ -47,19 +47,25 @@ class ExternalBankAccountUpdateResponse(BaseModel): """ owner_type: Literal["BUSINESS", "INDIVIDUAL"] + """Owner Type""" routing_number: str + """Routing Number""" - state: Literal["CLOSED", "ENABLED", "PAUSED"] + state: Literal["ENABLED", "CLOSED", "PAUSED"] + """Account State""" type: Literal["CHECKING", "SAVINGS"] + """Account Type""" verification_attempts: int """The number of attempts at verification""" verification_method: Literal["MANUAL", "MICRO_DEPOSIT", "PLAID", "PRENOTE"] + """Verification Method""" - verification_state: Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"] + verification_state: Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"] + """Verification State""" account_token: Optional[str] = None """Indicates which Lithic account the external account is associated with. @@ -69,10 +75,7 @@ class ExternalBankAccountUpdateResponse(BaseModel): """ address: Optional[ExternalBankAccountAddress] = None - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. - """ + """Address""" company_id: Optional[str] = None """Optional field that helps identify bank accounts in receipts""" @@ -81,11 +84,16 @@ class ExternalBankAccountUpdateResponse(BaseModel): """Date of Birth of the Individual that owns the external bank account""" doing_business_as: Optional[str] = None + """Doing Business As""" + + financial_account_token: Optional[str] = None + """The financial account token of the operating account to fund the micro deposits""" name: Optional[str] = None - """The nickname given to this record of External Bank Account""" + """The nickname for this External Bank Account""" user_defined_id: Optional[str] = None + """User Defined ID""" verification_failed_reason: Optional[str] = None """Optional free text description of the reason for the failed verification. diff --git a/src/lithic/types/external_bank_account_updated_webhook_event.py b/src/lithic/types/external_bank_account_updated_webhook_event.py new file mode 100644 index 00000000..4b7c5e3c --- /dev/null +++ b/src/lithic/types/external_bank_account_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .external_bank_account import ExternalBankAccount + +__all__ = ["ExternalBankAccountUpdatedWebhookEvent"] + + +class ExternalBankAccountUpdatedWebhookEvent(ExternalBankAccount): + event_type: Literal["external_bank_account.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/external_bank_accounts/__init__.py b/src/lithic/types/external_bank_accounts/__init__.py index 97d834ff..b30011e2 100644 --- a/src/lithic/types/external_bank_accounts/__init__.py +++ b/src/lithic/types/external_bank_accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py b/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py index 3f0ac427..44f17a67 100644 --- a/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py +++ b/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py b/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py index b101ce52..643962ad 100644 --- a/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py +++ b/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime @@ -32,7 +32,7 @@ class MicroDepositCreateResponse(BaseModel): """ currency: str - """currency of the external account 3-digit alphabetic ISO 4217 code""" + """currency of the external account 3-character alphabetic ISO 4217 code""" last_four: str """The last 4 digits of the bank account. @@ -47,19 +47,25 @@ class MicroDepositCreateResponse(BaseModel): """ owner_type: Literal["BUSINESS", "INDIVIDUAL"] + """Owner Type""" routing_number: str + """Routing Number""" - state: Literal["CLOSED", "ENABLED", "PAUSED"] + state: Literal["ENABLED", "CLOSED", "PAUSED"] + """Account State""" type: Literal["CHECKING", "SAVINGS"] + """Account Type""" verification_attempts: int """The number of attempts at verification""" verification_method: Literal["MANUAL", "MICRO_DEPOSIT", "PLAID", "PRENOTE"] + """Verification Method""" - verification_state: Literal["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS", "PENDING"] + verification_state: Literal["PENDING", "ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"] + """Verification State""" account_token: Optional[str] = None """Indicates which Lithic account the external account is associated with. @@ -69,10 +75,7 @@ class MicroDepositCreateResponse(BaseModel): """ address: Optional[ExternalBankAccountAddress] = None - """ - Address used during Address Verification Service (AVS) checks during - transactions if enabled via Auth Rules. - """ + """Address""" company_id: Optional[str] = None """Optional field that helps identify bank accounts in receipts""" @@ -81,11 +84,16 @@ class MicroDepositCreateResponse(BaseModel): """Date of Birth of the Individual that owns the external bank account""" doing_business_as: Optional[str] = None + """Doing Business As""" + + financial_account_token: Optional[str] = None + """The financial account token of the operating account to fund the micro deposits""" name: Optional[str] = None - """The nickname given to this record of External Bank Account""" + """The nickname for this External Bank Account""" user_defined_id: Optional[str] = None + """User Defined ID""" verification_failed_reason: Optional[str] = None """Optional free text description of the reason for the failed verification. diff --git a/src/lithic/types/external_payment.py b/src/lithic/types/external_payment.py new file mode 100644 index 00000000..47264c7d --- /dev/null +++ b/src/lithic/types/external_payment.py @@ -0,0 +1,97 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import date, datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ExternalPayment", "Event"] + + +class Event(BaseModel): + token: str + + amount: int + + created: datetime + + detailed_results: List[Literal["APPROVED", "INSUFFICIENT_FUNDS"]] + + effective_date: date + + memo: str + + result: Literal["APPROVED", "DECLINED"] + + type: Literal[ + "EXTERNAL_WIRE_INITIATED", + "EXTERNAL_WIRE_CANCELED", + "EXTERNAL_WIRE_SETTLED", + "EXTERNAL_WIRE_REVERSED", + "EXTERNAL_WIRE_RELEASED", + "EXTERNAL_ACH_INITIATED", + "EXTERNAL_ACH_CANCELED", + "EXTERNAL_ACH_SETTLED", + "EXTERNAL_ACH_REVERSED", + "EXTERNAL_ACH_RELEASED", + "EXTERNAL_TRANSFER_INITIATED", + "EXTERNAL_TRANSFER_CANCELED", + "EXTERNAL_TRANSFER_SETTLED", + "EXTERNAL_TRANSFER_REVERSED", + "EXTERNAL_TRANSFER_RELEASED", + "EXTERNAL_CHECK_INITIATED", + "EXTERNAL_CHECK_CANCELED", + "EXTERNAL_CHECK_SETTLED", + "EXTERNAL_CHECK_REVERSED", + "EXTERNAL_CHECK_RELEASED", + "EXTERNAL_FEDNOW_INITIATED", + "EXTERNAL_FEDNOW_CANCELED", + "EXTERNAL_FEDNOW_SETTLED", + "EXTERNAL_FEDNOW_REVERSED", + "EXTERNAL_FEDNOW_RELEASED", + "EXTERNAL_RTP_INITIATED", + "EXTERNAL_RTP_CANCELED", + "EXTERNAL_RTP_SETTLED", + "EXTERNAL_RTP_REVERSED", + "EXTERNAL_RTP_RELEASED", + ] + + +class ExternalPayment(BaseModel): + token: str + """Unique identifier for the transaction""" + + created: datetime + """ISO 8601 timestamp of when the transaction was created""" + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """The status of the transaction""" + + updated: datetime + """ISO 8601 timestamp of when the transaction was last updated""" + + category: Optional[ + Literal[ + "EXTERNAL_WIRE", "EXTERNAL_ACH", "EXTERNAL_CHECK", "EXTERNAL_FEDNOW", "EXTERNAL_RTP", "EXTERNAL_TRANSFER" + ] + ] = None + + currency: Optional[str] = None + + events: Optional[List[Event]] = None + + family: Optional[Literal["EXTERNAL_PAYMENT"]] = None + """EXTERNAL_PAYMENT - External Payment Response""" + + financial_account_token: Optional[str] = None + + payment_type: Optional[Literal["DEPOSIT", "WITHDRAWAL"]] = None + + pending_amount: Optional[int] = None + + result: Optional[Literal["APPROVED", "DECLINED"]] = None + + settled_amount: Optional[int] = None + + user_defined_id: Optional[str] = None diff --git a/src/lithic/types/external_payment_cancel_params.py b/src/lithic/types/external_payment_cancel_params.py new file mode 100644 index 00000000..23405208 --- /dev/null +++ b/src/lithic/types/external_payment_cancel_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ExternalPaymentCancelParams"] + + +class ExternalPaymentCancelParams(TypedDict, total=False): + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + + memo: str diff --git a/src/lithic/types/external_payment_create_params.py b/src/lithic/types/external_payment_create_params.py new file mode 100644 index 00000000..0977186c --- /dev/null +++ b/src/lithic/types/external_payment_create_params.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ExternalPaymentCreateParams"] + + +class ExternalPaymentCreateParams(TypedDict, total=False): + amount: Required[int] + + category: Required[ + Literal[ + "EXTERNAL_WIRE", "EXTERNAL_ACH", "EXTERNAL_CHECK", "EXTERNAL_FEDNOW", "EXTERNAL_RTP", "EXTERNAL_TRANSFER" + ] + ] + + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + + financial_account_token: Required[str] + + payment_type: Required[Literal["DEPOSIT", "WITHDRAWAL"]] + + token: str + + memo: str + + progress_to: Literal["SETTLED", "RELEASED"] + + user_defined_id: str diff --git a/src/lithic/types/external_payment_created_webhook_event.py b/src/lithic/types/external_payment_created_webhook_event.py new file mode 100644 index 00000000..e58e8357 --- /dev/null +++ b/src/lithic/types/external_payment_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .external_payment import ExternalPayment + +__all__ = ["ExternalPaymentCreatedWebhookEvent"] + + +class ExternalPaymentCreatedWebhookEvent(ExternalPayment): + event_type: Literal["external_payment.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/external_payment_list_params.py b/src/lithic/types/external_payment_list_params.py new file mode 100644 index 00000000..f54ac91c --- /dev/null +++ b/src/lithic/types/external_payment_list_params.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ExternalPaymentListParams"] + + +class ExternalPaymentListParams(TypedDict, total=False): + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + business_account_token: str + + category: Literal[ + "EXTERNAL_WIRE", "EXTERNAL_ACH", "EXTERNAL_CHECK", "EXTERNAL_FEDNOW", "EXTERNAL_RTP", "EXTERNAL_TRANSFER" + ] + """External Payment category to be returned.""" + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + financial_account_token: str + """ + Globally unique identifier for the financial account or card that will send the + funds. Accepted type dependent on the program's use case. + """ + + page_size: int + """Page size (for pagination).""" + + result: Literal["APPROVED", "DECLINED"] + """External Payment result to be returned.""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """Book transfer status to be returned.""" diff --git a/src/lithic/types/external_payment_release_params.py b/src/lithic/types/external_payment_release_params.py new file mode 100644 index 00000000..18f3ab78 --- /dev/null +++ b/src/lithic/types/external_payment_release_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ExternalPaymentReleaseParams"] + + +class ExternalPaymentReleaseParams(TypedDict, total=False): + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + + memo: str diff --git a/src/lithic/types/external_payment_reverse_params.py b/src/lithic/types/external_payment_reverse_params.py new file mode 100644 index 00000000..2949029f --- /dev/null +++ b/src/lithic/types/external_payment_reverse_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ExternalPaymentReverseParams"] + + +class ExternalPaymentReverseParams(TypedDict, total=False): + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + + memo: str diff --git a/src/lithic/types/external_payment_settle_params.py b/src/lithic/types/external_payment_settle_params.py new file mode 100644 index 00000000..984cdceb --- /dev/null +++ b/src/lithic/types/external_payment_settle_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ExternalPaymentSettleParams"] + + +class ExternalPaymentSettleParams(TypedDict, total=False): + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + + memo: str + + progress_to: Literal["SETTLED", "RELEASED"] diff --git a/src/lithic/types/external_payment_updated_webhook_event.py b/src/lithic/types/external_payment_updated_webhook_event.py new file mode 100644 index 00000000..8412e44a --- /dev/null +++ b/src/lithic/types/external_payment_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .external_payment import ExternalPayment + +__all__ = ["ExternalPaymentUpdatedWebhookEvent"] + + +class ExternalPaymentUpdatedWebhookEvent(ExternalPayment): + event_type: Literal["external_payment.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/external_resource.py b/src/lithic/types/external_resource.py new file mode 100644 index 00000000..97cbc595 --- /dev/null +++ b/src/lithic/types/external_resource.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel +from .external_resource_type import ExternalResourceType + +__all__ = ["ExternalResource"] + + +class ExternalResource(BaseModel): + """External resource associated with the management operation""" + + external_resource_token: str + """Token identifying the external resource""" + + external_resource_type: ExternalResourceType + """Type of external resource associated with the management operation""" + + external_resource_sub_token: Optional[str] = None + """Token identifying the external resource sub-resource""" diff --git a/src/lithic/types/external_resource_type.py b/src/lithic/types/external_resource_type.py new file mode 100644 index 00000000..ed047939 --- /dev/null +++ b/src/lithic/types/external_resource_type.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ExternalResourceType"] + +ExternalResourceType: TypeAlias = Literal["STATEMENT", "COLLECTION", "DISPUTE", "UNKNOWN"] diff --git a/src/lithic/types/financial_account.py b/src/lithic/types/financial_account.py index 9c5cee34..4ca056c1 100644 --- a/src/lithic/types/financial_account.py +++ b/src/lithic/types/financial_account.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime @@ -6,27 +6,66 @@ from .._models import BaseModel -__all__ = ["FinancialAccount"] +__all__ = ["FinancialAccount", "CreditConfiguration", "CreditConfigurationAutoCollectionConfiguration"] + + +class CreditConfigurationAutoCollectionConfiguration(BaseModel): + auto_collection_enabled: bool + """If auto collection is enabled for this account""" + + +class CreditConfiguration(BaseModel): + auto_collection_configuration: Optional[CreditConfigurationAutoCollectionConfiguration] = None + + credit_limit: Optional[int] = None + + credit_product_token: Optional[str] = None + """Globally unique identifier for the credit product""" + + external_bank_account_token: Optional[str] = None + + tier: Optional[str] = None + """Tier assigned to the financial account""" class FinancialAccount(BaseModel): token: str - """Globally unique identifier for the financial account.""" + """Globally unique identifier for the account""" + + account_token: Optional[str] = None created: datetime - """Date and time for when the financial account was first created.""" - type: Literal["ISSUING", "OPERATING", "RESERVE"] - """Type of financial account""" + credit_configuration: Optional[CreditConfiguration] = None + + is_for_benefit_of: bool + """Whether financial account is for the benefit of another entity""" + + nickname: Optional[str] = None + + status: Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"] + """Status of the financial account""" + + type: Literal[ + "ISSUING", + "RESERVE", + "OPERATING", + "CHARGED_OFF_FEES", + "CHARGED_OFF_INTEREST", + "CHARGED_OFF_PRINCIPAL", + "SECURITY", + "PROGRAM_RECEIVABLES", + "COLLECTION", + "PROGRAM_BANK_ACCOUNTS_PAYABLE", + ] updated: datetime - """Date and time for when the financial account was last updated.""" account_number: Optional[str] = None - """Account number for your Lithic-assigned bank account number, if applicable.""" - - nickname: Optional[str] = None - """User-defined nickname for the financial account.""" routing_number: Optional[str] = None - """Routing number for your Lithic-assigned bank account number, if applicable.""" + + substatus: Optional[ + Literal["CHARGED_OFF_DELINQUENT", "CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "DELINQUENT"] + ] = None + """Substatus for the financial account""" diff --git a/src/lithic/types/financial_account_balance.py b/src/lithic/types/financial_account_balance.py new file mode 100644 index 00000000..79bd194e --- /dev/null +++ b/src/lithic/types/financial_account_balance.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["FinancialAccountBalance"] + + +class FinancialAccountBalance(BaseModel): + """Balance of a Financial Account""" + + token: str + """Globally unique identifier for the financial account that holds this balance.""" + + available_amount: int + """Funds available for spend in the currency's smallest unit (e.g., cents for USD)""" + + created: datetime + """Date and time for when the balance was first created.""" + + currency: str + """3-character alphabetic ISO 4217 code for the local currency of the balance.""" + + last_transaction_event_token: str + """ + Globally unique identifier for the last financial transaction event that + impacted this balance. + """ + + last_transaction_token: str + """ + Globally unique identifier for the last financial transaction that impacted this + balance. + """ + + pending_amount: int + """Funds not available for spend due to card authorizations or pending ACH release. + + Shown in the currency's smallest unit (e.g., cents for USD). + """ + + total_amount: int + """ + The sum of available and pending balance in the currency's smallest unit (e.g., + cents for USD). + """ + + type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] + """Type of financial account.""" + + updated: datetime + """Date and time for when the balance was last updated.""" diff --git a/src/lithic/types/financial_account_create_params.py b/src/lithic/types/financial_account_create_params.py index f854cb14..62e7bec9 100644 --- a/src/lithic/types/financial_account_create_params.py +++ b/src/lithic/types/financial_account_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -13,3 +13,5 @@ class FinancialAccountCreateParams(TypedDict, total=False): type: Required[Literal["OPERATING"]] account_token: str + + is_for_benefit_of: bool diff --git a/src/lithic/types/financial_account_created_webhook_event.py b/src/lithic/types/financial_account_created_webhook_event.py new file mode 100644 index 00000000..bb052470 --- /dev/null +++ b/src/lithic/types/financial_account_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .financial_account import FinancialAccount + +__all__ = ["FinancialAccountCreatedWebhookEvent"] + + +class FinancialAccountCreatedWebhookEvent(FinancialAccount): + event_type: Literal["financial_account.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/financial_account_list_params.py b/src/lithic/types/financial_account_list_params.py index 86cb0d10..0228f2b6 100644 --- a/src/lithic/types/financial_account_list_params.py +++ b/src/lithic/types/financial_account_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -14,5 +14,5 @@ class FinancialAccountListParams(TypedDict, total=False): business_account_token: str """List financial accounts for a given business_account_token""" - type: Literal["ISSUING", "OPERATING", "RESERVE"] + type: Literal["ISSUING", "OPERATING", "RESERVE", "SECURITY"] """List financial accounts of a given type""" diff --git a/src/lithic/types/financial_account_register_account_number_params.py b/src/lithic/types/financial_account_register_account_number_params.py new file mode 100644 index 00000000..7611aaab --- /dev/null +++ b/src/lithic/types/financial_account_register_account_number_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["FinancialAccountRegisterAccountNumberParams"] + + +class FinancialAccountRegisterAccountNumberParams(TypedDict, total=False): + account_number: Required[str] diff --git a/src/lithic/types/financial_account_update_params.py b/src/lithic/types/financial_account_update_params.py index b2bd5e08..41bb68a8 100644 --- a/src/lithic/types/financial_account_update_params.py +++ b/src/lithic/types/financial_account_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_account_update_status_params.py b/src/lithic/types/financial_account_update_status_params.py new file mode 100644 index 00000000..5ae18b85 --- /dev/null +++ b/src/lithic/types/financial_account_update_status_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["FinancialAccountUpdateStatusParams"] + + +class FinancialAccountUpdateStatusParams(TypedDict, total=False): + status: Required[Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"]] + """Status of the financial account""" + + substatus: Required[ + Optional[Literal["CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "CHARGED_OFF_DELINQUENT"]] + ] + """Substatus for the financial account""" diff --git a/src/lithic/types/financial_account_updated_webhook_event.py b/src/lithic/types/financial_account_updated_webhook_event.py new file mode 100644 index 00000000..b1ffbcbe --- /dev/null +++ b/src/lithic/types/financial_account_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .financial_account import FinancialAccount + +__all__ = ["FinancialAccountUpdatedWebhookEvent"] + + +class FinancialAccountUpdatedWebhookEvent(FinancialAccount): + event_type: Literal["financial_account.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/financial_accounts/__init__.py b/src/lithic/types/financial_accounts/__init__.py index 048f281c..0a094585 100644 --- a/src/lithic/types/financial_accounts/__init__.py +++ b/src/lithic/types/financial_accounts/__init__.py @@ -1,8 +1,14 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations +from .loan_tape import LoanTape as LoanTape from .statement import Statement as Statement +from .statements import Statements as Statements +from .category_balances import CategoryBalances as CategoryBalances from .balance_list_params import BalanceListParams as BalanceListParams +from .loan_tape_list_params import LoanTapeListParams as LoanTapeListParams from .statement_list_params import StatementListParams as StatementListParams +from .financial_account_credit_config import FinancialAccountCreditConfig as FinancialAccountCreditConfig from .financial_transaction_list_params import FinancialTransactionListParams as FinancialTransactionListParams +from .credit_configuration_update_params import CreditConfigurationUpdateParams as CreditConfigurationUpdateParams diff --git a/src/lithic/types/financial_accounts/balance_list_params.py b/src/lithic/types/financial_accounts/balance_list_params.py index b80030a0..be24dded 100644 --- a/src/lithic/types/financial_accounts/balance_list_params.py +++ b/src/lithic/types/financial_accounts/balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/category_balances.py b/src/lithic/types/financial_accounts/category_balances.py new file mode 100644 index 00000000..bce87764 --- /dev/null +++ b/src/lithic/types/financial_accounts/category_balances.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["CategoryBalances"] + + +class CategoryBalances(BaseModel): + fees: int + + interest: int + + principal: int diff --git a/src/lithic/types/financial_accounts/credit_configuration_update_params.py b/src/lithic/types/financial_accounts/credit_configuration_update_params.py new file mode 100644 index 00000000..f63d4559 --- /dev/null +++ b/src/lithic/types/financial_accounts/credit_configuration_update_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["CreditConfigurationUpdateParams", "AutoCollectionConfiguration"] + + +class CreditConfigurationUpdateParams(TypedDict, total=False): + auto_collection_configuration: AutoCollectionConfiguration + + credit_limit: int + + credit_product_token: str + """Globally unique identifier for the credit product""" + + external_bank_account_token: str + + tier: str + """Tier to assign to a financial account""" + + +class AutoCollectionConfiguration(TypedDict, total=False): + auto_collection_enabled: bool + """If auto collection is enabled for this account""" diff --git a/src/lithic/types/financial_accounts/financial_account_credit_config.py b/src/lithic/types/financial_accounts/financial_account_credit_config.py new file mode 100644 index 00000000..67b3d16c --- /dev/null +++ b/src/lithic/types/financial_accounts/financial_account_credit_config.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["FinancialAccountCreditConfig", "AutoCollectionConfiguration"] + + +class AutoCollectionConfiguration(BaseModel): + auto_collection_enabled: bool + """If auto collection is enabled for this account""" + + +class FinancialAccountCreditConfig(BaseModel): + account_token: str + """Globally unique identifier for the account""" + + auto_collection_configuration: AutoCollectionConfiguration + + credit_limit: Optional[int] = None + + credit_product_token: Optional[str] = None + """Globally unique identifier for the credit product""" + + external_bank_account_token: Optional[str] = None + + tier: Optional[str] = None + """Tier assigned to the financial account""" diff --git a/src/lithic/types/financial_accounts/financial_transaction_list_params.py b/src/lithic/types/financial_accounts/financial_transaction_list_params.py index 68018ec2..9577fd78 100644 --- a/src/lithic/types/financial_accounts/financial_transaction_list_params.py +++ b/src/lithic/types/financial_accounts/financial_transaction_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -18,7 +18,7 @@ class FinancialTransactionListParams(TypedDict, total=False): Only entries created after the specified time will be included. UTC time zone. """ - category: Literal["ACH", "CARD", "TRANSFER"] + category: Literal["ACH", "CARD", "INTERNAL", "TRANSFER"] """Financial Transaction category to be returned.""" end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] diff --git a/src/lithic/types/financial_accounts/loan_tape.py b/src/lithic/types/financial_accounts/loan_tape.py new file mode 100644 index 00000000..8e4a585c --- /dev/null +++ b/src/lithic/types/financial_accounts/loan_tape.py @@ -0,0 +1,175 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import datetime +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from ..category_details import CategoryDetails +from ..statement_totals import StatementTotals +from .category_balances import CategoryBalances + +__all__ = [ + "LoanTape", + "AccountStanding", + "AccountStandingFinancialAccountState", + "Balances", + "InterestDetails", + "MinimumPaymentBalance", + "PreviousStatementBalance", +] + + +class AccountStandingFinancialAccountState(BaseModel): + """Information about the financial account state""" + + status: Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"] + """Status of the financial account""" + + substatus: Optional[ + Literal["CHARGED_OFF_DELINQUENT", "CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "DELINQUENT"] + ] = None + """Substatus for the financial account""" + + +class AccountStanding(BaseModel): + consecutive_full_payments_made: int + """Number of consecutive full payments made""" + + consecutive_minimum_payments_made: int + """Number of consecutive minimum payments made""" + + consecutive_minimum_payments_missed: int + """Number of consecutive minimum payments missed""" + + days_past_due: int + """Number of days past due""" + + financial_account_state: AccountStandingFinancialAccountState + """Information about the financial account state""" + + has_grace: bool + """Whether the account currently has grace or not""" + + period_number: int + """Current overall period number""" + + period_state: Literal["STANDARD", "PROMO", "PENALTY"] + + +class Balances(BaseModel): + due: CategoryBalances + """Amount due for the prior billing cycle. + + Any amounts not fully paid off on this due date will be considered past due the + next day + """ + + next_statement_due: CategoryBalances + """Amount due for the current billing cycle. + + Any amounts not paid off by early payments or credits will be considered due at + the end of the current billing period + """ + + past_due: CategoryBalances + """Amount not paid off on previous due dates""" + + past_statements_due: CategoryBalances + """Amount due for the past billing cycles.""" + + +class InterestDetails(BaseModel): + actual_interest_charged: Optional[int] = None + + daily_balance_amounts: CategoryDetails + + effective_apr: CategoryDetails + + interest_calculation_method: Literal["DAILY", "AVERAGE_DAILY"] + + interest_for_period: CategoryDetails + + prime_rate: Optional[str] = None + + minimum_interest_charged: Optional[int] = None + + +class MinimumPaymentBalance(BaseModel): + amount: int + + remaining: int + + +class PreviousStatementBalance(BaseModel): + amount: int + + remaining: int + + +class LoanTape(BaseModel): + token: str + """Globally unique identifier for a loan tape""" + + account_standing: AccountStanding + + available_credit: int + """Amount of credit available to spend in cents""" + + balances: Balances + + created: datetime.datetime + """Timestamp of when the loan tape was created""" + + credit_limit: int + """For prepay accounts, this is the minimum prepay balance that must be maintained. + + For charge card accounts, this is the maximum credit balance extended by a + lender + """ + + credit_product_token: str + """Globally unique identifier for a credit product""" + + date: datetime.date + """Date of transactions that this loan tape covers""" + + day_totals: StatementTotals + + ending_balance: int + """Balance at the end of the day""" + + excess_credits: int + """Excess credits in the form of provisional credits, payments, or purchase + refunds. + + If positive, the account is in net credit state with no outstanding balances. An + overpayment could land an account in this state + """ + + financial_account_token: str + """Globally unique identifier for a financial account""" + + interest_details: Optional[InterestDetails] = None + + minimum_payment_balance: MinimumPaymentBalance + + payment_allocation: CategoryBalances + + period_totals: StatementTotals + + previous_statement_balance: PreviousStatementBalance + + starting_balance: int + """Balance at the start of the day""" + + updated: datetime.datetime + """Timestamp of when the loan tape was updated""" + + version: int + """Version number of the loan tape. This starts at 1""" + + ytd_totals: StatementTotals + + tier: Optional[str] = None + """Interest tier to which this account belongs to""" diff --git a/src/lithic/types/financial_accounts/loan_tape_list_params.py b/src/lithic/types/financial_accounts/loan_tape_list_params.py new file mode 100644 index 00000000..e45b5bd2 --- /dev/null +++ b/src/lithic/types/financial_accounts/loan_tape_list_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["LoanTapeListParams"] + + +class LoanTapeListParams(TypedDict, total=False): + begin: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified date will be included. + """ + + end: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified date will be included. + """ + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + page_size: int + """Page size (for pagination).""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ diff --git a/src/lithic/types/financial_accounts/statement.py b/src/lithic/types/financial_accounts/statement.py index b30afc8a..37b8884d 100644 --- a/src/lithic/types/financial_accounts/statement.py +++ b/src/lithic/types/financial_accounts/statement.py @@ -1,48 +1,139 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional from datetime import date, datetime +from typing_extensions import Literal from ..._models import BaseModel +from ..category_details import CategoryDetails +from ..statement_totals import StatementTotals -__all__ = ["Statement"] +__all__ = [ + "Statement", + "AccountStanding", + "AccountStandingFinancialAccountState", + "AmountDue", + "InterestDetails", + "PayoffDetails", +] -class Statement(BaseModel): - token: str - """Globally unique identifier for a statement""" +class AccountStandingFinancialAccountState(BaseModel): + """Information about the financial account state""" + + status: Literal["OPEN", "CLOSED", "SUSPENDED", "PENDING"] + """Status of the financial account""" + + substatus: Optional[ + Literal["CHARGED_OFF_DELINQUENT", "CHARGED_OFF_FRAUD", "END_USER_REQUEST", "BANK_REQUEST", "DELINQUENT"] + ] = None + """Substatus for the financial account""" + + +class AccountStanding(BaseModel): + consecutive_full_payments_made: int + """Number of consecutive full payments made""" - ach_period_total: int - """Total payments during this billing period.""" + consecutive_minimum_payments_made: int + """Number of consecutive minimum payments made""" - ach_ytd_total: int - """Year-to-date settled payment total""" + consecutive_minimum_payments_missed: int + """Number of consecutive minimum payments missed""" - adjustments_period_total: int - """Total adjustments during this billing period.""" + days_past_due: int + """Number of days past due""" - adjustments_ytd_total: int - """Year-to-date settled adjustments total""" + financial_account_state: AccountStandingFinancialAccountState + """Information about the financial account state""" - amount_due: int - """Payment due at the end of the billing period. + has_grace: bool + """Whether the account currently has grace or not""" + + period_number: int + """Current overall period number""" + + period_state: Literal["STANDARD", "PROMO", "PENALTY"] + + +class AmountDue(BaseModel): + amount: int + """Payment due at the end of the billing period in cents. Negative amount indicates something is owed. If the amount owed is positive - (e.g., there was a net credit), then payment should be returned to the - cardholder via ACH. + there was a net credit. If auto-collections are enabled this is the amount that + will be requested on the payment due date + """ + + past_due: int + """Amount past due for statement in cents""" + + +class InterestDetails(BaseModel): + actual_interest_charged: Optional[int] = None + + daily_balance_amounts: CategoryDetails + + effective_apr: CategoryDetails + + interest_calculation_method: Literal["DAILY", "AVERAGE_DAILY"] + + interest_for_period: CategoryDetails + + prime_rate: Optional[str] = None + + minimum_interest_charged: Optional[int] = None + + +class PayoffDetails(BaseModel): + """Details on number and size of payments to pay off balance""" + + minimum_payment_months: str + """ + The number of months it would take to pay off the balance in full by only paying + the minimum payment. "NA" will signal negative or zero amortization + """ + + minimum_payment_total: str + """ + The sum of all interest and principal paid, in cents, when only paying minimum + monthly payment. "NA" will signal negative or zero amortization """ + payoff_period_length_months: Optional[int] = None + """Number of months to full pay off""" + + payoff_period_monthly_payment_amount: Optional[int] = None + """ + The amount needed to be paid, in cents, each month in order to pay off current + balance in the payoff period + """ + + payoff_period_payment_total: Optional[int] = None + """ + The sum of all interest and principal paid, in cents, when paying off in the + payoff period + """ + + +class Statement(BaseModel): + token: str + """Globally unique identifier for a statement""" + + account_standing: AccountStanding + + amount_due: AmountDue + available_credit: int - """Amount of credit available to spend""" + """Amount of credit available to spend in cents""" created: datetime """Timestamp of when the statement was created""" credit_limit: int - """For prepay accounts, this is the minimum prepay balance that must be maintained. + """This is the maximum credit balance extended by the lender in cents""" - For charge card accounts, this is the maximum credit balance extended by a - lender. - """ + credit_product_token: str + """Globally unique identifier for a credit product""" days_in_billing_cycle: int """Number of days in the billing cycle""" @@ -50,23 +141,16 @@ class Statement(BaseModel): ending_balance: int """Balance at the end of the billing period. - For charge cards, this should be the same at the statement amount due. + For charge cards, this should be the same at the statement amount due in cents """ financial_account_token: str """Globally unique identifier for a financial account""" - payment_due_date: date + payment_due_date: Optional[date] = None """Date when the payment is due""" - purchases_period_total: int - """ - Total settled card transactions during this billing period, determined by - liability date. - """ - - purchases_ytd_total: int - """Year-to-date settled card transaction total""" + period_totals: StatementTotals starting_balance: int """Balance at the start of the billing period""" @@ -77,5 +161,20 @@ class Statement(BaseModel): statement_start_date: date """Date when the billing period began""" + statement_type: Literal["INITIAL", "PERIOD_END", "FINAL"] + updated: datetime """Timestamp of when the statement was updated""" + + ytd_totals: StatementTotals + + interest_details: Optional[InterestDetails] = None + + next_payment_due_date: Optional[date] = None + """Date when the next payment is due""" + + next_statement_end_date: Optional[date] = None + """Date when the next billing period will end""" + + payoff_details: Optional[PayoffDetails] = None + """Details on number and size of payments to pay off balance""" diff --git a/src/lithic/types/financial_accounts/statement_list_params.py b/src/lithic/types/financial_accounts/statement_list_params.py index 7006945c..47a42258 100644 --- a/src/lithic/types/financial_accounts/statement_list_params.py +++ b/src/lithic/types/financial_accounts/statement_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -30,6 +30,9 @@ class StatementListParams(TypedDict, total=False): Used to retrieve the previous page of results before this item. """ + include_initial_statements: bool + """Whether to include the initial statement. It is not included by default.""" + page_size: int """Page size (for pagination).""" diff --git a/src/lithic/types/financial_accounts/statements/__init__.py b/src/lithic/types/financial_accounts/statements/__init__.py index 1c4ae2ca..b5274717 100644 --- a/src/lithic/types/financial_accounts/statements/__init__.py +++ b/src/lithic/types/financial_accounts/statements/__init__.py @@ -1,6 +1,7 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations +from .statements import Statements as Statements +from .statement_line_items import StatementLineItems as StatementLineItems from .line_item_list_params import LineItemListParams as LineItemListParams -from .line_item_list_response import LineItemListResponse as LineItemListResponse diff --git a/src/lithic/types/financial_accounts/statements/line_item_list_params.py b/src/lithic/types/financial_accounts/statements/line_item_list_params.py index 5c585b1c..1a7a7d2d 100644 --- a/src/lithic/types/financial_accounts/statements/line_item_list_params.py +++ b/src/lithic/types/financial_accounts/statements/line_item_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,6 +9,7 @@ class LineItemListParams(TypedDict, total=False): financial_account_token: Required[str] + """Globally unique identifier for financial account.""" ending_before: str """A cursor representing an item's token before which a page of results should end. diff --git a/src/lithic/types/financial_accounts/statements/line_item_list_response.py b/src/lithic/types/financial_accounts/statements/line_item_list_response.py deleted file mode 100644 index ab713ba3..00000000 --- a/src/lithic/types/financial_accounts/statements/line_item_list_response.py +++ /dev/null @@ -1,100 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import Optional -from datetime import date, datetime -from typing_extensions import Literal - -from ...._models import BaseModel - -__all__ = ["LineItemListResponse"] - - -class LineItemListResponse(BaseModel): - token: str - """Globally unique identifier for a Statement Line Item""" - - amount: int - - category: Literal["ACH", "CARD", "TRANSFER"] - - created: datetime - """Timestamp of when the line item was generated""" - - currency: str - """3-digit alphabetic ISO 4217 code for the settling currency of the transaction""" - - event_type: Literal[ - "ACH_INSUFFICIENT_FUNDS", - "ACH_ORIGINATION_PENDING", - "ACH_ORIGINATION_RELEASED", - "ACH_RECEIPT_PENDING", - "ACH_RECEIPT_RELEASED", - "ACH_RETURN", - "AUTHORIZATION", - "AUTHORIZATION_ADVICE", - "AUTHORIZATION_EXPIRY", - "AUTHORIZATION_REVERSAL", - "BALANCE_INQUIRY", - "CLEARING", - "CORRECTION_CREDIT", - "CORRECTION_DEBIT", - "CREDIT_AUTHORIZATION", - "CREDIT_AUTHORIZATION_ADVICE", - "FINANCIAL_AUTHORIZATION", - "FINANCIAL_CREDIT_AUTHORIZATION", - "RETURN", - "RETURN_REVERSAL", - "TRANSFER", - "TRANSFER_INSUFFICIENT_FUNDS", - ] - """Event types: - - - `ACH_INSUFFICIENT_FUNDS` - Attempted ACH origination declined due to - insufficient balance. - - `ACH_ORIGINATION_PENDING` - ACH origination pending release from an ACH hold. - - `ACH_ORIGINATION_RELEASED` - ACH origination released from pending to - available balance. - - `ACH_RECEIPT_PENDING` - ACH receipt pending release from an ACH holder. - - `ACH_RECEIPT_RELEASED` - ACH receipt released from pending to available - balance. - - `ACH_RETURN` - ACH origination returned by the Receiving Depository Financial - Institution. - - `AUTHORIZATION` - Authorize a card transaction. - - `AUTHORIZATION_ADVICE` - Advice on a card transaction. - - `AUTHORIZATION_EXPIRY` - Card Authorization has expired and reversed by - Lithic. - - `AUTHORIZATION_REVERSAL` - Card Authorization was reversed by the merchant. - - `BALANCE_INQUIRY` - A card balance inquiry (typically a $0 authorization) has - occurred on a card. - - `CLEARING` - Card Transaction is settled. - - `CORRECTION_DEBIT` - Manual card transaction correction (Debit). - - `CORRECTION_CREDIT` - Manual card transaction correction (Credit). - - `CREDIT_AUTHORIZATION` - A refund or credit card authorization from a - merchant. - - `CREDIT_AUTHORIZATION_ADVICE` - A credit card authorization was approved on - your behalf by the network. - - `FINANCIAL_AUTHORIZATION` - A request from a merchant to debit card funds - without additional clearing. - - `FINANCIAL_CREDIT_AUTHORIZATION` - A request from a merchant to refund or - credit card funds without additional clearing. - - `RETURN` - A card refund has been processed on the transaction. - - `RETURN_REVERSAL` - A card refund has been reversed (e.g., when a merchant - reverses an incorrect refund). - - `TRANSFER` - Successful internal transfer of funds between financial accounts. - - `TRANSFER_INSUFFICIENT_FUNDS` - Declined internl transfer of funds due to - insufficient balance of the sender. - """ - - financial_account_token: str - """Globally unique identifier for a financial account""" - - financial_transaction_token: str - """Globally unique identifier for a financial transaction""" - - settled_date: date - """Date that the transaction settled""" - - card_token: Optional[str] = None - """Globally unique identifier for a card""" - - descriptor: Optional[str] = None diff --git a/src/lithic/types/financial_accounts/statements/statement_line_items.py b/src/lithic/types/financial_accounts/statements/statement_line_items.py new file mode 100644 index 00000000..c1816c30 --- /dev/null +++ b/src/lithic/types/financial_accounts/statements/statement_line_items.py @@ -0,0 +1,162 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import date, datetime +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["StatementLineItems", "Data"] + + +class Data(BaseModel): + token: str + """Globally unique identifier for a Statement Line Item""" + + amount: int + """Transaction amount in cents""" + + category: Literal[ + "ACH", + "BALANCE_OR_FUNDING", + "FEE", + "REWARD", + "ADJUSTMENT", + "DERECOGNITION", + "DISPUTE", + "CARD", + "EXTERNAL_ACH", + "EXTERNAL_CHECK", + "EXTERNAL_FEDNOW", + "EXTERNAL_RTP", + "EXTERNAL_TRANSFER", + "EXTERNAL_WIRE", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_FEE", + "MANAGEMENT_REWARD", + "MANAGEMENT_DISBURSEMENT", + "PROGRAM_FUNDING", + ] + + created: datetime + """Timestamp of when the line item was generated""" + + currency: str + """ + 3-character alphabetic ISO 4217 code for the settling currency of the + transaction + """ + + effective_date: date + """Date that the transaction effected the account balance""" + + event_type: Literal[ + "ACH_ORIGINATION_CANCELLED", + "ACH_ORIGINATION_INITIATED", + "ACH_ORIGINATION_PROCESSED", + "ACH_ORIGINATION_RELEASED", + "ACH_ORIGINATION_REJECTED", + "ACH_ORIGINATION_REVIEWED", + "ACH_ORIGINATION_SETTLED", + "ACH_RECEIPT_PROCESSED", + "ACH_RECEIPT_RELEASED", + "ACH_RECEIPT_SETTLED", + "ACH_RETURN_INITIATED", + "ACH_RETURN_PROCESSED", + "ACH_RETURN_REJECTED", + "ACH_RETURN_SETTLED", + "AUTHORIZATION", + "AUTHORIZATION_ADVICE", + "AUTHORIZATION_EXPIRY", + "AUTHORIZATION_REVERSAL", + "BALANCE_INQUIRY", + "BILLING_ERROR", + "BILLING_ERROR_REVERSAL", + "CARD_TO_CARD", + "CASH_BACK", + "CASH_BACK_REVERSAL", + "CLEARING", + "COLLECTION", + "CORRECTION_CREDIT", + "CORRECTION_DEBIT", + "CREDIT_AUTHORIZATION", + "CREDIT_AUTHORIZATION_ADVICE", + "CURRENCY_CONVERSION", + "CURRENCY_CONVERSION_REVERSAL", + "DISPUTE_WON", + "EXTERNAL_ACH_CANCELED", + "EXTERNAL_ACH_INITIATED", + "EXTERNAL_ACH_RELEASED", + "EXTERNAL_ACH_REVERSED", + "EXTERNAL_ACH_SETTLED", + "EXTERNAL_CHECK_CANCELED", + "EXTERNAL_CHECK_INITIATED", + "EXTERNAL_CHECK_RELEASED", + "EXTERNAL_CHECK_REVERSED", + "EXTERNAL_CHECK_SETTLED", + "EXTERNAL_FEDNOW_CANCELED", + "EXTERNAL_FEDNOW_INITIATED", + "EXTERNAL_FEDNOW_RELEASED", + "EXTERNAL_FEDNOW_REVERSED", + "EXTERNAL_FEDNOW_SETTLED", + "EXTERNAL_RTP_CANCELED", + "EXTERNAL_RTP_INITIATED", + "EXTERNAL_RTP_RELEASED", + "EXTERNAL_RTP_REVERSED", + "EXTERNAL_RTP_SETTLED", + "EXTERNAL_TRANSFER_CANCELED", + "EXTERNAL_TRANSFER_INITIATED", + "EXTERNAL_TRANSFER_RELEASED", + "EXTERNAL_TRANSFER_REVERSED", + "EXTERNAL_TRANSFER_SETTLED", + "EXTERNAL_WIRE_CANCELED", + "EXTERNAL_WIRE_INITIATED", + "EXTERNAL_WIRE_RELEASED", + "EXTERNAL_WIRE_REVERSED", + "EXTERNAL_WIRE_SETTLED", + "FINANCIAL_AUTHORIZATION", + "FINANCIAL_CREDIT_AUTHORIZATION", + "INTEREST", + "INTEREST_REVERSAL", + "INTERNAL_ADJUSTMENT", + "LATE_PAYMENT", + "LATE_PAYMENT_REVERSAL", + "LOSS_WRITE_OFF", + "PROVISIONAL_CREDIT", + "PROVISIONAL_CREDIT_REVERSAL", + "SERVICE", + "RETURN", + "RETURN_REVERSAL", + "TRANSFER", + "TRANSFER_INSUFFICIENT_FUNDS", + "RETURNED_PAYMENT", + "RETURNED_PAYMENT_REVERSAL", + "LITHIC_NETWORK_PAYMENT", + "ANNUAL", + "ANNUAL_REVERSAL", + "QUARTERLY", + "QUARTERLY_REVERSAL", + "MONTHLY", + "MONTHLY_REVERSAL", + ] + + financial_account_token: str + """Globally unique identifier for a financial account""" + + financial_transaction_event_token: str + """Globally unique identifier for a financial transaction event""" + + financial_transaction_token: str + """Globally unique identifier for a financial transaction""" + + card_token: Optional[str] = None + """Globally unique identifier for a card""" + + descriptor: Optional[str] = None + + +class StatementLineItems(BaseModel): + data: List[Data] + + has_more: bool diff --git a/src/lithic/types/financial_accounts/statements/statements.py b/src/lithic/types/financial_accounts/statements/statements.py new file mode 100644 index 00000000..b01ae134 --- /dev/null +++ b/src/lithic/types/financial_accounts/statements/statements.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ...._models import BaseModel +from ..statement import Statement + +__all__ = ["Statements"] + + +class Statements(BaseModel): + data: List[Statement] + + has_more: bool diff --git a/src/lithic/types/financial_transaction.py b/src/lithic/types/financial_transaction.py index 6936858a..6b3032e7 100644 --- a/src/lithic/types/financial_transaction.py +++ b/src/lithic/types/financial_transaction.py @@ -1,107 +1,25 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import List from datetime import datetime from typing_extensions import Literal from .._models import BaseModel +from .shared.financial_event import FinancialEvent -__all__ = ["FinancialTransaction", "Event"] - - -class Event(BaseModel): - token: Optional[str] = None - """Globally unique identifier.""" - - amount: Optional[int] = None - """ - Amount of the financial event that has been settled in the currency's smallest - unit (e.g., cents). - """ - - created: Optional[datetime] = None - """Date and time when the financial event occurred. UTC time zone.""" - - result: Optional[Literal["APPROVED", "DECLINED"]] = None - """ - APPROVED financial events were successful while DECLINED financial events were - declined by user, Lithic, or the network. - """ - - type: Optional[ - Literal[ - "ACH_INSUFFICIENT_FUNDS", - "ACH_ORIGINATION_PENDING", - "ACH_ORIGINATION_RELEASED", - "ACH_RECEIPT_PENDING", - "ACH_RECEIPT_RELEASED", - "ACH_RETURN", - "AUTHORIZATION", - "AUTHORIZATION_ADVICE", - "AUTHORIZATION_EXPIRY", - "AUTHORIZATION_REVERSAL", - "BALANCE_INQUIRY", - "CLEARING", - "CORRECTION_CREDIT", - "CORRECTION_DEBIT", - "CREDIT_AUTHORIZATION", - "CREDIT_AUTHORIZATION_ADVICE", - "FINANCIAL_AUTHORIZATION", - "FINANCIAL_CREDIT_AUTHORIZATION", - "RETURN", - "RETURN_REVERSAL", - "TRANSFER", - "TRANSFER_INSUFFICIENT_FUNDS", - ] - ] = None - """Event types: - - - `ACH_INSUFFICIENT_FUNDS` - Attempted ACH origination declined due to - insufficient balance. - - `ACH_ORIGINATION_PENDING` - ACH origination pending release from an ACH hold. - - `ACH_ORIGINATION_RELEASED` - ACH origination released from pending to - available balance. - - `ACH_RECEIPT_PENDING` - ACH receipt pending release from an ACH holder. - - `ACH_RECEIPT_RELEASED` - ACH receipt released from pending to available - balance. - - `ACH_RETURN` - ACH origination returned by the Receiving Depository Financial - Institution. - - `AUTHORIZATION` - Authorize a card transaction. - - `AUTHORIZATION_ADVICE` - Advice on a card transaction. - - `AUTHORIZATION_EXPIRY` - Card Authorization has expired and reversed by - Lithic. - - `AUTHORIZATION_REVERSAL` - Card Authorization was reversed by the merchant. - - `BALANCE_INQUIRY` - A card balance inquiry (typically a $0 authorization) has - occurred on a card. - - `CLEARING` - Card Transaction is settled. - - `CORRECTION_DEBIT` - Manual card transaction correction (Debit). - - `CORRECTION_CREDIT` - Manual card transaction correction (Credit). - - `CREDIT_AUTHORIZATION` - A refund or credit card authorization from a - merchant. - - `CREDIT_AUTHORIZATION_ADVICE` - A credit card authorization was approved on - your behalf by the network. - - `FINANCIAL_AUTHORIZATION` - A request from a merchant to debit card funds - without additional clearing. - - `FINANCIAL_CREDIT_AUTHORIZATION` - A request from a merchant to refund or - credit card funds without additional clearing. - - `RETURN` - A card refund has been processed on the transaction. - - `RETURN_REVERSAL` - A card refund has been reversed (e.g., when a merchant - reverses an incorrect refund). - - `TRANSFER` - Successful internal transfer of funds between financial accounts. - - `TRANSFER_INSUFFICIENT_FUNDS` - Declined internl transfer of funds due to - insufficient balance of the sender. - """ +__all__ = ["FinancialTransaction"] class FinancialTransaction(BaseModel): token: str """Globally unique identifier.""" - category: Literal["ACH", "CARD", "TRANSFER"] + category: Literal["ACH", "CARD", "INTERNAL", "TRANSFER"] """Status types: - `CARD` - Issuing card transaction. - `ACH` - Transaction over ACH. + - `INTERNAL` - Transaction for internal adjustment. - `TRANSFER` - Internal transfer of funds between financial accounts in your program. """ @@ -110,7 +28,10 @@ class FinancialTransaction(BaseModel): """Date and time when the financial transaction first occurred. UTC time zone.""" currency: str - """3-digit alphabetic ISO 4217 code for the settling currency of the transaction.""" + """ + 3-character alphabetic ISO 4217 code for the settling currency of the + transaction. + """ descriptor: str """ @@ -118,7 +39,7 @@ class FinancialTransaction(BaseModel): to display to users. """ - events: List[Event] + events: List[FinancialEvent] """A list of all financial events that have modified this financial transaction.""" pending_amount: int @@ -140,16 +61,16 @@ class FinancialTransaction(BaseModel): (e.g., cents), including any acquirer fees. This may change over time. """ - status: Literal["DECLINED", "EXPIRED", "PENDING", "SETTLED", "VOIDED"] + status: Literal["DECLINED", "EXPIRED", "PENDING", "RETURNED", "SETTLED", "VOIDED"] """Status types: - - `DECLINED` - The card transaction was declined. - - `EXPIRED` - Lithic reversed the card authorization as it has passed its - expiration time. - - `PENDING` - Authorization is pending completion from the merchant or pending - release from ACH hold period - - `SETTLED` - The financial transaction is completed. - - `VOIDED` - The merchant has voided the previously pending card authorization. + - `DECLINED` - The transaction was declined. + - `EXPIRED` - The authorization as it has passed its expiration time. Card + transaction only. + - `PENDING` - The transaction is expected to settle. + - `RETURNED` - The transaction has been returned. + - `SETTLED` - The transaction is completed. + - `VOIDED` - The transaction was voided. Card transaction only. """ updated: datetime diff --git a/src/lithic/types/fraud/__init__.py b/src/lithic/types/fraud/__init__.py new file mode 100644 index 00000000..17325491 --- /dev/null +++ b/src/lithic/types/fraud/__init__.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .transaction_report_params import TransactionReportParams as TransactionReportParams +from .transaction_report_response import TransactionReportResponse as TransactionReportResponse +from .transaction_retrieve_response import TransactionRetrieveResponse as TransactionRetrieveResponse diff --git a/src/lithic/types/fraud/transaction_report_params.py b/src/lithic/types/fraud/transaction_report_params.py new file mode 100644 index 00000000..997d066e --- /dev/null +++ b/src/lithic/types/fraud/transaction_report_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["TransactionReportParams"] + + +class TransactionReportParams(TypedDict, total=False): + fraud_status: Required[Literal["SUSPECTED_FRAUD", "FRAUDULENT", "NOT_FRAUDULENT"]] + """ + The fraud status of the transaction, string (enum) supporting the following + values: + + - `SUSPECTED_FRAUD`: The transaction is suspected to be fraudulent, but this + hasn’t been confirmed. + - `FRAUDULENT`: The transaction is confirmed to be fraudulent. A transaction may + immediately be moved into this state, or be graduated into this state from the + `SUSPECTED_FRAUD` state. + - `NOT_FRAUDULENT`: The transaction is (explicitly) marked as not fraudulent. A + transaction may immediately be moved into this state, or be graduated into + this state from the `SUSPECTED_FRAUD` state. + """ + + comment: str + """ + Optional field providing additional information or context about why the + transaction is considered fraudulent. + """ + + fraud_type: Literal[ + "FIRST_PARTY_FRAUD", "ACCOUNT_TAKEOVER", "CARD_COMPROMISED", "IDENTITY_THEFT", "CARDHOLDER_MANIPULATION" + ] + """ + Specifies the type or category of fraud that the transaction is suspected or + confirmed to involve, string (enum) supporting the following values: + + - `FIRST_PARTY_FRAUD`: First-party fraud occurs when a legitimate account or + cardholder intentionally misuses financial services for personal gain. This + includes actions such as disputing legitimate transactions to obtain a refund, + abusing return policies, or defaulting on credit obligations without intent to + repay. + - `ACCOUNT_TAKEOVER`: Account takeover fraud occurs when a fraudster gains + unauthorized access to an existing account, modifies account settings, and + carries out fraudulent transactions. + - `CARD_COMPROMISED`: Card compromised fraud occurs when a fraudster gains + access to card details without taking over the account, such as through + physical card theft, cloning, or online data breaches. + - `IDENTITY_THEFT`: Identity theft fraud occurs when a fraudster uses stolen + personal information, such as Social Security numbers or addresses, to open + accounts, apply for loans, or conduct financial transactions in someone's + name. + - `CARDHOLDER_MANIPULATION`: This type of fraud occurs when a fraudster + manipulates or coerces a legitimate cardholder into unauthorized transactions, + often through social engineering tactics. + """ diff --git a/src/lithic/types/fraud/transaction_report_response.py b/src/lithic/types/fraud/transaction_report_response.py new file mode 100644 index 00000000..e0ec20f2 --- /dev/null +++ b/src/lithic/types/fraud/transaction_report_response.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TransactionReportResponse"] + + +class TransactionReportResponse(BaseModel): + fraud_status: Literal["SUSPECTED_FRAUD", "FRAUDULENT", "NOT_FRAUDULENT", "NO_REPORTED_FRAUD"] + """ + The fraud status of the transaction, string (enum) supporting the following + values: + + - `SUSPECTED_FRAUD`: The transaction is suspected to be fraudulent, but this + hasn’t been confirmed. + - `FRAUDULENT`: The transaction is confirmed to be fraudulent. A transaction may + immediately be moved into this state, or be graduated into this state from the + `SUSPECTED_FRAUD` state. + - `NOT_FRAUDULENT`: The transaction is (explicitly) marked as not fraudulent. A + transaction may immediately be moved into this state, or be graduated into + this state from the `SUSPECTED_FRAUD` state. + - `NO_REPORTED_FRAUD`: Indicates that no fraud report exists for the + transaction. It is the default state for transactions that have not been + analyzed or associated with any known fraudulent activity. + """ + + transaction_token: str + """ + The universally unique identifier (UUID) associated with the transaction being + reported. + """ + + comment: Optional[str] = None + """Provides additional context or details about the fraud report.""" + + created_at: Optional[datetime] = None + """Timestamp representing when the fraud report was created.""" + + fraud_type: Optional[ + Literal[ + "FIRST_PARTY_FRAUD", "ACCOUNT_TAKEOVER", "CARD_COMPROMISED", "IDENTITY_THEFT", "CARDHOLDER_MANIPULATION" + ] + ] = None + """ + Specifies the type or category of fraud that the transaction is suspected or + confirmed to involve, string (enum) supporting the following values: + + - `FIRST_PARTY_FRAUD`: First-party fraud occurs when a legitimate account or + cardholder intentionally misuses financial services for personal gain. This + includes actions such as disputing legitimate transactions to obtain a refund, + abusing return policies, or defaulting on credit obligations without intent to + repay. + - `ACCOUNT_TAKEOVER`: Account takeover fraud occurs when a fraudster gains + unauthorized access to an existing account, modifies account settings, and + carries out fraudulent transactions. + - `CARD_COMPROMISED`: Card compromised fraud occurs when a fraudster gains + access to card details without taking over the account, such as through + physical card theft, cloning, or online data breaches. + - `IDENTITY_THEFT`: Identity theft fraud occurs when a fraudster uses stolen + personal information, such as Social Security numbers or addresses, to open + accounts, apply for loans, or conduct financial transactions in someone's + name. + - `CARDHOLDER_MANIPULATION`: This type of fraud occurs when a fraudster + manipulates or coerces a legitimate cardholder into unauthorized transactions, + often through social engineering tactics. + """ + + updated_at: Optional[datetime] = None + """Timestamp representing the last update to the fraud report.""" diff --git a/src/lithic/types/fraud/transaction_retrieve_response.py b/src/lithic/types/fraud/transaction_retrieve_response.py new file mode 100644 index 00000000..9e740279 --- /dev/null +++ b/src/lithic/types/fraud/transaction_retrieve_response.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TransactionRetrieveResponse"] + + +class TransactionRetrieveResponse(BaseModel): + fraud_status: Literal["SUSPECTED_FRAUD", "FRAUDULENT", "NOT_FRAUDULENT", "NO_REPORTED_FRAUD"] + """ + The fraud status of the transaction, string (enum) supporting the following + values: + + - `SUSPECTED_FRAUD`: The transaction is suspected to be fraudulent, but this + hasn’t been confirmed. + - `FRAUDULENT`: The transaction is confirmed to be fraudulent. A transaction may + immediately be moved into this state, or be graduated into this state from the + `SUSPECTED_FRAUD` state. + - `NOT_FRAUDULENT`: The transaction is (explicitly) marked as not fraudulent. A + transaction may immediately be moved into this state, or be graduated into + this state from the `SUSPECTED_FRAUD` state. + - `NO_REPORTED_FRAUD`: Indicates that no fraud report exists for the + transaction. It is the default state for transactions that have not been + analyzed or associated with any known fraudulent activity. + """ + + transaction_token: str + """ + The universally unique identifier (UUID) associated with the transaction being + reported. + """ + + comment: Optional[str] = None + """Provides additional context or details about the fraud report.""" + + created_at: Optional[datetime] = None + """Timestamp representing when the fraud report was created.""" + + fraud_type: Optional[ + Literal[ + "FIRST_PARTY_FRAUD", "ACCOUNT_TAKEOVER", "CARD_COMPROMISED", "IDENTITY_THEFT", "CARDHOLDER_MANIPULATION" + ] + ] = None + """ + Specifies the type or category of fraud that the transaction is suspected or + confirmed to involve, string (enum) supporting the following values: + + - `FIRST_PARTY_FRAUD`: First-party fraud occurs when a legitimate account or + cardholder intentionally misuses financial services for personal gain. This + includes actions such as disputing legitimate transactions to obtain a refund, + abusing return policies, or defaulting on credit obligations without intent to + repay. + - `ACCOUNT_TAKEOVER`: Account takeover fraud occurs when a fraudster gains + unauthorized access to an existing account, modifies account settings, and + carries out fraudulent transactions. + - `CARD_COMPROMISED`: Card compromised fraud occurs when a fraudster gains + access to card details without taking over the account, such as through + physical card theft, cloning, or online data breaches. + - `IDENTITY_THEFT`: Identity theft fraud occurs when a fraudster uses stolen + personal information, such as Social Security numbers or addresses, to open + accounts, apply for loans, or conduct financial transactions in someone's + name. + - `CARDHOLDER_MANIPULATION`: This type of fraud occurs when a fraudster + manipulates or coerces a legitimate cardholder into unauthorized transactions, + often through social engineering tactics. + """ + + updated_at: Optional[datetime] = None + """Timestamp representing the last update to the fraud report.""" diff --git a/src/lithic/types/funding_event.py b/src/lithic/types/funding_event.py new file mode 100644 index 00000000..be0e0c73 --- /dev/null +++ b/src/lithic/types/funding_event.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import date, datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["FundingEvent", "NetworkSettlementSummary"] + + +class NetworkSettlementSummary(BaseModel): + network_settlement_date: date + + settled_gross_amount: int + + +class FundingEvent(BaseModel): + token: str + """Unique token ID""" + + collection_resource_type: Literal["BOOK_TRANSFER", "PAYMENT"] + """Collection resource type""" + + collection_tokens: List[str] + """ + IDs of collections, further information can be gathered from the appropriate + collection API based on collection_resource_type + """ + + created: datetime + """Time of the creation""" + + high_watermark: datetime + """Time of the high watermark""" + + network_settlement_summary: List[NetworkSettlementSummary] + """Network settlement summary breakdown by network settlement date""" + + previous_high_watermark: datetime + """Time of the previous high watermark""" + + updated: datetime + """Time of the update""" diff --git a/src/lithic/types/funding_event_created_webhook_event.py b/src/lithic/types/funding_event_created_webhook_event.py new file mode 100644 index 00000000..cf914d9c --- /dev/null +++ b/src/lithic/types/funding_event_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .funding_event import FundingEvent + +__all__ = ["FundingEventCreatedWebhookEvent"] + + +class FundingEventCreatedWebhookEvent(FundingEvent): + event_type: Literal["funding_event.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/auth_rule_list_params.py b/src/lithic/types/funding_event_list_params.py similarity index 74% rename from src/lithic/types/auth_rule_list_params.py rename to src/lithic/types/funding_event_list_params.py index 581c186f..4ff35b55 100644 --- a/src/lithic/types/auth_rule_list_params.py +++ b/src/lithic/types/funding_event_list_params.py @@ -1,13 +1,13 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing_extensions import TypedDict -__all__ = ["AuthRuleListParams"] +__all__ = ["FundingEventListParams"] -class AuthRuleListParams(TypedDict, total=False): +class FundingEventListParams(TypedDict, total=False): ending_before: str """A cursor representing an item's token before which a page of results should end. diff --git a/src/lithic/types/funding_event_retrieve_details_response.py b/src/lithic/types/funding_event_retrieve_details_response.py new file mode 100644 index 00000000..df71850b --- /dev/null +++ b/src/lithic/types/funding_event_retrieve_details_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["FundingEventRetrieveDetailsResponse"] + + +class FundingEventRetrieveDetailsResponse(BaseModel): + token: str + """Unique token ID""" + + settlement_details_url: str + """URL of the settlement details""" + + settlement_summary_url: str + """URL of the settlement summary""" diff --git a/src/lithic/types/internal_transaction.py b/src/lithic/types/internal_transaction.py new file mode 100644 index 00000000..47281d49 --- /dev/null +++ b/src/lithic/types/internal_transaction.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["InternalTransaction", "Event"] + + +class Event(BaseModel): + token: str + + amount: int + + created: datetime + + result: Literal["APPROVED", "DECLINED"] + + type: Literal["INTERNAL_ADJUSTMENT"] + + +class InternalTransaction(BaseModel): + token: str + + category: Literal["INTERNAL"] + + created: datetime + + currency: str + + descriptor: str + + events: List[Event] + + pending_amount: int + + result: Literal["APPROVED", "DECLINED"] + + settled_amount: int + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + + updated: datetime diff --git a/src/lithic/types/internal_transaction_created_webhook_event.py b/src/lithic/types/internal_transaction_created_webhook_event.py new file mode 100644 index 00000000..f3a3043d --- /dev/null +++ b/src/lithic/types/internal_transaction_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .internal_transaction import InternalTransaction + +__all__ = ["InternalTransactionCreatedWebhookEvent"] + + +class InternalTransactionCreatedWebhookEvent(InternalTransaction): + event_type: Literal["internal_transaction.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/internal_transaction_updated_webhook_event.py b/src/lithic/types/internal_transaction_updated_webhook_event.py new file mode 100644 index 00000000..818cbf22 --- /dev/null +++ b/src/lithic/types/internal_transaction_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .internal_transaction import InternalTransaction + +__all__ = ["InternalTransactionUpdatedWebhookEvent"] + + +class InternalTransactionUpdatedWebhookEvent(InternalTransaction): + event_type: Literal["internal_transaction.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/kyb_business_entity.py b/src/lithic/types/kyb_business_entity.py new file mode 100644 index 00000000..a61f35ca --- /dev/null +++ b/src/lithic/types/kyb_business_entity.py @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["KYBBusinessEntity", "Address"] + + +class Address(BaseModel): + """ + Business''s physical address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYBBusinessEntity(BaseModel): + address: Address + """ + Business''s physical address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + + government_id: str + """Government-issued identification number. + + US Federal Employer Identification Numbers (EIN) are currently supported, + entered as full nine-digits, with or without hyphens. + """ + + legal_business_name: str + """Legal (formal) business name.""" + + phone_numbers: List[str] + """ + One or more of the business's phone number(s), entered as a list in E.164 + format. + """ + + dba_business_name: Optional[str] = None + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + parent_company: Optional[str] = None + """Parent company name (if applicable).""" diff --git a/src/lithic/types/kyb_param.py b/src/lithic/types/kyb_param.py new file mode 100644 index 00000000..b3131cd4 --- /dev/null +++ b/src/lithic/types/kyb_param.py @@ -0,0 +1,224 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr +from .shared_params.address import Address + +__all__ = ["KYBParam", "BeneficialOwnerIndividual", "BusinessEntity", "ControlPerson", "BeneficialOwnerEntity"] + + +class BeneficialOwnerIndividual(TypedDict, total=False): + """Individuals associated with a KYB application. Phone number is optional.""" + + address: Required[Address] + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Required[str] + """Individual's date of birth, as an RFC 3339 date.""" + + email: Required[str] + """ + Individual's email address. If utilizing Lithic for chargeback processing, this + customer email address may be used to communicate dispute status and resolution. + """ + + first_name: Required[str] + """Individual's first name, as it appears on government-issued identity documents.""" + + government_id: Required[str] + """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: Required[str] + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: str + """Individual's phone number, entered in E.164 format.""" + + +class BusinessEntity(TypedDict, total=False): + """ + Information for business for which the account is being opened and KYB is being run. + """ + + address: Required[Address] + """ + Business's physical address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + + government_id: Required[str] + """Government-issued identification number. + + US Federal Employer Identification Numbers (EIN) are currently supported, + entered as full nine-digits, with or without hyphens. + """ + + legal_business_name: Required[str] + """Legal (formal) business name.""" + + phone_numbers: Required[SequenceNotStr[str]] + """ + One or more of the business's phone number(s), entered as a list in E.164 + format. + """ + + dba_business_name: str + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + parent_company: str + """Parent company name (if applicable).""" + + +class ControlPerson(TypedDict, total=False): + """ + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, + Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access + to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. + See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) (Section II) for more background. + """ + + address: Required[Address] + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Required[str] + """Individual's date of birth, as an RFC 3339 date.""" + + email: Required[str] + """ + Individual's email address. If utilizing Lithic for chargeback processing, this + customer email address may be used to communicate dispute status and resolution. + """ + + first_name: Required[str] + """Individual's first name, as it appears on government-issued identity documents.""" + + government_id: Required[str] + """ + Government-issued identification number (required for identity verification and + compliance with banking regulations). Social Security Numbers (SSN) and + Individual Taxpayer Identification Numbers (ITIN) are currently supported, + entered as full nine-digits, with or without hyphens + """ + + last_name: Required[str] + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: str + """Individual's phone number, entered in E.164 format.""" + + +class BeneficialOwnerEntity(TypedDict, total=False): + address: Required[Address] + """ + Business's physical address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + + government_id: Required[str] + """Government-issued identification number. + + US Federal Employer Identification Numbers (EIN) are currently supported, + entered as full nine-digits, with or without hyphens. + """ + + legal_business_name: Required[str] + """Legal (formal) business name.""" + + phone_numbers: Required[SequenceNotStr[str]] + """ + One or more of the business's phone number(s), entered as a list in E.164 + format. + """ + + dba_business_name: str + """ + Any name that the business operates under that is not its legal business name + (if applicable). + """ + + parent_company: str + """Parent company name (if applicable).""" + + +class KYBParam(TypedDict, total=False): + beneficial_owner_individuals: Required[Iterable[BeneficialOwnerIndividual]] + """ + You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + """ + + business_entity: Required[BusinessEntity] + """ + Information for business for which the account is being opened and KYB is being + run. + """ + + control_person: Required[ControlPerson] + """ + An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + """ + + nature_of_business: Required[str] + """ + Short description of the company's line of business (i.e., what does the company + do?). + """ + + tos_timestamp: Required[str] + """ + An RFC 3339 timestamp indicating when the account holder accepted the applicable + legal agreements (e.g., cardholder terms) as agreed upon during API customer's + implementation with Lithic. + """ + + workflow: Required[Literal["KYB_BASIC", "KYB_BYO"]] + """Specifies the type of KYB workflow to run.""" + + beneficial_owner_entities: Iterable[BeneficialOwnerEntity] + """Deprecated.""" + + external_id: str + """ + A user provided id that can be used to link an account holder with an external + system + """ + + kyb_passed_timestamp: str + """ + An RFC 3339 timestamp indicating when precomputed KYB was completed on the + business with a pass result. + + This field is required only if workflow type is `KYB_BYO`. + """ + + website_url: str + """Company website URL.""" diff --git a/src/lithic/types/kyc_exempt_param.py b/src/lithic/types/kyc_exempt_param.py new file mode 100644 index 00000000..c0335220 --- /dev/null +++ b/src/lithic/types/kyc_exempt_param.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .shared_params.address import Address + +__all__ = ["KYCExemptParam"] + + +class KYCExemptParam(TypedDict, total=False): + address: Required[Address] + """ + KYC Exempt user's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. + """ + + email: Required[str] + """The KYC Exempt user's email""" + + first_name: Required[str] + """The KYC Exempt user's first name""" + + kyc_exemption_type: Required[Literal["AUTHORIZED_USER", "PREPAID_CARD_USER"]] + """Specifies the type of KYC Exempt user""" + + last_name: Required[str] + """The KYC Exempt user's last name""" + + phone_number: Required[str] + """The KYC Exempt user's phone number, entered in E.164 format.""" + + workflow: Required[Literal["KYC_EXEMPT"]] + """Specifies the workflow type. This must be 'KYC_EXEMPT'""" + + business_account_token: str + """ + Only applicable for customers using the KYC-Exempt workflow to enroll authorized + users of businesses. Pass the account_token of the enrolled business associated + with the AUTHORIZED_USER in this field. + """ + + external_id: str + """ + A user provided id that can be used to link an account holder with an external + system + """ diff --git a/src/lithic/types/account_holder_resubmit_params.py b/src/lithic/types/kyc_param.py similarity index 67% rename from src/lithic/types/account_holder_resubmit_params.py rename to src/lithic/types/kyc_param.py index acf50828..f4bbffb3 100644 --- a/src/lithic/types/account_holder_resubmit_params.py +++ b/src/lithic/types/kyc_param.py @@ -1,33 +1,20 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing_extensions import Literal, Required, TypedDict -from ..types import shared_params +from .shared_params.address import Address -__all__ = ["AccountHolderResubmitParams", "Individual"] +__all__ = ["KYCParam", "Individual"] -class AccountHolderResubmitParams(TypedDict, total=False): - individual: Required[Individual] +class Individual(TypedDict, total=False): """ - Information on individual for whom the account is being opened and KYC is being - re-run. + Information on individual for whom the account is being opened and KYC is being run. """ - tos_timestamp: Required[str] - """ - An RFC 3339 timestamp indicating when the account holder accepted the applicable - legal agreements (e.g., cardholder terms) as agreed upon during API customer's - implementation with Lithic. - """ - - workflow: Required[Literal["KYC_ADVANCED"]] - - -class Individual(TypedDict, total=False): - address: Required[shared_params.Address] + address: Required[Address] """ Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. @@ -58,3 +45,35 @@ class Individual(TypedDict, total=False): phone_number: Required[str] """Individual's phone number, entered in E.164 format.""" + + +class KYCParam(TypedDict, total=False): + individual: Required[Individual] + """ + Information on individual for whom the account is being opened and KYC is being + run. + """ + + tos_timestamp: Required[str] + """ + An RFC 3339 timestamp indicating when the account holder accepted the applicable + legal agreements (e.g., cardholder terms) as agreed upon during API customer's + implementation with Lithic. + """ + + workflow: Required[Literal["KYC_BASIC", "KYC_BYO"]] + """Specifies the type of KYC workflow to run.""" + + external_id: str + """ + A user provided id that can be used to link an account holder with an external + system + """ + + kyc_passed_timestamp: str + """ + An RFC 3339 timestamp indicating when precomputed KYC was completed on the + individual with a pass result. + + This field is required only if workflow type is `KYC_BYO`. + """ diff --git a/src/lithic/types/loan_tape_created_webhook_event.py b/src/lithic/types/loan_tape_created_webhook_event.py new file mode 100644 index 00000000..f2e66b35 --- /dev/null +++ b/src/lithic/types/loan_tape_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .financial_accounts.loan_tape import LoanTape + +__all__ = ["LoanTapeCreatedWebhookEvent"] + + +class LoanTapeCreatedWebhookEvent(LoanTape): + event_type: Literal["loan_tape.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/loan_tape_updated_webhook_event.py b/src/lithic/types/loan_tape_updated_webhook_event.py new file mode 100644 index 00000000..93d2ab16 --- /dev/null +++ b/src/lithic/types/loan_tape_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .financial_accounts.loan_tape import LoanTape + +__all__ = ["LoanTapeUpdatedWebhookEvent"] + + +class LoanTapeUpdatedWebhookEvent(LoanTape): + event_type: Literal["loan_tape.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/management_operation_create_params.py b/src/lithic/types/management_operation_create_params.py new file mode 100644 index 00000000..47d2dada --- /dev/null +++ b/src/lithic/types/management_operation_create_params.py @@ -0,0 +1,72 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ManagementOperationCreateParams"] + + +class ManagementOperationCreateParams(TypedDict, total=False): + amount: Required[int] + + category: Required[ + Literal[ + "MANAGEMENT_FEE", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_REWARD", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISBURSEMENT", + ] + ] + + direction: Required[Literal["CREDIT", "DEBIT"]] + + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + + event_type: Required[ + Literal[ + "LOSS_WRITE_OFF", + "CASH_BACK", + "CASH_BACK_REVERSAL", + "CURRENCY_CONVERSION", + "CURRENCY_CONVERSION_REVERSAL", + "INTEREST", + "INTEREST_REVERSAL", + "LATE_PAYMENT", + "LATE_PAYMENT_REVERSAL", + "BILLING_ERROR", + "BILLING_ERROR_REVERSAL", + "PROVISIONAL_CREDIT", + "PROVISIONAL_CREDIT_REVERSAL", + "RETURNED_PAYMENT", + "RETURNED_PAYMENT_REVERSAL", + "DISPUTE_WON", + "DISPUTE_WON_REVERSAL", + "DISBURSE", + "DISBURSE_REVERSAL", + "ANNUAL", + "ANNUAL_REVERSAL", + "QUARTERLY", + "QUARTERLY_REVERSAL", + "MONTHLY", + "MONTHLY_REVERSAL", + ] + ] + + financial_account_token: Required[str] + + token: str + + memo: str + + on_closed_account: Literal["FAIL", "USE_SUSPENSE"] + """What to do if the financial account is closed when posting an operation""" + + subtype: str + + user_defined_id: str diff --git a/src/lithic/types/management_operation_created_webhook_event.py b/src/lithic/types/management_operation_created_webhook_event.py new file mode 100644 index 00000000..e98b2246 --- /dev/null +++ b/src/lithic/types/management_operation_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .management_operation_transaction import ManagementOperationTransaction + +__all__ = ["ManagementOperationCreatedWebhookEvent"] + + +class ManagementOperationCreatedWebhookEvent(ManagementOperationTransaction): + event_type: Literal["management_operation.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/management_operation_list_params.py b/src/lithic/types/management_operation_list_params.py new file mode 100644 index 00000000..c026ad91 --- /dev/null +++ b/src/lithic/types/management_operation_list_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ManagementOperationListParams"] + + +class ManagementOperationListParams(TypedDict, total=False): + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + business_account_token: str + + category: Literal[ + "MANAGEMENT_FEE", "MANAGEMENT_DISPUTE", "MANAGEMENT_REWARD", "MANAGEMENT_ADJUSTMENT", "MANAGEMENT_DISBURSEMENT" + ] + """Management operation category to be returned.""" + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + financial_account_token: str + """Globally unique identifier for the financial account. + + Accepted type dependent on the program's use case. + """ + + page_size: int + """Page size (for pagination).""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """Management operation status to be returned.""" diff --git a/src/lithic/types/management_operation_reverse_params.py b/src/lithic/types/management_operation_reverse_params.py new file mode 100644 index 00000000..ed3d2583 --- /dev/null +++ b/src/lithic/types/management_operation_reverse_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ManagementOperationReverseParams"] + + +class ManagementOperationReverseParams(TypedDict, total=False): + effective_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + + memo: str diff --git a/src/lithic/types/management_operation_transaction.py b/src/lithic/types/management_operation_transaction.py new file mode 100644 index 00000000..16b8134a --- /dev/null +++ b/src/lithic/types/management_operation_transaction.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import date, datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .external_resource import ExternalResource + +__all__ = ["ManagementOperationTransaction", "Event", "TransactionSeries"] + + +class Event(BaseModel): + token: str + + amount: int + + created: datetime + + detailed_results: List[Literal["APPROVED", "INSUFFICIENT_FUNDS"]] + + effective_date: date + + memo: str + + result: Literal["APPROVED", "DECLINED"] + + type: Literal[ + "LOSS_WRITE_OFF", + "CASH_BACK", + "CASH_BACK_REVERSAL", + "CURRENCY_CONVERSION", + "CURRENCY_CONVERSION_REVERSAL", + "INTEREST", + "INTEREST_REVERSAL", + "LATE_PAYMENT", + "LATE_PAYMENT_REVERSAL", + "BILLING_ERROR", + "BILLING_ERROR_REVERSAL", + "PROVISIONAL_CREDIT", + "PROVISIONAL_CREDIT_REVERSAL", + "RETURNED_PAYMENT", + "RETURNED_PAYMENT_REVERSAL", + "DISPUTE_WON", + "DISPUTE_WON_REVERSAL", + "DISBURSE", + "DISBURSE_REVERSAL", + "ANNUAL", + "ANNUAL_REVERSAL", + "QUARTERLY", + "QUARTERLY_REVERSAL", + "MONTHLY", + "MONTHLY_REVERSAL", + ] + + subtype: Optional[str] = None + + +class TransactionSeries(BaseModel): + related_transaction_event_token: Optional[str] = None + + related_transaction_token: Optional[str] = None + + type: str + + +class ManagementOperationTransaction(BaseModel): + token: str + """Unique identifier for the transaction""" + + created: datetime + """ISO 8601 timestamp of when the transaction was created""" + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """The status of the transaction""" + + updated: datetime + """ISO 8601 timestamp of when the transaction was last updated""" + + category: Optional[ + Literal[ + "MANAGEMENT_FEE", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_REWARD", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISBURSEMENT", + ] + ] = None + + currency: Optional[str] = None + + direction: Optional[Literal["CREDIT", "DEBIT"]] = None + + events: Optional[List[Event]] = None + + external_resource: Optional[ExternalResource] = None + """External resource associated with the management operation""" + + family: Optional[Literal["MANAGEMENT_OPERATION"]] = None + """MANAGEMENT_OPERATION - Management Operation Transaction""" + + financial_account_token: Optional[str] = None + + pending_amount: Optional[int] = None + + result: Optional[Literal["APPROVED", "DECLINED"]] = None + + settled_amount: Optional[int] = None + + transaction_series: Optional[TransactionSeries] = None + + user_defined_id: Optional[str] = None diff --git a/src/lithic/types/management_operation_updated_webhook_event.py b/src/lithic/types/management_operation_updated_webhook_event.py new file mode 100644 index 00000000..2c05902e --- /dev/null +++ b/src/lithic/types/management_operation_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .management_operation_transaction import ManagementOperationTransaction + +__all__ = ["ManagementOperationUpdatedWebhookEvent"] + + +class ManagementOperationUpdatedWebhookEvent(ManagementOperationTransaction): + event_type: Literal["management_operation.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/message_attempt.py b/src/lithic/types/message_attempt.py index 7218ab67..66a1825f 100644 --- a/src/lithic/types/message_attempt.py +++ b/src/lithic/types/message_attempt.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime from typing_extensions import Literal @@ -9,6 +9,8 @@ class MessageAttempt(BaseModel): + """A subscription to specific event types.""" + token: str """Globally unique identifier.""" diff --git a/src/lithic/types/network_program.py b/src/lithic/types/network_program.py new file mode 100644 index 00000000..09f5bfe2 --- /dev/null +++ b/src/lithic/types/network_program.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["NetworkProgram"] + + +class NetworkProgram(BaseModel): + token: str + """Lithic-generated unique identifier for the program""" + + default_product_code: str + """Network product ID associated with this program.""" + + name: str + """The name of the network program.""" + + registered_program_identification_number: str + """RPIN value assigned by the network.""" diff --git a/src/lithic/types/network_program_list_params.py b/src/lithic/types/network_program_list_params.py new file mode 100644 index 00000000..f171f6e2 --- /dev/null +++ b/src/lithic/types/network_program_list_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["NetworkProgramListParams"] + + +class NetworkProgramListParams(TypedDict, total=False): + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date string in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + + page_size: int + """Page size (for pagination).""" diff --git a/src/lithic/types/network_total.py b/src/lithic/types/network_total.py new file mode 100644 index 00000000..132c96a4 --- /dev/null +++ b/src/lithic/types/network_total.py @@ -0,0 +1,77 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import date, datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["NetworkTotal", "Amounts"] + + +class Amounts(BaseModel): + gross_settlement: int + """Total settlement amount excluding interchange, in currency's smallest unit.""" + + interchange_fees: int + """Interchange amount, in currency's smallest unit.""" + + net_settlement: int + """ + `gross_settlement` net of `interchange_fees` and `visa_charges` (if applicable), + in currency's smallest unit. + """ + + visa_charges: Optional[int] = None + """Charges specific to Visa/Interlink, in currency's smallest unit.""" + + +class NetworkTotal(BaseModel): + token: str + """Globally unique identifier.""" + + amounts: Amounts + + created: datetime + """RFC 3339 timestamp for when the record was created. UTC time zone.""" + + currency: str + """3-character alphabetic ISO 4217 code.""" + + institution_id: str + """The institution that activity occurred on. + + For Mastercard: ICA (Interbank Card Association). For Maestro: institution ID. + For Visa: lowest level SRE (Settlement Reporting Entity). + """ + + is_complete: bool + """ + Indicates that all settlement records related to this Network Total are + available in the details endpoint. + """ + + network: Literal["AMEX", "VISA", "MASTERCARD", "MAESTRO", "INTERLINK"] + """Card network where the transaction took place. + + AMEX, VISA, MASTERCARD, MAESTRO, or INTERLINK. + """ + + report_date: date + """Date that the network total record applies to. YYYY-MM-DD format.""" + + settlement_institution_id: str + """The institution responsible for settlement. + + For Mastercard: same as `institution_id`. For Maestro: billing ICA. For Visa: + Funds Transfer SRE (FTSRE). + """ + + settlement_service: str + """Settlement service.""" + + updated: datetime + """RFC 3339 timestamp for when the record was last updated. UTC time zone.""" + + cycle: Optional[int] = None + """The clearing cycle that the network total record applies to. Mastercard only.""" diff --git a/src/lithic/types/network_total_created_webhook_event.py b/src/lithic/types/network_total_created_webhook_event.py new file mode 100644 index 00000000..09036dbc --- /dev/null +++ b/src/lithic/types/network_total_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .network_total import NetworkTotal + +__all__ = ["NetworkTotalCreatedWebhookEvent"] + + +class NetworkTotalCreatedWebhookEvent(NetworkTotal): + event_type: Literal["network_total.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/network_total_updated_webhook_event.py b/src/lithic/types/network_total_updated_webhook_event.py new file mode 100644 index 00000000..a783741b --- /dev/null +++ b/src/lithic/types/network_total_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .network_total import NetworkTotal + +__all__ = ["NetworkTotalUpdatedWebhookEvent"] + + +class NetworkTotalUpdatedWebhookEvent(NetworkTotal): + event_type: Literal["network_total.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/non_pci_card.py b/src/lithic/types/non_pci_card.py new file mode 100644 index 00000000..7c4bc07c --- /dev/null +++ b/src/lithic/types/non_pci_card.py @@ -0,0 +1,237 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .spend_limit_duration import SpendLimitDuration + +__all__ = ["NonPCICard", "Funding"] + + +class Funding(BaseModel): + """Deprecated: Funding account for the card.""" + + token: str + """A globally unique identifier for this FundingAccount.""" + + created: datetime + """ + An RFC 3339 string representing when this funding source was added to the Lithic + account. This may be `null`. UTC time zone. + """ + + last_four: str + """The last 4 digits of the account (e.g. + + bank account, debit card) associated with this FundingAccount. This may be null. + """ + + state: Literal["DELETED", "ENABLED", "PENDING"] + """State of funding source. + + Funding source states: _ `ENABLED` - The funding account is available to use for + card creation and transactions. _ `PENDING` - The funding account is still being + verified e.g. bank micro-deposits verification. \\** `DELETED` - The founding + account has been deleted. + """ + + type: Literal["DEPOSITORY_CHECKING", "DEPOSITORY_SAVINGS"] + """Types of funding source: \\** `DEPOSITORY_CHECKING` - Bank checking account. + + - `DEPOSITORY_SAVINGS` - Bank savings account. + """ + + account_name: Optional[str] = None + """Account name identifying the funding source. This may be `null`.""" + + nickname: Optional[str] = None + """The nickname given to the `FundingAccount` or `null` if it has no nickname.""" + + +class NonPCICard(BaseModel): + """Card details without PCI information""" + + token: str + """Globally unique identifier.""" + + account_token: str + """Globally unique identifier for the account to which the card belongs.""" + + card_program_token: str + """Globally unique identifier for the card program on which the card exists.""" + + created: datetime + """An RFC 3339 timestamp for when the card was created. UTC time zone.""" + + funding: Funding + """Deprecated: Funding account for the card.""" + + last_four: str + """Last four digits of the card number.""" + + pin_status: Literal["OK", "BLOCKED", "NOT_SET"] + """Indicates if a card is blocked due a PIN status issue (e.g. + + excessive incorrect attempts). + """ + + spend_limit: int + """Amount (in cents) to limit approved authorizations (e.g. + + 100000 would be a $1,000 limit). Transaction requests above the spend limit will + be declined. + """ + + spend_limit_duration: SpendLimitDuration + """Spend limit duration values: + + - `ANNUALLY` - Card will authorize transactions up to spend limit for the + trailing year. + - `FOREVER` - Card will authorize only up to spend limit for the entire lifetime + of the card. + - `MONTHLY` - Card will authorize transactions up to spend limit for the + trailing month. To support recurring monthly payments, which can occur on + different day every month, the time window we consider for monthly velocity + starts 6 days after the current calendar date one month prior. + - `TRANSACTION` - Card will authorize multiple transactions if each individual + transaction is under the spend limit. + """ + + state: Literal["CLOSED", "OPEN", "PAUSED", "PENDING_ACTIVATION", "PENDING_FULFILLMENT"] + """Card state values: \\** `CLOSED` - Card will no longer approve authorizations. + + Closing a card cannot be undone. _ `OPEN` - Card will approve authorizations (if + they match card and account parameters). _ `PAUSED` - Card will decline + authorizations, but can be resumed at a later time. _ `PENDING_FULFILLMENT` - + The initial state for cards of type `PHYSICAL`. The card is provisioned pending + manufacturing and fulfillment. Cards in this state can accept authorizations for + e-commerce purchases, but not for "Card Present" purchases where the physical + card itself is present. _ `PENDING_ACTIVATION` - At regular intervals, cards of + type `PHYSICAL` in state `PENDING_FULFILLMENT` are sent to the card production + warehouse and updated to state `PENDING_ACTIVATION`. Similar to + `PENDING_FULFILLMENT`, cards in this state can be used for e-commerce + transactions or can be added to mobile wallets. API clients should update the + card's state to `OPEN` only after the cardholder confirms receipt of the card. + In sandbox, the same daily batch fulfillment occurs, but no cards are actually + manufactured. + """ + + type: Literal["MERCHANT_LOCKED", "PHYSICAL", "SINGLE_USE", "VIRTUAL", "UNLOCKED", "DIGITAL_WALLET"] + """ + Card types: _ `VIRTUAL` - Card will authorize at any merchant and can be added + to a digital wallet like Apple Pay or Google Pay (if the card program is digital + wallet-enabled). _ `PHYSICAL` - Manufactured and sent to the cardholder. We + offer white label branding, credit, ATM, PIN debit, chip/EMV, NFC and magstripe + functionality. _ `SINGLE_USE` - Card is closed upon first successful + authorization. _ `MERCHANT_LOCKED` - _[Deprecated]_ Card is locked to the first + merchant that successfully authorizes the card. _ `UNLOCKED` - _[Deprecated]_ + Similar behavior to VIRTUAL cards, please use VIRTUAL instead. _ + `DIGITAL_WALLET` - _[Deprecated]_ Similar behavior to VIRTUAL cards, please use + VIRTUAL instead. + """ + + auth_rule_tokens: Optional[List[str]] = None + """List of identifiers for the Auth Rule(s) that are applied on the card. + + This field is deprecated and will no longer be populated in the `Card` object. + The key will be removed from the schema in a future release. Use the + `/auth_rules` endpoints to fetch Auth Rule information instead. + """ + + bulk_order_token: Optional[str] = None + """Globally unique identifier for the bulk order associated with this card. + + Only applicable to physical cards that are part of a bulk shipment + """ + + cardholder_currency: Optional[str] = None + """3-character alphabetic ISO 4217 code for the currency of the cardholder.""" + + comment: Optional[str] = None + """Additional context or information related to the card.""" + + digital_card_art_token: Optional[str] = None + """ + Specifies the digital card art to be displayed in the user's digital wallet + after tokenization. This artwork must be approved by Mastercard and configured + by Lithic to use. + """ + + exp_month: Optional[str] = None + """Two digit (MM) expiry month.""" + + exp_year: Optional[str] = None + """Four digit (yyyy) expiry year.""" + + hostname: Optional[str] = None + """Hostname of card's locked merchant (will be empty if not applicable).""" + + memo: Optional[str] = None + """Friendly name to identify the card.""" + + network_program_token: Optional[str] = None + """Globally unique identifier for the card's network program. + + Null if the card is not associated with a network program. Currently applicable + to Visa cards participating in Account Level Management only + """ + + pending_commands: Optional[List[str]] = None + """ + Indicates if there are offline PIN changes pending card interaction with an + offline PIN terminal. Possible commands are: CHANGE_PIN, UNBLOCK_PIN. Applicable + only to cards issued in markets supporting offline PINs. + """ + + product_id: Optional[str] = None + """Only applicable to cards of type `PHYSICAL`. + + This must be configured with Lithic before use. Specifies the configuration + (i.e., physical card art) that the card should be manufactured with. + """ + + replacement_for: Optional[str] = None + """ + If the card is a replacement for another card, the globally unique identifier + for the card that was replaced. + """ + + substatus: Optional[ + Literal[ + "LOST", + "COMPROMISED", + "DAMAGED", + "END_USER_REQUEST", + "ISSUER_REQUEST", + "NOT_ACTIVE", + "SUSPICIOUS_ACTIVITY", + "INTERNAL_REVIEW", + "EXPIRED", + "UNDELIVERABLE", + "OTHER", + ] + ] = None + """ + Card state substatus values: _ `LOST` - The physical card is no longer in the + cardholder's possession due to being lost or never received by the cardholder. _ + `COMPROMISED` - Card information has been exposed, potentially leading to + unauthorized access. This may involve physical card theft, cloning, or online + data breaches. _ `DAMAGED` - The physical card is not functioning properly, such + as having chip failures or a demagnetized magnetic stripe. _ + `END_USER_REQUEST` - The cardholder requested the closure of the card for + reasons unrelated to fraud or damage, such as switching to a different product + or closing the account. _ `ISSUER_REQUEST` - The issuer closed the card for + reasons unrelated to fraud or damage, such as account inactivity, product or + policy changes, or technology upgrades. _ `NOT_ACTIVE` - The card hasn’t had any + transaction activity for a specified period, applicable to statuses like + `PAUSED` or `CLOSED`. _ `SUSPICIOUS_ACTIVITY` - The card has one or more + suspicious transactions or activities that require review. This can involve + prompting the cardholder to confirm legitimate use or report confirmed fraud. _ + `INTERNAL_REVIEW` - The card is temporarily paused pending further internal + review. _ `EXPIRED` - The card has expired and has been closed without being + reissued. _ `UNDELIVERABLE` - The card cannot be delivered to the cardholder and + has been returned. \\** `OTHER` - The reason for the status does not fall into any + of the above categories. A comment can be provided to specify the reason. + """ diff --git a/src/lithic/types/owner_type.py b/src/lithic/types/owner_type.py index 9623609c..129346bf 100644 --- a/src/lithic/types/owner_type.py +++ b/src/lithic/types/owner_type.py @@ -1,7 +1,7 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias __all__ = ["OwnerType"] -OwnerType = Literal["BUSINESS", "INDIVIDUAL"] +OwnerType: TypeAlias = Literal["INDIVIDUAL", "BUSINESS"] diff --git a/src/lithic/types/parsed_webhook_event.py b/src/lithic/types/parsed_webhook_event.py new file mode 100644 index 00000000..25abe119 --- /dev/null +++ b/src/lithic/types/parsed_webhook_event.py @@ -0,0 +1,480 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias + +from .._models import BaseModel +from .kyb_business_entity import KYBBusinessEntity +from .three_ds_authentication import ThreeDSAuthentication +from .asa_request_webhook_event import AsaRequestWebhookEvent +from .card_created_webhook_event import CardCreatedWebhookEvent +from .card_renewed_webhook_event import CardRenewedWebhookEvent +from .card_shipped_webhook_event import CardShippedWebhookEvent +from .card_reissued_webhook_event import CardReissuedWebhookEvent +from .card_converted_webhook_event import CardConvertedWebhookEvent +from .balance_updated_webhook_event import BalanceUpdatedWebhookEvent +from .dispute_updated_webhook_event import DisputeUpdatedWebhookEvent +from .loan_tape_created_webhook_event import LoanTapeCreatedWebhookEvent +from .loan_tape_updated_webhook_event import LoanTapeUpdatedWebhookEvent +from .statements_created_webhook_event import StatementsCreatedWebhookEvent +from .tokenization_result_webhook_event import TokenizationResultWebhookEvent +from .tokenization_updated_webhook_event import TokenizationUpdatedWebhookEvent +from .funding_event_created_webhook_event import FundingEventCreatedWebhookEvent +from .network_total_created_webhook_event import NetworkTotalCreatedWebhookEvent +from .network_total_updated_webhook_event import NetworkTotalUpdatedWebhookEvent +from .account_holder_created_webhook_event import AccountHolderCreatedWebhookEvent +from .card_transaction_updated_webhook_event import CardTransactionUpdatedWebhookEvent +from .external_payment_created_webhook_event import ExternalPaymentCreatedWebhookEvent +from .external_payment_updated_webhook_event import ExternalPaymentUpdatedWebhookEvent +from .financial_account_created_webhook_event import FinancialAccountCreatedWebhookEvent +from .financial_account_updated_webhook_event import FinancialAccountUpdatedWebhookEvent +from .settlement_report_updated_webhook_event import SettlementReportUpdatedWebhookEvent +from .account_holder_verification_webhook_event import AccountHolderVerificationWebhookEvent +from .dispute_transaction_created_webhook_event import DisputeTransactionCreatedWebhookEvent +from .dispute_transaction_updated_webhook_event import DisputeTransactionUpdatedWebhookEvent +from .payment_transaction_created_webhook_event import PaymentTransactionCreatedWebhookEvent +from .payment_transaction_updated_webhook_event import PaymentTransactionUpdatedWebhookEvent +from .internal_transaction_created_webhook_event import InternalTransactionCreatedWebhookEvent +from .internal_transaction_updated_webhook_event import InternalTransactionUpdatedWebhookEvent +from .management_operation_created_webhook_event import ManagementOperationCreatedWebhookEvent +from .management_operation_updated_webhook_event import ManagementOperationUpdatedWebhookEvent +from .external_bank_account_created_webhook_event import ExternalBankAccountCreatedWebhookEvent +from .external_bank_account_updated_webhook_event import ExternalBankAccountUpdatedWebhookEvent +from .tokenization_approval_request_webhook_event import TokenizationApprovalRequestWebhookEvent +from .dispute_evidence_upload_failed_webhook_event import DisputeEvidenceUploadFailedWebhookEvent +from .account_holder_document_updated_webhook_event import AccountHolderDocumentUpdatedWebhookEvent +from .three_ds_authentication_created_webhook_event import ThreeDSAuthenticationCreatedWebhookEvent +from .three_ds_authentication_updated_webhook_event import ThreeDSAuthenticationUpdatedWebhookEvent +from .tokenization_decisioning_request_webhook_event import TokenizationDecisioningRequestWebhookEvent +from .book_transfer_transaction_created_webhook_event import BookTransferTransactionCreatedWebhookEvent +from .book_transfer_transaction_updated_webhook_event import BookTransferTransactionUpdatedWebhookEvent +from .three_ds_authentication_challenge_webhook_event import ThreeDSAuthenticationChallengeWebhookEvent +from .auth_rules_backtest_report_created_webhook_event import AuthRulesBacktestReportCreatedWebhookEvent +from .digital_wallet_tokenization_result_webhook_event import DigitalWalletTokenizationResultWebhookEvent +from .digital_wallet_tokenization_updated_webhook_event import DigitalWalletTokenizationUpdatedWebhookEvent +from .card_transaction_enhanced_data_created_webhook_event import CardTransactionEnhancedDataCreatedWebhookEvent +from .card_transaction_enhanced_data_updated_webhook_event import CardTransactionEnhancedDataUpdatedWebhookEvent +from .tokenization_two_factor_authentication_code_webhook_event import ( + TokenizationTwoFactorAuthenticationCodeWebhookEvent, +) +from .digital_wallet_tokenization_approval_request_webhook_event import ( + DigitalWalletTokenizationApprovalRequestWebhookEvent, +) +from .tokenization_two_factor_authentication_code_sent_webhook_event import ( + TokenizationTwoFactorAuthenticationCodeSentWebhookEvent, +) +from .digital_wallet_tokenization_two_factor_authentication_code_webhook_event import ( + DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent, +) +from .digital_wallet_tokenization_two_factor_authentication_code_sent_webhook_event import ( + DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent, +) + +__all__ = [ + "ParsedWebhookEvent", + "KYBPayload", + "KYBPayloadUpdateRequest", + "KYBPayloadUpdateRequestBeneficialOwnerIndividual", + "KYBPayloadUpdateRequestBeneficialOwnerIndividualAddress", + "KYBPayloadUpdateRequestControlPerson", + "KYBPayloadUpdateRequestControlPersonAddress", + "KYCPayload", + "KYCPayloadUpdateRequest", + "KYCPayloadUpdateRequestIndividual", + "KYCPayloadUpdateRequestIndividualAddress", + "LegacyPayload", +] + + +class KYBPayloadUpdateRequestBeneficialOwnerIndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYBPayloadUpdateRequestBeneficialOwnerIndividual(BaseModel): + address: Optional[KYBPayloadUpdateRequestBeneficialOwnerIndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYBPayloadUpdateRequestControlPersonAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYBPayloadUpdateRequestControlPerson(BaseModel): + """ + An individual with significant responsibility for managing the legal entity (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating Officer, Managing Member, General Partner, President, Vice President, or Treasurer). This can be an executive, or someone who will have program-wide access to the cards that Lithic will provide. In some cases, this individual could also be a beneficial owner listed above. See [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) (Section II) for more background. + """ + + address: Optional[KYBPayloadUpdateRequestControlPersonAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYBPayloadUpdateRequest(BaseModel): + """Original request to update the account holder.""" + + beneficial_owner_entities: Optional[List[KYBBusinessEntity]] = None + """Deprecated.""" + + beneficial_owner_individuals: Optional[List[KYBPayloadUpdateRequestBeneficialOwnerIndividual]] = None + """ + You must submit a list of all direct and indirect individuals with 25% or more + ownership in the company. A maximum of 4 beneficial owners can be submitted. If + no individual owns 25% of the company you do not need to send beneficial owner + information. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section I) for more background on individuals that should be included. + """ + + business_entity: Optional[KYBBusinessEntity] = None + """ + Information for business for which the account is being opened and KYB is being + run. + """ + + control_person: Optional[KYBPayloadUpdateRequestControlPerson] = None + """ + An individual with significant responsibility for managing the legal entity + (e.g., a Chief Executive Officer, Chief Financial Officer, Chief Operating + Officer, Managing Member, General Partner, President, Vice President, or + Treasurer). This can be an executive, or someone who will have program-wide + access to the cards that Lithic will provide. In some cases, this individual + could also be a beneficial owner listed above. See + [FinCEN requirements](https://www.fincen.gov/sites/default/files/shared/CDD_Rev6.7_Sept_2017_Certificate.pdf) + (Section II) for more background. + """ + + +class KYBPayload(BaseModel): + """KYB payload for an updated account holder.""" + + token: str + """The token of the account_holder that was created.""" + + update_request: KYBPayloadUpdateRequest + """Original request to update the account holder.""" + + event_type: Optional[Literal["account_holder.updated"]] = None + """The type of event that occurred.""" + + external_id: Optional[str] = None + """ + A user provided id that can be used to link an account holder with an external + system + """ + + nature_of_business: Optional[str] = None + """ + Short description of the company's line of business (i.e., what does the company + do?). + """ + + website_url: Optional[str] = None + """Company website URL.""" + + +class KYCPayloadUpdateRequestIndividualAddress(BaseModel): + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + address1: str + """Valid deliverable address (no PO boxes).""" + + city: str + """Name of city.""" + + country: str + """Valid country code. + + Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 + three-character format. + """ + + postal_code: str + """Valid postal code. + + Only USA ZIP codes are currently supported, entered as a five-digit ZIP or + nine-digit ZIP+4. + """ + + state: str + """Valid state code. + + Only USA state codes are currently supported, entered in uppercase ISO 3166-2 + two-character format. + """ + + address2: Optional[str] = None + """Unit or apartment number (if applicable).""" + + +class KYCPayloadUpdateRequestIndividual(BaseModel): + """ + Information on the individual for whom the account is being opened and KYC is being run. + """ + + address: Optional[KYCPayloadUpdateRequestIndividualAddress] = None + """ + Individual's current address - PO boxes, UPS drops, and FedEx drops are not + acceptable; APO/FPO are acceptable. Only USA addresses are currently supported. + """ + + dob: Optional[str] = None + """Individual's date of birth, as an RFC 3339 date.""" + + email: Optional[str] = None + """Individual's email address. + + If utilizing Lithic for chargeback processing, this customer email address may + be used to communicate dispute status and resolution. + """ + + first_name: Optional[str] = None + """Individual's first name, as it appears on government-issued identity documents.""" + + last_name: Optional[str] = None + """Individual's last name, as it appears on government-issued identity documents.""" + + phone_number: Optional[str] = None + """Individual's phone number, entered in E.164 format.""" + + +class KYCPayloadUpdateRequest(BaseModel): + """Original request to update the account holder.""" + + individual: Optional[KYCPayloadUpdateRequestIndividual] = None + """ + Information on the individual for whom the account is being opened and KYC is + being run. + """ + + +class KYCPayload(BaseModel): + """KYC payload for an updated account holder.""" + + token: str + """The token of the account_holder that was created.""" + + update_request: KYCPayloadUpdateRequest + """Original request to update the account holder.""" + + event_type: Optional[Literal["account_holder.updated"]] = None + """The type of event that occurred.""" + + external_id: Optional[str] = None + """ + A user provided id that can be used to link an account holder with an external + system + """ + + +class LegacyPayload(BaseModel): + """Legacy payload for an updated account holder.""" + + token: str + """The token of the account_holder that was created.""" + + business_account_token: Optional[str] = None + """ + If applicable, represents the business account token associated with the + account_holder. + """ + + created: Optional[datetime] = None + """When the account_holder updated event was created""" + + email: Optional[str] = None + """ + If updated, the newly updated email associated with the account_holder otherwise + the existing email is provided. + """ + + event_type: Optional[Literal["account_holder.updated"]] = None + """The type of event that occurred.""" + + external_id: Optional[str] = None + """If applicable, represents the external_id associated with the account_holder.""" + + first_name: Optional[str] = None + """If applicable, represents the account_holder's first name.""" + + last_name: Optional[str] = None + """If applicable, represents the account_holder's last name.""" + + legal_business_name: Optional[str] = None + """If applicable, represents the account_holder's business name.""" + + phone_number: Optional[str] = None + """ + If updated, the newly updated phone_number associated with the account_holder + otherwise the existing phone_number is provided. + """ + + +ParsedWebhookEvent: TypeAlias = Union[ + AccountHolderCreatedWebhookEvent, + KYBPayload, + KYCPayload, + LegacyPayload, + AccountHolderVerificationWebhookEvent, + AccountHolderDocumentUpdatedWebhookEvent, + AsaRequestWebhookEvent, + TokenizationDecisioningRequestWebhookEvent, + AuthRulesBacktestReportCreatedWebhookEvent, + BalanceUpdatedWebhookEvent, + BookTransferTransactionCreatedWebhookEvent, + BookTransferTransactionUpdatedWebhookEvent, + CardCreatedWebhookEvent, + CardConvertedWebhookEvent, + CardRenewedWebhookEvent, + CardReissuedWebhookEvent, + CardShippedWebhookEvent, + CardTransactionUpdatedWebhookEvent, + CardTransactionEnhancedDataCreatedWebhookEvent, + CardTransactionEnhancedDataUpdatedWebhookEvent, + DigitalWalletTokenizationApprovalRequestWebhookEvent, + DigitalWalletTokenizationResultWebhookEvent, + DigitalWalletTokenizationTwoFactorAuthenticationCodeWebhookEvent, + DigitalWalletTokenizationTwoFactorAuthenticationCodeSentWebhookEvent, + DigitalWalletTokenizationUpdatedWebhookEvent, + DisputeUpdatedWebhookEvent, + DisputeEvidenceUploadFailedWebhookEvent, + ExternalBankAccountCreatedWebhookEvent, + ExternalBankAccountUpdatedWebhookEvent, + ExternalPaymentCreatedWebhookEvent, + ExternalPaymentUpdatedWebhookEvent, + FinancialAccountCreatedWebhookEvent, + FinancialAccountUpdatedWebhookEvent, + FundingEventCreatedWebhookEvent, + LoanTapeCreatedWebhookEvent, + LoanTapeUpdatedWebhookEvent, + ManagementOperationCreatedWebhookEvent, + ManagementOperationUpdatedWebhookEvent, + InternalTransactionCreatedWebhookEvent, + InternalTransactionUpdatedWebhookEvent, + NetworkTotalCreatedWebhookEvent, + NetworkTotalUpdatedWebhookEvent, + PaymentTransactionCreatedWebhookEvent, + PaymentTransactionUpdatedWebhookEvent, + SettlementReportUpdatedWebhookEvent, + StatementsCreatedWebhookEvent, + ThreeDSAuthenticationCreatedWebhookEvent, + ThreeDSAuthenticationUpdatedWebhookEvent, + ThreeDSAuthenticationChallengeWebhookEvent, + TokenizationApprovalRequestWebhookEvent, + TokenizationResultWebhookEvent, + TokenizationTwoFactorAuthenticationCodeWebhookEvent, + TokenizationTwoFactorAuthenticationCodeSentWebhookEvent, + TokenizationUpdatedWebhookEvent, + ThreeDSAuthentication, + DisputeTransactionCreatedWebhookEvent, + DisputeTransactionUpdatedWebhookEvent, +] diff --git a/src/lithic/types/payment.py b/src/lithic/types/payment.py index ea0047c7..3b32efaa 100644 --- a/src/lithic/types/payment.py +++ b/src/lithic/types/payment.py @@ -1,35 +1,257 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional -from typing_extensions import Literal +from typing import List, Union, Optional +from datetime import date, datetime +from typing_extensions import Literal, TypeAlias from .._models import BaseModel -from .financial_transaction import FinancialTransaction +from .wire_party_details import WirePartyDetails -__all__ = ["Payment", "PaymentMethodAttributes"] +__all__ = [ + "Payment", + "Event", + "MethodAttributes", + "MethodAttributesACHMethodAttributes", + "MethodAttributesWireMethodAttributes", + "RelatedAccountTokens", +] -class PaymentMethodAttributes(BaseModel): - sec_code: Literal["CCD", "PPD", "WEB"] +class Event(BaseModel): + """Payment Event""" + + token: str + """Globally unique identifier.""" + + amount: int + """ + Amount of the financial event that has been settled in the currency's smallest + unit (e.g., cents). + """ + + created: datetime + """Date and time when the financial event occurred. UTC time zone.""" + + result: Literal["APPROVED", "DECLINED"] + """ + APPROVED financial events were successful while DECLINED financial events were + declined by user, Lithic, or the network. + """ + + type: Literal[ + "ACH_ORIGINATION_CANCELLED", + "ACH_ORIGINATION_INITIATED", + "ACH_ORIGINATION_PROCESSED", + "ACH_ORIGINATION_REJECTED", + "ACH_ORIGINATION_RELEASED", + "ACH_ORIGINATION_REVIEWED", + "ACH_ORIGINATION_SETTLED", + "ACH_RECEIPT_PROCESSED", + "ACH_RECEIPT_RELEASED", + "ACH_RECEIPT_SETTLED", + "ACH_RETURN_INITIATED", + "ACH_RETURN_PROCESSED", + "ACH_RETURN_REJECTED", + "ACH_RETURN_SETTLED", + ] + """Event types: + + - `ACH_ORIGINATION_INITIATED` - ACH origination received and pending + approval/release from an ACH hold. + - `ACH_ORIGINATION_REVIEWED` - ACH origination has completed the review process. + - `ACH_ORIGINATION_CANCELLED` - ACH origination has been cancelled. + - `ACH_ORIGINATION_PROCESSED` - ACH origination has been processed and sent to + the Federal Reserve. + - `ACH_ORIGINATION_SETTLED` - ACH origination has settled. + - `ACH_ORIGINATION_RELEASED` - ACH origination released from pending to + available balance. + - `ACH_ORIGINATION_REJECTED` - ACH origination was rejected and not sent to the + Federal Reserve. + - `ACH_RECEIPT_PROCESSED` - ACH receipt pending release from an ACH holder. + - `ACH_RECEIPT_SETTLED` - ACH receipt funds have settled. + - `ACH_RECEIPT_RELEASED` - ACH receipt released from pending to available + balance. + - `ACH_RETURN_INITIATED` - ACH initiated return for an ACH receipt. + - `ACH_RETURN_PROCESSED` - ACH receipt returned by the Receiving Depository + Financial Institution. + - `ACH_RETURN_SETTLED` - ACH return settled by the Receiving Depository + Financial Institution. + - `ACH_RETURN_REJECTED` - ACH return was rejected by the Receiving Depository + Financial Institution. + """ + + detailed_results: Optional[ + List[ + Literal[ + "APPROVED", + "DECLINED", + "FUNDS_INSUFFICIENT", + "ACCOUNT_INVALID", + "PROGRAM_TRANSACTION_LIMIT_EXCEEDED", + "PROGRAM_DAILY_LIMIT_EXCEEDED", + "PROGRAM_MONTHLY_LIMIT_EXCEEDED", + ] + ] + ] = None + """More detailed reasons for the event""" + + +class MethodAttributesACHMethodAttributes(BaseModel): + sec_code: Literal["CCD", "PPD", "WEB", "TEL", "CIE", "CTX"] + """SEC code for ACH transaction""" + + ach_hold_period: Optional[int] = None + """Number of days the ACH transaction is on hold""" + + addenda: Optional[str] = None + """Addenda information""" company_id: Optional[str] = None + """Company ID for the ACH transaction""" receipt_routing_number: Optional[str] = None + """Receipt routing number""" retries: Optional[int] = None + """Number of retries attempted""" return_reason_code: Optional[str] = None + """Return reason code if the transaction was returned""" + + trace_numbers: Optional[List[str]] = None + """Trace numbers for the ACH transaction""" + + +class MethodAttributesWireMethodAttributes(BaseModel): + wire_message_type: Optional[str] = None + """Type of wire message""" + + wire_network: Literal["FEDWIRE", "SWIFT"] + """Type of wire transfer""" + + creditor: Optional[WirePartyDetails] = None + + debtor: Optional[WirePartyDetails] = None + + message_id: Optional[str] = None + """ + Point to point reference identifier, as assigned by the instructing party, used + for tracking the message through the Fedwire system + """ + + remittance_information: Optional[str] = None + """Payment details or invoice reference""" + + +MethodAttributes: TypeAlias = Union[MethodAttributesACHMethodAttributes, MethodAttributesWireMethodAttributes] -class Payment(FinancialTransaction): +class RelatedAccountTokens(BaseModel): + """Account tokens related to a payment transaction""" + + account_token: Optional[str] = None + """Globally unique identifier for the account""" + + business_account_token: Optional[str] = None + """Globally unique identifier for the business account""" + + +class Payment(BaseModel): + """Payment transaction""" + + token: str + """Unique identifier for the transaction""" + + category: Literal[ + "ACH", + "BALANCE_OR_FUNDING", + "FEE", + "REWARD", + "ADJUSTMENT", + "DERECOGNITION", + "DISPUTE", + "CARD", + "EXTERNAL_ACH", + "EXTERNAL_CHECK", + "EXTERNAL_FEDNOW", + "EXTERNAL_RTP", + "EXTERNAL_TRANSFER", + "EXTERNAL_WIRE", + "MANAGEMENT_ADJUSTMENT", + "MANAGEMENT_DISPUTE", + "MANAGEMENT_FEE", + "MANAGEMENT_REWARD", + "MANAGEMENT_DISBURSEMENT", + "PROGRAM_FUNDING", + ] + """Transaction category""" + + created: datetime + """ISO 8601 timestamp of when the transaction was created""" + + descriptor: str + """Transaction descriptor""" + direction: Literal["CREDIT", "DEBIT"] + """Transfer direction""" + + events: List[Event] + """List of transaction events""" + + family: Literal["PAYMENT"] + """PAYMENT - Payment Transaction""" - method: Literal["ACH_NEXT_DAY", "ACH_SAME_DAY"] + financial_account_token: str + """Financial account token""" - method_attributes: PaymentMethodAttributes + method: Literal["ACH_NEXT_DAY", "ACH_SAME_DAY", "WIRE"] + """Transfer method""" - source: Literal["CUSTOMER", "LITHIC"] + method_attributes: MethodAttributes + """Method-specific attributes""" + + pending_amount: int + """Pending amount in cents""" + + related_account_tokens: Optional[RelatedAccountTokens] = None + """Account tokens related to a payment transaction""" + + result: Literal["APPROVED", "DECLINED"] + """Transaction result""" + + settled_amount: int + """Settled amount in cents""" + + source: Literal["LITHIC", "EXTERNAL", "CUSTOMER"] + """Transaction source""" + + status: Literal["PENDING", "SETTLED", "DECLINED", "REVERSED", "CANCELED", "RETURNED"] + """The status of the transaction""" + + updated: datetime + """ISO 8601 timestamp of when the transaction was last updated""" + + currency: Optional[str] = None + """Currency of the transaction in ISO 4217 format""" + + expected_release_date: Optional[date] = None + """Expected release date for the transaction""" external_bank_account_token: Optional[str] = None + """External bank account token""" + + type: Optional[ + Literal[ + "ORIGINATION_CREDIT", + "ORIGINATION_DEBIT", + "RECEIPT_CREDIT", + "RECEIPT_DEBIT", + "WIRE_INBOUND_PAYMENT", + "WIRE_INBOUND_ADMIN", + "WIRE_OUTBOUND_PAYMENT", + "WIRE_OUTBOUND_ADMIN", + ] + ] = None user_defined_id: Optional[str] = None + """User-defined identifier""" diff --git a/src/lithic/types/payment_create_params.py b/src/lithic/types/payment_create_params.py index 8916fa28..41c0e5f6 100644 --- a/src/lithic/types/payment_create_params.py +++ b/src/lithic/types/payment_create_params.py @@ -1,8 +1,11 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing_extensions import Literal, Required, TypedDict +from typing import Optional +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo __all__ = ["PaymentCreateParams", "MethodAttributes"] @@ -34,10 +37,7 @@ class PaymentCreateParams(TypedDict, total=False): class MethodAttributes(TypedDict, total=False): sec_code: Required[Literal["CCD", "PPD", "WEB"]] - company_id: str - - receipt_routing_number: str - - retries: int + ach_hold_period: Annotated[int, PropertyInfo(alias="ach_hold__period")] + """Number of days to hold the ACH payment""" - return_reason_code: str + addenda: Optional[str] diff --git a/src/lithic/types/payment_create_response.py b/src/lithic/types/payment_create_response.py index 19acafe9..bc71fce3 100644 --- a/src/lithic/types/payment_create_response.py +++ b/src/lithic/types/payment_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional @@ -9,5 +9,7 @@ class PaymentCreateResponse(Payment): + """Payment transaction""" + balance: Optional[Balance] = None - """Balance of a Financial Account""" + """Balance""" diff --git a/src/lithic/types/payment_list_params.py b/src/lithic/types/payment_list_params.py index 11dcd7b7..f2168b52 100644 --- a/src/lithic/types/payment_list_params.py +++ b/src/lithic/types/payment_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -12,12 +12,18 @@ class PaymentListParams(TypedDict, total=False): + account_token: str + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] """Date string in RFC 3339 format. Only entries created after the specified time will be included. UTC time zone. """ + business_account_token: str + + category: Literal["ACH"] + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] """Date string in RFC 3339 format. diff --git a/src/lithic/types/payment_retry_response.py b/src/lithic/types/payment_retry_response.py index e3e0fbce..da6979e8 100644 --- a/src/lithic/types/payment_retry_response.py +++ b/src/lithic/types/payment_retry_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional @@ -9,5 +9,7 @@ class PaymentRetryResponse(Payment): + """Payment transaction""" + balance: Optional[Balance] = None - """Balance of a Financial Account""" + """Balance""" diff --git a/src/lithic/types/payment_return_params.py b/src/lithic/types/payment_return_params.py new file mode 100644 index 00000000..a466b8c6 --- /dev/null +++ b/src/lithic/types/payment_return_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["PaymentReturnParams"] + + +class PaymentReturnParams(TypedDict, total=False): + financial_account_token: Required[str] + """Globally unique identifier for the financial account""" + + return_reason_code: Required[str] + """ACH return reason code indicating the reason for returning the payment. + + Supported codes include R01-R53 and R80-R85. For a complete list of return codes + and their meanings, see + [ACH Return Reasons](https://docs.lithic.com/docs/ach-overview#ach-return-reasons) + """ + + addenda: Optional[str] + """Optional additional information about the return. Limited to 44 characters""" + + date_of_death: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")] + """Date of death in YYYY-MM-DD format. + + Required when using return codes **R14** (representative payee deceased) or + **R15** (beneficiary or account holder deceased) + """ + + memo: Optional[str] + """Optional memo for the return. Limited to 10 characters""" diff --git a/src/lithic/types/payment_simulate_action_params.py b/src/lithic/types/payment_simulate_action_params.py new file mode 100644 index 00000000..1f242232 --- /dev/null +++ b/src/lithic/types/payment_simulate_action_params.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["PaymentSimulateActionParams"] + + +class PaymentSimulateActionParams(TypedDict, total=False): + event_type: Required[ + Literal[ + "ACH_ORIGINATION_REVIEWED", + "ACH_ORIGINATION_RELEASED", + "ACH_ORIGINATION_PROCESSED", + "ACH_ORIGINATION_SETTLED", + "ACH_RECEIPT_SETTLED", + "ACH_RECEIPT_RELEASED", + "ACH_RETURN_INITIATED", + "ACH_RETURN_PROCESSED", + "ACH_RETURN_SETTLED", + ] + ] + """Event Type""" + + date_of_death: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Date of Death for ACH Return""" + + decline_reason: Literal[ + "PROGRAM_TRANSACTION_LIMIT_EXCEEDED", "PROGRAM_DAILY_LIMIT_EXCEEDED", "PROGRAM_MONTHLY_LIMIT_EXCEEDED" + ] + """Decline reason""" + + return_addenda: str + """Return Addenda""" + + return_reason_code: str + """Return Reason Code""" diff --git a/src/lithic/types/payment_simulate_action_response.py b/src/lithic/types/payment_simulate_action_response.py new file mode 100644 index 00000000..15a8fe99 --- /dev/null +++ b/src/lithic/types/payment_simulate_action_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["PaymentSimulateActionResponse"] + + +class PaymentSimulateActionResponse(BaseModel): + debugging_request_id: str + """Debugging Request Id""" + + result: Literal["APPROVED", "DECLINED"] + """Request Result""" + + transaction_event_token: str + """Transaction Event Token""" diff --git a/src/lithic/types/payment_simulate_receipt_params.py b/src/lithic/types/payment_simulate_receipt_params.py new file mode 100644 index 00000000..4c574031 --- /dev/null +++ b/src/lithic/types/payment_simulate_receipt_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["PaymentSimulateReceiptParams"] + + +class PaymentSimulateReceiptParams(TypedDict, total=False): + token: Required[str] + """ + Customer-generated payment token used to uniquely identify the simulated payment + """ + + amount: Required[int] + """Amount""" + + financial_account_token: Required[str] + """Financial Account Token""" + + receipt_type: Required[Literal["RECEIPT_CREDIT", "RECEIPT_DEBIT"]] + """Receipt Type""" + + memo: str + """Memo""" diff --git a/src/lithic/types/payment_simulate_receipt_response.py b/src/lithic/types/payment_simulate_receipt_response.py new file mode 100644 index 00000000..e7dffe2d --- /dev/null +++ b/src/lithic/types/payment_simulate_receipt_response.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["PaymentSimulateReceiptResponse"] + + +class PaymentSimulateReceiptResponse(BaseModel): + debugging_request_id: str + """Debugging Request Id""" + + result: Literal["APPROVED", "DECLINED"] + """Request Result""" + + transaction_event_token: str + """Transaction Event Token""" diff --git a/src/lithic/types/payment_simulate_release_params.py b/src/lithic/types/payment_simulate_release_params.py index 452b2ac8..cec89e02 100644 --- a/src/lithic/types/payment_simulate_release_params.py +++ b/src/lithic/types/payment_simulate_release_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,3 +9,4 @@ class PaymentSimulateReleaseParams(TypedDict, total=False): payment_token: Required[str] + """Payment Token""" diff --git a/src/lithic/types/payment_simulate_release_response.py b/src/lithic/types/payment_simulate_release_response.py index 0326e62f..2d7be0f7 100644 --- a/src/lithic/types/payment_simulate_release_response.py +++ b/src/lithic/types/payment_simulate_release_response.py @@ -1,6 +1,5 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional from typing_extensions import Literal from .._models import BaseModel @@ -9,8 +8,11 @@ class PaymentSimulateReleaseResponse(BaseModel): - debugging_request_id: Optional[str] = None + debugging_request_id: str + """Debugging Request Id""" - result: Optional[Literal["APPROVED", "DECLINED"]] = None + result: Literal["APPROVED", "DECLINED"] + """Request Result""" - transaction_event_token: Optional[str] = None + transaction_event_token: str + """Transaction Event Token""" diff --git a/src/lithic/types/payment_simulate_return_params.py b/src/lithic/types/payment_simulate_return_params.py index d4d094c9..3a880585 100644 --- a/src/lithic/types/payment_simulate_return_params.py +++ b/src/lithic/types/payment_simulate_return_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,5 +9,7 @@ class PaymentSimulateReturnParams(TypedDict, total=False): payment_token: Required[str] + """Payment Token""" return_reason_code: str + """Return Reason Code""" diff --git a/src/lithic/types/payment_simulate_return_response.py b/src/lithic/types/payment_simulate_return_response.py index 9ceb94e9..14043ff6 100644 --- a/src/lithic/types/payment_simulate_return_response.py +++ b/src/lithic/types/payment_simulate_return_response.py @@ -1,6 +1,5 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional from typing_extensions import Literal from .._models import BaseModel @@ -9,8 +8,11 @@ class PaymentSimulateReturnResponse(BaseModel): - debugging_request_id: Optional[str] = None + debugging_request_id: str + """Debugging Request Id""" - result: Optional[Literal["APPROVED", "DECLINED"]] = None + result: Literal["APPROVED", "DECLINED"] + """Request Result""" - transaction_event_token: Optional[str] = None + transaction_event_token: str + """Transaction Event Token""" diff --git a/src/lithic/types/payment_transaction_created_webhook_event.py b/src/lithic/types/payment_transaction_created_webhook_event.py new file mode 100644 index 00000000..2d5f4a6e --- /dev/null +++ b/src/lithic/types/payment_transaction_created_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .payment import Payment + +__all__ = ["PaymentTransactionCreatedWebhookEvent"] + + +class PaymentTransactionCreatedWebhookEvent(Payment): + """Payment transaction""" + + event_type: Literal["payment_transaction.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/payment_transaction_updated_webhook_event.py b/src/lithic/types/payment_transaction_updated_webhook_event.py new file mode 100644 index 00000000..2a65aad2 --- /dev/null +++ b/src/lithic/types/payment_transaction_updated_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .payment import Payment + +__all__ = ["PaymentTransactionUpdatedWebhookEvent"] + + +class PaymentTransactionUpdatedWebhookEvent(Payment): + """Payment transaction""" + + event_type: Literal["payment_transaction.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/provision_response.py b/src/lithic/types/provision_response.py new file mode 100644 index 00000000..29a2446e --- /dev/null +++ b/src/lithic/types/provision_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["ProvisionResponse"] + + +class ProvisionResponse(BaseModel): + """Object containing the fields required to add a card to Apple Pay. + + Applies only to Apple Pay wallet. + """ + + activation_data: Optional[str] = FieldInfo(alias="activationData", default=None) + + encrypted_data: Optional[str] = FieldInfo(alias="encryptedData", default=None) + + ephemeral_public_key: Optional[str] = FieldInfo(alias="ephemeralPublicKey", default=None) diff --git a/src/lithic/types/reports/__init__.py b/src/lithic/types/reports/__init__.py index b7d013b8..1d825b95 100644 --- a/src/lithic/types/reports/__init__.py +++ b/src/lithic/types/reports/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/reports/settlement/__init__.py b/src/lithic/types/reports/settlement/__init__.py new file mode 100644 index 00000000..1d367429 --- /dev/null +++ b/src/lithic/types/reports/settlement/__init__.py @@ -0,0 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .network_total_list_params import NetworkTotalListParams as NetworkTotalListParams diff --git a/src/lithic/types/reports/settlement/network_total_list_params.py b/src/lithic/types/reports/settlement/network_total_list_params.py new file mode 100644 index 00000000..82a82c55 --- /dev/null +++ b/src/lithic/types/reports/settlement/network_total_list_params.py @@ -0,0 +1,62 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date, datetime +from typing_extensions import Literal, Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["NetworkTotalListParams"] + + +class NetworkTotalListParams(TypedDict, total=False): + begin: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Datetime in RFC 3339 format. + + Only entries created after the specified time will be included. UTC time zone. + """ + + end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Datetime in RFC 3339 format. + + Only entries created before the specified time will be included. UTC time zone. + """ + + ending_before: str + """A cursor representing an item's token before which a page of results should end. + + Used to retrieve the previous page of results before this item. + """ + + institution_id: str + """Institution ID to filter on.""" + + network: Literal["VISA", "MASTERCARD", "MAESTRO", "INTERLINK"] + """Network to filter on.""" + + page_size: int + """Number of records per page.""" + + report_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Singular report date to filter on (YYYY-MM-DD). + + Cannot be populated in conjunction with report_date_begin or report_date_end. + """ + + report_date_begin: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Earliest report date to filter on, inclusive (YYYY-MM-DD).""" + + report_date_end: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Latest report date to filter on, inclusive (YYYY-MM-DD).""" + + settlement_institution_id: str + """Settlement institution ID to filter on.""" + + starting_after: str + """A cursor representing an item's token after which a page of results should + begin. + + Used to retrieve the next page of results after this item. + """ diff --git a/src/lithic/types/reports/settlement_list_details_params.py b/src/lithic/types/reports/settlement_list_details_params.py index 8f142e31..6dc81d39 100644 --- a/src/lithic/types/reports/settlement_list_details_params.py +++ b/src/lithic/types/reports/settlement_list_details_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -15,7 +15,7 @@ class SettlementListDetailsParams(TypedDict, total=False): """ page_size: int - """Page size (for pagination).""" + """Number of records per page.""" starting_after: str """A cursor representing an item's token after which a page of results should diff --git a/src/lithic/types/required_document.py b/src/lithic/types/required_document.py new file mode 100644 index 00000000..7bc70273 --- /dev/null +++ b/src/lithic/types/required_document.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel + +__all__ = ["RequiredDocument"] + + +class RequiredDocument(BaseModel): + entity_token: str + """Globally unique identifier for an entity.""" + + status_reasons: List[str] + """ + Provides the status reasons that will be satisfied by providing one of the valid + documents. + """ + + valid_documents: List[str] + """ + A list of valid documents that will satisfy the KYC requirements for the + specified entity. + """ diff --git a/src/lithic/types/responder_endpoint_check_status_params.py b/src/lithic/types/responder_endpoint_check_status_params.py index bc4391ee..f21aef7e 100644 --- a/src/lithic/types/responder_endpoint_check_status_params.py +++ b/src/lithic/types/responder_endpoint_check_status_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/responder_endpoint_create_params.py b/src/lithic/types/responder_endpoint_create_params.py index ddda6e7d..36fe9caf 100644 --- a/src/lithic/types/responder_endpoint_create_params.py +++ b/src/lithic/types/responder_endpoint_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/responder_endpoint_create_response.py b/src/lithic/types/responder_endpoint_create_response.py index 24f83e30..198faf4e 100644 --- a/src/lithic/types/responder_endpoint_create_response.py +++ b/src/lithic/types/responder_endpoint_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/responder_endpoint_delete_params.py b/src/lithic/types/responder_endpoint_delete_params.py index e3482dab..59def61c 100644 --- a/src/lithic/types/responder_endpoint_delete_params.py +++ b/src/lithic/types/responder_endpoint_delete_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/responder_endpoint_status.py b/src/lithic/types/responder_endpoint_status.py index a6fb9929..f2a29aa1 100644 --- a/src/lithic/types/responder_endpoint_status.py +++ b/src/lithic/types/responder_endpoint_status.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/settlement_detail.py b/src/lithic/types/settlement_detail.py index d1ec8ce1..a8cd5fa3 100644 --- a/src/lithic/types/settlement_detail.py +++ b/src/lithic/types/settlement_detail.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime @@ -12,6 +12,8 @@ class OtherFeesDetails(BaseModel): + """The total gross amount of other fees by type.""" + isa: Optional[int] = FieldInfo(alias="ISA", default=None) @@ -21,19 +23,19 @@ class SettlementDetail(BaseModel): account_token: str """ - The most granular ID the network settles with (e.g., ICA for Mastercard, FTSRE - for Visa). + Globally unique identifier denoting the account that the associated transaction + occurred on. """ card_program_token: str """ Globally unique identifier denoting the card program that the associated - Transaction occurred on. + transaction occurred on. """ card_token: str """ - Globally unique identifier denoting the card that the associated Transaction + Globally unique identifier denoting the card that the associated transaction occurred on. """ @@ -41,7 +43,7 @@ class SettlementDetail(BaseModel): """Date and time when the transaction first occurred. UTC time zone.""" currency: str - """Three-digit alphabetic ISO 4217 code.""" + """Three-character alphabetic ISO 4217 code.""" disputes_gross_amount: int """The total gross amount of disputes settlements.""" @@ -76,7 +78,11 @@ class SettlementDetail(BaseModel): """Date of when the report was first generated.""" settlement_date: str - """Date of when money movement is triggered for the transaction.""" + """Date of when money movement is triggered for the transaction. + + One exception applies - for Mastercard dual message settlement, this is the + settlement advisement date, which is distinct from the date of money movement. + """ transaction_token: str """Globally unique identifier denoting the associated Transaction object.""" @@ -92,6 +98,7 @@ class SettlementDetail(BaseModel): "ARBITRATION", "CHARGEBACK", "CLEARING", + "COLLABORATION", "FEE", "FINANCIAL", "NON-FINANCIAL", @@ -102,3 +109,6 @@ class SettlementDetail(BaseModel): updated: datetime """Date and time when the transaction first occurred. UTC time zone.""" + + fee_description: Optional[str] = None + """Network's description of a fee, only present on records with type `FEE`.""" diff --git a/src/lithic/types/settlement_report.py b/src/lithic/types/settlement_report.py index 432433ba..9c85dd85 100644 --- a/src/lithic/types/settlement_report.py +++ b/src/lithic/types/settlement_report.py @@ -1,6 +1,6 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import List from datetime import datetime from .._models import BaseModel @@ -14,18 +14,39 @@ class SettlementReport(BaseModel): """Date and time when the transaction first occurred. UTC time zone.""" currency: str - """Three-digit alphabetic ISO 4217 code.""" + """3-character alphabetic ISO 4217 code. - details: List[Optional[SettlementSummaryDetails]] + (This field is deprecated and will be removed in a future version of the API.) + """ + + details: List[SettlementSummaryDetails] disputes_gross_amount: int - """The total gross amount of disputes settlements.""" + """The total gross amount of disputes settlements. + + (This field is deprecated and will be removed in a future version of the API. To + compute total amounts, Lithic recommends that customers sum the relevant + settlement amounts found within `details`.) + """ interchange_gross_amount: int - """The total amount of interchange.""" + """The total amount of interchange. + + (This field is deprecated and will be removed in a future version of the API. To + compute total amounts, Lithic recommends that customers sum the relevant + settlement amounts found within `details`.) + """ + + is_complete: bool + """Indicates that all data expected on the given report date is available.""" other_fees_gross_amount: int - """Total amount of gross other fees outside of interchange.""" + """Total amount of gross other fees outside of interchange. + + (This field is deprecated and will be removed in a future version of the API. To + compute total amounts, Lithic recommends that customers sum the relevant + settlement amounts found within `details`.) + """ report_date: str """Date of when the report was first generated.""" @@ -33,13 +54,18 @@ class SettlementReport(BaseModel): settled_net_amount: int """The total net amount of cash moved. - (net value of settled_gross_amount, interchange, fees). + (net value of settled_gross_amount, interchange, fees). (This field is + deprecated and will be removed in a future version of the API. To compute total + amounts, Lithic recommends that customers sum the relevant settlement amounts + found within `details`.) """ transactions_gross_amount: int """ The total amount of settlement impacting transactions (excluding interchange, - fees, and disputes). + fees, and disputes). (This field is deprecated and will be removed in a future + version of the API. To compute total amounts, Lithic recommends that customers + sum the relevant settlement amounts found within `details`.) """ updated: datetime diff --git a/src/lithic/types/settlement_report_updated_webhook_event.py b/src/lithic/types/settlement_report_updated_webhook_event.py new file mode 100644 index 00000000..c7435d5a --- /dev/null +++ b/src/lithic/types/settlement_report_updated_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .settlement_report import SettlementReport + +__all__ = ["SettlementReportUpdatedWebhookEvent"] + + +class SettlementReportUpdatedWebhookEvent(SettlementReport): + event_type: Literal["settlement_report.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/settlement_summary_details.py b/src/lithic/types/settlement_summary_details.py index 7da0a02c..1b04c769 100644 --- a/src/lithic/types/settlement_summary_details.py +++ b/src/lithic/types/settlement_summary_details.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from typing_extensions import Literal @@ -9,6 +9,9 @@ class SettlementSummaryDetails(BaseModel): + currency: Optional[str] = None + """3-character alphabetic ISO 4217 code.""" + disputes_gross_amount: Optional[int] = None """The total gross amount of disputes settlements.""" diff --git a/src/lithic/types/shared/__init__.py b/src/lithic/types/shared/__init__.py index 7646a183..fc923958 100644 --- a/src/lithic/types/shared/__init__.py +++ b/src/lithic/types/shared/__init__.py @@ -1,5 +1,11 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .address import Address as Address from .carrier import Carrier as Carrier +from .currency import Currency as Currency +from .document import Document as Document +from .merchant import Merchant as Merchant +from .financial_event import FinancialEvent as FinancialEvent from .shipping_address import ShippingAddress as ShippingAddress +from .account_financial_account_type import AccountFinancialAccountType as AccountFinancialAccountType +from .instance_financial_account_type import InstanceFinancialAccountType as InstanceFinancialAccountType diff --git a/src/lithic/types/shared/account_financial_account_type.py b/src/lithic/types/shared/account_financial_account_type.py new file mode 100644 index 00000000..2078afab --- /dev/null +++ b/src/lithic/types/shared/account_financial_account_type.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["AccountFinancialAccountType"] + +AccountFinancialAccountType: TypeAlias = Literal["ISSUING", "OPERATING"] diff --git a/src/lithic/types/shared/address.py b/src/lithic/types/shared/address.py index cabf52f7..3e7be23d 100644 --- a/src/lithic/types/shared/address.py +++ b/src/lithic/types/shared/address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional @@ -15,24 +15,25 @@ class Address(BaseModel): """Name of city.""" country: str - """Valid country code. - - Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 - three-character format. + """ + Valid country code, entered in uppercase ISO 3166-1 alpha-3 three-character + format. Only USA is currently supported for all workflows. KYC_EXEMPT supports + CAN additionally. """ postal_code: str """Valid postal code. - Only USA ZIP codes are currently supported, entered as a five-digit ZIP or - nine-digit ZIP+4. + USA postal codes (ZIP codes) are supported, entered as a five-digit postal code + or nine-digit postal code (ZIP+4) using the format 12345-1234. KYC_EXEMPT + supports Canadian postal codes. """ state: str """Valid state code. - Only USA state codes are currently supported, entered in uppercase ISO 3166-2 - two-character format. + USA state codes are supported, entered in uppercase ISO 3166-2 two-character + format. KYC_EXEMPT supports Canadian province codes. """ address2: Optional[str] = None diff --git a/src/lithic/types/shared/carrier.py b/src/lithic/types/shared/carrier.py index 40192c24..591cfb25 100644 --- a/src/lithic/types/shared/carrier.py +++ b/src/lithic/types/shared/carrier.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/shared/currency.py b/src/lithic/types/shared/currency.py new file mode 100644 index 00000000..cb3271df --- /dev/null +++ b/src/lithic/types/shared/currency.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["Currency"] + +Currency: TypeAlias = str diff --git a/src/lithic/types/shared/document.py b/src/lithic/types/shared/document.py new file mode 100644 index 00000000..04ddcafd --- /dev/null +++ b/src/lithic/types/shared/document.py @@ -0,0 +1,107 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["Document", "RequiredDocumentUpload"] + + +class RequiredDocumentUpload(BaseModel): + """Represents a single image of the document to upload.""" + + token: str + """Globally unique identifier for the document upload.""" + + accepted_entity_status_reasons: List[str] + """ + A list of status reasons associated with a KYB account holder that have been + satisfied by the document upload + """ + + created: datetime + """When the document upload was created""" + + image_type: Literal["FRONT", "BACK"] + """Type of image to upload.""" + + rejected_entity_status_reasons: List[str] + """ + A list of status reasons associated with a KYB account holder that have not been + satisfied by the document upload + """ + + status: Literal["ACCEPTED", "REJECTED", "PENDING_UPLOAD", "UPLOADED", "PARTIAL_APPROVAL"] + """Status of an account holder's document upload.""" + + status_reasons: List[ + Literal[ + "DOCUMENT_MISSING_REQUIRED_DATA", + "DOCUMENT_UPLOAD_TOO_BLURRY", + "FILE_SIZE_TOO_LARGE", + "INVALID_DOCUMENT_TYPE", + "INVALID_DOCUMENT_UPLOAD", + "INVALID_ENTITY", + "DOCUMENT_EXPIRED", + "DOCUMENT_ISSUED_GREATER_THAN_30_DAYS", + "DOCUMENT_TYPE_NOT_SUPPORTED", + "UNKNOWN_FAILURE_REASON", + "UNKNOWN_ERROR", + ] + ] + """Reasons for document image upload status.""" + + updated: datetime + """When the document upload was last updated""" + + upload_url: str + """URL to upload document image to. + + Note that the upload URLs expire after 7 days. If an upload URL expires, you can + refresh the URLs by retrieving the document upload from + `GET /account_holders/{account_holder_token}/documents`. + """ + + +class Document(BaseModel): + """ + Describes the document and the required document image uploads + required to re-run KYC + """ + + token: str + """Globally unique identifier for the document.""" + + account_holder_token: str + """Globally unique identifier for the account holder.""" + + document_type: Literal[ + "DRIVERS_LICENSE", + "PASSPORT", + "PASSPORT_CARD", + "EIN_LETTER", + "TAX_RETURN", + "OPERATING_AGREEMENT", + "CERTIFICATE_OF_FORMATION", + "CERTIFICATE_OF_GOOD_STANDING", + "ARTICLES_OF_INCORPORATION", + "ARTICLES_OF_ORGANIZATION", + "BYLAWS", + "GOVERNMENT_BUSINESS_LICENSE", + "PARTNERSHIP_AGREEMENT", + "SS4_FORM", + "BANK_STATEMENT", + "UTILITY_BILL_STATEMENT", + "SSN_CARD", + "ITIN_LETTER", + "FINCEN_BOI_REPORT", + ] + """Type of documentation to be submitted for verification of an account holder""" + + entity_token: str + """Globally unique identifier for an entity.""" + + required_document_uploads: List[RequiredDocumentUpload] + """Represents a single image of the document to upload.""" diff --git a/src/lithic/types/shared/financial_event.py b/src/lithic/types/shared/financial_event.py new file mode 100644 index 00000000..c2c1bb6a --- /dev/null +++ b/src/lithic/types/shared/financial_event.py @@ -0,0 +1,123 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FinancialEvent"] + + +class FinancialEvent(BaseModel): + """Financial Event""" + + token: Optional[str] = None + """Globally unique identifier.""" + + amount: Optional[int] = None + """ + Amount of the financial event that has been settled in the currency's smallest + unit (e.g., cents). + """ + + created: Optional[datetime] = None + """Date and time when the financial event occurred. UTC time zone.""" + + result: Optional[Literal["APPROVED", "DECLINED"]] = None + """ + APPROVED financial events were successful while DECLINED financial events were + declined by user, Lithic, or the network. + """ + + type: Optional[ + Literal[ + "ACH_ORIGINATION_CANCELLED", + "ACH_ORIGINATION_INITIATED", + "ACH_ORIGINATION_PROCESSED", + "ACH_ORIGINATION_RELEASED", + "ACH_ORIGINATION_REJECTED", + "ACH_ORIGINATION_REVIEWED", + "ACH_ORIGINATION_SETTLED", + "ACH_RECEIPT_PROCESSED", + "ACH_RECEIPT_RELEASED", + "ACH_RECEIPT_SETTLED", + "ACH_RETURN_INITIATED", + "ACH_RETURN_PROCESSED", + "ACH_RETURN_REJECTED", + "ACH_RETURN_SETTLED", + "AUTHORIZATION", + "AUTHORIZATION_ADVICE", + "AUTHORIZATION_EXPIRY", + "AUTHORIZATION_REVERSAL", + "BALANCE_INQUIRY", + "BILLING_ERROR", + "BILLING_ERROR_REVERSAL", + "CARD_TO_CARD", + "CASH_BACK", + "CASH_BACK_REVERSAL", + "CLEARING", + "COLLECTION", + "CORRECTION_CREDIT", + "CORRECTION_DEBIT", + "CREDIT_AUTHORIZATION", + "CREDIT_AUTHORIZATION_ADVICE", + "CURRENCY_CONVERSION", + "CURRENCY_CONVERSION_REVERSAL", + "DISPUTE_WON", + "EXTERNAL_ACH_CANCELED", + "EXTERNAL_ACH_INITIATED", + "EXTERNAL_ACH_RELEASED", + "EXTERNAL_ACH_REVERSED", + "EXTERNAL_ACH_SETTLED", + "EXTERNAL_CHECK_CANCELED", + "EXTERNAL_CHECK_INITIATED", + "EXTERNAL_CHECK_RELEASED", + "EXTERNAL_CHECK_REVERSED", + "EXTERNAL_CHECK_SETTLED", + "EXTERNAL_FEDNOW_CANCELED", + "EXTERNAL_FEDNOW_INITIATED", + "EXTERNAL_FEDNOW_RELEASED", + "EXTERNAL_FEDNOW_REVERSED", + "EXTERNAL_FEDNOW_SETTLED", + "EXTERNAL_RTP_CANCELED", + "EXTERNAL_RTP_INITIATED", + "EXTERNAL_RTP_RELEASED", + "EXTERNAL_RTP_REVERSED", + "EXTERNAL_RTP_SETTLED", + "EXTERNAL_TRANSFER_CANCELED", + "EXTERNAL_TRANSFER_INITIATED", + "EXTERNAL_TRANSFER_RELEASED", + "EXTERNAL_TRANSFER_REVERSED", + "EXTERNAL_TRANSFER_SETTLED", + "EXTERNAL_WIRE_CANCELED", + "EXTERNAL_WIRE_INITIATED", + "EXTERNAL_WIRE_RELEASED", + "EXTERNAL_WIRE_REVERSED", + "EXTERNAL_WIRE_SETTLED", + "FINANCIAL_AUTHORIZATION", + "FINANCIAL_CREDIT_AUTHORIZATION", + "INTEREST", + "INTEREST_REVERSAL", + "INTERNAL_ADJUSTMENT", + "LATE_PAYMENT", + "LATE_PAYMENT_REVERSAL", + "LOSS_WRITE_OFF", + "PROVISIONAL_CREDIT", + "PROVISIONAL_CREDIT_REVERSAL", + "SERVICE", + "RETURN", + "RETURN_REVERSAL", + "TRANSFER", + "TRANSFER_INSUFFICIENT_FUNDS", + "RETURNED_PAYMENT", + "RETURNED_PAYMENT_REVERSAL", + "LITHIC_NETWORK_PAYMENT", + "ANNUAL", + "ANNUAL_REVERSAL", + "QUARTERLY", + "QUARTERLY_REVERSAL", + "MONTHLY", + "MONTHLY_REVERSAL", + ] + ] = None diff --git a/src/lithic/types/shared/instance_financial_account_type.py b/src/lithic/types/shared/instance_financial_account_type.py new file mode 100644 index 00000000..72a4a348 --- /dev/null +++ b/src/lithic/types/shared/instance_financial_account_type.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["InstanceFinancialAccountType"] + +InstanceFinancialAccountType: TypeAlias = Literal[ + "ISSUING", + "RESERVE", + "OPERATING", + "CHARGED_OFF_FEES", + "CHARGED_OFF_INTEREST", + "CHARGED_OFF_PRINCIPAL", + "SECURITY", + "PROGRAM_RECEIVABLES", + "COLLECTION", + "PROGRAM_BANK_ACCOUNTS_PAYABLE", +] diff --git a/src/lithic/types/shared/merchant.py b/src/lithic/types/shared/merchant.py new file mode 100644 index 00000000..99c38f9e --- /dev/null +++ b/src/lithic/types/shared/merchant.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["Merchant"] + + +class Merchant(BaseModel): + acceptor_id: str + """Unique alphanumeric identifier for the payment card acceptor (merchant).""" + + acquiring_institution_id: str + """Unique numeric identifier of the acquiring institution.""" + + city: str + """City of card acceptor. + + Note that in many cases, particularly in card-not-present transactions, + merchants may send through a phone number or URL in this field. + """ + + country: str + """Country or entity of card acceptor. + + Possible values are: (1) all ISO 3166-1 alpha-3 country codes, (2) QZZ for + Kosovo, and (3) ANT for Netherlands Antilles. + """ + + descriptor: str + """Short description of card acceptor.""" + + mcc: str + """Merchant category code (MCC). + + A four-digit number listed in ISO 18245. An MCC is used to classify a business + by the types of goods or services it provides. + """ + + state: str + """Geographic state of card acceptor.""" diff --git a/src/lithic/types/shared/shipping_address.py b/src/lithic/types/shared/shipping_address.py index 13ef0949..68e69255 100644 --- a/src/lithic/types/shared/shipping_address.py +++ b/src/lithic/types/shared/shipping_address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional @@ -20,19 +20,22 @@ class ShippingAddress(BaseModel): first_name: str """Customer's first name. - This will be the first name printed on the physical card. + This will be the first name printed on the physical card. The combined length of + `first_name` and `last_name` may not exceed 25 characters. """ last_name: str """Customer's surname (family name). - This will be the last name printed on the physical card. + This will be the last name printed on the physical card. The combined length of + `first_name` and `last_name` may not exceed 25 characters. """ postal_code: str """Postal code (formerly zipcode). - For US addresses, either five-digit zipcode or nine-digit "ZIP+4". + For US addresses, either five-digit postal code or nine-digit postal code + (ZIP+4) using the format 12345-1234. """ state: str diff --git a/src/lithic/types/shared_params/__init__.py b/src/lithic/types/shared_params/__init__.py index 7646a183..3cd36cfe 100644 --- a/src/lithic/types/shared_params/__init__.py +++ b/src/lithic/types/shared_params/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .address import Address as Address from .carrier import Carrier as Carrier diff --git a/src/lithic/types/shared_params/address.py b/src/lithic/types/shared_params/address.py index f3bff9ee..d438e8bd 100644 --- a/src/lithic/types/shared_params/address.py +++ b/src/lithic/types/shared_params/address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -15,24 +15,25 @@ class Address(TypedDict, total=False): """Name of city.""" country: Required[str] - """Valid country code. - - Only USA is currently supported, entered in uppercase ISO 3166-1 alpha-3 - three-character format. + """ + Valid country code, entered in uppercase ISO 3166-1 alpha-3 three-character + format. Only USA is currently supported for all workflows. KYC_EXEMPT supports + CAN additionally. """ postal_code: Required[str] """Valid postal code. - Only USA ZIP codes are currently supported, entered as a five-digit ZIP or - nine-digit ZIP+4. + USA postal codes (ZIP codes) are supported, entered as a five-digit postal code + or nine-digit postal code (ZIP+4) using the format 12345-1234. KYC_EXEMPT + supports Canadian postal codes. """ state: Required[str] """Valid state code. - Only USA state codes are currently supported, entered in uppercase ISO 3166-2 - two-character format. + USA state codes are supported, entered in uppercase ISO 3166-2 two-character + format. KYC_EXEMPT supports Canadian province codes. """ address2: str diff --git a/src/lithic/types/shared_params/carrier.py b/src/lithic/types/shared_params/carrier.py index caf5e6fa..9c644c89 100644 --- a/src/lithic/types/shared_params/carrier.py +++ b/src/lithic/types/shared_params/carrier.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/shared_params/shipping_address.py b/src/lithic/types/shared_params/shipping_address.py index 96f980bc..263ad697 100644 --- a/src/lithic/types/shared_params/shipping_address.py +++ b/src/lithic/types/shared_params/shipping_address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -20,19 +20,22 @@ class ShippingAddress(TypedDict, total=False): first_name: Required[str] """Customer's first name. - This will be the first name printed on the physical card. + This will be the first name printed on the physical card. The combined length of + `first_name` and `last_name` may not exceed 25 characters. """ last_name: Required[str] """Customer's surname (family name). - This will be the last name printed on the physical card. + This will be the last name printed on the physical card. The combined length of + `first_name` and `last_name` may not exceed 25 characters. """ postal_code: Required[str] """Postal code (formerly zipcode). - For US addresses, either five-digit zipcode or nine-digit "ZIP+4". + For US addresses, either five-digit postal code or nine-digit postal code + (ZIP+4) using the format 12345-1234. """ state: Required[str] diff --git a/src/lithic/types/spend_limit_duration.py b/src/lithic/types/spend_limit_duration.py index c299270a..4018870f 100644 --- a/src/lithic/types/spend_limit_duration.py +++ b/src/lithic/types/spend_limit_duration.py @@ -1,7 +1,7 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias __all__ = ["SpendLimitDuration"] -SpendLimitDuration = Literal["ANNUALLY", "FOREVER", "MONTHLY", "TRANSACTION"] +SpendLimitDuration: TypeAlias = Literal["ANNUALLY", "FOREVER", "MONTHLY", "TRANSACTION"] diff --git a/src/lithic/types/statement_totals.py b/src/lithic/types/statement_totals.py new file mode 100644 index 00000000..0f4ac3e1 --- /dev/null +++ b/src/lithic/types/statement_totals.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["StatementTotals"] + + +class StatementTotals(BaseModel): + balance_transfers: int + """Opening balance transferred from previous account in cents""" + + cash_advances: int + """ATM and cashback transactions in cents""" + + credits: int + """ + Volume of credit management operation transactions less any balance transfers in + cents + """ + + debits: int + """Volume of debit management operation transactions less any interest in cents""" + + fees: int + """Volume of debit management operation transactions less any interest in cents""" + + interest: int + """Interest accrued in cents""" + + payments: int + """Any funds transfers which affective the balance in cents""" + + purchases: int + """Net card transaction volume less any cash advances in cents""" + + credit_details: Optional[object] = None + """Breakdown of credits""" + + debit_details: Optional[object] = None + """Breakdown of debits""" + + payment_details: Optional[object] = None + """Breakdown of payments""" diff --git a/src/lithic/types/statements_created_webhook_event.py b/src/lithic/types/statements_created_webhook_event.py new file mode 100644 index 00000000..35f22f42 --- /dev/null +++ b/src/lithic/types/statements_created_webhook_event.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .financial_accounts.statement import Statement + +__all__ = ["StatementsCreatedWebhookEvent"] + + +class StatementsCreatedWebhookEvent(Statement): + event_type: Literal["statements.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/three_ds/__init__.py b/src/lithic/types/three_ds/__init__.py index d73b4f7c..b80df9b3 100644 --- a/src/lithic/types/three_ds/__init__.py +++ b/src/lithic/types/three_ds/__init__.py @@ -1,8 +1,14 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations +from .challenge_result import ChallengeResult as ChallengeResult from .authentication_simulate_params import AuthenticationSimulateParams as AuthenticationSimulateParams -from .authentication_retrieve_response import AuthenticationRetrieveResponse as AuthenticationRetrieveResponse from .authentication_simulate_response import AuthenticationSimulateResponse as AuthenticationSimulateResponse from .decisioning_retrieve_secret_response import DecisioningRetrieveSecretResponse as DecisioningRetrieveSecretResponse +from .decisioning_challenge_response_params import ( + DecisioningChallengeResponseParams as DecisioningChallengeResponseParams, +) +from .authentication_simulate_otp_entry_params import ( + AuthenticationSimulateOtpEntryParams as AuthenticationSimulateOtpEntryParams, +) diff --git a/src/lithic/types/three_ds/authentication_simulate_otp_entry_params.py b/src/lithic/types/three_ds/authentication_simulate_otp_entry_params.py new file mode 100644 index 00000000..33eefed7 --- /dev/null +++ b/src/lithic/types/three_ds/authentication_simulate_otp_entry_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AuthenticationSimulateOtpEntryParams"] + + +class AuthenticationSimulateOtpEntryParams(TypedDict, total=False): + token: Required[str] + """ + A unique token returned as part of a /v1/three_ds_authentication/simulate call + that resulted in PENDING_CHALLENGE authentication result. + """ + + otp: Required[str] + """The OTP entered by the cardholder""" diff --git a/src/lithic/types/three_ds/authentication_simulate_params.py b/src/lithic/types/three_ds/authentication_simulate_params.py index 5281b070..2393ecae 100644 --- a/src/lithic/types/three_ds/authentication_simulate_params.py +++ b/src/lithic/types/three_ds/authentication_simulate_params.py @@ -1,22 +1,32 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["AuthenticationSimulateParams", "Merchant", "Transaction"] class AuthenticationSimulateParams(TypedDict, total=False): merchant: Required[Merchant] + """Merchant information for the simulated transaction""" pan: Required[str] """Sixteen digit card number.""" transaction: Required[Transaction] + """Transaction details for the simulation""" + + card_expiry_check: Literal["MATCH", "MISMATCH", "NOT_PRESENT"] + """When set will use the following values as part of the Simulated Authentication. + + When not set defaults to MATCH + """ class Merchant(TypedDict, total=False): + """Merchant information for the simulated transaction""" + id: Required[str] """Unique identifier to identify the payment card acceptor. @@ -38,12 +48,17 @@ class Merchant(TypedDict, total=False): """ name: Required[str] - """Merchant descriptor, corresponds to `descriptor` in authorization.""" + """Merchant descriptor, corresponds to `descriptor` in authorization. + + If CHALLENGE keyword is included, Lithic will trigger a challenge. + """ class Transaction(TypedDict, total=False): + """Transaction details for the simulation""" + amount: Required[int] """Amount (in cents) to authenticate.""" currency: Required[str] - """3-digit alphabetic ISO 4217 currency code.""" + """3-character alphabetic ISO 4217 currency code.""" diff --git a/src/lithic/types/three_ds/authentication_simulate_response.py b/src/lithic/types/three_ds/authentication_simulate_response.py index ac5664ba..2c376db6 100644 --- a/src/lithic/types/three_ds/authentication_simulate_response.py +++ b/src/lithic/types/three_ds/authentication_simulate_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional @@ -9,10 +9,4 @@ class AuthenticationSimulateResponse(BaseModel): token: Optional[str] = None - """ - A unique token to reference this transaction with later calls to void or clear - the authorization. - """ - - debugging_request_id: Optional[str] = None - """Debugging request ID to share with Lithic Support team.""" + """Globally unique identifier for the 3DS authentication.""" diff --git a/src/lithic/types/three_ds/challenge_result.py b/src/lithic/types/three_ds/challenge_result.py new file mode 100644 index 00000000..a9383bc1 --- /dev/null +++ b/src/lithic/types/three_ds/challenge_result.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ChallengeResult"] + +ChallengeResult: TypeAlias = Literal["APPROVE", "DECLINE_BY_CUSTOMER"] diff --git a/src/lithic/types/three_ds/decisioning_challenge_response_params.py b/src/lithic/types/three_ds/decisioning_challenge_response_params.py new file mode 100644 index 00000000..f6ba5df5 --- /dev/null +++ b/src/lithic/types/three_ds/decisioning_challenge_response_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .challenge_result import ChallengeResult + +__all__ = ["DecisioningChallengeResponseParams"] + + +class DecisioningChallengeResponseParams(TypedDict, total=False): + token: Required[str] + """ + Globally unique identifier for 3DS Authentication that resulted in + PENDING_CHALLENGE authentication result. + """ + + challenge_response: Required[ChallengeResult] + """Whether the Cardholder has approved or declined the issued Challenge""" diff --git a/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py b/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py index a14c545f..28f2569c 100644 --- a/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py +++ b/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/three_ds/authentication_retrieve_response.py b/src/lithic/types/three_ds_authentication.py similarity index 51% rename from src/lithic/types/three_ds/authentication_retrieve_response.py rename to src/lithic/types/three_ds_authentication.py index fcaf0626..3aebc6f8 100644 --- a/src/lithic/types/three_ds/authentication_retrieve_response.py +++ b/src/lithic/types/three_ds_authentication.py @@ -1,13 +1,13 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime from typing_extensions import Literal -from ..._models import BaseModel +from .._models import BaseModel __all__ = [ - "AuthenticationRetrieveResponse", + "ThreeDSAuthentication", "Cardholder", "CardholderBillingAddress", "CardholderShippingAddress", @@ -16,11 +16,14 @@ "AdditionalData", "App", "Browser", + "ChallengeMetadata", "Transaction", ] class CardholderBillingAddress(BaseModel): + """Object containing data on the billing address provided during the transaction.""" + address1: Optional[str] = None """First line of the street address provided by the cardholder.""" @@ -44,6 +47,8 @@ class CardholderBillingAddress(BaseModel): class CardholderShippingAddress(BaseModel): + """Object containing data on the shipping address provided during the transaction.""" + address1: Optional[str] = None """First line of the street address provided by the cardholder.""" @@ -67,12 +72,24 @@ class CardholderShippingAddress(BaseModel): class Cardholder(BaseModel): + """Object containing data about the cardholder provided during the transaction.""" + address_match: Optional[bool] = None """ Indicates whether the shipping address and billing address provided by the cardholder are the same. This value - and assessment of whether the addresses match - is provided directly in the 3DS request and is not determined by Lithic. - Maps to EMV 3DS field addrMatch. + Maps to EMV 3DS field `addrMatch`. + """ + + address_on_file_match: Optional[ + Literal["MATCH", "MATCH_ADDRESS_ONLY", "MATCH_ZIP_ONLY", "MISMATCH", "NOT_PRESENT"] + ] = None + """ + Lithic's evaluation result comparing the transaction's address data with the + cardholder KYC data if it exists. In the event Lithic does not have any + Cardholder KYC data, or the transaction does not contain any address data, + NOT_PRESENT will be returned """ billing_address: Optional[CardholderBillingAddress] = None @@ -81,28 +98,28 @@ class Cardholder(BaseModel): email: Optional[str] = None """ Email address that is either provided by the cardholder or is on file with the - merchant in a 3RI request. Maps to EMV 3DS field email. + merchant in a 3RI request. Maps to EMV 3DS field `email`. """ name: Optional[str] = None - """Name of the cardholder. Maps to EMV 3DS field cardholderName.""" + """Name of the cardholder. Maps to EMV 3DS field `cardholderName`.""" phone_number_home: Optional[str] = None - """Home phone number provided by the cardholder. + """Home phone number in E.164 format provided by the cardholder. - Maps to EMV 3DS fields homePhone.cc and homePhone.subscriber. + Maps to EMV 3DS fields `homePhone.cc` and `homePhone.subscriber`. """ phone_number_mobile: Optional[str] = None - """Mobile/cell phone number provided by the cardholder. + """Mobile/cell phone number in E.164 format provided by the cardholder. - Maps to EMV 3DS fields mobilePhone.cc and mobilePhone.subscriber. + Maps to EMV 3DS fields `mobilePhone.cc` and `mobilePhone.subscriber`. """ phone_number_work: Optional[str] = None - """Work phone number provided by the cardholder. + """Work phone number in E.164 format provided by the cardholder. - Maps to EMV 3DS fields workPhone.cc and workPhone.subscriber. + Maps to EMV 3DS fields `workPhone.cc` and `workPhone.subscriber`. """ shipping_address: Optional[CardholderShippingAddress] = None @@ -110,10 +127,14 @@ class Cardholder(BaseModel): class MerchantRiskIndicator(BaseModel): + """ + Object containing additional data indicating additional risk factors related to the e-commerce transaction. + """ + delivery_email_address: Optional[str] = None """ In transactions with electronic delivery, email address to which merchandise is - delivered. Maps to EMV 3DS field deliveryEmailAddress. + delivered. Maps to EMV 3DS field `deliveryEmailAddress`. """ delivery_time_frame: Optional[ @@ -121,44 +142,46 @@ class MerchantRiskIndicator(BaseModel): ] = None """The delivery time frame for the merchandise. - Maps to EMV 3DS field deliveryTimeframe. + Maps to EMV 3DS field `deliveryTimeframe`. """ - gift_card_amount: Optional[float] = None + gift_card_amount: Optional[int] = None """ In prepaid or gift card purchase transactions, purchase amount total in major units (e.g., a purchase of USD $205.10 would be 205). Maps to EMV 3DS field - giftCardAmount. + `giftCardAmount`. """ - gift_card_count: Optional[float] = None + gift_card_count: Optional[int] = None """ In prepaid or gift card purchase transactions, count of individual prepaid or - gift cards/codes purchased. Maps to EMV 3DS field giftCardCount. + gift cards/codes purchased. Maps to EMV 3DS field `giftCardCount`. """ gift_card_currency: Optional[str] = None """In prepaid or gift card purchase transactions, currency code of the gift card. - Maps to EMV 3DS field giftCardCurr. + Maps to EMV 3DS field `giftCardCurr`. Permitted values: ISO 4217 three-character + currency code (e.g., USD). """ order_availability: Optional[Literal["FUTURE_AVAILABILITY", "MERCHANDISE_AVAILABLE"]] = None """ Indicates whether the purchase is for merchandise that is available now or at a - future date. Maps to EMV 3DS field preOrderPurchaseInd. + future date. Maps to EMV 3DS field `preOrderPurchaseInd`. """ pre_order_available_date: Optional[datetime] = None """ In pre-order purchase transactions, the expected date that the merchandise will - be available. Maps to EMV 3DS field preOrderDate. + be available. Maps to EMV 3DS field `preOrderDate`. Permitted values: Date + string in the ISO 8601 format yyyy-MM-dd'T'hh:mm:ssZ """ reorder_items: Optional[Literal["FIRST_TIME_ORDERED", "REORDERED"]] = None """Indicates whether the cardholder is reordering previously purchased merchandise. - Maps to EMV 3DS field reorderItemsInd. + Maps to EMV 3DS field `reorderItemsInd`. """ shipping_method: Optional[ @@ -178,124 +201,241 @@ class MerchantRiskIndicator(BaseModel): If purchase includes one or more item, this indicator is used for the physical goods; if the purchase only includes digital goods, this indicator is used to - describe the most expensive item purchased. Maps to EMV 3DS field shipIndicator. + describe the most expensive item purchased. Maps to EMV 3DS field + `shipIndicator`. """ class Merchant(BaseModel): - id: str + """ + Object containing data about the merchant involved in the e-commerce transaction. + """ + + risk_indicator: MerchantRiskIndicator + """ + Object containing additional data indicating additional risk factors related to + the e-commerce transaction. + """ + + id: Optional[str] = None """Merchant identifier as assigned by the acquirer. - Maps to EMV 3DS field acquirerMerchantId. + Maps to EMV 3DS field `acquirerMerchantId`. May not be present for non-payment + authentications. """ - country: str + country: Optional[str] = None """Country code of the merchant requesting 3DS authentication. - Maps to EMV 3DS field merchantCountryCode. + Maps to EMV 3DS field `merchantCountryCode`. Permitted values: ISO 3166-1 + alpha-3 country code (e.g., USA). May not be present for non-payment + authentications. """ - mcc: str + mcc: Optional[str] = None """ Merchant category code assigned to the merchant that describes its business - activity type. Maps to EMV 3DS field mcc. + activity type. Maps to EMV 3DS field `mcc`. May not be present for non-payment + authentications. """ - name: str - """Name of the merchant. Maps to EMV 3DS field merchantName.""" + name: Optional[str] = None + """Name of the merchant. - risk_indicator: MerchantRiskIndicator - """ - Object containing additional data indicating additional risk factors related to - the e-commerce transaction. + Maps to EMV 3DS field `merchantName`. May not be present for non-payment + authentications. """ class AdditionalData(BaseModel): + """ + Object containing additional data about the 3DS request that is beyond the EMV 3DS standard spec (e.g., specific fields that only certain card networks send but are not required across all 3DS requests). + """ + network_decision: Optional[Literal["LOW_RISK", "NOT_LOW_RISK"]] = None """ Mastercard only: Indicates whether the network would have considered the authentication request to be low risk or not. """ - network_risk_score: Optional[float] = None + network_risk_score: Optional[int] = None """ Mastercard only: Assessment by the network of the authentication risk level, - with a higher value indicating a higher amount of risk. + with a higher value indicating a higher amount of risk. Permitted values: + Integer between 0-950, in increments of 50. """ class App(BaseModel): - device_info: Optional[str] = None + """Object containing data about the app used in the e-commerce transaction. + + Present if the channel is 'APP_BASED'. """ - Device information gathered from the cardholder's device - JSON name/value pairs - that is Base64url encoded. Maps to EMV 3DS field deviceInfo. + + device: Optional[str] = None + """Device model: e.g. "Apple iPhone 16".""" + + device_info: Optional[str] = None + """Raw device information - base64-encoded JSON object. + + Maps to EMV 3DS field `deviceInfo`. """ ip: Optional[str] = None - """External IP address used by the app generating the 3DS authentication request. + """IP address of the device.""" - Maps to EMV 3DS field appIp. - """ + latitude: Optional[float] = None + """Latitude coordinate of current device location.""" + + locale: Optional[str] = None + """Device locale: e.g. "en-US".""" + + longitude: Optional[float] = None + """Longitude coordinate of current device location.""" + + os: Optional[str] = None + """Operating System: e.g. "Android 12", "iOS 17.1".""" + + platform: Optional[str] = None + """Device platform: Android, iOS, Windows, etc.""" + + screen_height: Optional[int] = None + """Screen height in pixels.""" + + screen_width: Optional[int] = None + """Screen width in pixels.""" + + time_zone: Optional[str] = None + """Time zone offset in minutes between UTC and device local time.""" class Browser(BaseModel): + """Object containing data about the browser used in the e-commerce transaction. + + Present if the channel is 'BROWSER'. + """ + + accept_header: Optional[str] = None + """ + Content of the HTTP accept headers as sent from the cardholder's browser to the + 3DS requestor (e.g., merchant or digital wallet). + """ + ip: Optional[str] = None """ IP address of the browser as returned by the HTTP headers to the 3DS requestor - (e.g., merchant or digital wallet). Maps to EMV 3DS field browserIP. + (e.g., merchant or digital wallet). Maps to EMV 3DS field `browserIP`. """ java_enabled: Optional[bool] = None """Indicates whether the cardholder's browser has the ability to execute Java. - Maps to EMV 3DS field browserJavaEnabled. + Maps to EMV 3DS field `browserJavaEnabled`. """ javascript_enabled: Optional[bool] = None """Indicates whether the cardholder's browser has the ability to execute JavaScript. - Maps to EMV 3DS field browserJavascriptEnabled. + Maps to EMV 3DS field `browserJavascriptEnabled`. """ language: Optional[str] = None """Language of the cardholder's browser as defined in IETF BCP47. - Maps to EMV 3DS field browserLanguage. + Maps to EMV 3DS field `browserLanguage`. """ time_zone: Optional[str] = None - """ - Time zone of the cardholder's browser offset in minutes between UTC and the - cardholder browser's local time. The offset is positive if the local time is - behind UTC and negative if it is ahead. Maps to EMV 3DS field browserTz. + """Time zone offset in minutes between UTC and browser local time. + + Maps to EMV 3DS field `browserTz`. """ user_agent: Optional[str] = None - """Content of the HTTP user-agent header. Maps to EMV 3DS field browserUserAgent.""" + """Content of the HTTP user-agent header. + + Maps to EMV 3DS field `browserUserAgent`. + """ + + +class ChallengeMetadata(BaseModel): + """Metadata about the challenge method and delivery. + + Only present when a challenge is triggered. + """ + + method_type: Literal["SMS_OTP", "OUT_OF_BAND"] + """The type of challenge method used for authentication.""" + + status: Literal[ + "SUCCESS", + "PENDING", + "SMS_DELIVERY_FAILED", + "CARDHOLDER_TIMEOUT", + "CANCELED_VIA_CHALLENGE_UI", + "CANCELED_OOB", + "ATTEMPTS_EXCEEDED", + "ABORTED", + "ERROR", + ] + """Indicates the status of the challenge + + - SUCCESS - Cardholder completed the challenge successfully + - PENDING - Challenge was issued to the cardholder and was not completed yet + - SMS_DELIVERY_FAILED - Lithic confirmed undeliverability of the SMS to the + provided phone number. Relevant only for SMS_OTP method + - CARDHOLDER_TIMEOUT - Cardholder failed to complete the challenge within the + given challenge TTL + - CANCELED_VIA_CHALLENGE_UI - Cardholder canceled the challenge by selecting + "cancel" on the challenge UI + - CANCELED_OOB - Cardholder canceled the challenge out of band + - ATTEMPTS_EXCEEDED - Cardholder failed the challenge by either entering an + incorrect OTP more than the allowed number of times or requesting a new OTP + more than the allowed number of times + - ABORTED - Merchant aborted authentication after a challenge was requested + - ERROR - The challenge failed for a reason different than those documented + """ + + phone_number: Optional[str] = None + """The phone number used for delivering the OTP. Relevant only for SMS_OTP method.""" class Transaction(BaseModel): + """ + Object containing data about the e-commerce transaction for which the merchant is requesting authentication. + """ + amount: float """Amount of the purchase in minor units of currency with all punctuation removed. - Maps to EMV 3DS field purchaseAmount. + Maps to EMV 3DS field `purchaseAmount`. + """ + + cardholder_amount: Optional[float] = None + """Approximate amount of the purchase in minor units of cardholder currency. + + Derived from `amount` using a daily conversion rate. """ currency: str - """Currency of the purchase. Maps to EMV 3DS field purchaseCurrency.""" + """Currency of the purchase. + + Maps to EMV 3DS field `purchaseCurrency`. Permitted values: ISO 4217 + three-character currency code (e.g., USD). + """ currency_exponent: float """Minor units of currency, as specified in ISO 4217 currency exponent. - Maps to EMV 3DS field purchaseExponent. + Maps to EMV 3DS field `purchaseExponent`. """ date_time: datetime """ Date and time when the authentication was generated by the merchant/acquirer's - 3DS server. Maps to EMV 3DS field purchaseDate. + 3DS server. Maps to EMV 3DS field `purchaseDate`. Permitted values: Date string + in the ISO 8601 format yyyy-MM-dd'T'hh:mm:ssZ. """ type: Optional[ @@ -309,21 +449,26 @@ class Transaction(BaseModel): ] = None """Type of the transaction for which a 3DS authentication request is occurring. - Maps to EMV 3DS field transType. + Maps to EMV 3DS field `transType`. """ -class AuthenticationRetrieveResponse(BaseModel): +class ThreeDSAuthentication(BaseModel): + """Represents a 3DS authentication""" + token: str - """Globally unique identifier for the 3DS authentication.""" + """Globally unique identifier for the 3DS authentication. + + Permitted values: 36-digit version 4 UUID (including hyphens). + """ account_type: Optional[Literal["CREDIT", "DEBIT", "NOT_APPLICABLE"]] = None """Type of account/card that is being used for the transaction. - Maps to EMV 3DS field acctType. + Maps to EMV 3DS field `acctType`. """ - authentication_result: Optional[Literal["DECLINE", "SUCCESS"]] = None + authentication_result: Literal["DECLINE", "SUCCESS", "PENDING_CHALLENGE", "PENDING_DECISION"] """Indicates the outcome of the 3DS authentication process.""" card_expiry_check: Literal["MATCH", "MISMATCH", "NOT_PRESENT"] @@ -335,7 +480,7 @@ class AuthenticationRetrieveResponse(BaseModel): card_token: str """ Globally unique identifier for the card on which the 3DS authentication has - occurred. + occurred. Permitted values: 36-digit version 4 UUID (including hyphens). """ cardholder: Cardholder @@ -344,16 +489,14 @@ class AuthenticationRetrieveResponse(BaseModel): channel: Literal["APP_BASED", "BROWSER", "THREE_DS_REQUESTOR_INITIATED"] """Channel in which the authentication occurs. - Maps to EMV 3DS field deviceChannel. + Maps to EMV 3DS field `deviceChannel`. """ created: datetime - """Date and time when the authentication was created in Lithic's system.""" + """Date and time when the authentication was created in Lithic's system. - decision_made_by: Optional[ - Literal["CUSTOMER_ENDPOINT", "LITHIC_DEFAULT", "LITHIC_RULES", "NETWORK", "UNKNOWN"] - ] = None - """Entity that made the authentication decision.""" + Permitted values: Date string in the ISO 8601 format yyyy-MM-dd'T'hh:mm:ssZ. + """ merchant: Merchant """ @@ -368,6 +511,27 @@ class AuthenticationRetrieveResponse(BaseModel): populated. """ + three_ds_requestor_challenge_indicator: Literal[ + "NO_PREFERENCE", + "NO_CHALLENGE_REQUESTED", + "CHALLENGE_PREFERENCE", + "CHALLENGE_MANDATE", + "NO_CHALLENGE_RISK_ALREADY_ASSESSED", + "DATA_SHARE_ONLY", + "OTHER", + ] + """Indicates whether a challenge is requested for this transaction + + - `NO_PREFERENCE` - No Preference + - `NO_CHALLENGE_REQUESTED` - No Challenge Requested + - `CHALLENGE_PREFERENCE` - Challenge requested (3DS Requestor preference) + - `CHALLENGE_MANDATE` - Challenge requested (Mandate) + - `NO_CHALLENGE_RISK_ALREADY_ASSESSED` - No Challenge requested (Transactional + risk analysis is already performed) + - `DATA_SHARE_ONLY` - No Challenge requested (Data Share Only) + - `OTHER` - Other indicators not captured by above. These are rarely used + """ + additional_data: Optional[AdditionalData] = None """ Object containing additional data about the 3DS request that is beyond the EMV @@ -398,7 +562,7 @@ class AuthenticationRetrieveResponse(BaseModel): """ Type of authentication request - i.e., the type of transaction or interaction is causing the merchant to request an authentication. Maps to EMV 3DS field - threeDSRequestorAuthenticationInd. + `threeDSRequestorAuthenticationInd`. """ browser: Optional[Browser] = None @@ -407,6 +571,28 @@ class AuthenticationRetrieveResponse(BaseModel): Present if the channel is 'BROWSER'. """ + challenge_metadata: Optional[ChallengeMetadata] = None + """Metadata about the challenge method and delivery. + + Only present when a challenge is triggered. + """ + + challenge_orchestrated_by: Optional[Literal["LITHIC", "CUSTOMER", "NO_CHALLENGE"]] = None + """Entity that orchestrates the challenge. + + This won't be set for authentications for which a decision has not yet been made + (e.g. in-flight customer decisioning request). + """ + + decision_made_by: Optional[ + Literal["LITHIC_RULES", "LITHIC_DEFAULT", "CUSTOMER_RULES", "CUSTOMER_ENDPOINT", "NETWORK", "UNKNOWN"] + ] = None + """Entity that made the authentication decision. + + This won't be set for authentications for which a decision has not yet been made + (e.g. in-flight customer decisioning request). + """ + three_ri_request_type: Optional[ Literal[ "ACCOUNT_VERIFICATION", @@ -428,11 +614,11 @@ class AuthenticationRetrieveResponse(BaseModel): ] ] = None """ - Type of 3DS Requestor Initiated (3RI) request i.e., a 3DS authentication that + Type of 3DS Requestor Initiated (3RI) request — i.e., a 3DS authentication that takes place at the initiation of the merchant rather than the cardholder. The most common example of this is where a merchant is authenticating before billing for a recurring transaction such as a pay TV subscription or a utility bill. - Maps to EMV 3DS field threeRIInd. + Maps to EMV 3DS field `threeRIInd`. """ transaction: Optional[Transaction] = None diff --git a/src/lithic/types/three_ds_authentication_challenge_webhook_event.py b/src/lithic/types/three_ds_authentication_challenge_webhook_event.py new file mode 100644 index 00000000..3c04ea95 --- /dev/null +++ b/src/lithic/types/three_ds_authentication_challenge_webhook_event.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .three_ds_authentication import ThreeDSAuthentication + +__all__ = ["ThreeDSAuthenticationChallengeWebhookEvent", "Challenge"] + + +class Challenge(BaseModel): + """Represents a challenge object for 3DS authentication""" + + challenge_method_type: Literal["OUT_OF_BAND"] + """The type of challenge method issued to the cardholder""" + + expiry_time: datetime + """ISO-8601 time at which the challenge expires""" + + start_time: datetime + """ISO-8601 time at which the challenge has started""" + + app_requestor_url: Optional[str] = None + """Fully qualified app URL of the merchant app. + + This should be used to redirect the cardholder back to the merchant app after + completing an app-based challenge. This URL will only be populated if the 3DS + Requestor App is provided to the 3DS SDK. + """ + + +class ThreeDSAuthenticationChallengeWebhookEvent(BaseModel): + authentication_object: ThreeDSAuthentication + """Represents a 3DS authentication""" + + challenge: Challenge + """Represents a challenge object for 3DS authentication""" + + event_type: Literal["three_ds_authentication.challenge"] diff --git a/src/lithic/types/three_ds_authentication_created_webhook_event.py b/src/lithic/types/three_ds_authentication_created_webhook_event.py new file mode 100644 index 00000000..571ff99c --- /dev/null +++ b/src/lithic/types/three_ds_authentication_created_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .three_ds_authentication import ThreeDSAuthentication + +__all__ = ["ThreeDSAuthenticationCreatedWebhookEvent"] + + +class ThreeDSAuthenticationCreatedWebhookEvent(ThreeDSAuthentication): + """Represents a 3DS authentication""" + + event_type: Literal["three_ds_authentication.created"] + """The type of event that occurred.""" diff --git a/src/lithic/types/three_ds_authentication_updated_webhook_event.py b/src/lithic/types/three_ds_authentication_updated_webhook_event.py new file mode 100644 index 00000000..edb8a479 --- /dev/null +++ b/src/lithic/types/three_ds_authentication_updated_webhook_event.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .three_ds_authentication import ThreeDSAuthentication + +__all__ = ["ThreeDSAuthenticationUpdatedWebhookEvent"] + + +class ThreeDSAuthenticationUpdatedWebhookEvent(ThreeDSAuthentication): + """Represents a 3DS authentication""" + + event_type: Literal["three_ds_authentication.updated"] + """The type of event that occurred.""" diff --git a/src/lithic/types/token_info.py b/src/lithic/types/token_info.py new file mode 100644 index 00000000..6a425179 --- /dev/null +++ b/src/lithic/types/token_info.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["TokenInfo"] + + +class TokenInfo(BaseModel): + wallet_type: Literal["APPLE_PAY", "GOOGLE_PAY", "MASTERPASS", "MERCHANT", "OTHER", "SAMSUNG_PAY"] + """The wallet_type field will indicate the source of the token. + + Possible token sources include digital wallets (Apple, Google, or Samsung Pay), + merchant tokenization, and “other” sources like in-flight commerce. Masterpass + is not currently supported and is included for future use. + """ diff --git a/src/lithic/types/tokenization.py b/src/lithic/types/tokenization.py index f2511e31..d306d2c9 100644 --- a/src/lithic/types/tokenization.py +++ b/src/lithic/types/tokenization.py @@ -1,10 +1,13 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import List, Union, Optional from datetime import datetime from typing_extensions import Literal from .._models import BaseModel +from .tokenization_tfa_reason import TokenizationTfaReason +from .tokenization_rule_result import TokenizationRuleResult +from .tokenization_decline_reason import TokenizationDeclineReason __all__ = ["Tokenization", "Event"] @@ -25,6 +28,7 @@ class Event(BaseModel): "TOKEN_ACTIVATED", "TOKEN_CREATED", "TOKEN_DEACTIVATED", + "TOKEN_DELETED_FROM_CONSUMER_APP", "TOKEN_INACTIVE", "TOKEN_STATE_UNKNOWN", "TOKEN_SUSPENDED", @@ -33,6 +37,15 @@ class Event(BaseModel): ] = None """Enum representing the result of the tokenization event""" + rule_results: Optional[List[TokenizationRuleResult]] = None + """Results from rules that were evaluated for this tokenization""" + + tokenization_decline_reasons: Optional[List[TokenizationDeclineReason]] = None + """List of reasons why the tokenization was declined""" + + tokenization_tfa_reasons: Optional[List[TokenizationTfaReason]] = None + """List of reasons why two-factor authentication was required""" + type: Optional[ Literal[ "TOKENIZATION_2FA", @@ -58,27 +71,57 @@ class Tokenization(BaseModel): created_at: datetime """Date and time when the tokenization first occurred. UTC time zone.""" + dpan: Optional[str] = None + """The dynamic pan assigned to the token by the network.""" + status: Literal["ACTIVE", "DEACTIVATED", "INACTIVE", "PAUSED", "PENDING_2FA", "PENDING_ACTIVATION", "UNKNOWN"] """The status of the tokenization request""" - token_requestor_name: Literal[ - "AMAZON_ONE", - "ANDROID_PAY", - "APPLE_PAY", - "FITBIT_PAY", - "GARMIN_PAY", - "MICROSOFT_PAY", - "SAMSUNG_PAY", - "UNKNOWN", - "VISA_CHECKOUT", + token_requestor_name: Union[ + Literal[ + "AMAZON_ONE", + "ANDROID_PAY", + "APPLE_PAY", + "FACEBOOK", + "FITBIT_PAY", + "GARMIN_PAY", + "GOOGLE_PAY", + "MICROSOFT_PAY", + "NETFLIX", + "SAMSUNG_PAY", + "UNKNOWN", + "VISA_CHECKOUT", + ], + str, ] - """The entity that is requested the tokenization. Represents a Digital Wallet.""" + """The entity that requested the tokenization. + + For digital wallets, this will be one of the defined wallet types. For merchant + tokenizations, this will be a free-form merchant name string. + """ token_unique_reference: str """The network's unique reference for the tokenization.""" + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT"] + """The channel through which the tokenization was made.""" + updated_at: datetime """Latest date and time when the tokenization was updated. UTC time zone.""" + device_id: Optional[str] = None + """The device identifier associated with the tokenization.""" + + digital_card_art_token: Optional[str] = None + """ + Specifies the digital card art displayed in the user's digital wallet after + tokenization. This will be null if the tokenization was created without an + associated digital card art. See + [Flexible Card Art Guide](https://docs.lithic.com/docs/about-digital-wallets#flexible-card-art). + """ + events: Optional[List[Event]] = None """A list of events related to the tokenization.""" + + payment_account_reference_id: Optional[str] = None + """The network's unique reference for the card that is tokenized.""" diff --git a/src/lithic/types/tokenization_approval_request_webhook_event.py b/src/lithic/types/tokenization_approval_request_webhook_event.py new file mode 100644 index 00000000..9e6e6f11 --- /dev/null +++ b/src/lithic/types/tokenization_approval_request_webhook_event.py @@ -0,0 +1,83 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .device import Device +from .._models import BaseModel +from .tokenization_tfa_reason import TokenizationTfaReason +from .wallet_decisioning_info import WalletDecisioningInfo +from .tokenization_rule_result import TokenizationRuleResult +from .tokenization_decline_reason import TokenizationDeclineReason +from .digital_wallet_token_metadata import DigitalWalletTokenMetadata + +__all__ = ["TokenizationApprovalRequestWebhookEvent", "CustomerTokenizationDecision"] + + +class CustomerTokenizationDecision(BaseModel): + """Contains the metadata for the customer tokenization decision.""" + + outcome: Literal[ + "APPROVED", "DECLINED", "ERROR", "INVALID_RESPONSE", "REQUIRE_ADDITIONAL_AUTHENTICATION", "TIMEOUT" + ] + """The outcome of the customer's decision""" + + responder_url: str + """The customer's subscribed URL""" + + latency: Optional[str] = None + """Time in ms it took for the customer's URL to respond""" + + response_code: Optional[str] = None + """The response code that the customer provided""" + + +class TokenizationApprovalRequestWebhookEvent(BaseModel): + account_token: str + """Unique identifier for the user tokenizing a card""" + + card_token: str + """Unique identifier for the card being tokenized""" + + created: datetime + """Indicate when the request was received from Mastercard or Visa""" + + customer_tokenization_decision: Optional[CustomerTokenizationDecision] = None + """Contains the metadata for the customer tokenization decision.""" + + event_type: Literal["tokenization.approval_request"] + """The name of this event""" + + issuer_decision: Literal["APPROVED", "DENIED", "VERIFICATION_REQUIRED"] + """Whether Lithic decisioned on the token, and if so, what the decision was. + + APPROVED/VERIFICATION_REQUIRED/DENIED. + """ + + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT"] + """The channel through which the tokenization was made.""" + + tokenization_token: str + """Unique identifier for the digital wallet token attempt""" + + wallet_decisioning_info: WalletDecisioningInfo + + device: Optional[Device] = None + + digital_wallet_token_metadata: Optional[DigitalWalletTokenMetadata] = None + """Contains the metadata for the digital wallet being tokenized.""" + + rule_results: Optional[List[TokenizationRuleResult]] = None + """Results from rules that were evaluated for this tokenization""" + + tokenization_decline_reasons: Optional[List[TokenizationDeclineReason]] = None + """List of reasons why the tokenization was declined""" + + tokenization_source: Optional[ + Literal["ACCOUNT_ON_FILE", "CONTACTLESS_TAP", "MANUAL_PROVISION", "PUSH_PROVISION", "TOKEN", "UNKNOWN"] + ] = None + """The source of the tokenization.""" + + tokenization_tfa_reasons: Optional[List[TokenizationTfaReason]] = None + """List of reasons why two-factor authentication was required""" diff --git a/src/lithic/types/tokenization_decisioning_request_webhook_event.py b/src/lithic/types/tokenization_decisioning_request_webhook_event.py new file mode 100644 index 00000000..957d6266 --- /dev/null +++ b/src/lithic/types/tokenization_decisioning_request_webhook_event.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from .device import Device +from .._models import BaseModel +from .wallet_decisioning_info import WalletDecisioningInfo +from .digital_wallet_token_metadata import DigitalWalletTokenMetadata + +__all__ = ["TokenizationDecisioningRequestWebhookEvent"] + + +class TokenizationDecisioningRequestWebhookEvent(BaseModel): + """ + A webhook for tokenization decisioning sent to the customer's responder endpoint + """ + + account_token: str + """Unique identifier for the user tokenizing a card""" + + card_token: str + """Unique identifier for the card being tokenized""" + + created: datetime + """Indicate when the request was received from Mastercard or Visa""" + + event_type: Literal["digital_wallet.tokenization_approval_request"] + """The name of this event""" + + issuer_decision: Literal["APPROVED", "DENIED", "VERIFICATION_REQUIRED"] + """Whether Lithic decisioned on the token, and if so, what the decision was. + + APPROVED/VERIFICATION_REQUIRED/DENIED. + """ + + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT"] + """The channel through which the tokenization was made.""" + + tokenization_token: str + """Unique identifier for the digital wallet token attempt""" + + wallet_decisioning_info: WalletDecisioningInfo + + device: Optional[Device] = None + + digital_wallet_token_metadata: Optional[DigitalWalletTokenMetadata] = None + """Contains the metadata for the digital wallet being tokenized.""" + + tokenization_source: Optional[ + Literal["ACCOUNT_ON_FILE", "CONTACTLESS_TAP", "MANUAL_PROVISION", "PUSH_PROVISION", "TOKEN", "UNKNOWN"] + ] = None + """The source of the tokenization.""" diff --git a/src/lithic/types/tokenization_decisioning_rotate_secret_response.py b/src/lithic/types/tokenization_decisioning_rotate_secret_response.py index 21182641..e1428626 100644 --- a/src/lithic/types/tokenization_decisioning_rotate_secret_response.py +++ b/src/lithic/types/tokenization_decisioning_rotate_secret_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/tokenization_decline_reason.py b/src/lithic/types/tokenization_decline_reason.py new file mode 100644 index 00000000..29ed2118 --- /dev/null +++ b/src/lithic/types/tokenization_decline_reason.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["TokenizationDeclineReason"] + +TokenizationDeclineReason: TypeAlias = Literal[ + "ACCOUNT_SCORE_1", + "DEVICE_SCORE_1", + "ALL_WALLET_DECLINE_REASONS_PRESENT", + "WALLET_RECOMMENDED_DECISION_RED", + "CVC_MISMATCH", + "CARD_EXPIRY_MONTH_MISMATCH", + "CARD_EXPIRY_YEAR_MISMATCH", + "CARD_INVALID_STATE", + "CUSTOMER_RED_PATH", + "INVALID_CUSTOMER_RESPONSE", + "NETWORK_FAILURE", + "GENERIC_DECLINE", + "DIGITAL_CARD_ART_REQUIRED", +] diff --git a/src/lithic/types/tokenization_list_params.py b/src/lithic/types/tokenization_list_params.py index 41901c2b..082ae2c1 100644 --- a/src/lithic/types/tokenization_list_params.py +++ b/src/lithic/types/tokenization_list_params.py @@ -1,10 +1,10 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations from typing import Union from datetime import date -from typing_extensions import Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._utils import PropertyInfo @@ -39,3 +39,9 @@ class TokenizationListParams(TypedDict, total=False): Used to retrieve the next page of results after this item. """ + + tokenization_channel: Literal["DIGITAL_WALLET", "MERCHANT", "ALL"] + """Filter for tokenizations by tokenization channel. + + If this is not specified, only DIGITAL_WALLET tokenizations will be returned. + """ diff --git a/src/lithic/types/tokenization_resend_activation_code_params.py b/src/lithic/types/tokenization_resend_activation_code_params.py new file mode 100644 index 00000000..a128d015 --- /dev/null +++ b/src/lithic/types/tokenization_resend_activation_code_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["TokenizationResendActivationCodeParams"] + + +class TokenizationResendActivationCodeParams(TypedDict, total=False): + activation_method_type: Literal["EMAIL_TO_CARDHOLDER_ADDRESS", "TEXT_TO_CARDHOLDER_NUMBER"] + """ + The communication method that the user has selected to use to receive the + authentication code. Supported Values: Sms = "TEXT_TO_CARDHOLDER_NUMBER". Email + = "EMAIL_TO_CARDHOLDER_ADDRESS" + """ diff --git a/src/lithic/types/tokenization_result_webhook_event.py b/src/lithic/types/tokenization_result_webhook_event.py new file mode 100644 index 00000000..7be8bf57 --- /dev/null +++ b/src/lithic/types/tokenization_result_webhook_event.py @@ -0,0 +1,71 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .tokenization_tfa_reason import TokenizationTfaReason +from .tokenization_rule_result import TokenizationRuleResult + +__all__ = ["TokenizationResultWebhookEvent", "TokenizationResultDetails"] + + +class TokenizationResultDetails(BaseModel): + """The result of the tokenization request.""" + + issuer_decision: str + """Lithic's tokenization decision.""" + + tokenization_decline_reasons: List[ + Literal[ + "ACCOUNT_SCORE_1", + "ALL_WALLET_DECLINE_REASONS_PRESENT", + "CARD_EXPIRY_MONTH_MISMATCH", + "CARD_EXPIRY_YEAR_MISMATCH", + "CARD_INVALID_STATE", + "CUSTOMER_RED_PATH", + "CVC_MISMATCH", + "DEVICE_SCORE_1", + "GENERIC_DECLINE", + "INVALID_CUSTOMER_RESPONSE", + "NETWORK_FAILURE", + "WALLET_RECOMMENDED_DECISION_RED", + ] + ] + """List of reasons why the tokenization was declined""" + + customer_decision: Optional[str] = None + """The customer's tokenization decision if applicable.""" + + rule_results: Optional[List[TokenizationRuleResult]] = None + """Results from rules that were evaluated for this tokenization""" + + token_activated_date_time: Optional[datetime] = None + """An RFC 3339 timestamp indicating when the tokenization succeeded.""" + + tokenization_tfa_reasons: Optional[List[TokenizationTfaReason]] = None + """List of reasons why two-factor authentication was required""" + + wallet_decision: Optional[str] = None + """The wallet's recommended decision.""" + + +class TokenizationResultWebhookEvent(BaseModel): + account_token: str + """Account token""" + + card_token: str + """Card token""" + + created: datetime + """Created date""" + + event_type: Literal["tokenization.result"] + """The type of event that occurred.""" + + tokenization_result_details: TokenizationResultDetails + """The result of the tokenization request.""" + + tokenization_token: str + """Tokenization token""" diff --git a/src/lithic/types/tokenization_retrieve_response.py b/src/lithic/types/tokenization_retrieve_response.py deleted file mode 100644 index e0bc3a55..00000000 --- a/src/lithic/types/tokenization_retrieve_response.py +++ /dev/null @@ -1,12 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import Optional - -from .._models import BaseModel -from .tokenization import Tokenization - -__all__ = ["TokenizationRetrieveResponse"] - - -class TokenizationRetrieveResponse(BaseModel): - data: Optional[Tokenization] = None diff --git a/src/lithic/types/tokenization_rule_result.py b/src/lithic/types/tokenization_rule_result.py new file mode 100644 index 00000000..baef6611 --- /dev/null +++ b/src/lithic/types/tokenization_rule_result.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["TokenizationRuleResult"] + + +class TokenizationRuleResult(BaseModel): + auth_rule_token: Optional[str] = None + """The Auth Rule Token associated with the rule. + + If this is set to null, then the result was not associated with a + customer-configured rule. This may happen in cases where a tokenization is + declined or requires TFA due to a Lithic-configured security or compliance rule, + for example. + """ + + explanation: Optional[str] = None + """A human-readable explanation outlining the motivation for the rule's result""" + + name: Optional[str] = None + """The name for the rule, if any was configured""" + + result: Literal["APPROVED", "DECLINED", "REQUIRE_TFA", "ERROR"] + """The result associated with this rule""" diff --git a/src/lithic/types/tokenization_secret.py b/src/lithic/types/tokenization_secret.py index d91507b2..d699d84b 100644 --- a/src/lithic/types/tokenization_secret.py +++ b/src/lithic/types/tokenization_secret.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/tokenization_simulate_params.py b/src/lithic/types/tokenization_simulate_params.py index f5b20604..1b04cf6e 100644 --- a/src/lithic/types/tokenization_simulate_params.py +++ b/src/lithic/types/tokenization_simulate_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -17,7 +17,7 @@ class TokenizationSimulateParams(TypedDict, total=False): pan: Required[str] """The sixteen digit card number.""" - tokenization_source: Required[Literal["APPLE_PAY", "GOOGLE", "SAMSUNG_PAY"]] + tokenization_source: Required[Literal["APPLE_PAY", "GOOGLE", "SAMSUNG_PAY", "MERCHANT"]] """The source of the tokenization request.""" account_score: int @@ -32,5 +32,11 @@ class TokenizationSimulateParams(TypedDict, total=False): reputable an end user's device is. """ + entity: str + """ + Optional field to specify the token requestor name for a merchant token + simulation. Ignored when tokenization_source is not MERCHANT. + """ + wallet_recommended_decision: Literal["APPROVED", "DECLINED", "REQUIRE_ADDITIONAL_AUTHENTICATION"] """The decision that the Digital Wallet's recommend""" diff --git a/src/lithic/types/tokenization_simulate_response.py b/src/lithic/types/tokenization_simulate_response.py deleted file mode 100644 index e5fdd689..00000000 --- a/src/lithic/types/tokenization_simulate_response.py +++ /dev/null @@ -1,12 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from typing import List, Optional - -from .._models import BaseModel -from .tokenization import Tokenization - -__all__ = ["TokenizationSimulateResponse"] - - -class TokenizationSimulateResponse(BaseModel): - data: Optional[List[Tokenization]] = None diff --git a/src/lithic/types/tokenization_tfa_reason.py b/src/lithic/types/tokenization_tfa_reason.py new file mode 100644 index 00000000..deba1157 --- /dev/null +++ b/src/lithic/types/tokenization_tfa_reason.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["TokenizationTfaReason"] + +TokenizationTfaReason: TypeAlias = Literal[ + "WALLET_RECOMMENDED_TFA", + "SUSPICIOUS_ACTIVITY", + "DEVICE_RECENTLY_LOST", + "TOO_MANY_RECENT_ATTEMPTS", + "TOO_MANY_RECENT_TOKENS", + "TOO_MANY_DIFFERENT_CARDHOLDERS", + "OUTSIDE_HOME_TERRITORY", + "HAS_SUSPENDED_TOKENS", + "HIGH_RISK", + "ACCOUNT_SCORE_LOW", + "DEVICE_SCORE_LOW", + "CARD_STATE_TFA", + "HARDCODED_TFA", + "CUSTOMER_RULE_TFA", + "DEVICE_HOST_CARD_EMULATION", +] diff --git a/src/lithic/types/tokenization_two_factor_authentication_code_sent_webhook_event.py b/src/lithic/types/tokenization_two_factor_authentication_code_sent_webhook_event.py new file mode 100644 index 00000000..a2109317 --- /dev/null +++ b/src/lithic/types/tokenization_two_factor_authentication_code_sent_webhook_event.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["TokenizationTwoFactorAuthenticationCodeSentWebhookEvent", "ActivationMethod"] + + +class ActivationMethod(BaseModel): + type: Literal["EMAIL_TO_CARDHOLDER_ADDRESS", "TEXT_TO_CARDHOLDER_NUMBER"] + """ + The communication method that the user has selected to use to receive the + authentication code. Supported Values: Sms = "TEXT_TO_CARDHOLDER_NUMBER". Email + = "EMAIL_TO_CARDHOLDER_ADDRESS" + """ + + value: str + """ + The location to which the authentication code was sent. The format depends on + the ActivationMethod.Type field. If Type is Email, the Value will be the email + address. If the Type is Sms, the Value will be the phone number. + """ + + +class TokenizationTwoFactorAuthenticationCodeSentWebhookEvent(BaseModel): + account_token: str + """Unique identifier for the user tokenizing a card""" + + activation_method: ActivationMethod + + card_token: str + """Unique identifier for the card being tokenized""" + + created: datetime + """Indicate when the request was received from Mastercard or Visa""" + + event_type: Literal["tokenization.two_factor_authentication_code_sent"] + """The type of event that occurred.""" + + tokenization_token: str + """Unique identifier for the tokenization""" diff --git a/src/lithic/types/tokenization_two_factor_authentication_code_webhook_event.py b/src/lithic/types/tokenization_two_factor_authentication_code_webhook_event.py new file mode 100644 index 00000000..8ef559c5 --- /dev/null +++ b/src/lithic/types/tokenization_two_factor_authentication_code_webhook_event.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["TokenizationTwoFactorAuthenticationCodeWebhookEvent", "ActivationMethod"] + + +class ActivationMethod(BaseModel): + type: Literal["EMAIL_TO_CARDHOLDER_ADDRESS", "TEXT_TO_CARDHOLDER_NUMBER"] + """ + The communication method that the user has selected to use to receive the + authentication code. Supported Values: Sms = "TEXT_TO_CARDHOLDER_NUMBER". Email + = "EMAIL_TO_CARDHOLDER_ADDRESS" + """ + + value: str + """ + The location where the user wants to receive the authentication code. The format + depends on the ActivationMethod.Type field. If Type is Email, the Value will be + the email address. If the Type is Sms, the Value will be the phone number. + """ + + +class TokenizationTwoFactorAuthenticationCodeWebhookEvent(BaseModel): + account_token: str + """Unique identifier for the user tokenizing a card""" + + activation_method: ActivationMethod + + authentication_code: str + """Authentication code to provide to the user tokenizing a card.""" + + card_token: str + """Unique identifier for the card being tokenized""" + + created: datetime + """Indicate when the request was received from Mastercard or Visa""" + + event_type: Literal["tokenization.two_factor_authentication_code"] + """The type of event that occurred.""" + + tokenization_token: str + """Unique identifier for the tokenization""" diff --git a/src/lithic/types/tokenization_update_digital_card_art_params.py b/src/lithic/types/tokenization_update_digital_card_art_params.py new file mode 100644 index 00000000..2d17c61d --- /dev/null +++ b/src/lithic/types/tokenization_update_digital_card_art_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["TokenizationUpdateDigitalCardArtParams"] + + +class TokenizationUpdateDigitalCardArtParams(TypedDict, total=False): + digital_card_art_token: str + """ + Specifies the digital card art to be displayed in the user’s digital wallet for + a tokenization. This artwork must be approved by the network and configured by + Lithic to use. See + [Flexible Card Art Guide](https://docs.lithic.com/docs/about-digital-wallets#flexible-card-art). + """ diff --git a/src/lithic/types/tokenization_updated_webhook_event.py b/src/lithic/types/tokenization_updated_webhook_event.py new file mode 100644 index 00000000..9f3f090d --- /dev/null +++ b/src/lithic/types/tokenization_updated_webhook_event.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .tokenization import Tokenization + +__all__ = ["TokenizationUpdatedWebhookEvent"] + + +class TokenizationUpdatedWebhookEvent(BaseModel): + account_token: str + """Account token""" + + card_token: str + """Card token""" + + created: datetime + """Created date""" + + event_type: Literal["tokenization.updated"] + """The type of event that occurred.""" + + tokenization: Tokenization diff --git a/src/lithic/types/transaction.py b/src/lithic/types/transaction.py index f5b01ed6..6c0657ab 100644 --- a/src/lithic/types/transaction.py +++ b/src/lithic/types/transaction.py @@ -1,210 +1,106 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime from typing_extensions import Literal -from pydantic import Field as FieldInfo - from .._models import BaseModel +from .token_info import TokenInfo +from .shared.currency import Currency +from .shared.merchant import Merchant +from .cardholder_authentication import CardholderAuthentication __all__ = [ "Transaction", + "Amounts", + "AmountsCardholder", + "AmountsHold", + "AmountsMerchant", + "AmountsSettlement", "Avs", - "Event", - "Merchant", "Pos", "PosEntryMode", "PosTerminal", - "TokenInfo", - "CardholderAuthentication", + "Event", + "EventAmounts", + "EventAmountsCardholder", + "EventAmountsMerchant", + "EventAmountsSettlement", + "EventNetworkInfo", + "EventNetworkInfoAcquirer", + "EventNetworkInfoAmex", + "EventNetworkInfoMastercard", + "EventNetworkInfoVisa", + "EventRuleResult", + "EventNetworkSpecificData", + "EventNetworkSpecificDataMastercard", + "EventNetworkSpecificDataMastercardOnBehalfServiceResult", + "EventNetworkSpecificDataVisa", ] -class Avs(BaseModel): - address: Optional[str] = None - """Cardholder address""" +class AmountsCardholder(BaseModel): + amount: int + """ + The estimated settled amount of the transaction in the cardholder billing + currency. + """ - zipcode: Optional[str] = None - """Cardholder ZIP code""" + conversion_rate: str + """ + The exchange rate used to convert the merchant amount to the cardholder billing + amount. + """ + currency: Currency + """3-character alphabetic ISO 4217 currency""" -class Event(BaseModel): - token: str - """Globally unique identifier.""" +class AmountsHold(BaseModel): amount: int - """Amount of the transaction event (in cents), including any acquirer fees.""" + """The pending amount of the transaction in the anticipated settlement currency.""" - created: datetime - """RFC 3339 date and time this event entered the system. UTC time zone.""" + currency: Currency + """3-character alphabetic ISO 4217 currency""" - detailed_results: List[ - Literal[ - "ACCOUNT_DAILY_SPEND_LIMIT_EXCEEDED", - "ACCOUNT_INACTIVE", - "ACCOUNT_LIFETIME_SPEND_LIMIT_EXCEEDED", - "ACCOUNT_MONTHLY_SPEND_LIMIT_EXCEEDED", - "ACCOUNT_UNDER_REVIEW", - "ADDRESS_INCORRECT", - "APPROVED", - "AUTH_RULE_ALLOWED_COUNTRY", - "AUTH_RULE_ALLOWED_MCC", - "AUTH_RULE_BLOCKED_COUNTRY", - "AUTH_RULE_BLOCKED_MCC", - "CARD_CLOSED", - "CARD_CRYPTOGRAM_VALIDATION_FAILURE", - "CARD_EXPIRED", - "CARD_EXPIRY_DATE_INCORRECT", - "CARD_INVALID", - "CARD_PAUSED", - "CARD_PIN_INCORRECT", - "CARD_RESTRICTED", - "CARD_SECURITY_CODE_INCORRECT", - "CARD_SPEND_LIMIT_EXCEEDED", - "CONTACT_CARD_ISSUER", - "CUSTOMER_ASA_TIMEOUT", - "CUSTOM_ASA_RESULT", - "DECLINED", - "DO_NOT_HONOR", - "FORMAT_ERROR", - "INSUFFICIENT_FUNDING_SOURCE_BALANCE", - "INSUFFICIENT_FUNDS", - "LITHIC_SYSTEM_ERROR", - "LITHIC_SYSTEM_RATE_LIMIT", - "MALFORMED_ASA_RESPONSE", - "MERCHANT_INVALID", - "MERCHANT_LOCKED_CARD_ATTEMPTED_ELSEWHERE", - "MERCHANT_NOT_PERMITTED", - "OVER_REVERSAL_ATTEMPTED", - "PROGRAM_CARD_SPEND_LIMIT_EXCEEDED", - "PROGRAM_SUSPENDED", - "PROGRAM_USAGE_RESTRICTION", - "REVERSAL_UNMATCHED", - "SECURITY_VIOLATION", - "SINGLE_USE_CARD_REATTEMPTED", - "TRANSACTION_INVALID", - "TRANSACTION_NOT_PERMITTED_TO_ACQUIRER_OR_TERMINAL", - "TRANSACTION_NOT_PERMITTED_TO_ISSUER_OR_CARDHOLDER", - "TRANSACTION_PREVIOUSLY_COMPLETED", - "UNAUTHORIZED_MERCHANT", - ] - ] - result: Literal[ - "APPROVED", - "BANK_CONNECTION_ERROR", - "BANK_NOT_VERIFIED", - "CARD_CLOSED", - "CARD_PAUSED", - "DECLINED", - "FRAUD_ADVICE", - "INACTIVE_ACCOUNT", - "INCORRECT_PIN", - "INSUFFICIENT_FUNDS", - "INVALID_CARD_DETAILS", - "MERCHANT_BLACKLIST", - "SINGLE_USE_RECHARGED", - "SWITCH_INOPERATIVE_ADVICE", - "UNAUTHORIZED_MERCHANT", - "UNKNOWN_HOST_TIMEOUT", - "USER_TRANSACTION_LIMIT", - ] - """`APPROVED` or decline reason. - - Result types: - - - `ACCOUNT_STATE_TRANSACTION_FAIL` - Contact - [support@lithic.com](mailto:support@lithic.com). - - `APPROVED` - Transaction is approved. - - `BANK_CONNECTION_ERROR` - Please reconnect a funding source. - - `BANK_NOT_VERIFIED` - Please confirm the funding source. - - `CARD_CLOSED` - Card state was closed at the time of authorization. - - `CARD_PAUSED` - Card state was paused at the time of authorization. - - `FRAUD_ADVICE` - Transaction declined due to risk. - - `INACTIVE_ACCOUNT` - Account is inactive. Contact - [support@lithic.com](mailto:support@lithic.com). - - `INCORRECT_PIN` - PIN verification failed. - - `INVALID_CARD_DETAILS` - Incorrect CVV or expiry date. - - `INSUFFICIENT_FUNDS` - Please ensure the funding source is connected and up to - date. - - `MERCHANT_BLACKLIST` - This merchant is disallowed on the platform. - - `SINGLE_USE_RECHARGED` - Single use card attempted multiple times. - - `SWITCH_INOPERATIVE_ADVICE` - Network error, re-attempt the transaction. - - `UNAUTHORIZED_MERCHANT` - Merchant locked card attempted at different - merchant. - - `UNKNOWN_HOST_TIMEOUT` - Network error, re-attempt the transaction. - - `USER_TRANSACTION_LIMIT` - User-set spend limit exceeded. - """ +class AmountsMerchant(BaseModel): + amount: int + """The settled amount of the transaction in the merchant currency.""" - type: Literal[ - "AUTHORIZATION", - "AUTHORIZATION_ADVICE", - "AUTHORIZATION_EXPIRY", - "AUTHORIZATION_REVERSAL", - "BALANCE_INQUIRY", - "CLEARING", - "CORRECTION_CREDIT", - "CORRECTION_DEBIT", - "CREDIT_AUTHORIZATION", - "CREDIT_AUTHORIZATION_ADVICE", - "FINANCIAL_AUTHORIZATION", - "FINANCIAL_CREDIT_AUTHORIZATION", - "RETURN", - "RETURN_REVERSAL", - "VOID", - ] - """Event types: + currency: Currency + """3-character alphabetic ISO 4217 currency""" - - `AUTHORIZATION` - Authorize a transaction. - - `AUTHORIZATION_ADVICE` - Advice on a transaction. - - `AUTHORIZATION_EXPIRY` - Authorization has expired and reversed by Lithic. - - `AUTHORIZATION_REVERSAL` - Authorization was reversed by the merchant. - - `BALANCE_INQUIRY` - A balance inquiry (typically a $0 authorization) has - occurred on a card. - - `CLEARING` - Transaction is settled. - - `CORRECTION_DEBIT` - Manual transaction correction (Debit). - - `CORRECTION_CREDIT` - Manual transaction correction (Credit). - - `CREDIT_AUTHORIZATION` - A refund or credit authorization from a merchant. - - `CREDIT_AUTHORIZATION_ADVICE` - A credit authorization was approved on your - behalf by the network. - - `FINANCIAL_AUTHORIZATION` - A request from a merchant to debit funds without - additional clearing. - - `FINANCIAL_CREDIT_AUTHORIZATION` - A request from a merchant to refund or - credit funds without additional clearing. - - `RETURN` - A refund has been processed on the transaction. - - `RETURN_REVERSAL` - A refund has been reversed (e.g., when a merchant reverses - an incorrect refund). - """ +class AmountsSettlement(BaseModel): + amount: int + """The settled amount of the transaction in the settlement currency.""" -class Merchant(BaseModel): - acceptor_id: Optional[str] = None - """Unique identifier to identify the payment card acceptor.""" + currency: Currency + """3-character alphabetic ISO 4217 currency""" - city: Optional[str] = None - """City of card acceptor.""" - country: Optional[str] = None - """Uppercase country of card acceptor (see ISO 8583 specs).""" +class Amounts(BaseModel): + cardholder: AmountsCardholder - descriptor: Optional[str] = None - """Short description of card acceptor.""" + hold: AmountsHold - mcc: Optional[str] = None - """Merchant category code (MCC). + merchant: AmountsMerchant + + settlement: AmountsSettlement - A four-digit number listed in ISO 18245. An MCC is used to classify a business - by the types of goods or services it provides. - """ - state: Optional[str] = None - """Geographic state of card acceptor (see ISO 8583 specs).""" +class Avs(BaseModel): + address: str + """Cardholder address""" + + zipcode: str + """Cardholder ZIP code""" class PosEntryMode(BaseModel): card: Literal["NOT_PRESENT", "PREAUTHORIZED", "PRESENT", "UNKNOWN"] - """Card status""" + """Card presence indicator""" cardholder: Literal[ "DEFERRED_BILLING", @@ -218,7 +114,7 @@ class PosEntryMode(BaseModel): "TELEPHONE_ORDER", "UNKNOWN", ] - """Cardholder Presence status""" + """Cardholder presence indicator""" pan: Literal[ "AUTO_ENTRY", @@ -240,7 +136,7 @@ class PosEntryMode(BaseModel): """Method of entry for the PAN""" pin_entered: bool - """True if the PIN was entered""" + """Indicates whether the cardholder entered the PIN. True if the PIN was entered.""" class PosTerminal(BaseModel): @@ -248,6 +144,15 @@ class PosTerminal(BaseModel): """True if a clerk is present at the sale.""" card_retention_capable: bool + """True if the terminal is capable of retaining the card.""" + + on_premise: bool + """True if the sale was made at the place of business (vs. mobile).""" + + operator: Literal["ADMINISTRATIVE", "CARDHOLDER", "CARD_ACCEPTOR", "UNKNOWN"] + """The person that is designated to swipe the card""" + + partial_approval_capable: bool """True if the terminal is capable of partial approval. Partial approval is when part of a transaction is approved and another payment @@ -257,12 +162,6 @@ class PosTerminal(BaseModel): payment of $15. """ - on_premise: bool - """True if the sale was made at the place of business (vs. mobile).""" - - operator: Literal["ADMINISTRATIVE", "CARDHOLDER", "CARD_ACCEPTOR", "UNKNOWN"] - """The person that is designed to swipe the card""" - pin_capability: Literal["CAPABLE", "INOPERATIVE", "NOT_CAPABLE", "UNSPECIFIED"] """Status of whether the POS is able to accept PINs""" @@ -288,12 +187,18 @@ class PosTerminal(BaseModel): "TELEVISION", "TELLER", "TRAVELERS_CHECK_MACHINE", - "UNKNOWN", "VENDING", "VOICE", + "UNKNOWN", ] """POS Type""" + acceptor_terminal_id: Optional[str] = None + """ + Uniquely identifies a terminal at the card acceptor location of acquiring + institutions or merchant POS Systems + """ + class Pos(BaseModel): entry_mode: PosEntryMode @@ -301,159 +206,418 @@ class Pos(BaseModel): terminal: PosTerminal -class TokenInfo(BaseModel): - wallet_type: Optional[Literal["APPLE_PAY", "GOOGLE_PAY", "MASTERPASS", "MERCHANT", "OTHER", "SAMSUNG_PAY"]] = None - """Source of the token""" +class EventAmountsCardholder(BaseModel): + amount: int + """Amount of the event in the cardholder billing currency.""" + + conversion_rate: str + """ + Exchange rate used to convert the merchant amount to the cardholder billing + amount. + """ + + currency: Currency + """3-character alphabetic ISO 4217 currency""" + + +class EventAmountsMerchant(BaseModel): + amount: int + """Amount of the event in the merchant currency.""" + + currency: Currency + """3-character alphabetic ISO 4217 currency""" + + +class EventAmountsSettlement(BaseModel): + amount: int + """Amount of the event, if it is financial, in the settlement currency. + + Non-financial events do not contain this amount because they do not move funds. + """ + + conversion_rate: str + """Exchange rate used to convert the merchant amount to the settlement amount.""" + + currency: Currency + """3-character alphabetic ISO 4217 currency""" + + +class EventAmounts(BaseModel): + cardholder: EventAmountsCardholder + + merchant: EventAmountsMerchant + + settlement: Optional[EventAmountsSettlement] = None + + +class EventNetworkInfoAcquirer(BaseModel): + acquirer_reference_number: Optional[str] = None + """ + Identifier assigned by the acquirer, applicable to dual-message transactions + only. The acquirer reference number (ARN) is only populated once a transaction + has been cleared, and it is not available in all transactions (such as automated + fuel dispenser transactions). A single transaction can contain multiple ARNs if + the merchant sends multiple clearings. + """ + + retrieval_reference_number: Optional[str] = None + """Identifier assigned by the acquirer.""" + + +class EventNetworkInfoAmex(BaseModel): + original_transaction_id: Optional[str] = None + """Identifier assigned by American Express. + + Matches the `transaction_id` of a prior related event. May be populated in + incremental authorizations (authorization requests that augment a previously + authorized amount), authorization advices, financial authorizations, and + clearings. + """ + + transaction_id: Optional[str] = None + """ + Identifier assigned by American Express to link original messages to subsequent + messages. Guaranteed by American Express to be unique for each original + authorization and financial authorization. + """ + + +class EventNetworkInfoMastercard(BaseModel): + banknet_reference_number: Optional[str] = None + """Identifier assigned by Mastercard. + + Guaranteed by Mastercard to be unique for any transaction within a specific + financial network on any processing day. + """ + + original_banknet_reference_number: Optional[str] = None + """Identifier assigned by Mastercard. + + Matches the `banknet_reference_number` of a prior related event. May be + populated in authorization reversals, incremental authorizations (authorization + requests that augment a previously authorized amount), automated fuel dispenser + authorization advices and clearings, and financial authorizations. If the + original banknet reference number contains all zeroes, then no actual reference + number could be found by the network or acquirer. If Mastercard converts a + transaction from dual-message to single-message, such as for certain ATM + transactions, it will populate the original banknet reference number in the + resulting financial authorization with the banknet reference number of the + initial authorization, which Lithic does not receive. + """ + + original_switch_serial_number: Optional[str] = None + """Identifier assigned by Mastercard. + + Matches the `switch_serial_number` of a prior related event. May be populated in + returns and return reversals. Applicable to single-message transactions only. + """ + + switch_serial_number: Optional[str] = None + """ + Identifier assigned by Mastercard, applicable to single-message transactions + only. + """ + + +class EventNetworkInfoVisa(BaseModel): + original_transaction_id: Optional[str] = None + """Identifier assigned by Visa. + + Matches the `transaction_id` of a prior related event. May be populated in + incremental authorizations (authorization requests that augment a previously + authorized amount), authorization advices, financial authorizations, and + clearings. + """ + + transaction_id: Optional[str] = None + """Identifier assigned by Visa to link original messages to subsequent messages. + + Guaranteed by Visa to be unique for each original authorization and financial + authorization. + """ + + +class EventNetworkInfo(BaseModel): + """Information provided by the card network in each event. + + This includes common identifiers shared between you, Lithic, the card network and in some cases the acquirer. These identifiers often link together events within the same transaction lifecycle and can be used to locate a particular transaction, such as during processing of disputes. Not all fields are available in all events, and the presence of these fields is dependent on the card network and the event type. If the field is populated by the network, we will pass it through as is unless otherwise specified. Please consult the official network documentation for more details about these fields and how to use them. + """ + + acquirer: Optional[EventNetworkInfoAcquirer] = None + + amex: Optional[EventNetworkInfoAmex] = None + + mastercard: Optional[EventNetworkInfoMastercard] = None + visa: Optional[EventNetworkInfoVisa] = None -class CardholderAuthentication(BaseModel): - three_ds_version: Optional[str] = FieldInfo(alias="3ds_version", default=None) - """3-D Secure Protocol version. Possible enum values: - - `1`: 3-D Secure Protocol version 1.x applied to the transaction. - - `2`: 3-D Secure Protocol version 2.x applied to the transaction. - - `null`: 3-D Secure was not used for the transaction +class EventRuleResult(BaseModel): + auth_rule_token: Optional[str] = None + """The Auth Rule Token associated with the rule from which the decline originated. + + If this is set to null, then the decline was not associated with a + customer-configured Auth Rule. This may happen in cases where a transaction is + declined due to a Lithic-configured security or compliance rule, for example. """ - acquirer_exemption: Literal[ - "AUTHENTICATION_OUTAGE_EXCEPTION", - "LOW_VALUE", - "MERCHANT_INITIATED_TRANSACTION", - "NONE", - "RECURRING_PAYMENT", - "SECURE_CORPORATE_PAYMENT", - "STRONG_CUSTOMER_AUTHENTICATION_DELEGATION", - "TRANSACTION_RISK_ANALYSIS", + explanation: Optional[str] = None + """A human-readable explanation outlining the motivation for the rule's decline.""" + + name: Optional[str] = None + """The name for the rule, if any was configured.""" + + result: Literal[ + "ACCOUNT_DAILY_SPEND_LIMIT_EXCEEDED", + "ACCOUNT_DELINQUENT", + "ACCOUNT_INACTIVE", + "ACCOUNT_LIFETIME_SPEND_LIMIT_EXCEEDED", + "ACCOUNT_MONTHLY_SPEND_LIMIT_EXCEEDED", + "ACCOUNT_PAUSED", + "ACCOUNT_UNDER_REVIEW", + "ADDRESS_INCORRECT", + "APPROVED", + "AUTH_RULE_ALLOWED_COUNTRY", + "AUTH_RULE_ALLOWED_MCC", + "AUTH_RULE_BLOCKED_COUNTRY", + "AUTH_RULE_BLOCKED_MCC", + "CARD_CLOSED", + "CARD_CRYPTOGRAM_VALIDATION_FAILURE", + "CARD_EXPIRED", + "CARD_EXPIRY_DATE_INCORRECT", + "CARD_INVALID", + "CARD_NOT_ACTIVATED", + "CARD_PAUSED", + "CARD_PIN_INCORRECT", + "CARD_RESTRICTED", + "CARD_SECURITY_CODE_INCORRECT", + "CARD_SPEND_LIMIT_EXCEEDED", + "CONTACT_CARD_ISSUER", + "CUSTOMER_ASA_TIMEOUT", + "CUSTOM_ASA_RESULT", + "DECLINED", + "DO_NOT_HONOR", + "DRIVER_NUMBER_INVALID", + "FORMAT_ERROR", + "INSUFFICIENT_FUNDING_SOURCE_BALANCE", + "INSUFFICIENT_FUNDS", + "LITHIC_SYSTEM_ERROR", + "LITHIC_SYSTEM_RATE_LIMIT", + "MALFORMED_ASA_RESPONSE", + "MERCHANT_INVALID", + "MERCHANT_LOCKED_CARD_ATTEMPTED_ELSEWHERE", + "MERCHANT_NOT_PERMITTED", + "OVER_REVERSAL_ATTEMPTED", + "PIN_BLOCKED", + "PROGRAM_CARD_SPEND_LIMIT_EXCEEDED", + "PROGRAM_SUSPENDED", + "PROGRAM_USAGE_RESTRICTION", + "REVERSAL_UNMATCHED", + "SECURITY_VIOLATION", + "SINGLE_USE_CARD_REATTEMPTED", + "SUSPECTED_FRAUD", + "TRANSACTION_INVALID", + "TRANSACTION_NOT_PERMITTED_TO_ACQUIRER_OR_TERMINAL", + "TRANSACTION_NOT_PERMITTED_TO_ISSUER_OR_CARDHOLDER", + "TRANSACTION_PREVIOUSLY_COMPLETED", + "UNAUTHORIZED_MERCHANT", + "VEHICLE_NUMBER_INVALID", + "CARDHOLDER_CHALLENGED", + "CARDHOLDER_CHALLENGE_FAILED", ] + """The detailed_result associated with this rule's decline.""" + + +class EventNetworkSpecificDataMastercardOnBehalfServiceResult(BaseModel): + result_1: str + """Indicates the results of the service processing.""" + + result_2: str + """Identifies the results of the service processing.""" + + service: str + """Indicates the service performed on the transaction.""" + + +class EventNetworkSpecificDataMastercard(BaseModel): + ecommerce_security_level_indicator: Optional[str] = None + """Indicates the electronic commerce security level and UCAF collection.""" + + on_behalf_service_result: Optional[List[EventNetworkSpecificDataMastercardOnBehalfServiceResult]] = None + """The On-behalf Service performed on the transaction and the results. + + Contains all applicable, on-behalf service results that were performed on a + given transaction. """ - Exemption applied by the ACS to authenticate the transaction without requesting - a challenge. Possible enum values: - - - `AUTHENTICATION_OUTAGE_EXCEPTION`: Authentication Outage Exception exemption. - - `LOW_VALUE`: Low Value Payment exemption. - - `MERCHANT_INITIATED_TRANSACTION`: Merchant Initiated Transaction (3RI). - - `NONE`: No exemption applied. - - `RECURRING_PAYMENT`: Recurring Payment exemption. - - `SECURE_CORPORATE_PAYMENT`: Secure Corporate Payment exemption. - - `STRONG_CUSTOMER_AUTHENTICATION_DELEGATION`: Strong Customer Authentication - Delegation exemption. - - `TRANSACTION_RISK_ANALYSIS`: Acquirer Low-Fraud and Transaction Risk Analysis - exemption. - - Maps to the 3-D Secure `transChallengeExemption` field. - """ - - authentication_result: Literal["ATTEMPTS", "DECLINE", "NONE", "SUCCESS"] - """Outcome of the 3DS authentication process. Possible enum values: - - - `SUCCESS`: 3DS authentication was successful and the transaction is considered - authenticated. - - `DECLINE`: 3DS authentication was attempted but was unsuccessful — i.e., the - issuer declined to authenticate the cardholder; note that Lithic populates - this value on a best-effort basis based on common data across the 3DS - authentication and ASA data elements. - - `ATTEMPTS`: 3DS authentication was attempted but full authentication did not - occur. A proof of attempted authenticated is provided by the merchant. - - `NONE`: 3DS authentication was not performed on the transaction. - """ - - decision_made_by: Literal["CUSTOMER_ENDPOINT", "LITHIC_DEFAULT", "LITHIC_RULES", "NETWORK", "UNKNOWN"] - """Indicator for which party made the 3DS authentication decision. - - Possible enum values: - - - `NETWORK`: A networks tand-in service decided on the outcome; for token - authentications (as indicated in the `liability_shift` attribute), this is the - default value - - `LITHIC_DEFAULT`: A default decision was made by Lithic, without running a - rules-based authentication; this value will be set on card programs that do - not participate in one of our two 3DS product tiers - - `LITHIC_RULES`: A rules-based authentication was conducted by Lithic and - Lithic decided on the outcome - - `CUSTOMER_ENDPOINT`: Lithic customer decided on the outcome based on a - real-time request sent to a configured endpoint - - `UNKNOWN`: Data on which party decided is unavailable - """ - - liability_shift: Literal["3DS_AUTHENTICATED", "ACQUIRER_EXEMPTION", "NONE", "TOKEN_AUTHENTICATED"] - """Indicates whether chargeback liability shift applies to the transaction. - - Possible enum values: - - - `3DS_AUTHENTICATED`: The transaction was fully authenticated through a 3-D - Secure flow, chargeback liability shift applies. - - `ACQUIRER_EXEMPTION`: The acquirer utilised an exemption to bypass Strong - Customer Authentication (`transStatus = N`, or `transStatus = I`). Liability - remains with the acquirer and in this case the `acquirer_exemption` field is - expected to be not `NONE`. - - `NONE`: Chargeback liability shift has not shifted to the issuer, i.e. the - merchant is liable. - - `TOKEN_AUTHENTICATED`: The transaction was a tokenized payment with validated - cryptography, possibly recurring. Chargeback liability shift to the issuer - applies. - """ - - three_ds_authentication_token: str - """ - Unique identifier you can use to match a given 3DS authentication and the - transaction. Note that in cases where liability shift does not occur, this token - is matched to the transaction on a best-effort basis. - """ - - verification_attempted: Literal["APP_LOGIN", "BIOMETRIC", "NONE", "OTHER", "OTP"] - """Verification attempted values: - - - `APP_LOGIN`: Out-of-band login verification was attempted by the ACS. - - `BIOMETRIC`: Out-of-band biometric verification was attempted by the ACS. - - `NONE`: No cardholder verification was attempted by the Access Control Server - (e.g. frictionless 3-D Secure flow, no 3-D Secure, or stand-in Risk Based - Analysis). - - `OTHER`: Other method was used by the ACS to verify the cardholder (e.g. - Mastercard Identity Check Express, recurring transactions, etc.) - - `OTP`: One-time password verification was attempted by the ACS. - """ - - verification_result: Literal["CANCELLED", "FAILED", "FRICTIONLESS", "NOT_ATTEMPTED", "REJECTED", "SUCCESS"] - """ - This field partially maps to the `transStatus` field in the - [EMVCo 3-D Secure specification](https://www.emvco.com/emv-technologies/3d-secure/) - and Mastercard SPA2 AAV leading indicators. - - Verification result values: - - - `CANCELLED`: Authentication/Account verification could not be performed, - `transStatus = U`. - - `FAILED`: Transaction was not authenticated. `transStatus = N`, note: the - utilization of exemptions could also result in `transStatus = N`, inspect the - `acquirer_exemption` field for more information. - - `FRICTIONLESS`: Attempts processing performed, the transaction was not - authenticated, but a proof of attempted authentication/verification is - provided. `transStatus = A` and the leading AAV indicator was one of {`kE`, - `kF`, `kQ`}. - - `NOT_ATTEMPTED`: A 3-D Secure flow was not applied to this transaction. - Leading AAV indicator was one of {`kN`, `kX`} or no AAV was provided for the - transaction. - - `REJECTED`: Authentication/Account Verification rejected; `transStatus = R`. - Issuer is rejecting authentication/verification and requests that - authorization not be attempted. - - `SUCCESS`: Authentication verification successful. `transStatus = Y` and - leading AAV indicator for the transaction was one of {`kA`, `kB`, `kC`, `kD`, - `kO`, `kP`, `kR`, `kS`}. - Note that the following `transStatus` values are not represented by this field: + transaction_type_identifier: Optional[str] = None + """Indicates the type of additional transaction purpose.""" + - - `C`: Challenge Required - - `D`: Challenge Required; decoupled authentication confirmed - - `I`: Informational only - - `S`: Challenge using Secure Payment Confirmation (SPC) +class EventNetworkSpecificDataVisa(BaseModel): + business_application_identifier: Optional[str] = None """ + Identifies the purpose or category of a transaction, used to classify and + process transactions according to Visa’s rules. + """ + + +class EventNetworkSpecificData(BaseModel): + mastercard: EventNetworkSpecificDataMastercard + + visa: EventNetworkSpecificDataVisa + + +class Event(BaseModel): + token: str + """Transaction event identifier.""" + + amount: int + """Amount of the event in the settlement currency.""" + + amounts: EventAmounts + + created: datetime + """RFC 3339 date and time this event entered the system. UTC time zone.""" + + detailed_results: List[ + Literal[ + "ACCOUNT_DAILY_SPEND_LIMIT_EXCEEDED", + "ACCOUNT_DELINQUENT", + "ACCOUNT_INACTIVE", + "ACCOUNT_LIFETIME_SPEND_LIMIT_EXCEEDED", + "ACCOUNT_MONTHLY_SPEND_LIMIT_EXCEEDED", + "ACCOUNT_PAUSED", + "ACCOUNT_UNDER_REVIEW", + "ADDRESS_INCORRECT", + "APPROVED", + "AUTH_RULE_ALLOWED_COUNTRY", + "AUTH_RULE_ALLOWED_MCC", + "AUTH_RULE_BLOCKED_COUNTRY", + "AUTH_RULE_BLOCKED_MCC", + "CARD_CLOSED", + "CARD_CRYPTOGRAM_VALIDATION_FAILURE", + "CARD_EXPIRED", + "CARD_EXPIRY_DATE_INCORRECT", + "CARD_INVALID", + "CARD_NOT_ACTIVATED", + "CARD_PAUSED", + "CARD_PIN_INCORRECT", + "CARD_RESTRICTED", + "CARD_SECURITY_CODE_INCORRECT", + "CARD_SPEND_LIMIT_EXCEEDED", + "CONTACT_CARD_ISSUER", + "CUSTOMER_ASA_TIMEOUT", + "CUSTOM_ASA_RESULT", + "DECLINED", + "DO_NOT_HONOR", + "DRIVER_NUMBER_INVALID", + "FORMAT_ERROR", + "INSUFFICIENT_FUNDING_SOURCE_BALANCE", + "INSUFFICIENT_FUNDS", + "LITHIC_SYSTEM_ERROR", + "LITHIC_SYSTEM_RATE_LIMIT", + "MALFORMED_ASA_RESPONSE", + "MERCHANT_INVALID", + "MERCHANT_LOCKED_CARD_ATTEMPTED_ELSEWHERE", + "MERCHANT_NOT_PERMITTED", + "OVER_REVERSAL_ATTEMPTED", + "PIN_BLOCKED", + "PROGRAM_CARD_SPEND_LIMIT_EXCEEDED", + "PROGRAM_SUSPENDED", + "PROGRAM_USAGE_RESTRICTION", + "REVERSAL_UNMATCHED", + "SECURITY_VIOLATION", + "SINGLE_USE_CARD_REATTEMPTED", + "SUSPECTED_FRAUD", + "TRANSACTION_INVALID", + "TRANSACTION_NOT_PERMITTED_TO_ACQUIRER_OR_TERMINAL", + "TRANSACTION_NOT_PERMITTED_TO_ISSUER_OR_CARDHOLDER", + "TRANSACTION_PREVIOUSLY_COMPLETED", + "UNAUTHORIZED_MERCHANT", + "VEHICLE_NUMBER_INVALID", + "CARDHOLDER_CHALLENGED", + "CARDHOLDER_CHALLENGE_FAILED", + ] + ] + + effective_polarity: Literal["CREDIT", "DEBIT"] + """Indicates whether the transaction event is a credit or debit to the account.""" + + network_info: Optional[EventNetworkInfo] = None + """Information provided by the card network in each event. + + This includes common identifiers shared between you, Lithic, the card network + and in some cases the acquirer. These identifiers often link together events + within the same transaction lifecycle and can be used to locate a particular + transaction, such as during processing of disputes. Not all fields are available + in all events, and the presence of these fields is dependent on the card network + and the event type. If the field is populated by the network, we will pass it + through as is unless otherwise specified. Please consult the official network + documentation for more details about these fields and how to use them. + """ + + result: Literal[ + "ACCOUNT_PAUSED", + "ACCOUNT_STATE_TRANSACTION_FAIL", + "APPROVED", + "BANK_CONNECTION_ERROR", + "BANK_NOT_VERIFIED", + "CARD_CLOSED", + "CARD_PAUSED", + "DECLINED", + "FRAUD_ADVICE", + "IGNORED_TTL_EXPIRY", + "SUSPECTED_FRAUD", + "INACTIVE_ACCOUNT", + "INCORRECT_PIN", + "INVALID_CARD_DETAILS", + "INSUFFICIENT_FUNDS", + "INSUFFICIENT_FUNDS_PRELOAD", + "INVALID_TRANSACTION", + "MERCHANT_BLACKLIST", + "ORIGINAL_NOT_FOUND", + "PREVIOUSLY_COMPLETED", + "SINGLE_USE_RECHARGED", + "SWITCH_INOPERATIVE_ADVICE", + "UNAUTHORIZED_MERCHANT", + "UNKNOWN_HOST_TIMEOUT", + "USER_TRANSACTION_LIMIT", + ] + + rule_results: List[EventRuleResult] + + type: Literal[ + "AUTHORIZATION", + "AUTHORIZATION_ADVICE", + "AUTHORIZATION_EXPIRY", + "AUTHORIZATION_REVERSAL", + "BALANCE_INQUIRY", + "CLEARING", + "CORRECTION_CREDIT", + "CORRECTION_DEBIT", + "CREDIT_AUTHORIZATION", + "CREDIT_AUTHORIZATION_ADVICE", + "FINANCIAL_AUTHORIZATION", + "FINANCIAL_CREDIT_AUTHORIZATION", + "RETURN", + "RETURN_REVERSAL", + ] + """Type of transaction event""" + + account_type: Optional[Literal["CHECKING", "SAVINGS"]] = None + + network_specific_data: Optional[EventNetworkSpecificData] = None class Transaction(BaseModel): token: str """Globally unique identifier.""" - acquirer_fee: int + account_token: str + """The token for the account associated with this transaction.""" + + acquirer_fee: Optional[int] = None """ Fee assessed by the merchant and paid for by the cardholder in the smallest unit of the currency. Will be zero if no fee is assessed. Rebates may be transmitted @@ -463,81 +627,75 @@ class Transaction(BaseModel): acquirer_reference_number: Optional[str] = None """ Unique identifier assigned to a transaction by the acquirer that can be used in - dispute and chargeback filing. + dispute and chargeback filing. This field has been deprecated in favor of the + `acquirer_reference_number` that resides in the event-level `network_info`. """ amount: int - """Authorization amount of the transaction (in cents), including any acquirer fees. - - This may change over time, and will represent the settled amount once the - transaction is settled. + """ + When the transaction is pending, this represents the authorization amount of the + transaction in the anticipated settlement currency. Once the transaction has + settled, this field represents the settled amount in the settlement currency. """ - authorization_amount: int - """Authorization amount (in cents) of the transaction, including any acquirer fees. + amounts: Amounts - This amount always represents the amount authorized for the transaction, - unaffected by settlement. + authorization_amount: Optional[int] = None + """ + The authorization amount of the transaction in the anticipated settlement + currency. """ - authorization_code: str + authorization_code: Optional[str] = None """ A fixed-width 6-digit numeric identifier that can be used to identify a transaction with networks. """ - avs: Avs + avs: Optional[Avs] = None card_token: str """Token for the card used in this transaction.""" + cardholder_authentication: Optional[CardholderAuthentication] = None + created: datetime """Date and time when the transaction first occurred. UTC time zone.""" - events: List[Event] - """A list of all events that have modified this transaction.""" + financial_account_token: Optional[str] = None merchant: Merchant - merchant_amount: int - """ - Analogous to the "amount" property, but will represent the amount in the - transaction's local currency (smallest unit), including any acquirer fees. - """ + merchant_amount: Optional[int] = None + """Analogous to the 'amount', but in the merchant currency.""" - merchant_authorization_amount: int - """ - Analogous to the "authorization_amount" property, but will represent the amount - in the transaction's local currency (smallest unit), including any acquirer - fees. - """ + merchant_authorization_amount: Optional[int] = None + """Analogous to the 'authorization_amount', but in the merchant currency.""" merchant_currency: str - """3-digit alphabetic ISO 4217 code for the local currency of the transaction.""" + """3-character alphabetic ISO 4217 code for the local currency of the transaction.""" - network: Optional[Literal["INTERLINK", "MAESTRO", "MASTERCARD", "UNKNOWN", "VISA"]] = None + network: Optional[Literal["AMEX", "INTERLINK", "MAESTRO", "MASTERCARD", "UNKNOWN", "VISA"]] = None """Card network of the authorization. - Can be `INTERLINK`, `MAESTRO`, `MASTERCARD`, `VISA`, or `UNKNOWN`. Value is - `UNKNOWN` when Lithic cannot determine the network code from the upstream - provider. + Value is `UNKNOWN` when Lithic cannot determine the network code from the + upstream provider. """ - network_risk_score: float + network_risk_score: Optional[int] = None """ Network-provided score assessing risk level associated with a given authorization. Scores are on a range of 0-999, with 0 representing the lowest risk and 999 representing the highest risk. For Visa transactions, where the raw score has a range of 0-99, Lithic will normalize the score by multiplying the raw score by 10x. - - A score may not be available for all authorizations, and where it is not, this - field will be set to null. """ pos: Pos result: Literal[ + "ACCOUNT_PAUSED", + "ACCOUNT_STATE_TRANSACTION_FAIL", "APPROVED", "BANK_CONNECTION_ERROR", "BANK_NOT_VERIFIED", @@ -545,36 +703,33 @@ class Transaction(BaseModel): "CARD_PAUSED", "DECLINED", "FRAUD_ADVICE", + "IGNORED_TTL_EXPIRY", + "SUSPECTED_FRAUD", "INACTIVE_ACCOUNT", "INCORRECT_PIN", - "INSUFFICIENT_FUNDS", "INVALID_CARD_DETAILS", + "INSUFFICIENT_FUNDS", + "INSUFFICIENT_FUNDS_PRELOAD", + "INVALID_TRANSACTION", "MERCHANT_BLACKLIST", + "ORIGINAL_NOT_FOUND", + "PREVIOUSLY_COMPLETED", "SINGLE_USE_RECHARGED", "SWITCH_INOPERATIVE_ADVICE", "UNAUTHORIZED_MERCHANT", "UNKNOWN_HOST_TIMEOUT", "USER_TRANSACTION_LIMIT", ] - """`APPROVED` or decline reason. See Event result types""" settled_amount: int - """ - Amount of the transaction that has been settled (in cents), including any - acquirer fees. This may change over time. - """ + """The settled amount of the transaction in the settlement currency.""" status: Literal["DECLINED", "EXPIRED", "PENDING", "SETTLED", "VOIDED"] - """Status types: + """Status of the transaction.""" - - `DECLINED` - The transaction was declined. - - `EXPIRED` - Lithic reversed the authorization as it has passed its expiration - time. - - `PENDING` - Authorization is pending completion from the merchant. - - `SETTLED` - The transaction is complete. - - `VOIDED` - The merchant has voided the previously pending authorization. - """ + token_info: Optional[TokenInfo] = None - token_info: TokenInfo + updated: datetime + """Date and time when the transaction last updated. UTC time zone.""" - cardholder_authentication: Optional[CardholderAuthentication] = None + events: Optional[List[Event]] = None diff --git a/src/lithic/types/transaction_list_params.py b/src/lithic/types/transaction_list_params.py index f9d88f22..d6e98242 100644 --- a/src/lithic/types/transaction_list_params.py +++ b/src/lithic/types/transaction_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -51,3 +51,6 @@ class TransactionListParams(TypedDict, total=False): Used to retrieve the next page of results after this item. """ + + status: Literal["PENDING", "VOIDED", "SETTLED", "DECLINED", "EXPIRED"] + """Filters for transactions using transaction status field.""" diff --git a/src/lithic/types/transaction_simulate_authorization_advice_params.py b/src/lithic/types/transaction_simulate_authorization_advice_params.py index da7f5307..5e973292 100644 --- a/src/lithic/types/transaction_simulate_authorization_advice_params.py +++ b/src/lithic/types/transaction_simulate_authorization_advice_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,7 +9,7 @@ class TransactionSimulateAuthorizationAdviceParams(TypedDict, total=False): token: Required[str] - """The transaction token returned from the /v1/simulate/authorize response.""" + """The transaction token returned from the /v1/simulate/authorize. response.""" amount: Required[int] """Amount (in cents) to authorize. diff --git a/src/lithic/types/transaction_simulate_authorization_advice_response.py b/src/lithic/types/transaction_simulate_authorization_advice_response.py index 7c48d3e9..4e8016cb 100644 --- a/src/lithic/types/transaction_simulate_authorization_advice_response.py +++ b/src/lithic/types/transaction_simulate_authorization_advice_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_authorization_params.py b/src/lithic/types/transaction_simulate_authorization_params.py index 8d0c9104..d4088968 100644 --- a/src/lithic/types/transaction_simulate_authorization_params.py +++ b/src/lithic/types/transaction_simulate_authorization_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -13,7 +13,7 @@ class TransactionSimulateAuthorizationParams(TypedDict, total=False): For credit authorizations and financial credit authorizations, any value entered will be converted into a negative amount in the simulated transaction. For - example, entering 100 in this field will appear as a -100 amount in the + example, entering 100 in this field will result in a -100 amount in the transaction. For balance inquiries, this field must be set to 0. """ @@ -41,7 +41,11 @@ class TransactionSimulateAuthorizationParams(TypedDict, total=False): """ merchant_currency: str - """3-digit alphabetic ISO 4217 currency code.""" + """3-character alphabetic ISO 4217 currency code. + + Note: Simulator only accepts USD, GBP, EUR and defaults to GBP if another ISO + 4217 code is provided + """ partial_approval_capable: bool """ @@ -50,6 +54,9 @@ class TransactionSimulateAuthorizationParams(TypedDict, total=False): must be used for the remainder. """ + pin: str + """Simulate entering a PIN. If omitted, PIN check will not be performed.""" + status: Literal[ "AUTHORIZATION", "BALANCE_INQUIRY", @@ -61,12 +68,12 @@ class TransactionSimulateAuthorizationParams(TypedDict, total=False): - `AUTHORIZATION` is a dual message purchase authorization, meaning a subsequent clearing step is required to settle the transaction. - - `BALANCE_INQUIRY` is a $0 authorization that includes a request for the - balance held on the card, and is most typically seen when a cardholder - requests to view a card's balance at an ATM. + - `BALANCE_INQUIRY` is a $0 authorization requesting the balance held on the + card, and is most often observed when a cardholder requests to view a card's + balance at an ATM. - `CREDIT_AUTHORIZATION` is a dual message request from a merchant to authorize - a refund or credit, meaning a subsequent clearing step is required to settle - the transaction. + a refund, meaning a subsequent clearing step is required to settle the + transaction. - `FINANCIAL_AUTHORIZATION` is a single message request from a merchant to debit funds immediately (such as an ATM withdrawal), and no subsequent clearing is required to settle the transaction. diff --git a/src/lithic/types/transaction_simulate_authorization_response.py b/src/lithic/types/transaction_simulate_authorization_response.py index 0766f63d..859424ac 100644 --- a/src/lithic/types/transaction_simulate_authorization_response.py +++ b/src/lithic/types/transaction_simulate_authorization_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_clearing_params.py b/src/lithic/types/transaction_simulate_clearing_params.py index dcf7a984..ab45523f 100644 --- a/src/lithic/types/transaction_simulate_clearing_params.py +++ b/src/lithic/types/transaction_simulate_clearing_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -12,11 +12,15 @@ class TransactionSimulateClearingParams(TypedDict, total=False): """The transaction token returned from the /v1/simulate/authorize response.""" amount: int - """Amount (in cents) to complete. + """Amount (in cents) to clear. - Typically this will match the original authorization, but may be more or less. + Typically this will match the amount in the original authorization, but can be + higher or lower. The sign of this amount will automatically match the sign of + the original authorization's amount. For example, entering 100 in this field + will result in a -100 amount in the transaction, if the original authorization + is a credit authorization. - If no amount is supplied to this endpoint, the amount of the transaction will be - captured. Any transaction that has any amount completed at all do not have - access to this behavior. + If `amount` is not set, the full amount of the transaction will be cleared. + Transactions that have already cleared, either partially or fully, cannot be + cleared again using this endpoint. """ diff --git a/src/lithic/types/transaction_simulate_clearing_response.py b/src/lithic/types/transaction_simulate_clearing_response.py index cd92cf8a..91af533f 100644 --- a/src/lithic/types/transaction_simulate_clearing_response.py +++ b/src/lithic/types/transaction_simulate_clearing_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_credit_authorization_advice_params.py b/src/lithic/types/transaction_simulate_credit_authorization_advice_params.py new file mode 100644 index 00000000..329a09ba --- /dev/null +++ b/src/lithic/types/transaction_simulate_credit_authorization_advice_params.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["TransactionSimulateCreditAuthorizationAdviceParams"] + + +class TransactionSimulateCreditAuthorizationAdviceParams(TypedDict, total=False): + amount: Required[int] + """Amount (in cents). + + Any value entered will be converted into a negative amount in the simulated + transaction. For example, entering 100 in this field will appear as a -100 + amount in the transaction. + """ + + descriptor: Required[str] + """Merchant descriptor.""" + + pan: Required[str] + """Sixteen digit card number.""" + + mcc: str + """Merchant category code for the transaction to be simulated. + + A four-digit number listed in ISO 18245. Supported merchant category codes can + be found + [here](https://docs.lithic.com/docs/transactions#merchant-category-codes-mccs). + """ + + merchant_acceptor_id: str + """Unique identifier to identify the payment card acceptor.""" diff --git a/src/lithic/types/transaction_simulate_credit_authorization_advice_response.py b/src/lithic/types/transaction_simulate_credit_authorization_advice_response.py new file mode 100644 index 00000000..f87a1cf9 --- /dev/null +++ b/src/lithic/types/transaction_simulate_credit_authorization_advice_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["TransactionSimulateCreditAuthorizationAdviceResponse"] + + +class TransactionSimulateCreditAuthorizationAdviceResponse(BaseModel): + token: Optional[str] = None + """A unique token to reference this transaction.""" + + debugging_request_id: Optional[str] = None + """Debugging request ID to share with Lithic Support team.""" diff --git a/src/lithic/types/transaction_simulate_credit_authorization_params.py b/src/lithic/types/transaction_simulate_credit_authorization_params.py index 6f165543..a25b00f0 100644 --- a/src/lithic/types/transaction_simulate_credit_authorization_params.py +++ b/src/lithic/types/transaction_simulate_credit_authorization_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_credit_authorization_response.py b/src/lithic/types/transaction_simulate_credit_authorization_response.py index 36d65d35..e50b3889 100644 --- a/src/lithic/types/transaction_simulate_credit_authorization_response.py +++ b/src/lithic/types/transaction_simulate_credit_authorization_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_return_params.py b/src/lithic/types/transaction_simulate_return_params.py index 46684e01..7487fe82 100644 --- a/src/lithic/types/transaction_simulate_return_params.py +++ b/src/lithic/types/transaction_simulate_return_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_return_response.py b/src/lithic/types/transaction_simulate_return_response.py index c898f734..6f67e632 100644 --- a/src/lithic/types/transaction_simulate_return_response.py +++ b/src/lithic/types/transaction_simulate_return_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_return_reversal_params.py b/src/lithic/types/transaction_simulate_return_reversal_params.py index 7b8ee082..f67a31d4 100644 --- a/src/lithic/types/transaction_simulate_return_reversal_params.py +++ b/src/lithic/types/transaction_simulate_return_reversal_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_return_reversal_response.py b/src/lithic/types/transaction_simulate_return_reversal_response.py index 30b4a9e0..abc9edd2 100644 --- a/src/lithic/types/transaction_simulate_return_reversal_response.py +++ b/src/lithic/types/transaction_simulate_return_reversal_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_void_params.py b/src/lithic/types/transaction_simulate_void_params.py index 95dd6e2f..950cfe80 100644 --- a/src/lithic/types/transaction_simulate_void_params.py +++ b/src/lithic/types/transaction_simulate_void_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -14,7 +14,9 @@ class TransactionSimulateVoidParams(TypedDict, total=False): amount: int """Amount (in cents) to void. - Typically this will match the original authorization, but may be less. + Typically this will match the amount in the original authorization, but can be + less. Applies to authorization reversals only. An authorization expiry will + always apply to the full pending amount. """ type: Literal["AUTHORIZATION_EXPIRY", "AUTHORIZATION_REVERSAL"] diff --git a/src/lithic/types/transaction_simulate_void_response.py b/src/lithic/types/transaction_simulate_void_response.py index a110202f..df321bf9 100644 --- a/src/lithic/types/transaction_simulate_void_response.py +++ b/src/lithic/types/transaction_simulate_void_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transactions/__init__.py b/src/lithic/types/transactions/__init__.py new file mode 100644 index 00000000..7a0111fd --- /dev/null +++ b/src/lithic/types/transactions/__init__.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .enhanced_commercial_data_retrieve_response import ( + EnhancedCommercialDataRetrieveResponse as EnhancedCommercialDataRetrieveResponse, +) diff --git a/src/lithic/types/transactions/enhanced_commercial_data_retrieve_response.py b/src/lithic/types/transactions/enhanced_commercial_data_retrieve_response.py new file mode 100644 index 00000000..9702760d --- /dev/null +++ b/src/lithic/types/transactions/enhanced_commercial_data_retrieve_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel +from .events.enhanced_data import EnhancedData + +__all__ = ["EnhancedCommercialDataRetrieveResponse"] + + +class EnhancedCommercialDataRetrieveResponse(BaseModel): + data: List[EnhancedData] diff --git a/src/lithic/types/transactions/events/__init__.py b/src/lithic/types/transactions/events/__init__.py new file mode 100644 index 00000000..e7119fc4 --- /dev/null +++ b/src/lithic/types/transactions/events/__init__.py @@ -0,0 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .enhanced_data import EnhancedData as EnhancedData diff --git a/src/lithic/types/transactions/events/enhanced_data.py b/src/lithic/types/transactions/events/enhanced_data.py new file mode 100644 index 00000000..21e0c7a3 --- /dev/null +++ b/src/lithic/types/transactions/events/enhanced_data.py @@ -0,0 +1,237 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import date +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["EnhancedData", "Common", "CommonLineItem", "CommonTax", "Fleet", "FleetAmountTotals", "FleetFuel"] + + +class CommonLineItem(BaseModel): + """An L2/L3 enhanced commercial data line item.""" + + amount: Optional[str] = None + """The price of the item purchased in merchant currency.""" + + description: Optional[str] = None + """A human-readable description of the item.""" + + product_code: Optional[str] = None + """An identifier for the item purchased.""" + + quantity: Optional[str] = None + """The quantity of the item purchased.""" + + +class CommonTax(BaseModel): + amount: Optional[int] = None + """The amount of tax collected.""" + + exempt: Optional[Literal["TAX_INCLUDED", "TAX_NOT_INCLUDED", "NOT_SUPPORTED"]] = None + """A flag indicating whether the transaction is tax exempt or not.""" + + merchant_tax_id: Optional[str] = None + """The tax ID of the merchant.""" + + +class Common(BaseModel): + line_items: List[CommonLineItem] + + tax: CommonTax + + customer_reference_number: Optional[str] = None + """A customer identifier.""" + + merchant_reference_number: Optional[str] = None + """A merchant identifier.""" + + order_date: Optional[date] = None + """The date of the order.""" + + +class FleetAmountTotals(BaseModel): + discount: Optional[int] = None + """The discount applied to the gross sale amount.""" + + gross_sale: Optional[int] = None + """The gross sale amount.""" + + net_sale: Optional[int] = None + """The amount after discount.""" + + +class FleetFuel(BaseModel): + quantity: Optional[str] = None + """The quantity of fuel purchased.""" + + type: Optional[ + Literal[ + "UNKNOWN", + "REGULAR", + "MID_PLUS", + "PREMIUM_SUPER", + "MID_PLUS_2", + "PREMIUM_SUPER_2", + "ETHANOL_5_7_BLEND", + "MID_PLUS_ETHANOL_5_7_PERCENT_BLEND", + "PREMIUM_SUPER_ETHANOL_5_7_PERCENT_BLEND", + "ETHANOL_7_7_PERCENT_BLEND", + "MID_PLUS_ETHANOL_7_7_PERCENT_BLEND", + "GREEN_GASOLINE_REGULAR", + "GREEN_GASOLINE_MID_PLUS", + "GREEN_GASOLINE_PREMIUM_SUPER", + "REGULAR_DIESEL_2", + "PREMIUM_DIESEL_2", + "REGULAR_DIESEL_1", + "COMPRESSED_NATURAL_GAS", + "LIQUID_PROPANE_GAS", + "LIQUID_NATURAL_GAS", + "E_85", + "REFORMULATED_1", + "REFORMULATED_2", + "REFORMULATED_3", + "REFORMULATED_4", + "REFORMULATED_5", + "DIESEL_OFF_ROAD_1_AND_2_NON_TAXABLE", + "DIESEL_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_OFF_ROAD_NON_TAXABLE", + "UNDEFINED_FUEL", + "RACING_FUEL", + "MID_PLUS_2_10_PERCENT_BLEND", + "PREMIUM_SUPER_2_10_PERCENT_BLEND", + "MID_PLUS_ETHANOL_2_15_PERCENT_BLEND", + "PREMIUM_SUPER_ETHANOL_2_15_PERCENT_BLEND", + "PREMIUM_SUPER_ETHANOL_7_7_PERCENT_BLEND", + "REGULAR_ETHANOL_10_PERCENT_BLEND", + "MID_PLUS_ETHANOL_10_PERCENT_BLEND", + "PREMIUM_SUPER_ETHANOL_10_PERCENT_BLEND", + "B2_DIESEL_BLEND_2_PERCENT_BIODIESEL", + "B5_DIESEL_BLEND_5_PERCENT_BIODIESEL", + "B10_DIESEL_BLEND_10_PERCENT_BIODIESEL", + "B11_DIESEL_BLEND_11_PERCENT_BIODIESEL", + "B15_DIESEL_BLEND_15_PERCENT_BIODIESEL", + "B20_DIESEL_BLEND_20_PERCENT_BIODIESEL", + "B100_DIESEL_BLEND_100_PERCENT_BIODIESEL", + "B1_DIESEL_BLEND_1_PERCENT_BIODIESEL", + "ADDITIZED_DIESEL_2", + "ADDITIZED_DIESEL_3", + "RENEWABLE_DIESEL_R95", + "RENEWABLE_DIESEL_BIODIESEL_6_20_PERCENT", + "DIESEL_EXHAUST_FLUID", + "PREMIUM_DIESEL_1", + "REGULAR_ETHANOL_15_PERCENT_BLEND", + "MID_PLUS_ETHANOL_15_PERCENT_BLEND", + "PREMIUM_SUPER_ETHANOL_15_PERCENT_BLEND", + "PREMIUM_DIESEL_BLEND_LESS_THAN_20_PERCENT_BIODIESEL", + "PREMIUM_DIESEL_BLEND_GREATER_THAN_20_PERCENT_BIODIESEL", + "B75_DIESEL_BLEND_75_PERCENT_BIODIESEL", + "B99_DIESEL_BLEND_99_PERCENT_BIODIESEL", + "MISCELLANEOUS_FUEL", + "JET_FUEL", + "AVIATION_FUEL_REGULAR", + "AVIATION_FUEL_PREMIUM", + "AVIATION_FUEL_JP8", + "AVIATION_FUEL_4", + "AVIATION_FUEL_5", + "BIOJET_DIESEL", + "AVIATION_BIOFUEL_GASOLINE", + "MISCELLANEOUS_AVIATION_FUEL", + "MARINE_FUEL_1", + "MARINE_FUEL_2", + "MARINE_FUEL_3", + "MARINE_FUEL_4", + "MARINE_FUEL_5", + "MARINE_OTHER", + "MARINE_DIESEL", + "MISCELLANEOUS_MARINE_FUEL", + "KEROSENE_LOW_SULFUR", + "WHITE_GAS", + "HEATING_OIL", + "OTHER_FUEL_NON_TAXABLE", + "KEROSENE_ULTRA_LOW_SULFUR", + "KEROSENE_LOW_SULFUR_NON_TAXABLE", + "KEROSENE_ULTRA_LOW_SULFUR_NON_TAXABLE", + "EVC_1_LEVEL_1_CHARGE_110V_15_AMP", + "EVC_2_LEVEL_2_CHARGE_240V_15_40_AMP", + "EVC_3_LEVEL_3_CHARGE_480V_3_PHASE_CHARGE", + "BIODIESEL_BLEND_2_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_5_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_10_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_11_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_15_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_20_PERCENT_OFF_ROAD_NON_TAXABLE", + "DIESEL_1_OFF_ROAD_NON_TAXABLE", + "DIESEL_2_OFF_ROAD_NON_TAXABLE", + "DIESEL_1_PREMIUM_OFF_ROAD_NON_TAXABLE", + "DIESEL_2_PREMIUM_OFF_ROAD_NON_TAXABLE", + "ADDITIVE_DOSAGE", + "ETHANOL_BLENDS_E16_E84", + "LOW_OCTANE_UNL", + "BLENDED_DIESEL_1_AND_2", + "OFF_ROAD_REGULAR_NON_TAXABLE", + "OFF_ROAD_MID_PLUS_NON_TAXABLE", + "OFF_ROAD_PREMIUM_SUPER_NON_TAXABLE", + "OFF_ROAD_MID_PLUS_2_NON_TAXABLE", + "OFF_ROAD_PREMIUM_SUPER_2_NON_TAXABLE", + "RECREATIONAL_FUEL_90_OCTANE", + "HYDROGEN_H35", + "HYDROGEN_H70", + "RENEWABLE_DIESEL_R95_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_1_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_75_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_99_PERCENT_OFF_ROAD_NON_TAXABLE", + "BIODIESEL_BLEND_100_PERCENT_OFF_ROAD_NON_TAXABLE", + "RENEWABLE_DIESEL_BIODIESEL_6_20_PERCENT_OFF_ROAD_NON_TAXABLE", + "MISCELLANEOUS_OTHER_FUEL", + ] + ] = None + """The type of fuel purchased.""" + + unit_of_measure: Optional[ + Literal["GALLONS", "LITERS", "POUNDS", "KILOGRAMS", "IMPERIAL_GALLONS", "NOT_APPLICABLE", "UNKNOWN"] + ] = None + """Unit of measure for fuel disbursement.""" + + unit_price: Optional[int] = None + """The price per unit of fuel.""" + + +class Fleet(BaseModel): + amount_totals: FleetAmountTotals + + fuel: FleetFuel + + driver_number: Optional[str] = None + """ + The driver number entered into the terminal at the time of sale, with leading + zeros stripped. + """ + + odometer: Optional[int] = None + """The odometer reading entered into the terminal at the time of sale.""" + + service_type: Optional[Literal["UNKNOWN", "UNDEFINED", "SELF_SERVICE", "FULL_SERVICE", "NON_FUEL_ONLY"]] = None + """The type of fuel service.""" + + vehicle_number: Optional[str] = None + """ + The vehicle number entered into the terminal at the time of sale, with leading + zeros stripped. + """ + + +class EnhancedData(BaseModel): + token: str + """A unique identifier for the enhanced commercial data.""" + + common: Common + + event_token: str + """The token of the event that the enhanced data is associated with.""" + + fleet: List[Fleet] + + transaction_token: str + """The token of the transaction that the enhanced data is associated with.""" diff --git a/src/lithic/types/verification_method.py b/src/lithic/types/verification_method.py index 9972e037..008d53fb 100644 --- a/src/lithic/types/verification_method.py +++ b/src/lithic/types/verification_method.py @@ -1,7 +1,7 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias __all__ = ["VerificationMethod"] -VerificationMethod = Literal["MANUAL", "MICRO_DEPOSIT", "PLAID", "PRENOTE"] +VerificationMethod: TypeAlias = Literal["MANUAL", "MICRO_DEPOSIT", "PRENOTE", "EXTERNALLY_VERIFIED", "UNVERIFIED"] diff --git a/src/lithic/types/wallet_decisioning_info.py b/src/lithic/types/wallet_decisioning_info.py new file mode 100644 index 00000000..d75b3992 --- /dev/null +++ b/src/lithic/types/wallet_decisioning_info.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["WalletDecisioningInfo"] + + +class WalletDecisioningInfo(BaseModel): + account_score: Optional[str] = None + """Score given to the account by the Wallet Provider""" + + device_score: Optional[str] = None + """Score given to the device by the Wallet Provider""" + + recommended_decision: Optional[str] = None + """The decision recommended by the Wallet Provider""" + + recommendation_reasons: Optional[List[str]] = None + """ + Reasons provided to the Wallet Provider on how the recommended decision was + reached + """ diff --git a/src/lithic/types/wire_party_details.py b/src/lithic/types/wire_party_details.py new file mode 100644 index 00000000..46caa4a2 --- /dev/null +++ b/src/lithic/types/wire_party_details.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["WirePartyDetails"] + + +class WirePartyDetails(BaseModel): + account_number: Optional[str] = None + """Account number""" + + agent_id: Optional[str] = None + """Routing number or BIC of the financial institution""" + + agent_name: Optional[str] = None + """Name of the financial institution""" + + name: Optional[str] = None + """Name of the person or company""" diff --git a/tests/__init__.py b/tests/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/__init__.py b/tests/api_resources/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/__init__.py +++ b/tests/api_resources/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/accounts/__init__.py b/tests/api_resources/accounts/__init__.py deleted file mode 100644 index 1016754e..00000000 --- a/tests/api_resources/accounts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. diff --git a/tests/api_resources/accounts/test_credit_configurations.py b/tests/api_resources/accounts/test_credit_configurations.py deleted file mode 100644 index e6e1c3f9..00000000 --- a/tests/api_resources/accounts/test_credit_configurations.py +++ /dev/null @@ -1,196 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from lithic import Lithic, AsyncLithic -from tests.utils import assert_matches_type -from lithic.types import BusinessAccount - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestCreditConfigurations: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_retrieve(self, client: Lithic) -> None: - credit_configuration = client.accounts.credit_configurations.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Lithic) -> None: - response = client.accounts.credit_configurations.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - credit_configuration = response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Lithic) -> None: - with client.accounts.credit_configurations.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - credit_configuration = response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Lithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_token` but received ''"): - client.accounts.credit_configurations.with_raw_response.retrieve( - "", - ) - - @parametrize - def test_method_update(self, client: Lithic) -> None: - credit_configuration = client.accounts.credit_configurations.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - def test_method_update_with_all_params(self, client: Lithic) -> None: - credit_configuration = client.accounts.credit_configurations.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - billing_period=0, - credit_limit=0, - external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - payment_period=0, - ) - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - def test_raw_response_update(self, client: Lithic) -> None: - response = client.accounts.credit_configurations.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - credit_configuration = response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - def test_streaming_response_update(self, client: Lithic) -> None: - with client.accounts.credit_configurations.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - credit_configuration = response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_update(self, client: Lithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_token` but received ''"): - client.accounts.credit_configurations.with_raw_response.update( - "", - ) - - -class TestAsyncCreditConfigurations: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_retrieve(self, async_client: AsyncLithic) -> None: - credit_configuration = await async_client.accounts.credit_configurations.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: - response = await async_client.accounts.credit_configurations.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - credit_configuration = response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: - async with async_client.accounts.credit_configurations.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - credit_configuration = await response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_token` but received ''"): - await async_client.accounts.credit_configurations.with_raw_response.retrieve( - "", - ) - - @parametrize - async def test_method_update(self, async_client: AsyncLithic) -> None: - credit_configuration = await async_client.accounts.credit_configurations.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: - credit_configuration = await async_client.accounts.credit_configurations.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - billing_period=0, - credit_limit=0, - external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - payment_period=0, - ) - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - async def test_raw_response_update(self, async_client: AsyncLithic) -> None: - response = await async_client.accounts.credit_configurations.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - credit_configuration = response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - @parametrize - async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: - async with async_client.accounts.credit_configurations.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - credit_configuration = await response.parse() - assert_matches_type(BusinessAccount, credit_configuration, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_update(self, async_client: AsyncLithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_token` but received ''"): - await async_client.accounts.credit_configurations.with_raw_response.update( - "", - ) diff --git a/tests/api_resources/auth_rules/__init__.py b/tests/api_resources/auth_rules/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/auth_rules/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/auth_rules/test_v2.py b/tests/api_resources/auth_rules/test_v2.py new file mode 100644 index 00000000..3167da4d --- /dev/null +++ b/tests/api_resources/auth_rules/test_v2.py @@ -0,0 +1,1402 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic._utils import parse_date +from lithic.pagination import SyncCursorPage, AsyncCursorPage +from lithic.types.auth_rules import ( + AuthRule, + V2RetrieveReportResponse, + V2RetrieveFeaturesResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestV2: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create_overload_1(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + business_account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + event_stream="AUTHORIZATION", + name="name", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_create_overload_1(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_1(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_2(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_2(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + event_stream="AUTHORIZATION", + name="name", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_create_overload_2(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_2(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_3(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_3(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + event_stream="AUTHORIZATION", + excluded_card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_create_overload_3(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_3(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update_overload_1(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_method_update_with_all_params_overload_1(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + business_account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + state="INACTIVE", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_update_overload_1(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_update_overload_1(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_overload_1(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.update( + auth_rule_token="", + ) + + @parametrize + def test_method_update_overload_2(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_method_update_with_all_params_overload_2(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + state="INACTIVE", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_update_overload_2(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_update_overload_2(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_overload_2(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.update( + auth_rule_token="", + ) + + @parametrize + def test_method_update_overload_3(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_method_update_with_all_params_overload_3(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + excluded_card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + program_level=True, + state="INACTIVE", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_update_overload_3(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_update_overload_3(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_overload_3(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.update( + auth_rule_token="", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.list() + assert_matches_type(SyncCursorPage[AuthRule], v2, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ending_before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_stream="AUTHORIZATION", + event_streams=["AUTHORIZATION"], + page_size=1, + scope="PROGRAM", + starting_after="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncCursorPage[AuthRule], v2, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(SyncCursorPage[AuthRule], v2, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(SyncCursorPage[AuthRule], v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert v2 is None + + @parametrize + def test_raw_response_delete(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert v2 is None + + @parametrize + def test_streaming_response_delete(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert v2 is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_draft(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_method_draft_with_all_params(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_draft(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_draft(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_draft(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.draft( + auth_rule_token="", + ) + + @parametrize + def test_method_promote(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.promote( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_raw_response_promote(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.promote( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + def test_streaming_response_promote(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.promote( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_promote(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.promote( + "", + ) + + @parametrize + def test_method_retrieve_features(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + @parametrize + def test_method_retrieve_features_with_all_params(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + @parametrize + def test_raw_response_retrieve_features(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_features(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_features(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.retrieve_features( + auth_rule_token="", + ) + + @parametrize + def test_method_retrieve_report(self, client: Lithic) -> None: + v2 = client.auth_rules.v2.retrieve_report( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) + assert_matches_type(V2RetrieveReportResponse, v2, path=["response"]) + + @parametrize + def test_raw_response_retrieve_report(self, client: Lithic) -> None: + response = client.auth_rules.v2.with_raw_response.retrieve_report( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(V2RetrieveReportResponse, v2, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_report(self, client: Lithic) -> None: + with client.auth_rules.v2.with_streaming_response.retrieve_report( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = response.parse() + assert_matches_type(V2RetrieveReportResponse, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_report(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.with_raw_response.retrieve_report( + auth_rule_token="", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) + + +class TestAsyncV2: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + business_account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + event_stream="AUTHORIZATION", + name="name", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_1(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_2(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + event_stream="AUTHORIZATION", + name="name", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.create( + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + type="CONDITIONAL_BLOCK", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_3(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_3(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + event_stream="AUTHORIZATION", + excluded_card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_3(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_3(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.create( + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + program_level=True, + type="CONDITIONAL_BLOCK", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update_overload_1(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_method_update_with_all_params_overload_1(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + business_account_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + state="INACTIVE", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_update_overload_1(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_update_overload_1(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_overload_1(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.update( + auth_rule_token="", + ) + + @parametrize + async def test_method_update_overload_2(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_method_update_with_all_params_overload_2(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + state="INACTIVE", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_update_overload_2(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_update_overload_2(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_overload_2(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.update( + auth_rule_token="", + ) + + @parametrize + async def test_method_update_overload_3(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_method_update_with_all_params_overload_3(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + excluded_card_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + name="name", + program_level=True, + state="INACTIVE", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_update_overload_3(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_update_overload_3(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.update( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_overload_3(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.update( + auth_rule_token="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.list() + assert_matches_type(AsyncCursorPage[AuthRule], v2, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ending_before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_stream="AUTHORIZATION", + event_streams=["AUTHORIZATION"], + page_size=1, + scope="PROGRAM", + starting_after="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncCursorPage[AuthRule], v2, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AsyncCursorPage[AuthRule], v2, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AsyncCursorPage[AuthRule], v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert v2 is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert v2 is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert v2 is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_draft(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_method_draft_with_all_params(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + parameters={ + "conditions": [ + { + "attribute": "MCC", + "operation": "IS_ONE_OF", + "value": "string", + } + ] + }, + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_draft(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_draft(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.draft( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_draft(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.draft( + auth_rule_token="", + ) + + @parametrize + async def test_method_promote(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.promote( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_raw_response_promote(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.promote( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + @parametrize + async def test_streaming_response_promote(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.promote( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(AuthRule, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_promote(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.promote( + "", + ) + + @parametrize + async def test_method_retrieve_features(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + @parametrize + async def test_method_retrieve_features_with_all_params(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_features(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_features(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.retrieve_features( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(V2RetrieveFeaturesResponse, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_features(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.retrieve_features( + auth_rule_token="", + ) + + @parametrize + async def test_method_retrieve_report(self, async_client: AsyncLithic) -> None: + v2 = await async_client.auth_rules.v2.retrieve_report( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) + assert_matches_type(V2RetrieveReportResponse, v2, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_report(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.with_raw_response.retrieve_report( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + v2 = response.parse() + assert_matches_type(V2RetrieveReportResponse, v2, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_report(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.with_streaming_response.retrieve_report( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + v2 = await response.parse() + assert_matches_type(V2RetrieveReportResponse, v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_report(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.with_raw_response.retrieve_report( + auth_rule_token="", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ) diff --git a/tests/api_resources/auth_rules/v2/__init__.py b/tests/api_resources/auth_rules/v2/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/auth_rules/v2/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/auth_rules/v2/test_backtests.py b/tests/api_resources/auth_rules/v2/test_backtests.py new file mode 100644 index 00000000..851c4ff2 --- /dev/null +++ b/tests/api_resources/auth_rules/v2/test_backtests.py @@ -0,0 +1,219 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic._utils import parse_datetime +from lithic.types.auth_rules.v2 import BacktestResults, BacktestCreateResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBacktests: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + backtest = client.auth_rules.v2.backtests.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Lithic) -> None: + backtest = client.auth_rules.v2.backtests.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + start=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.auth_rules.v2.backtests.with_raw_response.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + backtest = response.parse() + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.auth_rules.v2.backtests.with_streaming_response.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + backtest = response.parse() + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.backtests.with_raw_response.create( + auth_rule_token="", + ) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + backtest = client.auth_rules.v2.backtests.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BacktestResults, backtest, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.auth_rules.v2.backtests.with_raw_response.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + backtest = response.parse() + assert_matches_type(BacktestResults, backtest, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.auth_rules.v2.backtests.with_streaming_response.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + backtest = response.parse() + assert_matches_type(BacktestResults, backtest, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + client.auth_rules.v2.backtests.with_raw_response.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="", + ) + + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `auth_rule_backtest_token` but received ''" + ): + client.auth_rules.v2.backtests.with_raw_response.retrieve( + auth_rule_backtest_token="", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncBacktests: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + backtest = await async_client.auth_rules.v2.backtests.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: + backtest = await async_client.auth_rules.v2.backtests.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + start=parse_datetime("2019-12-27T18:11:19.117Z"), + ) + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.backtests.with_raw_response.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + backtest = response.parse() + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.backtests.with_streaming_response.create( + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + backtest = await response.parse() + assert_matches_type(BacktestCreateResponse, backtest, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.backtests.with_raw_response.create( + auth_rule_token="", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + backtest = await async_client.auth_rules.v2.backtests.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BacktestResults, backtest, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.auth_rules.v2.backtests.with_raw_response.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + backtest = response.parse() + assert_matches_type(BacktestResults, backtest, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.auth_rules.v2.backtests.with_streaming_response.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + backtest = await response.parse() + assert_matches_type(BacktestResults, backtest, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): + await async_client.auth_rules.v2.backtests.with_raw_response.retrieve( + auth_rule_backtest_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auth_rule_token="", + ) + + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `auth_rule_backtest_token` but received ''" + ): + await async_client.auth_rules.v2.backtests.with_raw_response.retrieve( + auth_rule_backtest_token="", + auth_rule_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/cards/__init__.py b/tests/api_resources/cards/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/cards/__init__.py +++ b/tests/api_resources/cards/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/cards/test_aggregate_balances.py b/tests/api_resources/cards/test_aggregate_balances.py deleted file mode 100644 index 57e508e7..00000000 --- a/tests/api_resources/cards/test_aggregate_balances.py +++ /dev/null @@ -1,89 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from lithic import Lithic, AsyncLithic -from tests.utils import assert_matches_type -from lithic.pagination import SyncSinglePage, AsyncSinglePage -from lithic.types.cards import AggregateBalanceListResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestAggregateBalances: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_list(self, client: Lithic) -> None: - aggregate_balance = client.cards.aggregate_balances.list() - assert_matches_type(SyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - @parametrize - def test_method_list_with_all_params(self, client: Lithic) -> None: - aggregate_balance = client.cards.aggregate_balances.list( - account_token="string", - business_account_token="string", - ) - assert_matches_type(SyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - @parametrize - def test_raw_response_list(self, client: Lithic) -> None: - response = client.cards.aggregate_balances.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - aggregate_balance = response.parse() - assert_matches_type(SyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - @parametrize - def test_streaming_response_list(self, client: Lithic) -> None: - with client.cards.aggregate_balances.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - aggregate_balance = response.parse() - assert_matches_type(SyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncAggregateBalances: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_list(self, async_client: AsyncLithic) -> None: - aggregate_balance = await async_client.cards.aggregate_balances.list() - assert_matches_type(AsyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: - aggregate_balance = await async_client.cards.aggregate_balances.list( - account_token="string", - business_account_token="string", - ) - assert_matches_type(AsyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - @parametrize - async def test_raw_response_list(self, async_client: AsyncLithic) -> None: - response = await async_client.cards.aggregate_balances.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - aggregate_balance = response.parse() - assert_matches_type(AsyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - @parametrize - async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: - async with async_client.cards.aggregate_balances.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - aggregate_balance = await response.parse() - assert_matches_type(AsyncSinglePage[AggregateBalanceListResponse], aggregate_balance, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/cards/test_balances.py b/tests/api_resources/cards/test_balances.py index 2fab472e..87a8896f 100644 --- a/tests/api_resources/cards/test_balances.py +++ b/tests/api_resources/cards/test_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,7 +9,7 @@ from lithic import Lithic, AsyncLithic from tests.utils import assert_matches_type -from lithic.types import Balance +from lithic.types import FinancialAccountBalance from lithic._utils import parse_datetime from lithic.pagination import SyncSinglePage, AsyncSinglePage @@ -22,40 +22,40 @@ class TestBalances: @parametrize def test_method_list(self, client: Lithic) -> None: balance = client.cards.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: balance = client.cards.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", balance_date=parse_datetime("2019-12-27T18:11:19.117Z"), last_transaction_event_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize def test_raw_response_list(self, client: Lithic) -> None: response = client.cards.balances.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = response.parse() - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize def test_streaming_response_list(self, client: Lithic) -> None: with client.cards.balances.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = response.parse() - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) assert cast(Any, response.is_closed) is True @@ -63,50 +63,52 @@ def test_streaming_response_list(self, client: Lithic) -> None: def test_path_params_list(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): client.cards.balances.with_raw_response.list( - "", + card_token="", ) class TestAsyncBalances: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: balance = await async_client.cards.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: balance = await async_client.cards.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", balance_date=parse_datetime("2019-12-27T18:11:19.117Z"), last_transaction_event_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncLithic) -> None: response = await async_client.cards.balances.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = response.parse() - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async with async_client.cards.balances.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = await response.parse() - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) assert cast(Any, response.is_closed) is True @@ -114,5 +116,5 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async def test_path_params_list(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): await async_client.cards.balances.with_raw_response.list( - "", + card_token="", ) diff --git a/tests/api_resources/cards/test_financial_transactions.py b/tests/api_resources/cards/test_financial_transactions.py index 3d580cee..16c512b6 100644 --- a/tests/api_resources/cards/test_financial_transactions.py +++ b/tests/api_resources/cards/test_financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -22,7 +22,7 @@ class TestFinancialTransactions: @parametrize def test_method_retrieve(self, client: Lithic) -> None: financial_transaction = client.cards.financial_transactions.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(FinancialTransaction, financial_transaction, path=["response"]) @@ -30,7 +30,7 @@ def test_method_retrieve(self, client: Lithic) -> None: @parametrize def test_raw_response_retrieve(self, client: Lithic) -> None: response = client.cards.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -42,7 +42,7 @@ def test_raw_response_retrieve(self, client: Lithic) -> None: @parametrize def test_streaming_response_retrieve(self, client: Lithic) -> None: with client.cards.financial_transactions.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -57,7 +57,7 @@ def test_streaming_response_retrieve(self, client: Lithic) -> None: def test_path_params_retrieve(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): client.cards.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="", ) @@ -65,27 +65,27 @@ def test_path_params_retrieve(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_transaction_token` but received ''" ): client.cards.financial_transactions.with_raw_response.retrieve( - "", + financial_transaction_token="", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize def test_method_list(self, client: Lithic) -> None: financial_transaction = client.cards.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(SyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: financial_transaction = client.cards.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), category="CARD", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", result="APPROVED", - starting_after="string", + starting_after="starting_after", status="DECLINED", ) assert_matches_type(SyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @@ -93,7 +93,7 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_list(self, client: Lithic) -> None: response = client.cards.financial_transactions.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -104,7 +104,7 @@ def test_raw_response_list(self, client: Lithic) -> None: @parametrize def test_streaming_response_list(self, client: Lithic) -> None: with client.cards.financial_transactions.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -118,17 +118,19 @@ def test_streaming_response_list(self, client: Lithic) -> None: def test_path_params_list(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): client.cards.financial_transactions.with_raw_response.list( - "", + card_token="", ) class TestAsyncFinancialTransactions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: financial_transaction = await async_client.cards.financial_transactions.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(FinancialTransaction, financial_transaction, path=["response"]) @@ -136,7 +138,7 @@ async def test_method_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: response = await async_client.cards.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -148,7 +150,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: async with async_client.cards.financial_transactions.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -163,7 +165,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> N async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): await async_client.cards.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_token="", ) @@ -171,27 +173,27 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_transaction_token` but received ''" ): await async_client.cards.financial_transactions.with_raw_response.retrieve( - "", + financial_transaction_token="", card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: financial_transaction = await async_client.cards.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AsyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: financial_transaction = await async_client.cards.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), category="CARD", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", result="APPROVED", - starting_after="string", + starting_after="starting_after", status="DECLINED", ) assert_matches_type(AsyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @@ -199,7 +201,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N @parametrize async def test_raw_response_list(self, async_client: AsyncLithic) -> None: response = await async_client.cards.financial_transactions.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -210,7 +212,7 @@ async def test_raw_response_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async with async_client.cards.financial_transactions.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -224,5 +226,5 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async def test_path_params_list(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): await async_client.cards.financial_transactions.with_raw_response.list( - "", + card_token="", ) diff --git a/tests/api_resources/credit_products/__init__.py b/tests/api_resources/credit_products/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/credit_products/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/credit_products/test_extended_credit.py b/tests/api_resources/credit_products/test_extended_credit.py new file mode 100644 index 00000000..15338e53 --- /dev/null +++ b/tests/api_resources/credit_products/test_extended_credit.py @@ -0,0 +1,100 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types.credit_products import ExtendedCredit + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExtendedCredit: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + extended_credit = client.credit_products.extended_credit.retrieve( + "credit_product_token", + ) + assert_matches_type(ExtendedCredit, extended_credit, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.credit_products.extended_credit.with_raw_response.retrieve( + "credit_product_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + extended_credit = response.parse() + assert_matches_type(ExtendedCredit, extended_credit, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.credit_products.extended_credit.with_streaming_response.retrieve( + "credit_product_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + extended_credit = response.parse() + assert_matches_type(ExtendedCredit, extended_credit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `credit_product_token` but received ''"): + client.credit_products.extended_credit.with_raw_response.retrieve( + "", + ) + + +class TestAsyncExtendedCredit: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + extended_credit = await async_client.credit_products.extended_credit.retrieve( + "credit_product_token", + ) + assert_matches_type(ExtendedCredit, extended_credit, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.credit_products.extended_credit.with_raw_response.retrieve( + "credit_product_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + extended_credit = response.parse() + assert_matches_type(ExtendedCredit, extended_credit, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.credit_products.extended_credit.with_streaming_response.retrieve( + "credit_product_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + extended_credit = await response.parse() + assert_matches_type(ExtendedCredit, extended_credit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `credit_product_token` but received ''"): + await async_client.credit_products.extended_credit.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/credit_products/test_prime_rates.py b/tests/api_resources/credit_products/test_prime_rates.py new file mode 100644 index 00000000..1c328156 --- /dev/null +++ b/tests/api_resources/credit_products/test_prime_rates.py @@ -0,0 +1,211 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic._utils import parse_date +from lithic.types.credit_products import PrimeRateRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPrimeRates: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + prime_rate = client.credit_products.prime_rates.create( + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) + assert prime_rate is None + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.credit_products.prime_rates.with_raw_response.create( + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + prime_rate = response.parse() + assert prime_rate is None + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.credit_products.prime_rates.with_streaming_response.create( + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + prime_rate = response.parse() + assert prime_rate is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `credit_product_token` but received ''"): + client.credit_products.prime_rates.with_raw_response.create( + credit_product_token="", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + prime_rate = client.credit_products.prime_rates.retrieve( + credit_product_token="credit_product_token", + ) + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Lithic) -> None: + prime_rate = client.credit_products.prime_rates.retrieve( + credit_product_token="credit_product_token", + ending_before=parse_date("2019-12-27"), + starting_after=parse_date("2019-12-27"), + ) + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.credit_products.prime_rates.with_raw_response.retrieve( + credit_product_token="credit_product_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + prime_rate = response.parse() + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.credit_products.prime_rates.with_streaming_response.retrieve( + credit_product_token="credit_product_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + prime_rate = response.parse() + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `credit_product_token` but received ''"): + client.credit_products.prime_rates.with_raw_response.retrieve( + credit_product_token="", + ) + + +class TestAsyncPrimeRates: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + prime_rate = await async_client.credit_products.prime_rates.create( + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) + assert prime_rate is None + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.credit_products.prime_rates.with_raw_response.create( + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + prime_rate = response.parse() + assert prime_rate is None + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.credit_products.prime_rates.with_streaming_response.create( + credit_product_token="credit_product_token", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + prime_rate = await response.parse() + assert prime_rate is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `credit_product_token` but received ''"): + await async_client.credit_products.prime_rates.with_raw_response.create( + credit_product_token="", + effective_date=parse_date("2019-12-27"), + rate="rate", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + prime_rate = await async_client.credit_products.prime_rates.retrieve( + credit_product_token="credit_product_token", + ) + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncLithic) -> None: + prime_rate = await async_client.credit_products.prime_rates.retrieve( + credit_product_token="credit_product_token", + ending_before=parse_date("2019-12-27"), + starting_after=parse_date("2019-12-27"), + ) + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.credit_products.prime_rates.with_raw_response.retrieve( + credit_product_token="credit_product_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + prime_rate = response.parse() + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.credit_products.prime_rates.with_streaming_response.retrieve( + credit_product_token="credit_product_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + prime_rate = await response.parse() + assert_matches_type(PrimeRateRetrieveResponse, prime_rate, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `credit_product_token` but received ''"): + await async_client.credit_products.prime_rates.with_raw_response.retrieve( + credit_product_token="", + ) diff --git a/tests/api_resources/events/__init__.py b/tests/api_resources/events/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/events/__init__.py +++ b/tests/api_resources/events/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/events/test_event_subscriptions.py b/tests/api_resources/events/test_event_subscriptions.py new file mode 100644 index 00000000..710e7893 --- /dev/null +++ b/tests/api_resources/events/test_event_subscriptions.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEventSubscriptions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_resend(self, client: Lithic) -> None: + event_subscription = client.events.event_subscriptions.resend( + event_subscription_token="event_subscription_token", + event_token="event_token", + ) + assert event_subscription is None + + @parametrize + def test_raw_response_resend(self, client: Lithic) -> None: + response = client.events.event_subscriptions.with_raw_response.resend( + event_subscription_token="event_subscription_token", + event_token="event_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + event_subscription = response.parse() + assert event_subscription is None + + @parametrize + def test_streaming_response_resend(self, client: Lithic) -> None: + with client.events.event_subscriptions.with_streaming_response.resend( + event_subscription_token="event_subscription_token", + event_token="event_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + event_subscription = response.parse() + assert event_subscription is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_resend(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `event_token` but received ''"): + client.events.event_subscriptions.with_raw_response.resend( + event_subscription_token="event_subscription_token", + event_token="", + ) + + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" + ): + client.events.event_subscriptions.with_raw_response.resend( + event_subscription_token="", + event_token="event_token", + ) + + +class TestAsyncEventSubscriptions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_resend(self, async_client: AsyncLithic) -> None: + event_subscription = await async_client.events.event_subscriptions.resend( + event_subscription_token="event_subscription_token", + event_token="event_token", + ) + assert event_subscription is None + + @parametrize + async def test_raw_response_resend(self, async_client: AsyncLithic) -> None: + response = await async_client.events.event_subscriptions.with_raw_response.resend( + event_subscription_token="event_subscription_token", + event_token="event_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + event_subscription = response.parse() + assert event_subscription is None + + @parametrize + async def test_streaming_response_resend(self, async_client: AsyncLithic) -> None: + async with async_client.events.event_subscriptions.with_streaming_response.resend( + event_subscription_token="event_subscription_token", + event_token="event_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + event_subscription = await response.parse() + assert event_subscription is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_resend(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `event_token` but received ''"): + await async_client.events.event_subscriptions.with_raw_response.resend( + event_subscription_token="event_subscription_token", + event_token="", + ) + + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" + ): + await async_client.events.event_subscriptions.with_raw_response.resend( + event_subscription_token="", + event_token="event_token", + ) diff --git a/tests/api_resources/events/test_subscriptions.py b/tests/api_resources/events/test_subscriptions.py index adab0a0d..0a87188d 100644 --- a/tests/api_resources/events/test_subscriptions.py +++ b/tests/api_resources/events/test_subscriptions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -33,9 +33,9 @@ def test_method_create(self, client: Lithic) -> None: def test_method_create_with_all_params(self, client: Lithic) -> None: subscription = client.events.subscriptions.create( url="https://example.com", - description="string", + description="description", disabled=True, - event_types=["account_holder.created", "account_holder.updated", "account_holder.verification"], + event_types=["account_holder_document.updated"], ) assert_matches_type(EventSubscription, subscription, path=["response"]) @@ -66,14 +66,14 @@ def test_streaming_response_create(self, client: Lithic) -> None: @parametrize def test_method_retrieve(self, client: Lithic) -> None: subscription = client.events.subscriptions.retrieve( - "string", + "event_subscription_token", ) assert_matches_type(EventSubscription, subscription, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.retrieve( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -84,7 +84,7 @@ def test_raw_response_retrieve(self, client: Lithic) -> None: @parametrize def test_streaming_response_retrieve(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.retrieve( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -106,7 +106,7 @@ def test_path_params_retrieve(self, client: Lithic) -> None: @parametrize def test_method_update(self, client: Lithic) -> None: subscription = client.events.subscriptions.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", ) assert_matches_type(EventSubscription, subscription, path=["response"]) @@ -114,18 +114,18 @@ def test_method_update(self, client: Lithic) -> None: @parametrize def test_method_update_with_all_params(self, client: Lithic) -> None: subscription = client.events.subscriptions.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", - description="string", + description="description", disabled=True, - event_types=["account_holder.created", "account_holder.updated", "account_holder.verification"], + event_types=["account_holder_document.updated"], ) assert_matches_type(EventSubscription, subscription, path=["response"]) @parametrize def test_raw_response_update(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", ) @@ -137,7 +137,7 @@ def test_raw_response_update(self, client: Lithic) -> None: @parametrize def test_streaming_response_update(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", ) as response: assert not response.is_closed @@ -154,7 +154,7 @@ def test_path_params_update(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): client.events.subscriptions.with_raw_response.update( - "", + event_subscription_token="", url="https://example.com", ) @@ -166,9 +166,9 @@ def test_method_list(self, client: Lithic) -> None: @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: subscription = client.events.subscriptions.list( - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(SyncCursorPage[EventSubscription], subscription, path=["response"]) @@ -192,19 +192,19 @@ def test_streaming_response_list(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_method_delete(self, client: Lithic) -> None: subscription = client.events.subscriptions.delete( - "string", + "event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_raw_response_delete(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.delete( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -212,11 +212,11 @@ def test_raw_response_delete(self, client: Lithic) -> None: subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_streaming_response_delete(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.delete( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -226,7 +226,7 @@ def test_streaming_response_delete(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_path_params_delete(self, client: Lithic) -> None: with pytest.raises( @@ -239,19 +239,19 @@ def test_path_params_delete(self, client: Lithic) -> None: @parametrize def test_method_list_attempts(self, client: Lithic) -> None: subscription = client.events.subscriptions.list_attempts( - "string", + event_subscription_token="event_subscription_token", ) assert_matches_type(SyncCursorPage[MessageAttempt], subscription, path=["response"]) @parametrize def test_method_list_attempts_with_all_params(self, client: Lithic) -> None: subscription = client.events.subscriptions.list_attempts( - "string", + event_subscription_token="event_subscription_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", status="FAILED", ) assert_matches_type(SyncCursorPage[MessageAttempt], subscription, path=["response"]) @@ -259,7 +259,7 @@ def test_method_list_attempts_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_list_attempts(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.list_attempts( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -270,7 +270,7 @@ def test_raw_response_list_attempts(self, client: Lithic) -> None: @parametrize def test_streaming_response_list_attempts(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.list_attempts( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -286,32 +286,32 @@ def test_path_params_list_attempts(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): client.events.subscriptions.with_raw_response.list_attempts( - "", + event_subscription_token="", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_method_recover(self, client: Lithic) -> None: subscription = client.events.subscriptions.recover( - "string", + event_subscription_token="event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_method_recover_with_all_params(self, client: Lithic) -> None: subscription = client.events.subscriptions.recover( - "string", + event_subscription_token="event_subscription_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_raw_response_recover(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.recover( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -319,11 +319,11 @@ def test_raw_response_recover(self, client: Lithic) -> None: subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_streaming_response_recover(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.recover( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -333,39 +333,39 @@ def test_streaming_response_recover(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_path_params_recover(self, client: Lithic) -> None: with pytest.raises( ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): client.events.subscriptions.with_raw_response.recover( - "", + event_subscription_token="", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_method_replay_missing(self, client: Lithic) -> None: subscription = client.events.subscriptions.replay_missing( - "string", + event_subscription_token="event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_method_replay_missing_with_all_params(self, client: Lithic) -> None: subscription = client.events.subscriptions.replay_missing( - "string", + event_subscription_token="event_subscription_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_raw_response_replay_missing(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.replay_missing( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -373,11 +373,11 @@ def test_raw_response_replay_missing(self, client: Lithic) -> None: subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_streaming_response_replay_missing(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.replay_missing( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -387,27 +387,27 @@ def test_streaming_response_replay_missing(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_path_params_replay_missing(self, client: Lithic) -> None: with pytest.raises( ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): client.events.subscriptions.with_raw_response.replay_missing( - "", + event_subscription_token="", ) @parametrize def test_method_retrieve_secret(self, client: Lithic) -> None: subscription = client.events.subscriptions.retrieve_secret( - "string", + "event_subscription_token", ) assert_matches_type(SubscriptionRetrieveSecretResponse, subscription, path=["response"]) @parametrize def test_raw_response_retrieve_secret(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.retrieve_secret( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -418,7 +418,7 @@ def test_raw_response_retrieve_secret(self, client: Lithic) -> None: @parametrize def test_streaming_response_retrieve_secret(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.retrieve_secret( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -437,19 +437,19 @@ def test_path_params_retrieve_secret(self, client: Lithic) -> None: "", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_method_rotate_secret(self, client: Lithic) -> None: subscription = client.events.subscriptions.rotate_secret( - "string", + "event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_raw_response_rotate_secret(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.rotate_secret( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -457,11 +457,11 @@ def test_raw_response_rotate_secret(self, client: Lithic) -> None: subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_streaming_response_rotate_secret(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.rotate_secret( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -471,7 +471,7 @@ def test_streaming_response_rotate_secret(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize def test_path_params_rotate_secret(self, client: Lithic) -> None: with pytest.raises( @@ -484,22 +484,22 @@ def test_path_params_rotate_secret(self, client: Lithic) -> None: @parametrize def test_method_send_simulated_example(self, client: Lithic) -> None: subscription = client.events.subscriptions.send_simulated_example( - "string", + event_subscription_token="event_subscription_token", ) assert subscription is None @parametrize def test_method_send_simulated_example_with_all_params(self, client: Lithic) -> None: subscription = client.events.subscriptions.send_simulated_example( - "string", - event_type="account_holder.created", + event_subscription_token="event_subscription_token", + event_type="account_holder_document.updated", ) assert subscription is None @parametrize def test_raw_response_send_simulated_example(self, client: Lithic) -> None: response = client.events.subscriptions.with_raw_response.send_simulated_example( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -510,7 +510,7 @@ def test_raw_response_send_simulated_example(self, client: Lithic) -> None: @parametrize def test_streaming_response_send_simulated_example(self, client: Lithic) -> None: with client.events.subscriptions.with_streaming_response.send_simulated_example( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -526,12 +526,14 @@ def test_path_params_send_simulated_example(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): client.events.subscriptions.with_raw_response.send_simulated_example( - "", + event_subscription_token="", ) class TestAsyncSubscriptions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: @@ -544,9 +546,9 @@ async def test_method_create(self, async_client: AsyncLithic) -> None: async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.create( url="https://example.com", - description="string", + description="description", disabled=True, - event_types=["account_holder.created", "account_holder.updated", "account_holder.verification"], + event_types=["account_holder_document.updated"], ) assert_matches_type(EventSubscription, subscription, path=["response"]) @@ -577,14 +579,14 @@ async def test_streaming_response_create(self, async_client: AsyncLithic) -> Non @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.retrieve( - "string", + "event_subscription_token", ) assert_matches_type(EventSubscription, subscription, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.retrieve( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -595,7 +597,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.retrieve( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -617,7 +619,7 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", ) assert_matches_type(EventSubscription, subscription, path=["response"]) @@ -625,18 +627,18 @@ async def test_method_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", - description="string", + description="description", disabled=True, - event_types=["account_holder.created", "account_holder.updated", "account_holder.verification"], + event_types=["account_holder_document.updated"], ) assert_matches_type(EventSubscription, subscription, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", ) @@ -648,7 +650,7 @@ async def test_raw_response_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.update( - "string", + event_subscription_token="event_subscription_token", url="https://example.com", ) as response: assert not response.is_closed @@ -665,7 +667,7 @@ async def test_path_params_update(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): await async_client.events.subscriptions.with_raw_response.update( - "", + event_subscription_token="", url="https://example.com", ) @@ -677,9 +679,9 @@ async def test_method_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.list( - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(AsyncCursorPage[EventSubscription], subscription, path=["response"]) @@ -703,19 +705,19 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_method_delete(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.delete( - "string", + "event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_raw_response_delete(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.delete( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -723,11 +725,11 @@ async def test_raw_response_delete(self, async_client: AsyncLithic) -> None: subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_streaming_response_delete(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.delete( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -737,7 +739,7 @@ async def test_streaming_response_delete(self, async_client: AsyncLithic) -> Non assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_path_params_delete(self, async_client: AsyncLithic) -> None: with pytest.raises( @@ -750,19 +752,19 @@ async def test_path_params_delete(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_list_attempts(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.list_attempts( - "string", + event_subscription_token="event_subscription_token", ) assert_matches_type(AsyncCursorPage[MessageAttempt], subscription, path=["response"]) @parametrize async def test_method_list_attempts_with_all_params(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.list_attempts( - "string", + event_subscription_token="event_subscription_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", status="FAILED", ) assert_matches_type(AsyncCursorPage[MessageAttempt], subscription, path=["response"]) @@ -770,7 +772,7 @@ async def test_method_list_attempts_with_all_params(self, async_client: AsyncLit @parametrize async def test_raw_response_list_attempts(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.list_attempts( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -781,7 +783,7 @@ async def test_raw_response_list_attempts(self, async_client: AsyncLithic) -> No @parametrize async def test_streaming_response_list_attempts(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.list_attempts( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -797,32 +799,32 @@ async def test_path_params_list_attempts(self, async_client: AsyncLithic) -> Non ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): await async_client.events.subscriptions.with_raw_response.list_attempts( - "", + event_subscription_token="", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_method_recover(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.recover( - "string", + event_subscription_token="event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_method_recover_with_all_params(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.recover( - "string", + event_subscription_token="event_subscription_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_raw_response_recover(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.recover( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -830,11 +832,11 @@ async def test_raw_response_recover(self, async_client: AsyncLithic) -> None: subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_streaming_response_recover(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.recover( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -844,39 +846,39 @@ async def test_streaming_response_recover(self, async_client: AsyncLithic) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_path_params_recover(self, async_client: AsyncLithic) -> None: with pytest.raises( ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): await async_client.events.subscriptions.with_raw_response.recover( - "", + event_subscription_token="", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_method_replay_missing(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.replay_missing( - "string", + event_subscription_token="event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_method_replay_missing_with_all_params(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.replay_missing( - "string", + event_subscription_token="event_subscription_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_raw_response_replay_missing(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.replay_missing( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -884,11 +886,11 @@ async def test_raw_response_replay_missing(self, async_client: AsyncLithic) -> N subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_streaming_response_replay_missing(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.replay_missing( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -898,27 +900,27 @@ async def test_streaming_response_replay_missing(self, async_client: AsyncLithic assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_path_params_replay_missing(self, async_client: AsyncLithic) -> None: with pytest.raises( ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): await async_client.events.subscriptions.with_raw_response.replay_missing( - "", + event_subscription_token="", ) @parametrize async def test_method_retrieve_secret(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.retrieve_secret( - "string", + "event_subscription_token", ) assert_matches_type(SubscriptionRetrieveSecretResponse, subscription, path=["response"]) @parametrize async def test_raw_response_retrieve_secret(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.retrieve_secret( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -929,7 +931,7 @@ async def test_raw_response_retrieve_secret(self, async_client: AsyncLithic) -> @parametrize async def test_streaming_response_retrieve_secret(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.retrieve_secret( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -948,19 +950,19 @@ async def test_path_params_retrieve_secret(self, async_client: AsyncLithic) -> N "", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_method_rotate_secret(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.rotate_secret( - "string", + "event_subscription_token", ) assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_raw_response_rotate_secret(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.rotate_secret( - "string", + "event_subscription_token", ) assert response.is_closed is True @@ -968,11 +970,11 @@ async def test_raw_response_rotate_secret(self, async_client: AsyncLithic) -> No subscription = response.parse() assert subscription is None - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_streaming_response_rotate_secret(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.rotate_secret( - "string", + "event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -982,7 +984,7 @@ async def test_streaming_response_rotate_secret(self, async_client: AsyncLithic) assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + @pytest.mark.skip(reason="Prism Mock server doesn't want Accept header, but server requires it.") @parametrize async def test_path_params_rotate_secret(self, async_client: AsyncLithic) -> None: with pytest.raises( @@ -995,22 +997,22 @@ async def test_path_params_rotate_secret(self, async_client: AsyncLithic) -> Non @parametrize async def test_method_send_simulated_example(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.send_simulated_example( - "string", + event_subscription_token="event_subscription_token", ) assert subscription is None @parametrize async def test_method_send_simulated_example_with_all_params(self, async_client: AsyncLithic) -> None: subscription = await async_client.events.subscriptions.send_simulated_example( - "string", - event_type="account_holder.created", + event_subscription_token="event_subscription_token", + event_type="account_holder_document.updated", ) assert subscription is None @parametrize async def test_raw_response_send_simulated_example(self, async_client: AsyncLithic) -> None: response = await async_client.events.subscriptions.with_raw_response.send_simulated_example( - "string", + event_subscription_token="event_subscription_token", ) assert response.is_closed is True @@ -1021,7 +1023,7 @@ async def test_raw_response_send_simulated_example(self, async_client: AsyncLith @parametrize async def test_streaming_response_send_simulated_example(self, async_client: AsyncLithic) -> None: async with async_client.events.subscriptions.with_streaming_response.send_simulated_example( - "string", + event_subscription_token="event_subscription_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1037,5 +1039,5 @@ async def test_path_params_send_simulated_example(self, async_client: AsyncLithi ValueError, match=r"Expected a non-empty value for `event_subscription_token` but received ''" ): await async_client.events.subscriptions.with_raw_response.send_simulated_example( - "", + event_subscription_token="", ) diff --git a/tests/api_resources/external_bank_accounts/__init__.py b/tests/api_resources/external_bank_accounts/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/external_bank_accounts/__init__.py +++ b/tests/api_resources/external_bank_accounts/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/external_bank_accounts/test_micro_deposits.py b/tests/api_resources/external_bank_accounts/test_micro_deposits.py index 98d2ef7b..8513afa9 100644 --- a/tests/api_resources/external_bank_accounts/test_micro_deposits.py +++ b/tests/api_resources/external_bank_accounts/test_micro_deposits.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -20,16 +20,16 @@ class TestMicroDeposits: @parametrize def test_method_create(self, client: Lithic) -> None: micro_deposit = client.external_bank_accounts.micro_deposits.create( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - micro_deposits=[0, 0, 0], + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + micro_deposits=[0, 0], ) assert_matches_type(MicroDepositCreateResponse, micro_deposit, path=["response"]) @parametrize def test_raw_response_create(self, client: Lithic) -> None: response = client.external_bank_accounts.micro_deposits.with_raw_response.create( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - micro_deposits=[0, 0, 0], + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + micro_deposits=[0, 0], ) assert response.is_closed is True @@ -40,8 +40,8 @@ def test_raw_response_create(self, client: Lithic) -> None: @parametrize def test_streaming_response_create(self, client: Lithic) -> None: with client.external_bank_accounts.micro_deposits.with_streaming_response.create( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - micro_deposits=[0, 0, 0], + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + micro_deposits=[0, 0], ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -57,27 +57,29 @@ def test_path_params_create(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" ): client.external_bank_accounts.micro_deposits.with_raw_response.create( - "", - micro_deposits=[0, 0, 0], + external_bank_account_token="", + micro_deposits=[0, 0], ) class TestAsyncMicroDeposits: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: micro_deposit = await async_client.external_bank_accounts.micro_deposits.create( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - micro_deposits=[0, 0, 0], + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + micro_deposits=[0, 0], ) assert_matches_type(MicroDepositCreateResponse, micro_deposit, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncLithic) -> None: response = await async_client.external_bank_accounts.micro_deposits.with_raw_response.create( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - micro_deposits=[0, 0, 0], + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + micro_deposits=[0, 0], ) assert response.is_closed is True @@ -88,8 +90,8 @@ async def test_raw_response_create(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: async with async_client.external_bank_accounts.micro_deposits.with_streaming_response.create( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - micro_deposits=[0, 0, 0], + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + micro_deposits=[0, 0], ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -105,6 +107,6 @@ async def test_path_params_create(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" ): await async_client.external_bank_accounts.micro_deposits.with_raw_response.create( - "", - micro_deposits=[0, 0, 0], + external_bank_account_token="", + micro_deposits=[0, 0], ) diff --git a/tests/api_resources/financial_accounts/__init__.py b/tests/api_resources/financial_accounts/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/financial_accounts/__init__.py +++ b/tests/api_resources/financial_accounts/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/financial_accounts/statements/__init__.py b/tests/api_resources/financial_accounts/statements/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/financial_accounts/statements/__init__.py +++ b/tests/api_resources/financial_accounts/statements/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/financial_accounts/statements/test_line_items.py b/tests/api_resources/financial_accounts/statements/test_line_items.py index b6be34e2..3b5fd860 100644 --- a/tests/api_resources/financial_accounts/statements/test_line_items.py +++ b/tests/api_resources/financial_accounts/statements/test_line_items.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -10,7 +10,7 @@ from lithic import Lithic, AsyncLithic from tests.utils import assert_matches_type from lithic.pagination import SyncCursorPage, AsyncCursorPage -from lithic.types.financial_accounts.statements import LineItemListResponse +from lithic.types.financial_accounts.statements.statement_line_items import Data base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,45 +21,45 @@ class TestLineItems: @parametrize def test_method_list(self, client: Lithic) -> None: line_item = client.financial_accounts.statements.line_items.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(SyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(SyncCursorPage[Data], line_item, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: line_item = client.financial_accounts.statements.line_items.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) - assert_matches_type(SyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(SyncCursorPage[Data], line_item, path=["response"]) @parametrize def test_raw_response_list(self, client: Lithic) -> None: response = client.financial_accounts.statements.line_items.with_raw_response.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" line_item = response.parse() - assert_matches_type(SyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(SyncCursorPage[Data], line_item, path=["response"]) @parametrize def test_streaming_response_list(self, client: Lithic) -> None: with client.financial_accounts.statements.line_items.with_streaming_response.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" line_item = response.parse() - assert_matches_type(SyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(SyncCursorPage[Data], line_item, path=["response"]) assert cast(Any, response.is_closed) is True @@ -69,62 +69,64 @@ def test_path_params_list(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): client.financial_accounts.statements.line_items.with_raw_response.list( - "string", + statement_token="statement_token", financial_account_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `statement_token` but received ''"): client.financial_accounts.statements.line_items.with_raw_response.list( - "", + statement_token="", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) class TestAsyncLineItems: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: line_item = await async_client.financial_accounts.statements.line_items.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AsyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(AsyncCursorPage[Data], line_item, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: line_item = await async_client.financial_accounts.statements.line_items.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) - assert_matches_type(AsyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(AsyncCursorPage[Data], line_item, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.statements.line_items.with_raw_response.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" line_item = response.parse() - assert_matches_type(AsyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(AsyncCursorPage[Data], line_item, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.statements.line_items.with_streaming_response.list( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" line_item = await response.parse() - assert_matches_type(AsyncCursorPage[LineItemListResponse], line_item, path=["response"]) + assert_matches_type(AsyncCursorPage[Data], line_item, path=["response"]) assert cast(Any, response.is_closed) is True @@ -134,12 +136,12 @@ async def test_path_params_list(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): await async_client.financial_accounts.statements.line_items.with_raw_response.list( - "string", + statement_token="statement_token", financial_account_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `statement_token` but received ''"): await async_client.financial_accounts.statements.line_items.with_raw_response.list( - "", + statement_token="", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) diff --git a/tests/api_resources/financial_accounts/test_balances.py b/tests/api_resources/financial_accounts/test_balances.py index f5b2f846..2d2680e7 100644 --- a/tests/api_resources/financial_accounts/test_balances.py +++ b/tests/api_resources/financial_accounts/test_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,7 +9,7 @@ from lithic import Lithic, AsyncLithic from tests.utils import assert_matches_type -from lithic.types import Balance +from lithic.types import FinancialAccountBalance from lithic._utils import parse_datetime from lithic.pagination import SyncSinglePage, AsyncSinglePage @@ -22,40 +22,40 @@ class TestBalances: @parametrize def test_method_list(self, client: Lithic) -> None: balance = client.financial_accounts.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: balance = client.financial_accounts.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", balance_date=parse_datetime("2019-12-27T18:11:19.117Z"), last_transaction_event_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize def test_raw_response_list(self, client: Lithic) -> None: response = client.financial_accounts.balances.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = response.parse() - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize def test_streaming_response_list(self, client: Lithic) -> None: with client.financial_accounts.balances.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = response.parse() - assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(SyncSinglePage[FinancialAccountBalance], balance, path=["response"]) assert cast(Any, response.is_closed) is True @@ -65,50 +65,52 @@ def test_path_params_list(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): client.financial_accounts.balances.with_raw_response.list( - "", + financial_account_token="", ) class TestAsyncBalances: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: balance = await async_client.financial_accounts.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: balance = await async_client.financial_accounts.balances.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", balance_date=parse_datetime("2019-12-27T18:11:19.117Z"), last_transaction_event_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.balances.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = response.parse() - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.balances.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" balance = await response.parse() - assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) + assert_matches_type(AsyncSinglePage[FinancialAccountBalance], balance, path=["response"]) assert cast(Any, response.is_closed) is True @@ -118,5 +120,5 @@ async def test_path_params_list(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): await async_client.financial_accounts.balances.with_raw_response.list( - "", + financial_account_token="", ) diff --git a/tests/api_resources/financial_accounts/test_credit_configuration.py b/tests/api_resources/financial_accounts/test_credit_configuration.py new file mode 100644 index 00000000..9cd59b59 --- /dev/null +++ b/tests/api_resources/financial_accounts/test_credit_configuration.py @@ -0,0 +1,208 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types.financial_accounts import FinancialAccountCreditConfig + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCreditConfiguration: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + credit_configuration = client.financial_accounts.credit_configuration.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.financial_accounts.credit_configuration.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_configuration = response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.financial_accounts.credit_configuration.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_configuration = response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.credit_configuration.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Lithic) -> None: + credit_configuration = client.financial_accounts.credit_configuration.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Lithic) -> None: + credit_configuration = client.financial_accounts.credit_configuration.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auto_collection_configuration={"auto_collection_enabled": True}, + credit_limit=0, + credit_product_token="credit_product_token", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + tier="x", + ) + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Lithic) -> None: + response = client.financial_accounts.credit_configuration.with_raw_response.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_configuration = response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Lithic) -> None: + with client.financial_accounts.credit_configuration.with_streaming_response.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_configuration = response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.credit_configuration.with_raw_response.update( + financial_account_token="", + ) + + +class TestAsyncCreditConfiguration: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + credit_configuration = await async_client.financial_accounts.credit_configuration.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.credit_configuration.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_configuration = response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.credit_configuration.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_configuration = await response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.credit_configuration.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncLithic) -> None: + credit_configuration = await async_client.financial_accounts.credit_configuration.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: + credit_configuration = await async_client.financial_accounts.credit_configuration.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + auto_collection_configuration={"auto_collection_enabled": True}, + credit_limit=0, + credit_product_token="credit_product_token", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + tier="x", + ) + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.credit_configuration.with_raw_response.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_configuration = response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.credit_configuration.with_streaming_response.update( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_configuration = await response.parse() + assert_matches_type(FinancialAccountCreditConfig, credit_configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.credit_configuration.with_raw_response.update( + financial_account_token="", + ) diff --git a/tests/api_resources/financial_accounts/test_financial_transactions.py b/tests/api_resources/financial_accounts/test_financial_transactions.py index 4d97ef36..7bc8a301 100644 --- a/tests/api_resources/financial_accounts/test_financial_transactions.py +++ b/tests/api_resources/financial_accounts/test_financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -22,7 +22,7 @@ class TestFinancialTransactions: @parametrize def test_method_retrieve(self, client: Lithic) -> None: financial_transaction = client.financial_accounts.financial_transactions.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(FinancialTransaction, financial_transaction, path=["response"]) @@ -30,7 +30,7 @@ def test_method_retrieve(self, client: Lithic) -> None: @parametrize def test_raw_response_retrieve(self, client: Lithic) -> None: response = client.financial_accounts.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -42,7 +42,7 @@ def test_raw_response_retrieve(self, client: Lithic) -> None: @parametrize def test_streaming_response_retrieve(self, client: Lithic) -> None: with client.financial_accounts.financial_transactions.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -59,7 +59,7 @@ def test_path_params_retrieve(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): client.financial_accounts.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="", ) @@ -67,27 +67,27 @@ def test_path_params_retrieve(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_transaction_token` but received ''" ): client.financial_accounts.financial_transactions.with_raw_response.retrieve( - "", + financial_transaction_token="", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize def test_method_list(self, client: Lithic) -> None: financial_transaction = client.financial_accounts.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(SyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: financial_transaction = client.financial_accounts.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), category="ACH", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", result="APPROVED", - starting_after="string", + starting_after="starting_after", status="DECLINED", ) assert_matches_type(SyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @@ -95,7 +95,7 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_list(self, client: Lithic) -> None: response = client.financial_accounts.financial_transactions.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -106,7 +106,7 @@ def test_raw_response_list(self, client: Lithic) -> None: @parametrize def test_streaming_response_list(self, client: Lithic) -> None: with client.financial_accounts.financial_transactions.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -122,17 +122,19 @@ def test_path_params_list(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): client.financial_accounts.financial_transactions.with_raw_response.list( - "", + financial_account_token="", ) class TestAsyncFinancialTransactions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: financial_transaction = await async_client.financial_accounts.financial_transactions.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(FinancialTransaction, financial_transaction, path=["response"]) @@ -140,7 +142,7 @@ async def test_method_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -152,7 +154,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.financial_transactions.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -169,7 +171,7 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): await async_client.financial_accounts.financial_transactions.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_token="", ) @@ -177,27 +179,27 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_transaction_token` but received ''" ): await async_client.financial_accounts.financial_transactions.with_raw_response.retrieve( - "", + financial_transaction_token="", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: financial_transaction = await async_client.financial_accounts.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AsyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: financial_transaction = await async_client.financial_accounts.financial_transactions.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), category="ACH", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", result="APPROVED", - starting_after="string", + starting_after="starting_after", status="DECLINED", ) assert_matches_type(AsyncSinglePage[FinancialTransaction], financial_transaction, path=["response"]) @@ -205,7 +207,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N @parametrize async def test_raw_response_list(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.financial_transactions.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -216,7 +218,7 @@ async def test_raw_response_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.financial_transactions.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -232,5 +234,5 @@ async def test_path_params_list(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): await async_client.financial_accounts.financial_transactions.with_raw_response.list( - "", + financial_account_token="", ) diff --git a/tests/api_resources/financial_accounts/test_loan_tapes.py b/tests/api_resources/financial_accounts/test_loan_tapes.py new file mode 100644 index 00000000..dd935ec9 --- /dev/null +++ b/tests/api_resources/financial_accounts/test_loan_tapes.py @@ -0,0 +1,230 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic._utils import parse_date +from lithic.pagination import SyncCursorPage, AsyncCursorPage +from lithic.types.financial_accounts import LoanTape + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestLoanTapes: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + loan_tape = client.financial_accounts.loan_tapes.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(LoanTape, loan_tape, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.financial_accounts.loan_tapes.with_raw_response.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + loan_tape = response.parse() + assert_matches_type(LoanTape, loan_tape, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.financial_accounts.loan_tapes.with_streaming_response.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + loan_tape = response.parse() + assert_matches_type(LoanTape, loan_tape, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.loan_tapes.with_raw_response.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `loan_tape_token` but received ''"): + client.financial_accounts.loan_tapes.with_raw_response.retrieve( + loan_tape_token="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + loan_tape = client.financial_accounts.loan_tapes.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncCursorPage[LoanTape], loan_tape, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + loan_tape = client.financial_accounts.loan_tapes.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ending_before="ending_before", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(SyncCursorPage[LoanTape], loan_tape, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.financial_accounts.loan_tapes.with_raw_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + loan_tape = response.parse() + assert_matches_type(SyncCursorPage[LoanTape], loan_tape, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.financial_accounts.loan_tapes.with_streaming_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + loan_tape = response.parse() + assert_matches_type(SyncCursorPage[LoanTape], loan_tape, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.loan_tapes.with_raw_response.list( + financial_account_token="", + ) + + +class TestAsyncLoanTapes: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + loan_tape = await async_client.financial_accounts.loan_tapes.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(LoanTape, loan_tape, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.loan_tapes.with_raw_response.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + loan_tape = response.parse() + assert_matches_type(LoanTape, loan_tape, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.loan_tapes.with_streaming_response.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + loan_tape = await response.parse() + assert_matches_type(LoanTape, loan_tape, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.loan_tapes.with_raw_response.retrieve( + loan_tape_token="loan_tape_token", + financial_account_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `loan_tape_token` but received ''"): + await async_client.financial_accounts.loan_tapes.with_raw_response.retrieve( + loan_tape_token="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + loan_tape = await async_client.financial_accounts.loan_tapes.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncCursorPage[LoanTape], loan_tape, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + loan_tape = await async_client.financial_accounts.loan_tapes.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_date("2019-12-27"), + end=parse_date("2019-12-27"), + ending_before="ending_before", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(AsyncCursorPage[LoanTape], loan_tape, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.loan_tapes.with_raw_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + loan_tape = response.parse() + assert_matches_type(AsyncCursorPage[LoanTape], loan_tape, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.loan_tapes.with_streaming_response.list( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + loan_tape = await response.parse() + assert_matches_type(AsyncCursorPage[LoanTape], loan_tape, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.loan_tapes.with_raw_response.list( + financial_account_token="", + ) diff --git a/tests/api_resources/financial_accounts/test_statements.py b/tests/api_resources/financial_accounts/test_statements.py index 7af808f3..f7f27d48 100644 --- a/tests/api_resources/financial_accounts/test_statements.py +++ b/tests/api_resources/financial_accounts/test_statements.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -22,7 +22,7 @@ class TestStatements: @parametrize def test_method_retrieve(self, client: Lithic) -> None: statement = client.financial_accounts.statements.retrieve( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Statement, statement, path=["response"]) @@ -30,7 +30,7 @@ def test_method_retrieve(self, client: Lithic) -> None: @parametrize def test_raw_response_retrieve(self, client: Lithic) -> None: response = client.financial_accounts.statements.with_raw_response.retrieve( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -42,7 +42,7 @@ def test_raw_response_retrieve(self, client: Lithic) -> None: @parametrize def test_streaming_response_retrieve(self, client: Lithic) -> None: with client.financial_accounts.statements.with_streaming_response.retrieve( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -59,39 +59,40 @@ def test_path_params_retrieve(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): client.financial_accounts.statements.with_raw_response.retrieve( - "string", + statement_token="statement_token", financial_account_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `statement_token` but received ''"): client.financial_accounts.statements.with_raw_response.retrieve( - "", + statement_token="", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize def test_method_list(self, client: Lithic) -> None: statement = client.financial_accounts.statements.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(SyncCursorPage[Statement], statement, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: statement = client.financial_accounts.statements.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_date("2019-12-27"), end=parse_date("2019-12-27"), - ending_before="string", + ending_before="ending_before", + include_initial_statements=True, page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(SyncCursorPage[Statement], statement, path=["response"]) @parametrize def test_raw_response_list(self, client: Lithic) -> None: response = client.financial_accounts.statements.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -102,7 +103,7 @@ def test_raw_response_list(self, client: Lithic) -> None: @parametrize def test_streaming_response_list(self, client: Lithic) -> None: with client.financial_accounts.statements.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -118,17 +119,19 @@ def test_path_params_list(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): client.financial_accounts.statements.with_raw_response.list( - "", + financial_account_token="", ) class TestAsyncStatements: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: statement = await async_client.financial_accounts.statements.retrieve( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Statement, statement, path=["response"]) @@ -136,7 +139,7 @@ async def test_method_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.statements.with_raw_response.retrieve( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -148,7 +151,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.statements.with_streaming_response.retrieve( - "string", + statement_token="statement_token", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -165,39 +168,40 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): await async_client.financial_accounts.statements.with_raw_response.retrieve( - "string", + statement_token="statement_token", financial_account_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `statement_token` but received ''"): await async_client.financial_accounts.statements.with_raw_response.retrieve( - "", + statement_token="", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: statement = await async_client.financial_accounts.statements.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AsyncCursorPage[Statement], statement, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: statement = await async_client.financial_accounts.statements.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_date("2019-12-27"), end=parse_date("2019-12-27"), - ending_before="string", + ending_before="ending_before", + include_initial_statements=True, page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(AsyncCursorPage[Statement], statement, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.statements.with_raw_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -208,7 +212,7 @@ async def test_raw_response_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.statements.with_streaming_response.list( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -224,5 +228,5 @@ async def test_path_params_list(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): await async_client.financial_accounts.statements.with_raw_response.list( - "", + financial_account_token="", ) diff --git a/tests/api_resources/fraud/__init__.py b/tests/api_resources/fraud/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/fraud/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/fraud/test_transactions.py b/tests/api_resources/fraud/test_transactions.py new file mode 100644 index 00000000..73188a6e --- /dev/null +++ b/tests/api_resources/fraud/test_transactions.py @@ -0,0 +1,204 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types.fraud import TransactionReportResponse, TransactionRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestTransactions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + transaction = client.fraud.transactions.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + assert_matches_type(TransactionRetrieveResponse, transaction, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.fraud.transactions.with_raw_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert_matches_type(TransactionRetrieveResponse, transaction, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.fraud.transactions.with_streaming_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = response.parse() + assert_matches_type(TransactionRetrieveResponse, transaction, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + client.fraud.transactions.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_report(self, client: Lithic) -> None: + transaction = client.fraud.transactions.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + ) + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + @parametrize + def test_method_report_with_all_params(self, client: Lithic) -> None: + transaction = client.fraud.transactions.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + comment="comment", + fraud_type="FIRST_PARTY_FRAUD", + ) + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + @parametrize + def test_raw_response_report(self, client: Lithic) -> None: + response = client.fraud.transactions.with_raw_response.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + @parametrize + def test_streaming_response_report(self, client: Lithic) -> None: + with client.fraud.transactions.with_streaming_response.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = response.parse() + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_report(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + client.fraud.transactions.with_raw_response.report( + transaction_token="", + fraud_status="SUSPECTED_FRAUD", + ) + + +class TestAsyncTransactions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + transaction = await async_client.fraud.transactions.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + assert_matches_type(TransactionRetrieveResponse, transaction, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.fraud.transactions.with_raw_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert_matches_type(TransactionRetrieveResponse, transaction, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.fraud.transactions.with_streaming_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = await response.parse() + assert_matches_type(TransactionRetrieveResponse, transaction, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + await async_client.fraud.transactions.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_report(self, async_client: AsyncLithic) -> None: + transaction = await async_client.fraud.transactions.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + ) + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + @parametrize + async def test_method_report_with_all_params(self, async_client: AsyncLithic) -> None: + transaction = await async_client.fraud.transactions.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + comment="comment", + fraud_type="FIRST_PARTY_FRAUD", + ) + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + @parametrize + async def test_raw_response_report(self, async_client: AsyncLithic) -> None: + response = await async_client.fraud.transactions.with_raw_response.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + @parametrize + async def test_streaming_response_report(self, async_client: AsyncLithic) -> None: + async with async_client.fraud.transactions.with_streaming_response.report( + transaction_token="00000000-0000-0000-0000-000000000000", + fraud_status="SUSPECTED_FRAUD", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = await response.parse() + assert_matches_type(TransactionReportResponse, transaction, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_report(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + await async_client.fraud.transactions.with_raw_response.report( + transaction_token="", + fraud_status="SUSPECTED_FRAUD", + ) diff --git a/tests/api_resources/reports/__init__.py b/tests/api_resources/reports/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/reports/__init__.py +++ b/tests/api_resources/reports/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/reports/settlement/__init__.py b/tests/api_resources/reports/settlement/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/reports/settlement/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/reports/settlement/test_network_totals.py b/tests/api_resources/reports/settlement/test_network_totals.py new file mode 100644 index 00000000..256915ae --- /dev/null +++ b/tests/api_resources/reports/settlement/test_network_totals.py @@ -0,0 +1,186 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import NetworkTotal +from lithic._utils import parse_date, parse_datetime +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestNetworkTotals: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + network_total = client.reports.settlement.network_totals.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(NetworkTotal, network_total, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.reports.settlement.network_totals.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_total = response.parse() + assert_matches_type(NetworkTotal, network_total, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.reports.settlement.network_totals.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_total = response.parse() + assert_matches_type(NetworkTotal, network_total, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `token` but received ''"): + client.reports.settlement.network_totals.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + network_total = client.reports.settlement.network_totals.list() + assert_matches_type(SyncCursorPage[NetworkTotal], network_total, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + network_total = client.reports.settlement.network_totals.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + institution_id="institution_id", + network="VISA", + page_size=1, + report_date=parse_date("2019-12-27"), + report_date_begin=parse_date("2019-12-27"), + report_date_end=parse_date("2019-12-27"), + settlement_institution_id="settlement_institution_id", + starting_after="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SyncCursorPage[NetworkTotal], network_total, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.reports.settlement.network_totals.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_total = response.parse() + assert_matches_type(SyncCursorPage[NetworkTotal], network_total, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.reports.settlement.network_totals.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_total = response.parse() + assert_matches_type(SyncCursorPage[NetworkTotal], network_total, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncNetworkTotals: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + network_total = await async_client.reports.settlement.network_totals.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(NetworkTotal, network_total, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.reports.settlement.network_totals.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_total = response.parse() + assert_matches_type(NetworkTotal, network_total, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.reports.settlement.network_totals.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_total = await response.parse() + assert_matches_type(NetworkTotal, network_total, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `token` but received ''"): + await async_client.reports.settlement.network_totals.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + network_total = await async_client.reports.settlement.network_totals.list() + assert_matches_type(AsyncCursorPage[NetworkTotal], network_total, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + network_total = await async_client.reports.settlement.network_totals.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + institution_id="institution_id", + network="VISA", + page_size=1, + report_date=parse_date("2019-12-27"), + report_date_begin=parse_date("2019-12-27"), + report_date_end=parse_date("2019-12-27"), + settlement_institution_id="settlement_institution_id", + starting_after="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AsyncCursorPage[NetworkTotal], network_total, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.reports.settlement.network_totals.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_total = response.parse() + assert_matches_type(AsyncCursorPage[NetworkTotal], network_total, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.reports.settlement.network_totals.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_total = await response.parse() + assert_matches_type(AsyncCursorPage[NetworkTotal], network_total, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/reports/test_settlement.py b/tests/api_resources/reports/test_settlement.py index 5ee7842b..3e9243a4 100644 --- a/tests/api_resources/reports/test_settlement.py +++ b/tests/api_resources/reports/test_settlement.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -22,24 +22,24 @@ class TestSettlement: @parametrize def test_method_list_details(self, client: Lithic) -> None: settlement = client.reports.settlement.list_details( - parse_date("2019-12-27"), + report_date=parse_date("2023-09-01"), ) assert_matches_type(SyncCursorPage[SettlementDetail], settlement, path=["response"]) @parametrize def test_method_list_details_with_all_params(self, client: Lithic) -> None: settlement = client.reports.settlement.list_details( - parse_date("2019-12-27"), - ending_before="string", + report_date=parse_date("2023-09-01"), + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(SyncCursorPage[SettlementDetail], settlement, path=["response"]) @parametrize def test_raw_response_list_details(self, client: Lithic) -> None: response = client.reports.settlement.with_raw_response.list_details( - parse_date("2019-12-27"), + report_date=parse_date("2023-09-01"), ) assert response.is_closed is True @@ -50,7 +50,7 @@ def test_raw_response_list_details(self, client: Lithic) -> None: @parametrize def test_streaming_response_list_details(self, client: Lithic) -> None: with client.reports.settlement.with_streaming_response.list_details( - parse_date("2019-12-27"), + report_date=parse_date("2023-09-01"), ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -64,20 +64,20 @@ def test_streaming_response_list_details(self, client: Lithic) -> None: def test_path_params_list_details(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `report_date` but received ''"): client.reports.settlement.with_raw_response.list_details( - "", + report_date="", ) @parametrize def test_method_summary(self, client: Lithic) -> None: settlement = client.reports.settlement.summary( - parse_date("2019-12-27"), + parse_date("2023-09-01"), ) assert_matches_type(SettlementReport, settlement, path=["response"]) @parametrize def test_raw_response_summary(self, client: Lithic) -> None: response = client.reports.settlement.with_raw_response.summary( - parse_date("2019-12-27"), + parse_date("2023-09-01"), ) assert response.is_closed is True @@ -88,7 +88,7 @@ def test_raw_response_summary(self, client: Lithic) -> None: @parametrize def test_streaming_response_summary(self, client: Lithic) -> None: with client.reports.settlement.with_streaming_response.summary( - parse_date("2019-12-27"), + parse_date("2023-09-01"), ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -107,29 +107,31 @@ def test_path_params_summary(self, client: Lithic) -> None: class TestAsyncSettlement: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list_details(self, async_client: AsyncLithic) -> None: settlement = await async_client.reports.settlement.list_details( - parse_date("2019-12-27"), + report_date=parse_date("2023-09-01"), ) assert_matches_type(AsyncCursorPage[SettlementDetail], settlement, path=["response"]) @parametrize async def test_method_list_details_with_all_params(self, async_client: AsyncLithic) -> None: settlement = await async_client.reports.settlement.list_details( - parse_date("2019-12-27"), - ending_before="string", + report_date=parse_date("2023-09-01"), + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(AsyncCursorPage[SettlementDetail], settlement, path=["response"]) @parametrize async def test_raw_response_list_details(self, async_client: AsyncLithic) -> None: response = await async_client.reports.settlement.with_raw_response.list_details( - parse_date("2019-12-27"), + report_date=parse_date("2023-09-01"), ) assert response.is_closed is True @@ -140,7 +142,7 @@ async def test_raw_response_list_details(self, async_client: AsyncLithic) -> Non @parametrize async def test_streaming_response_list_details(self, async_client: AsyncLithic) -> None: async with async_client.reports.settlement.with_streaming_response.list_details( - parse_date("2019-12-27"), + report_date=parse_date("2023-09-01"), ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -154,20 +156,20 @@ async def test_streaming_response_list_details(self, async_client: AsyncLithic) async def test_path_params_list_details(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `report_date` but received ''"): await async_client.reports.settlement.with_raw_response.list_details( - "", + report_date="", ) @parametrize async def test_method_summary(self, async_client: AsyncLithic) -> None: settlement = await async_client.reports.settlement.summary( - parse_date("2019-12-27"), + parse_date("2023-09-01"), ) assert_matches_type(SettlementReport, settlement, path=["response"]) @parametrize async def test_raw_response_summary(self, async_client: AsyncLithic) -> None: response = await async_client.reports.settlement.with_raw_response.summary( - parse_date("2019-12-27"), + parse_date("2023-09-01"), ) assert response.is_closed is True @@ -178,7 +180,7 @@ async def test_raw_response_summary(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_summary(self, async_client: AsyncLithic) -> None: async with async_client.reports.settlement.with_streaming_response.summary( - parse_date("2019-12-27"), + parse_date("2023-09-01"), ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_account_activity.py b/tests/api_resources/test_account_activity.py new file mode 100644 index 00000000..f4af2163 --- /dev/null +++ b/tests/api_resources/test_account_activity.py @@ -0,0 +1,189 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import ( + AccountActivityListResponse, + AccountActivityRetrieveTransactionResponse, +) +from lithic._utils import parse_datetime +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAccountActivity: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + account_activity = client.account_activity.list() + assert_matches_type(SyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + account_activity = client.account_activity.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="ACH", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + result="APPROVED", + starting_after="starting_after", + status="DECLINED", + ) + assert_matches_type(SyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.account_activity.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_activity = response.parse() + assert_matches_type(SyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.account_activity.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_activity = response.parse() + assert_matches_type(SyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve_transaction(self, client: Lithic) -> None: + account_activity = client.account_activity.retrieve_transaction( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountActivityRetrieveTransactionResponse, account_activity, path=["response"]) + + @parametrize + def test_raw_response_retrieve_transaction(self, client: Lithic) -> None: + response = client.account_activity.with_raw_response.retrieve_transaction( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_activity = response.parse() + assert_matches_type(AccountActivityRetrieveTransactionResponse, account_activity, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_transaction(self, client: Lithic) -> None: + with client.account_activity.with_streaming_response.retrieve_transaction( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_activity = response.parse() + assert_matches_type(AccountActivityRetrieveTransactionResponse, account_activity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_transaction(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + client.account_activity.with_raw_response.retrieve_transaction( + "", + ) + + +class TestAsyncAccountActivity: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + account_activity = await async_client.account_activity.list() + assert_matches_type(AsyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + account_activity = await async_client.account_activity.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="ACH", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + result="APPROVED", + starting_after="starting_after", + status="DECLINED", + ) + assert_matches_type(AsyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.account_activity.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_activity = response.parse() + assert_matches_type(AsyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.account_activity.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_activity = await response.parse() + assert_matches_type(AsyncCursorPage[AccountActivityListResponse], account_activity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve_transaction(self, async_client: AsyncLithic) -> None: + account_activity = await async_client.account_activity.retrieve_transaction( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountActivityRetrieveTransactionResponse, account_activity, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_transaction(self, async_client: AsyncLithic) -> None: + response = await async_client.account_activity.with_raw_response.retrieve_transaction( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_activity = response.parse() + assert_matches_type(AccountActivityRetrieveTransactionResponse, account_activity, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_transaction(self, async_client: AsyncLithic) -> None: + async with async_client.account_activity.with_streaming_response.retrieve_transaction( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_activity = await response.parse() + assert_matches_type(AccountActivityRetrieveTransactionResponse, account_activity, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_transaction(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + await async_client.account_activity.with_raw_response.retrieve_transaction( + "", + ) diff --git a/tests/api_resources/test_account_holders.py b/tests/api_resources/test_account_holders.py index 76c01a0c..3c723a7d 100644 --- a/tests/api_resources/test_account_holders.py +++ b/tests/api_resources/test_account_holders.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -11,12 +11,14 @@ from tests.utils import assert_matches_type from lithic.types import ( AccountHolder, - AccountHolderDocument, AccountHolderCreateResponse, AccountHolderUpdateResponse, AccountHolderListDocumentsResponse, + AccountHolderSimulateEnrollmentReviewResponse, ) +from lithic._utils import parse_datetime from lithic.pagination import SyncSinglePage, AsyncSinglePage +from lithic.types.shared import Document base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -27,44 +29,6 @@ class TestAccountHolders: @parametrize def test_method_create_overload_1(self, client: Lithic) -> None: account_holder = client.account_holders.create( - beneficial_owner_entities=[ - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - ], beneficial_owner_individuals=[ { "address": { @@ -79,35 +43,7 @@ def test_method_create_overload_1(self, client: Lithic) -> None: "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, + } ], business_entity={ "address": { @@ -119,7 +55,7 @@ def test_method_create_overload_1(self, client: Lithic) -> None: }, "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], + "phone_numbers": ["+15555555555"], }, control_person={ "address": { @@ -144,146 +80,58 @@ def test_method_create_overload_1(self, client: Lithic) -> None: @parametrize def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: account_holder = client.account_holders.create( - beneficial_owner_entities=[ - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dba_business_name": "string", - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dba_business_name": "string", - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dba_business_name": "string", - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], - }, - ], beneficial_owner_individuals=[ { "address": { "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", - }, + "phone_number": "+15555555555", + } ], business_entity={ "address": { "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, - "dba_business_name": "string", "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], + "phone_numbers": ["+15555555555"], + "dba_business_name": "dba_business_name", + "parent_company": "parent_company", }, control_person={ "address": { "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, nature_of_business="Software company selling solutions to the restaurant industry", tos_timestamp="2018-05-29T21:16:05Z", workflow="KYB_BASIC", - external_id="string", - kyb_passed_timestamp="2018-05-29T21:16:05Z", - website_url="www.mybusiness.com", - ) - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) - - @parametrize - def test_raw_response_create_overload_1(self, client: Lithic) -> None: - response = client.account_holders.with_raw_response.create( beneficial_owner_entities=[ { "address": { @@ -292,36 +140,24 @@ def test_raw_response_create_overload_1(self, client: Lithic) -> None: "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, + "phone_numbers": ["+15555555555"], + "dba_business_name": "dba_business_name", + "parent_company": "parent_company", + } ], + external_id="external_id", + kyb_passed_timestamp="2018-05-29T21:16:05Z", + website_url="www.mybusiness.com", + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + def test_raw_response_create_overload_1(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.create( beneficial_owner_individuals=[ { "address": { @@ -336,35 +172,7 @@ def test_raw_response_create_overload_1(self, client: Lithic) -> None: "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, + } ], business_entity={ "address": { @@ -376,7 +184,7 @@ def test_raw_response_create_overload_1(self, client: Lithic) -> None: }, "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], + "phone_numbers": ["+15555555555"], }, control_person={ "address": { @@ -405,44 +213,6 @@ def test_raw_response_create_overload_1(self, client: Lithic) -> None: @parametrize def test_streaming_response_create_overload_1(self, client: Lithic) -> None: with client.account_holders.with_streaming_response.create( - beneficial_owner_entities=[ - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - ], beneficial_owner_individuals=[ { "address": { @@ -457,36 +227,125 @@ def test_streaming_response_create_overload_1(self, client: Lithic) -> None: "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", + } + ], + business_entity={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", + "government_id": "114-123-1513", + "legal_business_name": "Acme, Inc.", + "phone_numbers": ["+15555555555"], + }, + control_person={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + }, + nature_of_business="Software company selling solutions to the restaurant industry", + tos_timestamp="2018-05-29T21:16:05Z", + workflow="KYB_BASIC", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = response.parse() + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_2(self, client: Lithic) -> None: + account_holder = client.account_holders.create( + business_entity={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "legal_business_name": "Acme, Inc.", + }, + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_2(self, client: Lithic) -> None: + account_holder = client.account_holders.create( + business_entity={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + "address2": "address2", + }, + "legal_business_name": "Acme, Inc.", + "dba_business_name": "dba_business_name", + "government_id": "114-123-1513", + "parent_company": "parent_company", + "phone_numbers": ["+15555555555"], + }, + beneficial_owner_individuals=[ + { + "address": { + "address1": "123 Old Forest Way", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - }, + "phone_number": "+15555555555", + } ], + control_person={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + "address2": "address2", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", + }, + external_id="external_id", + nature_of_business="Software company selling solutions to the restaurant industry", + tos_timestamp="2022-03-08 08:00:00", + website_url="www.mybusiness.com", + workflow="KYB_DELEGATED", + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + def test_raw_response_create_overload_2(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.create( business_entity={ "address": { "address1": "123 Old Forest Way", @@ -495,11 +354,19 @@ def test_streaming_response_create_overload_1(self, client: Lithic) -> None: "postal_code": "68022", "state": "NE", }, - "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], }, - control_person={ + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_2(self, client: Lithic) -> None: + with client.account_holders.with_streaming_response.create( + business_entity={ "address": { "address1": "123 Old Forest Way", "city": "Omaha", @@ -507,15 +374,8 @@ def test_streaming_response_create_overload_1(self, client: Lithic) -> None: "postal_code": "68022", "state": "NE", }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", + "legal_business_name": "Acme, Inc.", }, - nature_of_business="Software company selling solutions to the restaurant industry", - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYB_BASIC", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -526,7 +386,7 @@ def test_streaming_response_create_overload_1(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_create_overload_2(self, client: Lithic) -> None: + def test_method_create_overload_3(self, client: Lithic) -> None: account_holder = client.account_holders.create( individual={ "address": { @@ -541,41 +401,41 @@ def test_method_create_overload_2(self, client: Lithic) -> None: "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, - tos_timestamp="string", - workflow="KYC_ADVANCED", + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - def test_method_create_with_all_params_overload_2(self, client: Lithic) -> None: + def test_method_create_with_all_params_overload_3(self, client: Lithic) -> None: account_holder = client.account_holders.create( individual={ "address": { "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, - tos_timestamp="string", - workflow="KYC_ADVANCED", - external_id="string", - kyc_passed_timestamp="string", + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", + external_id="external_id", + kyc_passed_timestamp="kyc_passed_timestamp", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - def test_raw_response_create_overload_2(self, client: Lithic) -> None: + def test_raw_response_create_overload_3(self, client: Lithic) -> None: response = client.account_holders.with_raw_response.create( individual={ "address": { @@ -590,10 +450,10 @@ def test_raw_response_create_overload_2(self, client: Lithic) -> None: "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, - tos_timestamp="string", - workflow="KYC_ADVANCED", + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", ) assert response.is_closed is True @@ -602,7 +462,7 @@ def test_raw_response_create_overload_2(self, client: Lithic) -> None: assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - def test_streaming_response_create_overload_2(self, client: Lithic) -> None: + def test_streaming_response_create_overload_3(self, client: Lithic) -> None: with client.account_holders.with_streaming_response.create( individual={ "address": { @@ -617,10 +477,10 @@ def test_streaming_response_create_overload_2(self, client: Lithic) -> None: "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, - tos_timestamp="string", - workflow="KYC_ADVANCED", + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -631,47 +491,61 @@ def test_streaming_response_create_overload_2(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_method_create_overload_3(self, client: Lithic) -> None: + def test_method_create_overload_4(self, client: Lithic) -> None: account_holder = client.account_holders.create( - email="string", - first_name="string", + address={ + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + email="email", + first_name="first_name", kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", + last_name="last_name", + phone_number="phone_number", workflow="KYC_EXEMPT", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - def test_method_create_with_all_params_overload_3(self, client: Lithic) -> None: + def test_method_create_with_all_params_overload_4(self, client: Lithic) -> None: account_holder = client.account_holders.create( - email="string", - first_name="string", - kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", - workflow="KYC_EXEMPT", address={ "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, - business_account_token="string", - external_id="string", + email="email", + first_name="first_name", + kyc_exemption_type="AUTHORIZED_USER", + last_name="last_name", + phone_number="phone_number", + workflow="KYC_EXEMPT", + business_account_token="business_account_token", + external_id="external_id", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - def test_raw_response_create_overload_3(self, client: Lithic) -> None: + def test_raw_response_create_overload_4(self, client: Lithic) -> None: response = client.account_holders.with_raw_response.create( - email="string", - first_name="string", + address={ + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + email="email", + first_name="first_name", kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", + last_name="last_name", + phone_number="phone_number", workflow="KYC_EXEMPT", ) @@ -681,13 +555,20 @@ def test_raw_response_create_overload_3(self, client: Lithic) -> None: assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - def test_streaming_response_create_overload_3(self, client: Lithic) -> None: + def test_streaming_response_create_overload_4(self, client: Lithic) -> None: with client.account_holders.with_streaming_response.create( - email="string", - first_name="string", + address={ + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + email="email", + first_name="first_name", kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", + last_name="last_name", + phone_number="phone_number", workflow="KYC_EXEMPT", ) as response: assert not response.is_closed @@ -737,26 +618,96 @@ def test_path_params_retrieve(self, client: Lithic) -> None: ) @parametrize - def test_method_update(self, client: Lithic) -> None: + def test_method_update_overload_1(self, client: Lithic) -> None: account_holder = client.account_holders.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - def test_method_update_with_all_params(self, client: Lithic) -> None: + def test_method_update_with_all_params_overload_1(self, client: Lithic) -> None: account_holder = client.account_holders.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - business_account_token="string", - email="string", - phone_number="string", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + beneficial_owner_entities=[ + { + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dba_business_name": "dba_business_name", + "government_id": "114-123-1513", + "legal_business_name": "Acme, Inc.", + "parent_company": "parent_company", + "phone_numbers": ["+15555555555"], + } + ], + beneficial_owner_individuals=[ + { + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", + } + ], + business_entity={ + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dba_business_name": "dba_business_name", + "government_id": "114-123-1513", + "legal_business_name": "Acme, Inc.", + "parent_company": "parent_company", + "phone_numbers": ["+15555555555"], + }, + control_person={ + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", + }, + external_id="external_id", + nature_of_business="Software company selling solutions to the restaurant industry", + website_url="www.mybusiness.com", ) assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - def test_raw_response_update(self, client: Lithic) -> None: + def test_raw_response_update_overload_1(self, client: Lithic) -> None: response = client.account_holders.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -765,9 +716,9 @@ def test_raw_response_update(self, client: Lithic) -> None: assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - def test_streaming_response_update(self, client: Lithic) -> None: + def test_streaming_response_update_overload_1(self, client: Lithic) -> None: with client.account_holders.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -778,222 +729,245 @@ def test_streaming_response_update(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_update(self, client: Lithic) -> None: + def test_path_params_update_overload_1(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): client.account_holders.with_raw_response.update( - "", + account_holder_token="", ) @parametrize - def test_method_list(self, client: Lithic) -> None: - account_holder = client.account_holders.list() - assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + def test_method_update_overload_2(self, client: Lithic) -> None: + account_holder = client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - def test_method_list_with_all_params(self, client: Lithic) -> None: - account_holder = client.account_holders.list( - ending_before="string", - external_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - limit=0, - starting_after="string", + def test_method_update_with_all_params_overload_2(self, client: Lithic) -> None: + account_holder = client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_id="external_id", + individual={ + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", + }, ) - assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - def test_raw_response_list(self, client: Lithic) -> None: - response = client.account_holders.with_raw_response.list() + def test_raw_response_update_overload_2(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - def test_streaming_response_list(self, client: Lithic) -> None: - with client.account_holders.with_streaming_response.list() as response: + def test_streaming_response_update_overload_2(self, client: Lithic) -> None: + with client.account_holders.with_streaming_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize - def test_method_list_documents(self, client: Lithic) -> None: - account_holder = client.account_holders.list_documents( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AccountHolderListDocumentsResponse, account_holder, path=["response"]) + def test_path_params_update_overload_2(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): + client.account_holders.with_raw_response.update( + account_holder_token="", + ) @parametrize - def test_raw_response_list_documents(self, client: Lithic) -> None: - response = client.account_holders.with_raw_response.list_documents( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + def test_method_update_overload_3(self, client: Lithic) -> None: + account_holder = client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) + + @parametrize + def test_method_update_with_all_params_overload_3(self, client: Lithic) -> None: + account_holder = client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + business_account_token="business_account_token", + email="email", + first_name="first_name", + last_name="last_name", + legal_business_name="legal_business_name", + phone_number="phone_number", + ) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) + + @parametrize + def test_raw_response_update_overload_3(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderListDocumentsResponse, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - def test_streaming_response_list_documents(self, client: Lithic) -> None: - with client.account_holders.with_streaming_response.list_documents( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + def test_streaming_response_update_overload_3(self, client: Lithic) -> None: + with client.account_holders.with_streaming_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderListDocumentsResponse, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_list_documents(self, client: Lithic) -> None: + def test_path_params_update_overload_3(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): - client.account_holders.with_raw_response.list_documents( - "", + client.account_holders.with_raw_response.update( + account_holder_token="", ) @parametrize - def test_method_resubmit(self, client: Lithic) -> None: - account_holder = client.account_holders.resubmit( + def test_method_list(self, client: Lithic) -> None: + account_holder = client.account_holders.list() + assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + account_holder = client.account_holders.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + email="email", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + external_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + first_name="first_name", + last_name="last_name", + legal_business_name="legal_business_name", + limit=0, + phone_number="phone_number", + starting_after="starting_after", + ) + assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.account_holders.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = response.parse() + assert_matches_type(SyncSinglePage[AccountHolder], account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_list_documents(self, client: Lithic) -> None: + account_holder = client.account_holders.list_documents( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", ) - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(AccountHolderListDocumentsResponse, account_holder, path=["response"]) @parametrize - def test_raw_response_resubmit(self, client: Lithic) -> None: - response = client.account_holders.with_raw_response.resubmit( + def test_raw_response_list_documents(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.list_documents( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(AccountHolderListDocumentsResponse, account_holder, path=["response"]) @parametrize - def test_streaming_response_resubmit(self, client: Lithic) -> None: - with client.account_holders.with_streaming_response.resubmit( + def test_streaming_response_list_documents(self, client: Lithic) -> None: + with client.account_holders.with_streaming_response.list_documents( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(AccountHolderListDocumentsResponse, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_resubmit(self, client: Lithic) -> None: + def test_path_params_list_documents(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): - client.account_holders.with_raw_response.resubmit( + client.account_holders.with_raw_response.list_documents( "", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", ) @parametrize def test_method_retrieve_document(self, client: Lithic) -> None: account_holder = client.account_holders.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize def test_raw_response_retrieve_document(self, client: Lithic) -> None: response = client.account_holders.with_raw_response.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize def test_streaming_response_retrieve_document(self, client: Lithic) -> None: with client.account_holders.with_streaming_response.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @@ -1001,47 +975,128 @@ def test_streaming_response_retrieve_document(self, client: Lithic) -> None: def test_path_params_retrieve_document(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): client.account_holders.with_raw_response.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", account_holder_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `document_token` but received ''"): client.account_holders.with_raw_response.retrieve_document( - "", + document_token="", account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) + @parametrize + def test_method_simulate_enrollment_document_review(self, client: Lithic) -> None: + account_holder = client.account_holders.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", + ) + assert_matches_type(Document, account_holder, path=["response"]) + + @parametrize + def test_method_simulate_enrollment_document_review_with_all_params(self, client: Lithic) -> None: + account_holder = client.account_holders.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", + accepted_entity_status_reasons=["string"], + status_reason="DOCUMENT_MISSING_REQUIRED_DATA", + ) + assert_matches_type(Document, account_holder, path=["response"]) + + @parametrize + def test_raw_response_simulate_enrollment_document_review(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(Document, account_holder, path=["response"]) + + @parametrize + def test_streaming_response_simulate_enrollment_document_review(self, client: Lithic) -> None: + with client.account_holders.with_streaming_response.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = response.parse() + assert_matches_type(Document, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_simulate_enrollment_review(self, client: Lithic) -> None: + account_holder = client.account_holders.simulate_enrollment_review() + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) + + @parametrize + def test_method_simulate_enrollment_review_with_all_params(self, client: Lithic) -> None: + account_holder = client.account_holders.simulate_enrollment_review( + account_holder_token="1415964d-4400-4d79-9fb3-eee0faaee4e4", + status="ACCEPTED", + status_reasons=["PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE"], + ) + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) + + @parametrize + def test_raw_response_simulate_enrollment_review(self, client: Lithic) -> None: + response = client.account_holders.with_raw_response.simulate_enrollment_review() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) + + @parametrize + def test_streaming_response_simulate_enrollment_review(self, client: Lithic) -> None: + with client.account_holders.with_streaming_response.simulate_enrollment_review() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = response.parse() + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_upload_document(self, client: Lithic) -> None: account_holder = client.account_holders.upload_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - document_type="drivers_license", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize def test_raw_response_upload_document(self, client: Lithic) -> None: response = client.account_holders.with_raw_response.upload_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - document_type="drivers_license", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize def test_streaming_response_upload_document(self, client: Lithic) -> None: with client.account_holders.with_streaming_response.upload_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - document_type="drivers_license", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @@ -1049,18 +1104,21 @@ def test_streaming_response_upload_document(self, client: Lithic) -> None: def test_path_params_upload_document(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): client.account_holders.with_raw_response.upload_document( - "", - document_type="drivers_license", + account_holder_token="", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) class TestAsyncAccountHolders: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None: account_holder = await async_client.account_holders.create( - beneficial_owner_entities=[ + beneficial_owner_individuals=[ { "address": { "address1": "123 Old Forest Way", @@ -1069,50 +1127,49 @@ async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None "postal_code": "68022", "state": "NE", }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + } + ], + business_entity={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - ], - beneficial_owner_individuals=[ - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", + "government_id": "114-123-1513", + "legal_business_name": "Acme, Inc.", + "phone_numbers": ["+15555555555"], + }, + control_person={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + }, + nature_of_business="Software company selling solutions to the restaurant industry", + tos_timestamp="2018-05-29T21:16:05Z", + workflow="KYB_BASIC", + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.create( + beneficial_owner_individuals=[ { "address": { "address1": "123 Old Forest Way", @@ -1120,27 +1177,15 @@ async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, + "phone_number": "+15555555555", + } ], business_entity={ "address": { @@ -1149,10 +1194,13 @@ async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], + "phone_numbers": ["+15555555555"], + "dba_business_name": "dba_business_name", + "parent_company": "parent_company", }, control_person={ "address": { @@ -1161,90 +1209,48 @@ async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", + "phone_number": "+15555555555", }, nature_of_business="Software company selling solutions to the restaurant industry", tos_timestamp="2018-05-29T21:16:05Z", workflow="KYB_BASIC", - ) - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) - - @parametrize - async def test_method_create_with_all_params_overload_1(self, async_client: AsyncLithic) -> None: - account_holder = await async_client.account_holders.create( beneficial_owner_entities=[ { "address": { "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dba_business_name": "string", - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dba_business_name": "string", - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, - "dba_business_name": "string", "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], - }, + "phone_numbers": ["+15555555555"], + "dba_business_name": "dba_business_name", + "parent_company": "parent_company", + } ], + external_id="external_id", + kyb_passed_timestamp="2018-05-29T21:16:05Z", + website_url="www.mybusiness.com", + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.create( beneficial_owner_individuals=[ { "address": { "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", @@ -1255,44 +1261,23 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - { - "address": { - "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, + } ], business_entity={ "address": { "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", }, - "dba_business_name": "string", "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "parent_company": "string", - "phone_numbers": ["+12124007676"], + "phone_numbers": ["+15555555555"], }, control_person={ "address": { "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", @@ -1303,58 +1288,20 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", }, nature_of_business="Software company selling solutions to the restaurant industry", tos_timestamp="2018-05-29T21:16:05Z", workflow="KYB_BASIC", - external_id="string", - kyb_passed_timestamp="2018-05-29T21:16:05Z", - website_url="www.mybusiness.com", ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) -> None: - response = await async_client.account_holders.with_raw_response.create( - beneficial_owner_entities=[ - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - ], + async def test_streaming_response_create_overload_1(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.create( beneficial_owner_individuals=[ { "address": { @@ -1369,35 +1316,7 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) - "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, + } ], business_entity={ "address": { @@ -1409,7 +1328,7 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) - }, "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], + "phone_numbers": ["+15555555555"], }, control_person={ "address": { @@ -1428,54 +1347,49 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) - nature_of_business="Software company selling solutions to the restaurant industry", tos_timestamp="2018-05-29T21:16:05Z", workflow="KYB_BASIC", - ) + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - account_holder = response.parse() - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + account_holder = await response.parse() + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True @parametrize - async def test_streaming_response_create_overload_1(self, async_client: AsyncLithic) -> None: - async with async_client.account_holders.with_streaming_response.create( - beneficial_owner_entities=[ - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], + async def test_method_create_overload_2(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.create( + business_entity={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "government_id": "114-123-1513", - "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], + "legal_business_name": "Acme, Inc.", + }, + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.create( + business_entity={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + "address2": "address2", }, - ], + "legal_business_name": "Acme, Inc.", + "dba_business_name": "dba_business_name", + "government_id": "114-123-1513", + "parent_company": "parent_company", + "phone_numbers": ["+15555555555"], + }, beneficial_owner_individuals=[ { "address": { @@ -1484,42 +1398,43 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncLit "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, - { - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - }, + "phone_number": "+15555555555", + } ], + control_person={ + "address": { + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + "address2": "address2", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", + }, + external_id="external_id", + nature_of_business="Software company selling solutions to the restaurant industry", + tos_timestamp="2022-03-08 08:00:00", + website_url="www.mybusiness.com", + workflow="KYB_DELEGATED", + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.create( business_entity={ "address": { "address1": "123 Old Forest Way", @@ -1528,11 +1443,19 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncLit "postal_code": "68022", "state": "NE", }, - "government_id": "114-123-1513", "legal_business_name": "Acme, Inc.", - "phone_numbers": ["+12124007676"], }, - control_person={ + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.create( + business_entity={ "address": { "address1": "123 Old Forest Way", "city": "Omaha", @@ -1540,15 +1463,8 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncLit "postal_code": "68022", "state": "NE", }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", + "legal_business_name": "Acme, Inc.", }, - nature_of_business="Software company selling solutions to the restaurant industry", - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYB_BASIC", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1559,7 +1475,7 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncLit assert cast(Any, response.is_closed) is True @parametrize - async def test_method_create_overload_2(self, async_client: AsyncLithic) -> None: + async def test_method_create_overload_3(self, async_client: AsyncLithic) -> None: account_holder = await async_client.account_holders.create( individual={ "address": { @@ -1574,41 +1490,41 @@ async def test_method_create_overload_2(self, async_client: AsyncLithic) -> None "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, - tos_timestamp="string", - workflow="KYC_ADVANCED", + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - async def test_method_create_with_all_params_overload_2(self, async_client: AsyncLithic) -> None: + async def test_method_create_with_all_params_overload_3(self, async_client: AsyncLithic) -> None: account_holder = await async_client.account_holders.create( individual={ "address": { "address1": "123 Old Forest Way", - "address2": "string", "city": "Omaha", "country": "USA", "postal_code": "68022", "state": "NE", + "address2": "address2", }, "dob": "1991-03-08 08:00:00", "email": "tom@middle-earth.com", "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, - tos_timestamp="string", - workflow="KYC_ADVANCED", - external_id="string", - kyc_passed_timestamp="string", + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", + external_id="external_id", + kyc_passed_timestamp="kyc_passed_timestamp", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - async def test_raw_response_create_overload_2(self, async_client: AsyncLithic) -> None: + async def test_raw_response_create_overload_3(self, async_client: AsyncLithic) -> None: response = await async_client.account_holders.with_raw_response.create( individual={ "address": { @@ -1623,10 +1539,10 @@ async def test_raw_response_create_overload_2(self, async_client: AsyncLithic) - "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", + "phone_number": "+15555555555", }, - tos_timestamp="string", - workflow="KYC_ADVANCED", + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", ) assert response.is_closed is True @@ -1635,7 +1551,7 @@ async def test_raw_response_create_overload_2(self, async_client: AsyncLithic) - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @parametrize - async def test_streaming_response_create_overload_2(self, async_client: AsyncLithic) -> None: + async def test_streaming_response_create_overload_3(self, async_client: AsyncLithic) -> None: async with async_client.account_holders.with_streaming_response.create( individual={ "address": { @@ -1650,146 +1566,359 @@ async def test_streaming_response_create_overload_2(self, async_client: AsyncLit "first_name": "Tom", "government_id": "111-23-1412", "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="string", - workflow="KYC_ADVANCED", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - account_holder = await response.parse() - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_create_overload_3(self, async_client: AsyncLithic) -> None: - account_holder = await async_client.account_holders.create( - email="string", - first_name="string", - kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", - workflow="KYC_EXEMPT", - ) - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) - - @parametrize - async def test_method_create_with_all_params_overload_3(self, async_client: AsyncLithic) -> None: - account_holder = await async_client.account_holders.create( - email="string", - first_name="string", - kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", - workflow="KYC_EXEMPT", - address={ - "address1": "123 Old Forest Way", - "address2": "string", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", + "phone_number": "+15555555555", + }, + tos_timestamp="tos_timestamp", + workflow="KYC_BASIC", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = await response.parse() + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_4(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.create( + address={ + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + email="email", + first_name="first_name", + kyc_exemption_type="AUTHORIZED_USER", + last_name="last_name", + phone_number="phone_number", + workflow="KYC_EXEMPT", + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_4(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.create( + address={ + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + "address2": "address2", + }, + email="email", + first_name="first_name", + kyc_exemption_type="AUTHORIZED_USER", + last_name="last_name", + phone_number="phone_number", + workflow="KYC_EXEMPT", + business_account_token="business_account_token", + external_id="external_id", + ) + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_4(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.create( + address={ + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + email="email", + first_name="first_name", + kyc_exemption_type="AUTHORIZED_USER", + last_name="last_name", + phone_number="phone_number", + workflow="KYC_EXEMPT", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_4(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.create( + address={ + "address1": "123 Old Forest Way", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + email="email", + first_name="first_name", + kyc_exemption_type="AUTHORIZED_USER", + last_name="last_name", + phone_number="phone_number", + workflow="KYC_EXEMPT", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = await response.parse() + assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountHolder, account_holder, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(AccountHolder, account_holder, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = await response.parse() + assert_matches_type(AccountHolder, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): + await async_client.account_holders.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update_overload_1(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) + + @parametrize + async def test_method_update_with_all_params_overload_1(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + beneficial_owner_entities=[ + { + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dba_business_name": "dba_business_name", + "government_id": "114-123-1513", + "legal_business_name": "Acme, Inc.", + "parent_company": "parent_company", + "phone_numbers": ["+15555555555"], + } + ], + beneficial_owner_individuals=[ + { + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", + } + ], + business_entity={ + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dba_business_name": "dba_business_name", + "government_id": "114-123-1513", + "legal_business_name": "Acme, Inc.", + "parent_company": "parent_company", + "phone_numbers": ["+15555555555"], + }, + control_person={ + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", }, - business_account_token="string", - external_id="string", + external_id="external_id", + nature_of_business="Software company selling solutions to the restaurant industry", + website_url="www.mybusiness.com", ) - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - async def test_raw_response_create_overload_3(self, async_client: AsyncLithic) -> None: - response = await async_client.account_holders.with_raw_response.create( - email="string", - first_name="string", - kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", - workflow="KYC_EXEMPT", + async def test_raw_response_update_overload_1(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - async def test_streaming_response_create_overload_3(self, async_client: AsyncLithic) -> None: - async with async_client.account_holders.with_streaming_response.create( - email="string", - first_name="string", - kyc_exemption_type="AUTHORIZED_USER", - last_name="string", - phone_number="string", - workflow="KYC_EXEMPT", + async def test_streaming_response_update_overload_1(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = await response.parse() - assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize - async def test_method_retrieve(self, async_client: AsyncLithic) -> None: - account_holder = await async_client.account_holders.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + async def test_path_params_update_overload_1(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): + await async_client.account_holders.with_raw_response.update( + account_holder_token="", + ) + + @parametrize + async def test_method_update_overload_2(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: - response = await async_client.account_holders.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + async def test_method_update_with_all_params_overload_2(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_id="external_id", + individual={ + "entity_token": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "address": { + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + "dob": "1991-03-08 08:00:00", + "email": "tom@middle-earth.com", + "first_name": "Tom", + "government_id": "111-23-1412", + "last_name": "Bombadil", + "phone_number": "+15555555555", + }, + ) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) + + @parametrize + async def test_raw_response_update_overload_2(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: - async with async_client.account_holders.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + async def test_streaming_response_update_overload_2(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.update( + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = await response.parse() - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + async def test_path_params_update_overload_2(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): - await async_client.account_holders.with_raw_response.retrieve( - "", + await async_client.account_holders.with_raw_response.update( + account_holder_token="", ) @parametrize - async def test_method_update(self, async_client: AsyncLithic) -> None: + async def test_method_update_overload_3(self, async_client: AsyncLithic) -> None: account_holder = await async_client.account_holders.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: + async def test_method_update_with_all_params_overload_3(self, async_client: AsyncLithic) -> None: account_holder = await async_client.account_holders.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - business_account_token="string", - email="string", - phone_number="string", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "123 Old Forest Way", + "address2": "address2", + "city": "Omaha", + "country": "USA", + "postal_code": "68022", + "state": "NE", + }, + business_account_token="business_account_token", + email="email", + first_name="first_name", + last_name="last_name", + legal_business_name="legal_business_name", + phone_number="phone_number", ) assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - async def test_raw_response_update(self, async_client: AsyncLithic) -> None: + async def test_raw_response_update_overload_3(self, async_client: AsyncLithic) -> None: response = await async_client.account_holders.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -1798,9 +1927,9 @@ async def test_raw_response_update(self, async_client: AsyncLithic) -> None: assert_matches_type(AccountHolderUpdateResponse, account_holder, path=["response"]) @parametrize - async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: + async def test_streaming_response_update_overload_3(self, async_client: AsyncLithic) -> None: async with async_client.account_holders.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -1811,10 +1940,10 @@ async def test_streaming_response_update(self, async_client: AsyncLithic) -> Non assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_update(self, async_client: AsyncLithic) -> None: + async def test_path_params_update_overload_3(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): await async_client.account_holders.with_raw_response.update( - "", + account_holder_token="", ) @parametrize @@ -1825,10 +1954,17 @@ async def test_method_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: account_holder = await async_client.account_holders.list( - ending_before="string", + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + email="email", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", external_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + first_name="first_name", + last_name="last_name", + legal_business_name="legal_business_name", limit=0, - starting_after="string", + phone_number="phone_number", + starting_after="starting_after", ) assert_matches_type(AsyncSinglePage[AccountHolder], account_holder, path=["response"]) @@ -1891,190 +2027,165 @@ async def test_path_params_list_documents(self, async_client: AsyncLithic) -> No ) @parametrize - async def test_method_resubmit(self, async_client: AsyncLithic) -> None: - account_holder = await async_client.account_holders.resubmit( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", + async def test_method_retrieve_document(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.retrieve_document( + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize - async def test_raw_response_resubmit(self, async_client: AsyncLithic) -> None: - response = await async_client.account_holders.with_raw_response.resubmit( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", + async def test_raw_response_retrieve_document(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.retrieve_document( + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize - async def test_streaming_response_resubmit(self, async_client: AsyncLithic) -> None: - async with async_client.account_holders.with_streaming_response.resubmit( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", + async def test_streaming_response_retrieve_document(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.retrieve_document( + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = await response.parse() - assert_matches_type(AccountHolder, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_resubmit(self, async_client: AsyncLithic) -> None: + async def test_path_params_retrieve_document(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): - await async_client.account_holders.with_raw_response.resubmit( - "", - individual={ - "address": { - "address1": "123 Old Forest Way", - "city": "Omaha", - "country": "USA", - "postal_code": "68022", - "state": "NE", - }, - "dob": "1991-03-08 08:00:00", - "email": "tom@middle-earth.com", - "first_name": "Tom", - "government_id": "111-23-1412", - "last_name": "Bombadil", - "phone_number": "+12124007676", - }, - tos_timestamp="2018-05-29T21:16:05Z", - workflow="KYC_ADVANCED", + await async_client.account_holders.with_raw_response.retrieve_document( + document_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_holder_token="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `document_token` but received ''"): + await async_client.account_holders.with_raw_response.retrieve_document( + document_token="", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize - async def test_method_retrieve_document(self, async_client: AsyncLithic) -> None: - account_holder = await async_client.account_holders.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + async def test_method_simulate_enrollment_document_review(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", ) - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize - async def test_raw_response_retrieve_document(self, async_client: AsyncLithic) -> None: - response = await async_client.account_holders.with_raw_response.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + async def test_method_simulate_enrollment_document_review_with_all_params(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", + accepted_entity_status_reasons=["string"], + status_reason="DOCUMENT_MISSING_REQUIRED_DATA", + ) + assert_matches_type(Document, account_holder, path=["response"]) + + @parametrize + async def test_raw_response_simulate_enrollment_document_review(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize - async def test_streaming_response_retrieve_document(self, async_client: AsyncLithic) -> None: - async with async_client.account_holders.with_streaming_response.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + async def test_streaming_response_simulate_enrollment_document_review(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.simulate_enrollment_document_review( + document_upload_token="b11cd67b-0a52-4180-8365-314f3def5426", + status="UPLOADED", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = await response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_retrieve_document(self, async_client: AsyncLithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): - await async_client.account_holders.with_raw_response.retrieve_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_holder_token="", - ) + async def test_method_simulate_enrollment_review(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.simulate_enrollment_review() + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `document_token` but received ''"): - await async_client.account_holders.with_raw_response.retrieve_document( - "", - account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + @parametrize + async def test_method_simulate_enrollment_review_with_all_params(self, async_client: AsyncLithic) -> None: + account_holder = await async_client.account_holders.simulate_enrollment_review( + account_holder_token="1415964d-4400-4d79-9fb3-eee0faaee4e4", + status="ACCEPTED", + status_reasons=["PRIMARY_BUSINESS_ENTITY_ID_VERIFICATION_FAILURE"], + ) + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) + + @parametrize + async def test_raw_response_simulate_enrollment_review(self, async_client: AsyncLithic) -> None: + response = await async_client.account_holders.with_raw_response.simulate_enrollment_review() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + account_holder = response.parse() + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) + + @parametrize + async def test_streaming_response_simulate_enrollment_review(self, async_client: AsyncLithic) -> None: + async with async_client.account_holders.with_streaming_response.simulate_enrollment_review() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account_holder = await response.parse() + assert_matches_type(AccountHolderSimulateEnrollmentReviewResponse, account_holder, path=["response"]) + + assert cast(Any, response.is_closed) is True @parametrize async def test_method_upload_document(self, async_client: AsyncLithic) -> None: account_holder = await async_client.account_holders.upload_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - document_type="drivers_license", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize async def test_raw_response_upload_document(self, async_client: AsyncLithic) -> None: response = await async_client.account_holders.with_raw_response.upload_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - document_type="drivers_license", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) @parametrize async def test_streaming_response_upload_document(self, async_client: AsyncLithic) -> None: async with async_client.account_holders.with_streaming_response.upload_document( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - document_type="drivers_license", + account_holder_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" account_holder = await response.parse() - assert_matches_type(AccountHolderDocument, account_holder, path=["response"]) + assert_matches_type(Document, account_holder, path=["response"]) assert cast(Any, response.is_closed) is True @@ -2082,6 +2193,7 @@ async def test_streaming_response_upload_document(self, async_client: AsyncLithi async def test_path_params_upload_document(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_holder_token` but received ''"): await async_client.account_holders.with_raw_response.upload_document( - "", - document_type="drivers_license", + account_holder_token="", + document_type="EIN_LETTER", + entity_token="83cf25ae-c14f-4d10-9fa2-0119f36c7286", ) diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py index be99c07d..e9938613 100644 --- a/tests/api_resources/test_accounts.py +++ b/tests/api_resources/test_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -61,7 +61,7 @@ def test_path_params_retrieve(self, client: Lithic) -> None: @parametrize def test_method_update(self, client: Lithic) -> None: account = client.accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Account, account, path=["response"]) @@ -69,18 +69,20 @@ def test_method_update(self, client: Lithic) -> None: @parametrize def test_method_update_with_all_params(self, client: Lithic) -> None: account = client.accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + comment="comment", daily_spend_limit=1000, lifetime_spend_limit=0, monthly_spend_limit=0, state="ACTIVE", + substatus="FRAUD_IDENTIFIED", verification_address={ - "address1": "string", - "address2": "string", - "city": "string", - "country": "string", - "postal_code": "string", - "state": "string", + "address1": "address1", + "address2": "address2", + "city": "city", + "country": "country", + "postal_code": "postal_code", + "state": "state", }, ) assert_matches_type(Account, account, path=["response"]) @@ -89,7 +91,7 @@ def test_method_update_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_update(self, client: Lithic) -> None: response = client.accounts.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -101,7 +103,7 @@ def test_raw_response_update(self, client: Lithic) -> None: @parametrize def test_streaming_response_update(self, client: Lithic) -> None: with client.accounts.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -116,7 +118,7 @@ def test_streaming_response_update(self, client: Lithic) -> None: def test_path_params_update(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_token` but received ''"): client.accounts.with_raw_response.update( - "", + account_token="", ) @parametrize @@ -129,9 +131,9 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: account = client.accounts.list( begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(SyncCursorPage[Account], account, path=["response"]) @@ -195,7 +197,9 @@ def test_path_params_retrieve_spend_limits(self, client: Lithic) -> None: class TestAsyncAccounts: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: @@ -239,7 +243,7 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update(self, async_client: AsyncLithic) -> None: account = await async_client.accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Account, account, path=["response"]) @@ -247,18 +251,20 @@ async def test_method_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: account = await async_client.accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + comment="comment", daily_spend_limit=1000, lifetime_spend_limit=0, monthly_spend_limit=0, state="ACTIVE", + substatus="FRAUD_IDENTIFIED", verification_address={ - "address1": "string", - "address2": "string", - "city": "string", - "country": "string", - "postal_code": "string", - "state": "string", + "address1": "address1", + "address2": "address2", + "city": "city", + "country": "country", + "postal_code": "postal_code", + "state": "state", }, ) assert_matches_type(Account, account, path=["response"]) @@ -267,7 +273,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> @parametrize async def test_raw_response_update(self, async_client: AsyncLithic) -> None: response = await async_client.accounts.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -279,7 +285,7 @@ async def test_raw_response_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: async with async_client.accounts.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -294,7 +300,7 @@ async def test_streaming_response_update(self, async_client: AsyncLithic) -> Non async def test_path_params_update(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_token` but received ''"): await async_client.accounts.with_raw_response.update( - "", + account_token="", ) @parametrize @@ -307,9 +313,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N account = await async_client.accounts.list( begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(AsyncCursorPage[Account], account, path=["response"]) diff --git a/tests/api_resources/test_aggregate_balances.py b/tests/api_resources/test_aggregate_balances.py deleted file mode 100644 index 5065d562..00000000 --- a/tests/api_resources/test_aggregate_balances.py +++ /dev/null @@ -1,87 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from lithic import Lithic, AsyncLithic -from tests.utils import assert_matches_type -from lithic.types import AggregateBalance -from lithic.pagination import SyncSinglePage, AsyncSinglePage - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestAggregateBalances: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_list(self, client: Lithic) -> None: - aggregate_balance = client.aggregate_balances.list() - assert_matches_type(SyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - @parametrize - def test_method_list_with_all_params(self, client: Lithic) -> None: - aggregate_balance = client.aggregate_balances.list( - financial_account_type="ISSUING", - ) - assert_matches_type(SyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - @parametrize - def test_raw_response_list(self, client: Lithic) -> None: - response = client.aggregate_balances.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - aggregate_balance = response.parse() - assert_matches_type(SyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - @parametrize - def test_streaming_response_list(self, client: Lithic) -> None: - with client.aggregate_balances.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - aggregate_balance = response.parse() - assert_matches_type(SyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncAggregateBalances: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_list(self, async_client: AsyncLithic) -> None: - aggregate_balance = await async_client.aggregate_balances.list() - assert_matches_type(AsyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: - aggregate_balance = await async_client.aggregate_balances.list( - financial_account_type="ISSUING", - ) - assert_matches_type(AsyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - @parametrize - async def test_raw_response_list(self, async_client: AsyncLithic) -> None: - response = await async_client.aggregate_balances.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - aggregate_balance = response.parse() - assert_matches_type(AsyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - @parametrize - async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: - async with async_client.aggregate_balances.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - aggregate_balance = await response.parse() - assert_matches_type(AsyncSinglePage[AggregateBalance], aggregate_balance, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_auth_rules.py b/tests/api_resources/test_auth_rules.py deleted file mode 100644 index ddbcdcb1..00000000 --- a/tests/api_resources/test_auth_rules.py +++ /dev/null @@ -1,509 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from lithic import Lithic, AsyncLithic -from tests.utils import assert_matches_type -from lithic.types import ( - AuthRule, - AuthRuleRemoveResponse, - AuthRuleRetrieveResponse, -) -from lithic.pagination import SyncCursorPage, AsyncCursorPage - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestAuthRules: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_create(self, client: Lithic) -> None: - auth_rule = client.auth_rules.create() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_method_create_with_all_params(self, client: Lithic) -> None: - auth_rule = client.auth_rules.create( - account_tokens=["3fa85f64-5717-4562-b3fc-2c963f66afa6"], - allowed_countries=["MEX"], - allowed_mcc=["3000"], - blocked_countries=["CAN", "USA"], - blocked_mcc=["5811", "5812"], - card_tokens=["3fa85f64-5717-4562-b3fc-2c963f66afa6"], - program_level=False, - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_raw_response_create(self, client: Lithic) -> None: - response = client.auth_rules.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_streaming_response_create(self, client: Lithic) -> None: - with client.auth_rules.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_retrieve(self, client: Lithic) -> None: - auth_rule = client.auth_rules.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AuthRuleRetrieveResponse, auth_rule, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Lithic) -> None: - response = client.auth_rules.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRuleRetrieveResponse, auth_rule, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Lithic) -> None: - with client.auth_rules.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = response.parse() - assert_matches_type(AuthRuleRetrieveResponse, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Lithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): - client.auth_rules.with_raw_response.retrieve( - "", - ) - - @parametrize - def test_method_update(self, client: Lithic) -> None: - auth_rule = client.auth_rules.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_method_update_with_all_params(self, client: Lithic) -> None: - auth_rule = client.auth_rules.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - allowed_countries=["USA"], - allowed_mcc=["3000", "3001"], - blocked_countries=["string", "string", "string"], - blocked_mcc=["string", "string", "string"], - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_raw_response_update(self, client: Lithic) -> None: - response = client.auth_rules.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_streaming_response_update(self, client: Lithic) -> None: - with client.auth_rules.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_update(self, client: Lithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): - client.auth_rules.with_raw_response.update( - "", - ) - - @parametrize - def test_method_list(self, client: Lithic) -> None: - auth_rule = client.auth_rules.list() - assert_matches_type(SyncCursorPage[AuthRule], auth_rule, path=["response"]) - - @parametrize - def test_method_list_with_all_params(self, client: Lithic) -> None: - auth_rule = client.auth_rules.list( - ending_before="string", - page_size=1, - starting_after="string", - ) - assert_matches_type(SyncCursorPage[AuthRule], auth_rule, path=["response"]) - - @parametrize - def test_raw_response_list(self, client: Lithic) -> None: - response = client.auth_rules.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(SyncCursorPage[AuthRule], auth_rule, path=["response"]) - - @parametrize - def test_streaming_response_list(self, client: Lithic) -> None: - with client.auth_rules.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = response.parse() - assert_matches_type(SyncCursorPage[AuthRule], auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_apply(self, client: Lithic) -> None: - auth_rule = client.auth_rules.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_method_apply_with_all_params(self, client: Lithic) -> None: - auth_rule = client.auth_rules.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_tokens=["string", "string", "string"], - card_tokens=["1336a403-2447-4b36-a009-6fbb852ee675", "df942c4e-9130-4ab5-b067-778a2c55b357"], - program_level=True, - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_raw_response_apply(self, client: Lithic) -> None: - response = client.auth_rules.with_raw_response.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - def test_streaming_response_apply(self, client: Lithic) -> None: - with client.auth_rules.with_streaming_response.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_apply(self, client: Lithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): - client.auth_rules.with_raw_response.apply( - "", - ) - - @parametrize - def test_method_remove(self, client: Lithic) -> None: - auth_rule = client.auth_rules.remove() - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - @parametrize - def test_method_remove_with_all_params(self, client: Lithic) -> None: - auth_rule = client.auth_rules.remove( - account_tokens=["string", "string", "string"], - card_tokens=["string", "string", "string"], - program_level=False, - ) - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - @parametrize - def test_raw_response_remove(self, client: Lithic) -> None: - response = client.auth_rules.with_raw_response.remove() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - @parametrize - def test_streaming_response_remove(self, client: Lithic) -> None: - with client.auth_rules.with_streaming_response.remove() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = response.parse() - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncAuthRules: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_create(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.create() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.create( - account_tokens=["3fa85f64-5717-4562-b3fc-2c963f66afa6"], - allowed_countries=["MEX"], - allowed_mcc=["3000"], - blocked_countries=["CAN", "USA"], - blocked_mcc=["5811", "5812"], - card_tokens=["3fa85f64-5717-4562-b3fc-2c963f66afa6"], - program_level=False, - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_raw_response_create(self, async_client: AsyncLithic) -> None: - response = await async_client.auth_rules.with_raw_response.create() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: - async with async_client.auth_rules.with_streaming_response.create() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = await response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_retrieve(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AuthRuleRetrieveResponse, auth_rule, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: - response = await async_client.auth_rules.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRuleRetrieveResponse, auth_rule, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: - async with async_client.auth_rules.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = await response.parse() - assert_matches_type(AuthRuleRetrieveResponse, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): - await async_client.auth_rules.with_raw_response.retrieve( - "", - ) - - @parametrize - async def test_method_update(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - allowed_countries=["USA"], - allowed_mcc=["3000", "3001"], - blocked_countries=["string", "string", "string"], - blocked_mcc=["string", "string", "string"], - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_raw_response_update(self, async_client: AsyncLithic) -> None: - response = await async_client.auth_rules.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: - async with async_client.auth_rules.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = await response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_update(self, async_client: AsyncLithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): - await async_client.auth_rules.with_raw_response.update( - "", - ) - - @parametrize - async def test_method_list(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.list() - assert_matches_type(AsyncCursorPage[AuthRule], auth_rule, path=["response"]) - - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.list( - ending_before="string", - page_size=1, - starting_after="string", - ) - assert_matches_type(AsyncCursorPage[AuthRule], auth_rule, path=["response"]) - - @parametrize - async def test_raw_response_list(self, async_client: AsyncLithic) -> None: - response = await async_client.auth_rules.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AsyncCursorPage[AuthRule], auth_rule, path=["response"]) - - @parametrize - async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: - async with async_client.auth_rules.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = await response.parse() - assert_matches_type(AsyncCursorPage[AuthRule], auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_apply(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_method_apply_with_all_params(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_tokens=["string", "string", "string"], - card_tokens=["1336a403-2447-4b36-a009-6fbb852ee675", "df942c4e-9130-4ab5-b067-778a2c55b357"], - program_level=True, - ) - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_raw_response_apply(self, async_client: AsyncLithic) -> None: - response = await async_client.auth_rules.with_raw_response.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - @parametrize - async def test_streaming_response_apply(self, async_client: AsyncLithic) -> None: - async with async_client.auth_rules.with_streaming_response.apply( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = await response.parse() - assert_matches_type(AuthRule, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_apply(self, async_client: AsyncLithic) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `auth_rule_token` but received ''"): - await async_client.auth_rules.with_raw_response.apply( - "", - ) - - @parametrize - async def test_method_remove(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.remove() - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - @parametrize - async def test_method_remove_with_all_params(self, async_client: AsyncLithic) -> None: - auth_rule = await async_client.auth_rules.remove( - account_tokens=["string", "string", "string"], - card_tokens=["string", "string", "string"], - program_level=False, - ) - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - @parametrize - async def test_raw_response_remove(self, async_client: AsyncLithic) -> None: - response = await async_client.auth_rules.with_raw_response.remove() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth_rule = response.parse() - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - @parametrize - async def test_streaming_response_remove(self, async_client: AsyncLithic) -> None: - async with async_client.auth_rules.with_streaming_response.remove() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth_rule = await response.parse() - assert_matches_type(AuthRuleRemoveResponse, auth_rule, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_auth_stream_enrollment.py b/tests/api_resources/test_auth_stream_enrollment.py index ac3763d5..7e7c97f4 100644 --- a/tests/api_resources/test_auth_stream_enrollment.py +++ b/tests/api_resources/test_auth_stream_enrollment.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -69,7 +69,9 @@ def test_streaming_response_rotate_secret(self, client: Lithic) -> None: class TestAsyncAuthStreamEnrollment: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve_secret(self, async_client: AsyncLithic) -> None: diff --git a/tests/api_resources/test_balances.py b/tests/api_resources/test_balances.py index b8a6b28a..6db2188b 100644 --- a/tests/api_resources/test_balances.py +++ b/tests/api_resources/test_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -29,6 +29,7 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: balance = client.balances.list( account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", balance_date=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_type="ISSUING", ) assert_matches_type(SyncSinglePage[Balance], balance, path=["response"]) @@ -55,7 +56,9 @@ def test_streaming_response_list(self, client: Lithic) -> None: class TestAsyncBalances: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: @@ -67,6 +70,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N balance = await async_client.balances.list( account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", balance_date=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", financial_account_type="ISSUING", ) assert_matches_type(AsyncSinglePage[Balance], balance, path=["response"]) diff --git a/tests/api_resources/test_book_transfers.py b/tests/api_resources/test_book_transfers.py new file mode 100644 index 00000000..38aa6510 --- /dev/null +++ b/tests/api_resources/test_book_transfers.py @@ -0,0 +1,404 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import ( + BookTransferResponse, +) +from lithic._utils import parse_datetime +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBookTransfers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + book_transfer = client.book_transfers.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Lithic) -> None: + book_transfer = client.book_transfers.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_id="external_id", + memo="memo", + on_closed_account="FAIL", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.book_transfers.with_raw_response.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.book_transfers.with_streaming_response.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + book_transfer = client.book_transfers.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.book_transfers.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.book_transfers.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `book_transfer_token` but received ''"): + client.book_transfers.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + book_transfer = client.book_transfers.list() + assert_matches_type(SyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + book_transfer = client.book_transfers.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="ADJUSTMENT", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + result="APPROVED", + starting_after="starting_after", + status="DECLINED", + ) + assert_matches_type(SyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.book_transfers.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(SyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.book_transfers.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = response.parse() + assert_matches_type(SyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_reverse(self, client: Lithic) -> None: + book_transfer = client.book_transfers.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_method_reverse_with_all_params(self, client: Lithic) -> None: + book_transfer = client.book_transfers.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + memo="memo", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_raw_response_reverse(self, client: Lithic) -> None: + response = client.book_transfers.with_raw_response.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + def test_streaming_response_reverse(self, client: Lithic) -> None: + with client.book_transfers.with_streaming_response.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_reverse(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `book_transfer_token` but received ''"): + client.book_transfers.with_raw_response.reverse( + book_transfer_token="", + ) + + +class TestAsyncBookTransfers: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + book_transfer = await async_client.book_transfers.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: + book_transfer = await async_client.book_transfers.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_id="external_id", + memo="memo", + on_closed_account="FAIL", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.book_transfers.with_raw_response.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.book_transfers.with_streaming_response.create( + amount=1, + category="ADJUSTMENT", + from_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + subtype="subtype", + to_financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + type="ATM_BALANCE_INQUIRY", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = await response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + book_transfer = await async_client.book_transfers.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.book_transfers.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.book_transfers.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = await response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `book_transfer_token` but received ''"): + await async_client.book_transfers.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + book_transfer = await async_client.book_transfers.list() + assert_matches_type(AsyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + book_transfer = await async_client.book_transfers.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="ADJUSTMENT", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + result="APPROVED", + starting_after="starting_after", + status="DECLINED", + ) + assert_matches_type(AsyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.book_transfers.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(AsyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.book_transfers.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = await response.parse() + assert_matches_type(AsyncCursorPage[BookTransferResponse], book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_reverse(self, async_client: AsyncLithic) -> None: + book_transfer = await async_client.book_transfers.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_method_reverse_with_all_params(self, async_client: AsyncLithic) -> None: + book_transfer = await async_client.book_transfers.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + memo="memo", + ) + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_raw_response_reverse(self, async_client: AsyncLithic) -> None: + response = await async_client.book_transfers.with_raw_response.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + book_transfer = response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + @parametrize + async def test_streaming_response_reverse(self, async_client: AsyncLithic) -> None: + async with async_client.book_transfers.with_streaming_response.reverse( + book_transfer_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + book_transfer = await response.parse() + assert_matches_type(BookTransferResponse, book_transfer, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_reverse(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `book_transfer_token` but received ''"): + await async_client.book_transfers.with_raw_response.reverse( + book_transfer_token="", + ) diff --git a/tests/api_resources/test_card_bulk_orders.py b/tests/api_resources/test_card_bulk_orders.py new file mode 100644 index 00000000..a8ec599d --- /dev/null +++ b/tests/api_resources/test_card_bulk_orders.py @@ -0,0 +1,382 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import ( + CardBulkOrder, +) +from lithic._utils import parse_datetime +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCardBulkOrders: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + card_bulk_order = client.card_bulk_orders.create( + customer_product_id="custom-card-design-123", + shipping_address={ + "address1": "123 Main Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Johnny", + "last_name": "Appleseed", + "postal_code": "10001", + "state": "NY", + }, + shipping_method="BULK_EXPEDITED", + ) + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.card_bulk_orders.with_raw_response.create( + customer_product_id="custom-card-design-123", + shipping_address={ + "address1": "123 Main Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Johnny", + "last_name": "Appleseed", + "postal_code": "10001", + "state": "NY", + }, + shipping_method="BULK_EXPEDITED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.card_bulk_orders.with_streaming_response.create( + customer_product_id="custom-card-design-123", + shipping_address={ + "address1": "123 Main Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Johnny", + "last_name": "Appleseed", + "postal_code": "10001", + "state": "NY", + }, + shipping_method="BULK_EXPEDITED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + card_bulk_order = client.card_bulk_orders.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.card_bulk_orders.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.card_bulk_orders.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `bulk_order_token` but received ''"): + client.card_bulk_orders.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Lithic) -> None: + card_bulk_order = client.card_bulk_orders.update( + bulk_order_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="LOCKED", + ) + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Lithic) -> None: + response = client.card_bulk_orders.with_raw_response.update( + bulk_order_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="LOCKED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Lithic) -> None: + with client.card_bulk_orders.with_streaming_response.update( + bulk_order_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="LOCKED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `bulk_order_token` but received ''"): + client.card_bulk_orders.with_raw_response.update( + bulk_order_token="", + status="LOCKED", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + card_bulk_order = client.card_bulk_orders.list() + assert_matches_type(SyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + card_bulk_order = client.card_bulk_orders.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(SyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.card_bulk_orders.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(SyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.card_bulk_orders.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = response.parse() + assert_matches_type(SyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncCardBulkOrders: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + card_bulk_order = await async_client.card_bulk_orders.create( + customer_product_id="custom-card-design-123", + shipping_address={ + "address1": "123 Main Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Johnny", + "last_name": "Appleseed", + "postal_code": "10001", + "state": "NY", + }, + shipping_method="BULK_EXPEDITED", + ) + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.card_bulk_orders.with_raw_response.create( + customer_product_id="custom-card-design-123", + shipping_address={ + "address1": "123 Main Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Johnny", + "last_name": "Appleseed", + "postal_code": "10001", + "state": "NY", + }, + shipping_method="BULK_EXPEDITED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.card_bulk_orders.with_streaming_response.create( + customer_product_id="custom-card-design-123", + shipping_address={ + "address1": "123 Main Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Johnny", + "last_name": "Appleseed", + "postal_code": "10001", + "state": "NY", + }, + shipping_method="BULK_EXPEDITED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = await response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + card_bulk_order = await async_client.card_bulk_orders.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.card_bulk_orders.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.card_bulk_orders.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = await response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `bulk_order_token` but received ''"): + await async_client.card_bulk_orders.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncLithic) -> None: + card_bulk_order = await async_client.card_bulk_orders.update( + bulk_order_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="LOCKED", + ) + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncLithic) -> None: + response = await async_client.card_bulk_orders.with_raw_response.update( + bulk_order_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="LOCKED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: + async with async_client.card_bulk_orders.with_streaming_response.update( + bulk_order_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="LOCKED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = await response.parse() + assert_matches_type(CardBulkOrder, card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `bulk_order_token` but received ''"): + await async_client.card_bulk_orders.with_raw_response.update( + bulk_order_token="", + status="LOCKED", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + card_bulk_order = await async_client.card_bulk_orders.list() + assert_matches_type(AsyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + card_bulk_order = await async_client.card_bulk_orders.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(AsyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.card_bulk_orders.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card_bulk_order = response.parse() + assert_matches_type(AsyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.card_bulk_orders.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card_bulk_order = await response.parse() + assert_matches_type(AsyncCursorPage[CardBulkOrder], card_bulk_order, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_card_product.py b/tests/api_resources/test_card_product.py deleted file mode 100644 index 12bceacc..00000000 --- a/tests/api_resources/test_card_product.py +++ /dev/null @@ -1,72 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from lithic import Lithic, AsyncLithic -from tests.utils import assert_matches_type -from lithic.types import CardProductCreditDetailResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestCardProduct: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_credit_detail(self, client: Lithic) -> None: - card_product = client.card_product.credit_detail() - assert_matches_type(CardProductCreditDetailResponse, card_product, path=["response"]) - - @parametrize - def test_raw_response_credit_detail(self, client: Lithic) -> None: - response = client.card_product.with_raw_response.credit_detail() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - card_product = response.parse() - assert_matches_type(CardProductCreditDetailResponse, card_product, path=["response"]) - - @parametrize - def test_streaming_response_credit_detail(self, client: Lithic) -> None: - with client.card_product.with_streaming_response.credit_detail() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - card_product = response.parse() - assert_matches_type(CardProductCreditDetailResponse, card_product, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncCardProduct: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_credit_detail(self, async_client: AsyncLithic) -> None: - card_product = await async_client.card_product.credit_detail() - assert_matches_type(CardProductCreditDetailResponse, card_product, path=["response"]) - - @parametrize - async def test_raw_response_credit_detail(self, async_client: AsyncLithic) -> None: - response = await async_client.card_product.with_raw_response.credit_detail() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - card_product = response.parse() - assert_matches_type(CardProductCreditDetailResponse, card_product, path=["response"]) - - @parametrize - async def test_streaming_response_credit_detail(self, async_client: AsyncLithic) -> None: - async with async_client.card_product.with_streaming_response.credit_detail() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - card_product = await response.parse() - assert_matches_type(CardProductCreditDetailResponse, card_product, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_card_programs.py b/tests/api_resources/test_card_programs.py index 72cb866b..9c32d3c7 100644 --- a/tests/api_resources/test_card_programs.py +++ b/tests/api_resources/test_card_programs.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -64,9 +64,9 @@ def test_method_list(self, client: Lithic) -> None: @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: card_program = client.card_programs.list( - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(SyncCursorPage[CardProgram], card_program, path=["response"]) @@ -92,7 +92,9 @@ def test_streaming_response_list(self, client: Lithic) -> None: class TestAsyncCardPrograms: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: @@ -140,9 +142,9 @@ async def test_method_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: card_program = await async_client.card_programs.list( - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(AsyncCursorPage[CardProgram], card_program, path=["response"]) diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index a01331e0..8a1a3a57 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -11,8 +11,10 @@ from tests.utils import assert_matches_type from lithic.types import ( Card, + NonPCICard, CardSpendLimits, CardProvisionResponse, + CardWebProvisionResponse, ) from lithic._utils import parse_datetime from lithic.pagination import SyncCursorPage, AsyncCursorPage @@ -35,27 +37,31 @@ def test_method_create_with_all_params(self, client: Lithic) -> None: card = client.cards.create( type="VIRTUAL", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - card_program_token="00000000-0000-0000-1000-000000000000", - carrier={"qr_code_url": "string"}, - digital_card_art_token="00000000-0000-0000-1000-000000000000", + bulk_order_token="5e9483eb-8103-4e16-9794-2106111b2eca", + card_program_token="5e9483eb-8103-4e16-9794-2106111b2eca", + carrier={"qr_code_url": "qr_code_url"}, + digital_card_art_token="5e9483eb-8103-4e16-9794-2106111b2eca", exp_month="06", exp_year="2027", memo="New Card", - pin="string", + pin="pin", product_id="1", - replacement_for="00000000-0000-0000-1000-000000000000", + replacement_account_token="5e9483eb-8103-4e16-9794-2106111b2eca", + replacement_comment="replacement_comment", + replacement_for="5e9483eb-8103-4e16-9794-2106111b2eca", + replacement_substatus="LOST", shipping_address={ "address1": "5 Broad Street", - "address2": "Unit 25A", "city": "NEW YORK", "country": "USA", - "email": "johnny@appleseed.com", "first_name": "Michael", "last_name": "Bluth", - "line2_text": "The Bluth Company", - "phone_number": "+12124007676", "postal_code": "10001-1809", "state": "NY", + "address2": "Unit 25A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", }, shipping_method="2_DAY", spend_limit=1000, @@ -129,28 +135,31 @@ def test_path_params_retrieve(self, client: Lithic) -> None: @parametrize def test_method_update(self, client: Lithic) -> None: card = client.cards.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Card, card, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: Lithic) -> None: card = client.cards.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - auth_rule_token="string", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + comment="comment", digital_card_art_token="00000000-0000-0000-1000-000000000000", memo="Updated Name", - pin="string", + network_program_token="00000000-0000-0000-1000-000000000000", + pin="pin", + pin_status="OK", spend_limit=100, spend_limit_duration="FOREVER", state="OPEN", + substatus="LOST", ) assert_matches_type(Card, card, path=["response"]) @parametrize def test_raw_response_update(self, client: Lithic) -> None: response = client.cards.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -161,7 +170,7 @@ def test_raw_response_update(self, client: Lithic) -> None: @parametrize def test_streaming_response_update(self, client: Lithic) -> None: with client.cards.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -175,13 +184,13 @@ def test_streaming_response_update(self, client: Lithic) -> None: def test_path_params_update(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): client.cards.with_raw_response.update( - "", + card_token="", ) @parametrize def test_method_list(self, client: Lithic) -> None: card = client.cards.list() - assert_matches_type(SyncCursorPage[Card], card, path=["response"]) + assert_matches_type(SyncCursorPage[NonPCICard], card, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: @@ -189,12 +198,13 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", + memo="memo", page_size=1, - starting_after="string", + starting_after="starting_after", state="CLOSED", ) - assert_matches_type(SyncCursorPage[Card], card, path=["response"]) + assert_matches_type(SyncCursorPage[NonPCICard], card, path=["response"]) @parametrize def test_raw_response_list(self, client: Lithic) -> None: @@ -203,7 +213,7 @@ def test_raw_response_list(self, client: Lithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" card = response.parse() - assert_matches_type(SyncCursorPage[Card], card, path=["response"]) + assert_matches_type(SyncCursorPage[NonPCICard], card, path=["response"]) @parametrize def test_streaming_response_list(self, client: Lithic) -> None: @@ -212,23 +222,120 @@ def test_streaming_response_list(self, client: Lithic) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" card = response.parse() - assert_matches_type(SyncCursorPage[Card], card, path=["response"]) + assert_matches_type(SyncCursorPage[NonPCICard], card, path=["response"]) assert cast(Any, response.is_closed) is True + @parametrize + def test_method_convert_physical(self, client: Lithic) -> None: + card = client.cards.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) + assert_matches_type(Card, card, path=["response"]) + + @parametrize + def test_method_convert_physical_with_all_params(self, client: Lithic) -> None: + card = client.cards.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + "address2": "Unit 5A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", + }, + carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, + product_id="100", + shipping_method="STANDARD", + ) + assert_matches_type(Card, card, path=["response"]) + + @parametrize + def test_raw_response_convert_physical(self, client: Lithic) -> None: + response = client.cards.with_raw_response.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = response.parse() + assert_matches_type(Card, card, path=["response"]) + + @parametrize + def test_streaming_response_convert_physical(self, client: Lithic) -> None: + with client.cards.with_streaming_response.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = response.parse() + assert_matches_type(Card, card, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_convert_physical(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): + client.cards.with_raw_response.convert_physical( + card_token="", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) + @parametrize def test_method_embed(self, client: Lithic) -> None: card = client.cards.embed( - embed_request="string", - hmac="string", + embed_request="embed_request", + hmac="hmac", ) assert_matches_type(str, card, path=["response"]) @parametrize def test_raw_response_embed(self, client: Lithic) -> None: response = client.cards.with_raw_response.embed( - embed_request="string", - hmac="string", + embed_request="embed_request", + hmac="hmac", ) assert response.is_closed is True @@ -239,8 +346,8 @@ def test_raw_response_embed(self, client: Lithic) -> None: @parametrize def test_streaming_response_embed(self, client: Lithic) -> None: with client.cards.with_streaming_response.embed( - embed_request="string", - hmac="string", + embed_request="embed_request", + hmac="hmac", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -250,30 +357,20 @@ def test_streaming_response_embed(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True - def test_get_embed_html(self, client: Lithic) -> None: - html = client.cards.get_embed_html(token="foo") - assert "html" in html - - def test_get_embed_url(self, client: Lithic) -> None: - url = client.cards.get_embed_url(token="foo") - params = set( # pyright: ignore[reportUnknownVariableType] - url.params.keys() # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] - ) - assert "hmac" in params - assert "embed_request" in params - @parametrize def test_method_provision(self, client: Lithic) -> None: card = client.cards.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(CardProvisionResponse, card, path=["response"]) @parametrize def test_method_provision_with_all_params(self, client: Lithic) -> None: card = client.cards.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", certificate="U3RhaW5sZXNzIHJvY2tz", + client_device_id="client_device_id", + client_wallet_account_id="client_wallet_account_id", digital_wallet="GOOGLE_PAY", nonce="U3RhaW5sZXNzIHJvY2tz", nonce_signature="U3RhaW5sZXNzIHJvY2tz", @@ -283,7 +380,7 @@ def test_method_provision_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_provision(self, client: Lithic) -> None: response = client.cards.with_raw_response.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -294,7 +391,7 @@ def test_raw_response_provision(self, client: Lithic) -> None: @parametrize def test_streaming_response_provision(self, client: Lithic) -> None: with client.cards.with_streaming_response.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -308,34 +405,34 @@ def test_streaming_response_provision(self, client: Lithic) -> None: def test_path_params_provision(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): client.cards.with_raw_response.provision( - "", + card_token="", ) @parametrize def test_method_reissue(self, client: Lithic) -> None: card = client.cards.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Card, card, path=["response"]) @parametrize def test_method_reissue_with_all_params(self, client: Lithic) -> None: card = client.cards.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, product_id="100", shipping_address={ "address1": "5 Broad Street", - "address2": "Unit 5A", "city": "NEW YORK", "country": "USA", - "email": "johnny@appleseed.com", "first_name": "Janet", "last_name": "Yellen", - "line2_text": "The Bluth Company", - "phone_number": "+12124007676", "postal_code": "10001", "state": "NY", + "address2": "Unit 5A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", }, shipping_method="STANDARD", ) @@ -344,7 +441,7 @@ def test_method_reissue_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_reissue(self, client: Lithic) -> None: response = client.cards.with_raw_response.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -355,7 +452,7 @@ def test_raw_response_reissue(self, client: Lithic) -> None: @parametrize def test_streaming_response_reissue(self, client: Lithic) -> None: with client.cards.with_streaming_response.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -369,13 +466,13 @@ def test_streaming_response_reissue(self, client: Lithic) -> None: def test_path_params_reissue(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): client.cards.with_raw_response.reissue( - "", + card_token="", ) @parametrize def test_method_renew(self, client: Lithic) -> None: card = client.cards.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -391,19 +488,19 @@ def test_method_renew(self, client: Lithic) -> None: @parametrize def test_method_renew_with_all_params(self, client: Lithic) -> None: card = client.cards.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", - "address2": "Unit 5A", "city": "NEW YORK", "country": "USA", - "email": "johnny@appleseed.com", "first_name": "Janet", "last_name": "Yellen", - "line2_text": "The Bluth Company", - "phone_number": "+12124007676", "postal_code": "10001", "state": "NY", + "address2": "Unit 5A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", }, carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, exp_month="06", @@ -416,7 +513,7 @@ def test_method_renew_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_renew(self, client: Lithic) -> None: response = client.cards.with_raw_response.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -436,7 +533,7 @@ def test_raw_response_renew(self, client: Lithic) -> None: @parametrize def test_streaming_response_renew(self, client: Lithic) -> None: with client.cards.with_streaming_response.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -459,7 +556,7 @@ def test_streaming_response_renew(self, client: Lithic) -> None: def test_path_params_renew(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): client.cards.with_raw_response.renew( - "", + card_token="", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -540,9 +637,60 @@ def test_streaming_response_search_by_pan(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_web_provision(self, client: Lithic) -> None: + card = client.cards.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + @parametrize + def test_method_web_provision_with_all_params(self, client: Lithic) -> None: + card = client.cards.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + client_device_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + client_wallet_account_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + digital_wallet="APPLE_PAY", + server_session_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + @parametrize + def test_raw_response_web_provision(self, client: Lithic) -> None: + response = client.cards.with_raw_response.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = response.parse() + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + @parametrize + def test_streaming_response_web_provision(self, client: Lithic) -> None: + with client.cards.with_streaming_response.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = response.parse() + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_web_provision(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): + client.cards.with_raw_response.web_provision( + card_token="", + ) + class TestAsyncCards: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: @@ -556,27 +704,31 @@ async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> card = await async_client.cards.create( type="VIRTUAL", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - card_program_token="00000000-0000-0000-1000-000000000000", - carrier={"qr_code_url": "string"}, - digital_card_art_token="00000000-0000-0000-1000-000000000000", + bulk_order_token="5e9483eb-8103-4e16-9794-2106111b2eca", + card_program_token="5e9483eb-8103-4e16-9794-2106111b2eca", + carrier={"qr_code_url": "qr_code_url"}, + digital_card_art_token="5e9483eb-8103-4e16-9794-2106111b2eca", exp_month="06", exp_year="2027", memo="New Card", - pin="string", + pin="pin", product_id="1", - replacement_for="00000000-0000-0000-1000-000000000000", + replacement_account_token="5e9483eb-8103-4e16-9794-2106111b2eca", + replacement_comment="replacement_comment", + replacement_for="5e9483eb-8103-4e16-9794-2106111b2eca", + replacement_substatus="LOST", shipping_address={ "address1": "5 Broad Street", - "address2": "Unit 25A", "city": "NEW YORK", "country": "USA", - "email": "johnny@appleseed.com", "first_name": "Michael", "last_name": "Bluth", - "line2_text": "The Bluth Company", - "phone_number": "+12124007676", "postal_code": "10001-1809", "state": "NY", + "address2": "Unit 25A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", }, shipping_method="2_DAY", spend_limit=1000, @@ -650,28 +802,31 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update(self, async_client: AsyncLithic) -> None: card = await async_client.cards.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Card, card, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: card = await async_client.cards.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - auth_rule_token="string", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + comment="comment", digital_card_art_token="00000000-0000-0000-1000-000000000000", memo="Updated Name", - pin="string", + network_program_token="00000000-0000-0000-1000-000000000000", + pin="pin", + pin_status="OK", spend_limit=100, spend_limit_duration="FOREVER", state="OPEN", + substatus="LOST", ) assert_matches_type(Card, card, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncLithic) -> None: response = await async_client.cards.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -682,7 +837,7 @@ async def test_raw_response_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: async with async_client.cards.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -696,13 +851,13 @@ async def test_streaming_response_update(self, async_client: AsyncLithic) -> Non async def test_path_params_update(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): await async_client.cards.with_raw_response.update( - "", + card_token="", ) @parametrize async def test_method_list(self, async_client: AsyncLithic) -> None: card = await async_client.cards.list() - assert_matches_type(AsyncCursorPage[Card], card, path=["response"]) + assert_matches_type(AsyncCursorPage[NonPCICard], card, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: @@ -710,12 +865,13 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", + memo="memo", page_size=1, - starting_after="string", + starting_after="starting_after", state="CLOSED", ) - assert_matches_type(AsyncCursorPage[Card], card, path=["response"]) + assert_matches_type(AsyncCursorPage[NonPCICard], card, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncLithic) -> None: @@ -724,7 +880,7 @@ async def test_raw_response_list(self, async_client: AsyncLithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" card = response.parse() - assert_matches_type(AsyncCursorPage[Card], card, path=["response"]) + assert_matches_type(AsyncCursorPage[NonPCICard], card, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: @@ -733,23 +889,120 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" card = await response.parse() - assert_matches_type(AsyncCursorPage[Card], card, path=["response"]) + assert_matches_type(AsyncCursorPage[NonPCICard], card, path=["response"]) assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_convert_physical(self, async_client: AsyncLithic) -> None: + card = await async_client.cards.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) + assert_matches_type(Card, card, path=["response"]) + + @parametrize + async def test_method_convert_physical_with_all_params(self, async_client: AsyncLithic) -> None: + card = await async_client.cards.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + "address2": "Unit 5A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", + }, + carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, + product_id="100", + shipping_method="STANDARD", + ) + assert_matches_type(Card, card, path=["response"]) + + @parametrize + async def test_raw_response_convert_physical(self, async_client: AsyncLithic) -> None: + response = await async_client.cards.with_raw_response.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = response.parse() + assert_matches_type(Card, card, path=["response"]) + + @parametrize + async def test_streaming_response_convert_physical(self, async_client: AsyncLithic) -> None: + async with async_client.cards.with_streaming_response.convert_physical( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = await response.parse() + assert_matches_type(Card, card, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_convert_physical(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): + await async_client.cards.with_raw_response.convert_physical( + card_token="", + shipping_address={ + "address1": "5 Broad Street", + "city": "NEW YORK", + "country": "USA", + "first_name": "Janet", + "last_name": "Yellen", + "postal_code": "10001", + "state": "NY", + }, + ) + @parametrize async def test_method_embed(self, async_client: AsyncLithic) -> None: card = await async_client.cards.embed( - embed_request="string", - hmac="string", + embed_request="embed_request", + hmac="hmac", ) assert_matches_type(str, card, path=["response"]) @parametrize async def test_raw_response_embed(self, async_client: AsyncLithic) -> None: response = await async_client.cards.with_raw_response.embed( - embed_request="string", - hmac="string", + embed_request="embed_request", + hmac="hmac", ) assert response.is_closed is True @@ -760,8 +1013,8 @@ async def test_raw_response_embed(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_embed(self, async_client: AsyncLithic) -> None: async with async_client.cards.with_streaming_response.embed( - embed_request="string", - hmac="string", + embed_request="embed_request", + hmac="hmac", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -771,30 +1024,20 @@ async def test_streaming_response_embed(self, async_client: AsyncLithic) -> None assert cast(Any, response.is_closed) is True - async def test_get_embed_html(self, async_client: AsyncLithic) -> None: - html = await async_client.cards.get_embed_html(token="foo") - assert "html" in html - - def test_get_embed_url(self, async_client: Lithic) -> None: - url = async_client.cards.get_embed_url(token="foo") - params = set( # pyright: ignore[reportUnknownVariableType] - url.params.keys() # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] - ) - assert "hmac" in params - assert "embed_request" in params - @parametrize async def test_method_provision(self, async_client: AsyncLithic) -> None: card = await async_client.cards.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(CardProvisionResponse, card, path=["response"]) @parametrize async def test_method_provision_with_all_params(self, async_client: AsyncLithic) -> None: card = await async_client.cards.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", certificate="U3RhaW5sZXNzIHJvY2tz", + client_device_id="client_device_id", + client_wallet_account_id="client_wallet_account_id", digital_wallet="GOOGLE_PAY", nonce="U3RhaW5sZXNzIHJvY2tz", nonce_signature="U3RhaW5sZXNzIHJvY2tz", @@ -804,7 +1047,7 @@ async def test_method_provision_with_all_params(self, async_client: AsyncLithic) @parametrize async def test_raw_response_provision(self, async_client: AsyncLithic) -> None: response = await async_client.cards.with_raw_response.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -815,7 +1058,7 @@ async def test_raw_response_provision(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_provision(self, async_client: AsyncLithic) -> None: async with async_client.cards.with_streaming_response.provision( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -829,34 +1072,34 @@ async def test_streaming_response_provision(self, async_client: AsyncLithic) -> async def test_path_params_provision(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): await async_client.cards.with_raw_response.provision( - "", + card_token="", ) @parametrize async def test_method_reissue(self, async_client: AsyncLithic) -> None: card = await async_client.cards.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Card, card, path=["response"]) @parametrize async def test_method_reissue_with_all_params(self, async_client: AsyncLithic) -> None: card = await async_client.cards.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, product_id="100", shipping_address={ "address1": "5 Broad Street", - "address2": "Unit 5A", "city": "NEW YORK", "country": "USA", - "email": "johnny@appleseed.com", "first_name": "Janet", "last_name": "Yellen", - "line2_text": "The Bluth Company", - "phone_number": "+12124007676", "postal_code": "10001", "state": "NY", + "address2": "Unit 5A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", }, shipping_method="STANDARD", ) @@ -865,7 +1108,7 @@ async def test_method_reissue_with_all_params(self, async_client: AsyncLithic) - @parametrize async def test_raw_response_reissue(self, async_client: AsyncLithic) -> None: response = await async_client.cards.with_raw_response.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -876,7 +1119,7 @@ async def test_raw_response_reissue(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_reissue(self, async_client: AsyncLithic) -> None: async with async_client.cards.with_streaming_response.reissue( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -890,13 +1133,13 @@ async def test_streaming_response_reissue(self, async_client: AsyncLithic) -> No async def test_path_params_reissue(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): await async_client.cards.with_raw_response.reissue( - "", + card_token="", ) @parametrize async def test_method_renew(self, async_client: AsyncLithic) -> None: card = await async_client.cards.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -912,19 +1155,19 @@ async def test_method_renew(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_renew_with_all_params(self, async_client: AsyncLithic) -> None: card = await async_client.cards.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", - "address2": "Unit 5A", "city": "NEW YORK", "country": "USA", - "email": "johnny@appleseed.com", "first_name": "Janet", "last_name": "Yellen", - "line2_text": "The Bluth Company", - "phone_number": "+12124007676", "postal_code": "10001", "state": "NY", + "address2": "Unit 5A", + "email": "johnny@appleseed.com", + "line2_text": "The Bluth Company", + "phone_number": "+15555555555", }, carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, exp_month="06", @@ -937,7 +1180,7 @@ async def test_method_renew_with_all_params(self, async_client: AsyncLithic) -> @parametrize async def test_raw_response_renew(self, async_client: AsyncLithic) -> None: response = await async_client.cards.with_raw_response.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -957,7 +1200,7 @@ async def test_raw_response_renew(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_renew(self, async_client: AsyncLithic) -> None: async with async_client.cards.with_streaming_response.renew( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -980,7 +1223,7 @@ async def test_streaming_response_renew(self, async_client: AsyncLithic) -> None async def test_path_params_renew(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): await async_client.cards.with_raw_response.renew( - "", + card_token="", shipping_address={ "address1": "5 Broad Street", "city": "NEW YORK", @@ -1060,3 +1303,52 @@ async def test_streaming_response_search_by_pan(self, async_client: AsyncLithic) assert_matches_type(Card, card, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_web_provision(self, async_client: AsyncLithic) -> None: + card = await async_client.cards.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + @parametrize + async def test_method_web_provision_with_all_params(self, async_client: AsyncLithic) -> None: + card = await async_client.cards.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + client_device_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + client_wallet_account_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + digital_wallet="APPLE_PAY", + server_session_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + @parametrize + async def test_raw_response_web_provision(self, async_client: AsyncLithic) -> None: + response = await async_client.cards.with_raw_response.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + card = response.parse() + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + @parametrize + async def test_streaming_response_web_provision(self, async_client: AsyncLithic) -> None: + async with async_client.cards.with_streaming_response.web_provision( + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + card = await response.parse() + assert_matches_type(CardWebProvisionResponse, card, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_web_provision(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `card_token` but received ''"): + await async_client.cards.with_raw_response.web_provision( + card_token="", + ) diff --git a/tests/api_resources/test_top_level.py b/tests/api_resources/test_client.py similarity index 66% rename from tests/api_resources/test_top_level.py rename to tests/api_resources/test_client.py index 6f5c8ba6..804fc259 100644 --- a/tests/api_resources/test_top_level.py +++ b/tests/api_resources/test_client.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -14,13 +14,13 @@ base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -class TestTopLevel: +class TestClient: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_api_status(self, client: Lithic) -> None: - top_level = client.api_status() - assert_matches_type(APIStatus, top_level, path=["response"]) + client_ = client.api_status() + assert_matches_type(APIStatus, client_, path=["response"]) @parametrize def test_raw_response_api_status(self, client: Lithic) -> None: @@ -28,8 +28,8 @@ def test_raw_response_api_status(self, client: Lithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - top_level = response.parse() - assert_matches_type(APIStatus, top_level, path=["response"]) + client_ = response.parse() + assert_matches_type(APIStatus, client_, path=["response"]) @parametrize def test_streaming_response_api_status(self, client: Lithic) -> None: @@ -37,19 +37,21 @@ def test_streaming_response_api_status(self, client: Lithic) -> None: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - top_level = response.parse() - assert_matches_type(APIStatus, top_level, path=["response"]) + client_ = response.parse() + assert_matches_type(APIStatus, client_, path=["response"]) assert cast(Any, response.is_closed) is True -class TestAsyncTopLevel: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) +class TestAsyncClient: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_api_status(self, async_client: AsyncLithic) -> None: - top_level = await async_client.api_status() - assert_matches_type(APIStatus, top_level, path=["response"]) + client = await async_client.api_status() + assert_matches_type(APIStatus, client, path=["response"]) @parametrize async def test_raw_response_api_status(self, async_client: AsyncLithic) -> None: @@ -57,8 +59,8 @@ async def test_raw_response_api_status(self, async_client: AsyncLithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - top_level = response.parse() - assert_matches_type(APIStatus, top_level, path=["response"]) + client = response.parse() + assert_matches_type(APIStatus, client, path=["response"]) @parametrize async def test_streaming_response_api_status(self, async_client: AsyncLithic) -> None: @@ -66,7 +68,7 @@ async def test_streaming_response_api_status(self, async_client: AsyncLithic) -> assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - top_level = await response.parse() - assert_matches_type(APIStatus, top_level, path=["response"]) + client = await response.parse() + assert_matches_type(APIStatus, client, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_digital_card_art.py b/tests/api_resources/test_digital_card_art.py index 9d002777..7c873768 100644 --- a/tests/api_resources/test_digital_card_art.py +++ b/tests/api_resources/test_digital_card_art.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -66,9 +66,9 @@ def test_method_list(self, client: Lithic) -> None: @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: digital_card_art = client.digital_card_art.list( - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(SyncCursorPage[DigitalCardArt], digital_card_art, path=["response"]) @@ -94,7 +94,9 @@ def test_streaming_response_list(self, client: Lithic) -> None: class TestAsyncDigitalCardArt: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: @@ -144,9 +146,9 @@ async def test_method_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: digital_card_art = await async_client.digital_card_art.list( - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(AsyncCursorPage[DigitalCardArt], digital_card_art, path=["response"]) diff --git a/tests/api_resources/test_disputes.py b/tests/api_resources/test_disputes.py index c1db18b7..28821e5c 100644 --- a/tests/api_resources/test_disputes.py +++ b/tests/api_resources/test_disputes.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: Lithic) -> None: reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", customer_filed_date=parse_datetime("2021-06-28T22:53:15Z"), - customer_note="string", + customer_note="customer_note", ) assert_matches_type(Dispute, dispute, path=["response"]) @@ -111,17 +111,17 @@ def test_path_params_retrieve(self, client: Lithic) -> None: @parametrize def test_method_update(self, client: Lithic) -> None: dispute = client.disputes.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Dispute, dispute, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: Lithic) -> None: dispute = client.disputes.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", amount=0, customer_filed_date=parse_datetime("2019-12-27T18:11:19.117Z"), - customer_note="string", + customer_note="customer_note", reason="ATM_CASH_MISDISPENSE", ) assert_matches_type(Dispute, dispute, path=["response"]) @@ -129,7 +129,7 @@ def test_method_update_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_update(self, client: Lithic) -> None: response = client.disputes.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -140,7 +140,7 @@ def test_raw_response_update(self, client: Lithic) -> None: @parametrize def test_streaming_response_update(self, client: Lithic) -> None: with client.disputes.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -154,7 +154,7 @@ def test_streaming_response_update(self, client: Lithic) -> None: def test_path_params_update(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): client.disputes.with_raw_response.update( - "", + dispute_token="", ) @parametrize @@ -167,15 +167,11 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: dispute = client.disputes.list( begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", status="ARBITRATION", - transaction_tokens=[ - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ], + transaction_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], ) assert_matches_type(SyncCursorPage[Dispute], dispute, path=["response"]) @@ -240,7 +236,7 @@ def test_path_params_delete(self, client: Lithic) -> None: @parametrize def test_method_delete_evidence(self, client: Lithic) -> None: dispute = client.disputes.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @@ -248,7 +244,7 @@ def test_method_delete_evidence(self, client: Lithic) -> None: @parametrize def test_raw_response_delete_evidence(self, client: Lithic) -> None: response = client.disputes.with_raw_response.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -260,7 +256,7 @@ def test_raw_response_delete_evidence(self, client: Lithic) -> None: @parametrize def test_streaming_response_delete_evidence(self, client: Lithic) -> None: with client.disputes.with_streaming_response.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -275,35 +271,35 @@ def test_streaming_response_delete_evidence(self, client: Lithic) -> None: def test_path_params_delete_evidence(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): client.disputes.with_raw_response.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `evidence_token` but received ''"): client.disputes.with_raw_response.delete_evidence( - "", + evidence_token="", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize def test_method_initiate_evidence_upload(self, client: Lithic) -> None: dispute = client.disputes.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @parametrize def test_method_initiate_evidence_upload_with_all_params(self, client: Lithic) -> None: dispute = client.disputes.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - filename="string", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + filename="filename", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @parametrize def test_raw_response_initiate_evidence_upload(self, client: Lithic) -> None: response = client.disputes.with_raw_response.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -314,7 +310,7 @@ def test_raw_response_initiate_evidence_upload(self, client: Lithic) -> None: @parametrize def test_streaming_response_initiate_evidence_upload(self, client: Lithic) -> None: with client.disputes.with_streaming_response.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -328,32 +324,32 @@ def test_streaming_response_initiate_evidence_upload(self, client: Lithic) -> No def test_path_params_initiate_evidence_upload(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): client.disputes.with_raw_response.initiate_evidence_upload( - "", + dispute_token="", ) @parametrize def test_method_list_evidences(self, client: Lithic) -> None: dispute = client.disputes.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(SyncCursorPage[DisputeEvidence], dispute, path=["response"]) @parametrize def test_method_list_evidences_with_all_params(self, client: Lithic) -> None: dispute = client.disputes.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(SyncCursorPage[DisputeEvidence], dispute, path=["response"]) @parametrize def test_raw_response_list_evidences(self, client: Lithic) -> None: response = client.disputes.with_raw_response.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -364,7 +360,7 @@ def test_raw_response_list_evidences(self, client: Lithic) -> None: @parametrize def test_streaming_response_list_evidences(self, client: Lithic) -> None: with client.disputes.with_streaming_response.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -378,13 +374,13 @@ def test_streaming_response_list_evidences(self, client: Lithic) -> None: def test_path_params_list_evidences(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): client.disputes.with_raw_response.list_evidences( - "", + dispute_token="", ) @parametrize def test_method_retrieve_evidence(self, client: Lithic) -> None: dispute = client.disputes.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @@ -392,7 +388,7 @@ def test_method_retrieve_evidence(self, client: Lithic) -> None: @parametrize def test_raw_response_retrieve_evidence(self, client: Lithic) -> None: response = client.disputes.with_raw_response.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -404,7 +400,7 @@ def test_raw_response_retrieve_evidence(self, client: Lithic) -> None: @parametrize def test_streaming_response_retrieve_evidence(self, client: Lithic) -> None: with client.disputes.with_streaming_response.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -419,19 +415,21 @@ def test_streaming_response_retrieve_evidence(self, client: Lithic) -> None: def test_path_params_retrieve_evidence(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): client.disputes.with_raw_response.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `evidence_token` but received ''"): client.disputes.with_raw_response.retrieve_evidence( - "", + evidence_token="", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) class TestAsyncDisputes: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: @@ -449,7 +447,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", customer_filed_date=parse_datetime("2021-06-28T22:53:15Z"), - customer_note="string", + customer_note="customer_note", ) assert_matches_type(Dispute, dispute, path=["response"]) @@ -522,17 +520,17 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(Dispute, dispute, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", amount=0, customer_filed_date=parse_datetime("2019-12-27T18:11:19.117Z"), - customer_note="string", + customer_note="customer_note", reason="ATM_CASH_MISDISPENSE", ) assert_matches_type(Dispute, dispute, path=["response"]) @@ -540,7 +538,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> @parametrize async def test_raw_response_update(self, async_client: AsyncLithic) -> None: response = await async_client.disputes.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -551,7 +549,7 @@ async def test_raw_response_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: async with async_client.disputes.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -565,7 +563,7 @@ async def test_streaming_response_update(self, async_client: AsyncLithic) -> Non async def test_path_params_update(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): await async_client.disputes.with_raw_response.update( - "", + dispute_token="", ) @parametrize @@ -578,15 +576,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N dispute = await async_client.disputes.list( begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", status="ARBITRATION", - transaction_tokens=[ - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ], + transaction_tokens=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], ) assert_matches_type(AsyncCursorPage[Dispute], dispute, path=["response"]) @@ -651,7 +645,7 @@ async def test_path_params_delete(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_delete_evidence(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @@ -659,7 +653,7 @@ async def test_method_delete_evidence(self, async_client: AsyncLithic) -> None: @parametrize async def test_raw_response_delete_evidence(self, async_client: AsyncLithic) -> None: response = await async_client.disputes.with_raw_response.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -671,7 +665,7 @@ async def test_raw_response_delete_evidence(self, async_client: AsyncLithic) -> @parametrize async def test_streaming_response_delete_evidence(self, async_client: AsyncLithic) -> None: async with async_client.disputes.with_streaming_response.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -686,35 +680,35 @@ async def test_streaming_response_delete_evidence(self, async_client: AsyncLithi async def test_path_params_delete_evidence(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): await async_client.disputes.with_raw_response.delete_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `evidence_token` but received ''"): await async_client.disputes.with_raw_response.delete_evidence( - "", + evidence_token="", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @parametrize async def test_method_initiate_evidence_upload(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @parametrize async def test_method_initiate_evidence_upload_with_all_params(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - filename="string", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + filename="filename", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @parametrize async def test_raw_response_initiate_evidence_upload(self, async_client: AsyncLithic) -> None: response = await async_client.disputes.with_raw_response.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -725,7 +719,7 @@ async def test_raw_response_initiate_evidence_upload(self, async_client: AsyncLi @parametrize async def test_streaming_response_initiate_evidence_upload(self, async_client: AsyncLithic) -> None: async with async_client.disputes.with_streaming_response.initiate_evidence_upload( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -739,32 +733,32 @@ async def test_streaming_response_initiate_evidence_upload(self, async_client: A async def test_path_params_initiate_evidence_upload(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): await async_client.disputes.with_raw_response.initiate_evidence_upload( - "", + dispute_token="", ) @parametrize async def test_method_list_evidences(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(AsyncCursorPage[DisputeEvidence], dispute, path=["response"]) @parametrize async def test_method_list_evidences_with_all_params(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", ) assert_matches_type(AsyncCursorPage[DisputeEvidence], dispute, path=["response"]) @parametrize async def test_raw_response_list_evidences(self, async_client: AsyncLithic) -> None: response = await async_client.disputes.with_raw_response.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -775,7 +769,7 @@ async def test_raw_response_list_evidences(self, async_client: AsyncLithic) -> N @parametrize async def test_streaming_response_list_evidences(self, async_client: AsyncLithic) -> None: async with async_client.disputes.with_streaming_response.list_evidences( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -789,13 +783,13 @@ async def test_streaming_response_list_evidences(self, async_client: AsyncLithic async def test_path_params_list_evidences(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): await async_client.disputes.with_raw_response.list_evidences( - "", + dispute_token="", ) @parametrize async def test_method_retrieve_evidence(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(DisputeEvidence, dispute, path=["response"]) @@ -803,7 +797,7 @@ async def test_method_retrieve_evidence(self, async_client: AsyncLithic) -> None @parametrize async def test_raw_response_retrieve_evidence(self, async_client: AsyncLithic) -> None: response = await async_client.disputes.with_raw_response.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -815,7 +809,7 @@ async def test_raw_response_retrieve_evidence(self, async_client: AsyncLithic) - @parametrize async def test_streaming_response_retrieve_evidence(self, async_client: AsyncLithic) -> None: async with async_client.disputes.with_streaming_response.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed @@ -830,12 +824,12 @@ async def test_streaming_response_retrieve_evidence(self, async_client: AsyncLit async def test_path_params_retrieve_evidence(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): await async_client.disputes.with_raw_response.retrieve_evidence( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + evidence_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", dispute_token="", ) with pytest.raises(ValueError, match=r"Expected a non-empty value for `evidence_token` but received ''"): await async_client.disputes.with_raw_response.retrieve_evidence( - "", + evidence_token="", dispute_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) diff --git a/tests/api_resources/test_disputes_v2.py b/tests/api_resources/test_disputes_v2.py new file mode 100644 index 00000000..31ff9d31 --- /dev/null +++ b/tests/api_resources/test_disputes_v2.py @@ -0,0 +1,180 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import DisputeV2 +from lithic._utils import parse_datetime +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestDisputesV2: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + disputes_v2 = client.disputes_v2.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DisputeV2, disputes_v2, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.disputes_v2.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + disputes_v2 = response.parse() + assert_matches_type(DisputeV2, disputes_v2, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.disputes_v2.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + disputes_v2 = response.parse() + assert_matches_type(DisputeV2, disputes_v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): + client.disputes_v2.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + disputes_v2 = client.disputes_v2.list() + assert_matches_type(SyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + disputes_v2 = client.disputes_v2.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + disputed_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(SyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.disputes_v2.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + disputes_v2 = response.parse() + assert_matches_type(SyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.disputes_v2.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + disputes_v2 = response.parse() + assert_matches_type(SyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncDisputesV2: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + disputes_v2 = await async_client.disputes_v2.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DisputeV2, disputes_v2, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.disputes_v2.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + disputes_v2 = response.parse() + assert_matches_type(DisputeV2, disputes_v2, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.disputes_v2.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + disputes_v2 = await response.parse() + assert_matches_type(DisputeV2, disputes_v2, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `dispute_token` but received ''"): + await async_client.disputes_v2.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + disputes_v2 = await async_client.disputes_v2.list() + assert_matches_type(AsyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + disputes_v2 = await async_client.disputes_v2.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + disputed_transaction_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(AsyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.disputes_v2.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + disputes_v2 = response.parse() + assert_matches_type(AsyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.disputes_v2.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + disputes_v2 = await response.parse() + assert_matches_type(AsyncCursorPage[DisputeV2], disputes_v2, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_events.py b/tests/api_resources/test_events.py index 893dc631..05ed07bd 100644 --- a/tests/api_resources/test_events.py +++ b/tests/api_resources/test_events.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -22,14 +22,14 @@ class TestEvents: @parametrize def test_method_retrieve(self, client: Lithic) -> None: event = client.events.retrieve( - "string", + "event_token", ) assert_matches_type(Event, event, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Lithic) -> None: response = client.events.with_raw_response.retrieve( - "string", + "event_token", ) assert response.is_closed is True @@ -40,7 +40,7 @@ def test_raw_response_retrieve(self, client: Lithic) -> None: @parametrize def test_streaming_response_retrieve(self, client: Lithic) -> None: with client.events.with_streaming_response.retrieve( - "string", + "event_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -67,10 +67,10 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: event = client.events.list( begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", - event_types=["account_holder.created", "account_holder.updated", "account_holder.verification"], + ending_before="ending_before", + event_types=["account_holder_document.updated"], page_size=1, - starting_after="string", + starting_after="starting_after", with_content=True, ) assert_matches_type(SyncCursorPage[Event], event, path=["response"]) @@ -98,19 +98,19 @@ def test_streaming_response_list(self, client: Lithic) -> None: @parametrize def test_method_list_attempts(self, client: Lithic) -> None: event = client.events.list_attempts( - "string", + event_token="event_token", ) assert_matches_type(SyncCursorPage[MessageAttempt], event, path=["response"]) @parametrize def test_method_list_attempts_with_all_params(self, client: Lithic) -> None: event = client.events.list_attempts( - "string", + event_token="event_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", status="FAILED", ) assert_matches_type(SyncCursorPage[MessageAttempt], event, path=["response"]) @@ -118,7 +118,7 @@ def test_method_list_attempts_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_list_attempts(self, client: Lithic) -> None: response = client.events.with_raw_response.list_attempts( - "string", + event_token="event_token", ) assert response.is_closed is True @@ -129,7 +129,7 @@ def test_raw_response_list_attempts(self, client: Lithic) -> None: @parametrize def test_streaming_response_list_attempts(self, client: Lithic) -> None: with client.events.with_streaming_response.list_attempts( - "string", + event_token="event_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -143,31 +143,26 @@ def test_streaming_response_list_attempts(self, client: Lithic) -> None: def test_path_params_list_attempts(self, client: Lithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `event_token` but received ''"): client.events.with_raw_response.list_attempts( - "", + event_token="", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") - def test_method_resend(self, client: Lithic) -> None: - client.events.resend( - "string", - event_subscription_token="string", - ) - class TestAsyncEvents: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: event = await async_client.events.retrieve( - "string", + "event_token", ) assert_matches_type(Event, event, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: response = await async_client.events.with_raw_response.retrieve( - "string", + "event_token", ) assert response.is_closed is True @@ -178,7 +173,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: async with async_client.events.with_streaming_response.retrieve( - "string", + "event_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -205,10 +200,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N event = await async_client.events.list( begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", - event_types=["account_holder.created", "account_holder.updated", "account_holder.verification"], + ending_before="ending_before", + event_types=["account_holder_document.updated"], page_size=1, - starting_after="string", + starting_after="starting_after", with_content=True, ) assert_matches_type(AsyncCursorPage[Event], event, path=["response"]) @@ -236,19 +231,19 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_list_attempts(self, async_client: AsyncLithic) -> None: event = await async_client.events.list_attempts( - "string", + event_token="event_token", ) assert_matches_type(AsyncCursorPage[MessageAttempt], event, path=["response"]) @parametrize async def test_method_list_attempts_with_all_params(self, async_client: AsyncLithic) -> None: event = await async_client.events.list_attempts( - "string", + event_token="event_token", begin=parse_datetime("2019-12-27T18:11:19.117Z"), end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", status="FAILED", ) assert_matches_type(AsyncCursorPage[MessageAttempt], event, path=["response"]) @@ -256,7 +251,7 @@ async def test_method_list_attempts_with_all_params(self, async_client: AsyncLit @parametrize async def test_raw_response_list_attempts(self, async_client: AsyncLithic) -> None: response = await async_client.events.with_raw_response.list_attempts( - "string", + event_token="event_token", ) assert response.is_closed is True @@ -267,7 +262,7 @@ async def test_raw_response_list_attempts(self, async_client: AsyncLithic) -> No @parametrize async def test_streaming_response_list_attempts(self, async_client: AsyncLithic) -> None: async with async_client.events.with_streaming_response.list_attempts( - "string", + event_token="event_token", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -281,12 +276,5 @@ async def test_streaming_response_list_attempts(self, async_client: AsyncLithic) async def test_path_params_list_attempts(self, async_client: AsyncLithic) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `event_token` but received ''"): await async_client.events.with_raw_response.list_attempts( - "", + event_token="", ) - - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") - async def test_method_resend(self, async_client: AsyncLithic) -> None: - await async_client.events.resend( - "string", - event_subscription_token="string", - ) diff --git a/tests/api_resources/test_external_bank_accounts.py b/tests/api_resources/test_external_bank_accounts.py index 45f0d235..9418e6bc 100644 --- a/tests/api_resources/test_external_bank_accounts.py +++ b/tests/api_resources/test_external_bank_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -10,6 +10,7 @@ from lithic import Lithic, AsyncLithic from tests.utils import assert_matches_type from lithic.types import ( + ExternalBankAccount, ExternalBankAccountListResponse, ExternalBankAccountCreateResponse, ExternalBankAccountUpdateResponse, @@ -28,11 +29,12 @@ class TestExternalBankAccounts: @parametrize def test_method_create_overload_1(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", @@ -42,28 +44,29 @@ def test_method_create_overload_1(self, client: Lithic) -> None: @parametrize def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", address={ "address1": "x", - "address2": "x", "city": "x", "country": "USD", "postal_code": "11201", "state": "xx", + "address2": "x", }, - company_id="x", + company_id="sq", dob=parse_date("2019-12-27"), - doing_business_as="string", - name="x", - user_defined_id="string", + doing_business_as="x", + name="name", + user_defined_id="x", verification_enforcement=True, ) assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) @@ -71,11 +74,12 @@ def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: @parametrize def test_raw_response_create_overload_1(self, client: Lithic) -> None: response = client.external_bank_accounts.with_raw_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", @@ -89,11 +93,12 @@ def test_raw_response_create_overload_1(self, client: Lithic) -> None: @parametrize def test_streaming_response_create_overload_1(self, client: Lithic) -> None: with client.external_bank_accounts.with_streaming_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", @@ -109,35 +114,56 @@ def test_streaming_response_create_overload_1(self, client: Lithic) -> None: @parametrize def test_method_create_overload_2(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", ) assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) @parametrize def test_method_create_with_all_params_overload_2(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - company_id="x", + address={ + "address1": "x", + "city": "x", + "country": "USD", + "postal_code": "11201", + "state": "xx", + "address2": "x", + }, + company_id="sq", dob=parse_date("2019-12-27"), - doing_business_as="string", - user_defined_id="string", + doing_business_as="x", + name="name", + user_defined_id="x", ) assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) @parametrize def test_raw_response_create_overload_2(self, client: Lithic) -> None: response = client.external_bank_accounts.with_raw_response.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", ) assert response.is_closed is True @@ -148,10 +174,94 @@ def test_raw_response_create_overload_2(self, client: Lithic) -> None: @parametrize def test_streaming_response_create_overload_2(self, client: Lithic) -> None: with client.external_bank_accounts.with_streaming_response.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_3(self, client: Lithic) -> None: + external_bank_account = client.external_bank_accounts.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", + ) + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_3(self, client: Lithic) -> None: + external_bank_account = client.external_bank_accounts.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "x", + "city": "x", + "country": "USD", + "postal_code": "11201", + "state": "xx", + "address2": "x", + }, + company_id="sq", + dob=parse_date("2019-12-27"), + doing_business_as="x", + name="name", + user_defined_id="x", + ) + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + @parametrize + def test_raw_response_create_overload_3(self, client: Lithic) -> None: + response = client.external_bank_accounts.with_raw_response.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_3(self, client: Lithic) -> None: + with client.external_bank_accounts.with_streaming_response.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -204,36 +314,37 @@ def test_path_params_retrieve(self, client: Lithic) -> None: @parametrize def test_method_update(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExternalBankAccountUpdateResponse, external_bank_account, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", address={ "address1": "x", - "address2": "x", "city": "x", "country": "USD", "postal_code": "11201", "state": "xx", + "address2": "x", }, - company_id="x", + company_id="sq", dob=parse_date("2019-12-27"), - doing_business_as="string", - name="x", - owner="x", - owner_type="BUSINESS", - user_defined_id="string", + doing_business_as="x", + name="name", + owner="owner", + owner_type="INDIVIDUAL", + type="CHECKING", + user_defined_id="x", ) assert_matches_type(ExternalBankAccountUpdateResponse, external_bank_account, path=["response"]) @parametrize def test_raw_response_update(self, client: Lithic) -> None: response = client.external_bank_accounts.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -244,7 +355,7 @@ def test_raw_response_update(self, client: Lithic) -> None: @parametrize def test_streaming_response_update(self, client: Lithic) -> None: with client.external_bank_accounts.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -260,7 +371,7 @@ def test_path_params_update(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" ): client.external_bank_accounts.with_raw_response.update( - "", + external_bank_account_token="", ) @parametrize @@ -272,14 +383,14 @@ def test_method_list(self, client: Lithic) -> None: def test_method_list_with_all_params(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.list( account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_types=["CHECKING", "SAVINGS"], - countries=["string", "string", "string"], - ending_before="string", - owner_types=["BUSINESS", "INDIVIDUAL"], + account_types=["CHECKING"], + countries=["string"], + ending_before="ending_before", + owner_types=["INDIVIDUAL"], page_size=1, - starting_after="string", - states=["CLOSED", "ENABLED", "PAUSED"], - verification_states=["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"], + starting_after="starting_after", + states=["ENABLED"], + verification_states=["PENDING"], ) assert_matches_type(SyncCursorPage[ExternalBankAccountListResponse], external_bank_account, path=["response"]) @@ -308,14 +419,22 @@ def test_streaming_response_list(self, client: Lithic) -> None: @parametrize def test_method_retry_micro_deposits(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.retry_micro_deposits( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccountRetryMicroDepositsResponse, external_bank_account, path=["response"]) + + @parametrize + def test_method_retry_micro_deposits_with_all_params(self, client: Lithic) -> None: + external_bank_account = client.external_bank_accounts.retry_micro_deposits( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExternalBankAccountRetryMicroDepositsResponse, external_bank_account, path=["response"]) @parametrize def test_raw_response_retry_micro_deposits(self, client: Lithic) -> None: response = client.external_bank_accounts.with_raw_response.retry_micro_deposits( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -326,7 +445,7 @@ def test_raw_response_retry_micro_deposits(self, client: Lithic) -> None: @parametrize def test_streaming_response_retry_micro_deposits(self, client: Lithic) -> None: with client.external_bank_accounts.with_streaming_response.retry_micro_deposits( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -342,21 +461,112 @@ def test_path_params_retry_micro_deposits(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" ): client.external_bank_accounts.with_raw_response.retry_micro_deposits( + external_bank_account_token="", + ) + + @parametrize + def test_method_retry_prenote(self, client: Lithic) -> None: + external_bank_account = client.external_bank_accounts.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + def test_method_retry_prenote_with_all_params(self, client: Lithic) -> None: + external_bank_account = client.external_bank_accounts.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + def test_raw_response_retry_prenote(self, client: Lithic) -> None: + response = client.external_bank_accounts.with_raw_response.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + def test_streaming_response_retry_prenote(self, client: Lithic) -> None: + with client.external_bank_accounts.with_streaming_response.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retry_prenote(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" + ): + client.external_bank_accounts.with_raw_response.retry_prenote( + external_bank_account_token="", + ) + + @parametrize + def test_method_unpause(self, client: Lithic) -> None: + external_bank_account = client.external_bank_accounts.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + def test_raw_response_unpause(self, client: Lithic) -> None: + response = client.external_bank_accounts.with_raw_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + def test_streaming_response_unpause(self, client: Lithic) -> None: + with client.external_bank_accounts.with_streaming_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_unpause(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" + ): + client.external_bank_accounts.with_raw_response.unpause( "", ) class TestAsyncExternalBankAccounts: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", @@ -366,28 +576,29 @@ async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None @parametrize async def test_method_create_with_all_params_overload_1(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", address={ "address1": "x", - "address2": "x", "city": "x", "country": "USD", "postal_code": "11201", "state": "xx", + "address2": "x", }, - company_id="x", + company_id="sq", dob=parse_date("2019-12-27"), - doing_business_as="string", - name="x", - user_defined_id="string", + doing_business_as="x", + name="name", + user_defined_id="x", verification_enforcement=True, ) assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) @@ -395,11 +606,12 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn @parametrize async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) -> None: response = await async_client.external_bank_accounts.with_raw_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", @@ -413,11 +625,12 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) - @parametrize async def test_streaming_response_create_overload_1(self, async_client: AsyncLithic) -> None: async with async_client.external_bank_accounts.with_streaming_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", - owner="x", - owner_type="BUSINESS", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + owner="owner", + owner_type="INDIVIDUAL", routing_number="123456789", type="CHECKING", verification_method="MANUAL", @@ -433,35 +646,56 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncLit @parametrize async def test_method_create_overload_2(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", ) assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) @parametrize async def test_method_create_with_all_params_overload_2(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - company_id="x", + address={ + "address1": "x", + "city": "x", + "country": "USD", + "postal_code": "11201", + "state": "xx", + "address2": "x", + }, + company_id="sq", dob=parse_date("2019-12-27"), - doing_business_as="string", - user_defined_id="string", + doing_business_as="x", + name="name", + user_defined_id="x", ) assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) @parametrize async def test_raw_response_create_overload_2(self, async_client: AsyncLithic) -> None: response = await async_client.external_bank_accounts.with_raw_response.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", ) assert response.is_closed is True @@ -472,10 +706,94 @@ async def test_raw_response_create_overload_2(self, async_client: AsyncLithic) - @parametrize async def test_streaming_response_create_overload_2(self, async_client: AsyncLithic) -> None: async with async_client.external_bank_accounts.with_streaming_response.create( - owner="x", - owner_type="BUSINESS", - processor_token="x", - verification_method="MANUAL", + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="EXTERNALLY_VERIFIED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_bank_account = await response.parse() + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_3(self, async_client: AsyncLithic) -> None: + external_bank_account = await async_client.external_bank_accounts.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", + ) + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_3(self, async_client: AsyncLithic) -> None: + external_bank_account = await async_client.external_bank_accounts.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + address={ + "address1": "x", + "city": "x", + "country": "USD", + "postal_code": "11201", + "state": "xx", + "address2": "x", + }, + company_id="sq", + dob=parse_date("2019-12-27"), + doing_business_as="x", + name="name", + user_defined_id="x", + ) + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_3(self, async_client: AsyncLithic) -> None: + response = await async_client.external_bank_accounts.with_raw_response.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccountCreateResponse, external_bank_account, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_3(self, async_client: AsyncLithic) -> None: + async with async_client.external_bank_accounts.with_streaming_response.create( + account_number="12345678901234567", + country="USD", + currency="USD", + owner="owner", + owner_type="INDIVIDUAL", + routing_number="123456789", + type="CHECKING", + verification_method="UNVERIFIED", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -528,36 +846,37 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExternalBankAccountUpdateResponse, external_bank_account, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", address={ "address1": "x", - "address2": "x", "city": "x", "country": "USD", "postal_code": "11201", "state": "xx", + "address2": "x", }, - company_id="x", + company_id="sq", dob=parse_date("2019-12-27"), - doing_business_as="string", - name="x", - owner="x", - owner_type="BUSINESS", - user_defined_id="string", + doing_business_as="x", + name="name", + owner="owner", + owner_type="INDIVIDUAL", + type="CHECKING", + user_defined_id="x", ) assert_matches_type(ExternalBankAccountUpdateResponse, external_bank_account, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncLithic) -> None: response = await async_client.external_bank_accounts.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -568,7 +887,7 @@ async def test_raw_response_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: async with async_client.external_bank_accounts.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -584,7 +903,7 @@ async def test_path_params_update(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" ): await async_client.external_bank_accounts.with_raw_response.update( - "", + external_bank_account_token="", ) @parametrize @@ -596,14 +915,14 @@ async def test_method_list(self, async_client: AsyncLithic) -> None: async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.list( account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - account_types=["CHECKING", "SAVINGS"], - countries=["string", "string", "string"], - ending_before="string", - owner_types=["BUSINESS", "INDIVIDUAL"], + account_types=["CHECKING"], + countries=["string"], + ending_before="ending_before", + owner_types=["INDIVIDUAL"], page_size=1, - starting_after="string", - states=["CLOSED", "ENABLED", "PAUSED"], - verification_states=["ENABLED", "FAILED_VERIFICATION", "INSUFFICIENT_FUNDS"], + starting_after="starting_after", + states=["ENABLED"], + verification_states=["PENDING"], ) assert_matches_type(AsyncCursorPage[ExternalBankAccountListResponse], external_bank_account, path=["response"]) @@ -632,14 +951,22 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_retry_micro_deposits(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.retry_micro_deposits( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccountRetryMicroDepositsResponse, external_bank_account, path=["response"]) + + @parametrize + async def test_method_retry_micro_deposits_with_all_params(self, async_client: AsyncLithic) -> None: + external_bank_account = await async_client.external_bank_accounts.retry_micro_deposits( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExternalBankAccountRetryMicroDepositsResponse, external_bank_account, path=["response"]) @parametrize async def test_raw_response_retry_micro_deposits(self, async_client: AsyncLithic) -> None: response = await async_client.external_bank_accounts.with_raw_response.retry_micro_deposits( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -650,7 +977,7 @@ async def test_raw_response_retry_micro_deposits(self, async_client: AsyncLithic @parametrize async def test_streaming_response_retry_micro_deposits(self, async_client: AsyncLithic) -> None: async with async_client.external_bank_accounts.with_streaming_response.retry_micro_deposits( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -666,5 +993,93 @@ async def test_path_params_retry_micro_deposits(self, async_client: AsyncLithic) ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" ): await async_client.external_bank_accounts.with_raw_response.retry_micro_deposits( + external_bank_account_token="", + ) + + @parametrize + async def test_method_retry_prenote(self, async_client: AsyncLithic) -> None: + external_bank_account = await async_client.external_bank_accounts.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + async def test_method_retry_prenote_with_all_params(self, async_client: AsyncLithic) -> None: + external_bank_account = await async_client.external_bank_accounts.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + async def test_raw_response_retry_prenote(self, async_client: AsyncLithic) -> None: + response = await async_client.external_bank_accounts.with_raw_response.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + async def test_streaming_response_retry_prenote(self, async_client: AsyncLithic) -> None: + async with async_client.external_bank_accounts.with_streaming_response.retry_prenote( + external_bank_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_bank_account = await response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retry_prenote(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" + ): + await async_client.external_bank_accounts.with_raw_response.retry_prenote( + external_bank_account_token="", + ) + + @parametrize + async def test_method_unpause(self, async_client: AsyncLithic) -> None: + external_bank_account = await async_client.external_bank_accounts.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + async def test_raw_response_unpause(self, async_client: AsyncLithic) -> None: + response = await async_client.external_bank_accounts.with_raw_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_bank_account = response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + @parametrize + async def test_streaming_response_unpause(self, async_client: AsyncLithic) -> None: + async with async_client.external_bank_accounts.with_streaming_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_bank_account = await response.parse() + assert_matches_type(ExternalBankAccount, external_bank_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_unpause(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_bank_account_token` but received ''" + ): + await async_client.external_bank_accounts.with_raw_response.unpause( "", ) diff --git a/tests/api_resources/test_external_payments.py b/tests/api_resources/test_external_payments.py new file mode 100644 index 00000000..1f36397d --- /dev/null +++ b/tests/api_resources/test_external_payments.py @@ -0,0 +1,732 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import ( + ExternalPayment, +) +from lithic._utils import parse_date, parse_datetime +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExternalPayments: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + external_payment = client.external_payments.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Lithic) -> None: + external_payment = client.external_payments.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + memo="memo", + progress_to="SETTLED", + user_defined_id="user_defined_id", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.external_payments.with_raw_response.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.external_payments.with_streaming_response.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + external_payment = client.external_payments.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.external_payments.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.external_payments.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + client.external_payments.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + external_payment = client.external_payments.list() + assert_matches_type(SyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + external_payment = client.external_payments.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="EXTERNAL_WIRE", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + result="APPROVED", + starting_after="starting_after", + status="PENDING", + ) + assert_matches_type(SyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.external_payments.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(SyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.external_payments.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = response.parse() + assert_matches_type(SyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_cancel(self, client: Lithic) -> None: + external_payment = client.external_payments.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_method_cancel_with_all_params(self, client: Lithic) -> None: + external_payment = client.external_payments.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_raw_response_cancel(self, client: Lithic) -> None: + response = client.external_payments.with_raw_response.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_streaming_response_cancel(self, client: Lithic) -> None: + with client.external_payments.with_streaming_response.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_cancel(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + client.external_payments.with_raw_response.cancel( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + def test_method_release(self, client: Lithic) -> None: + external_payment = client.external_payments.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_method_release_with_all_params(self, client: Lithic) -> None: + external_payment = client.external_payments.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_raw_response_release(self, client: Lithic) -> None: + response = client.external_payments.with_raw_response.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_streaming_response_release(self, client: Lithic) -> None: + with client.external_payments.with_streaming_response.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_release(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + client.external_payments.with_raw_response.release( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + def test_method_reverse(self, client: Lithic) -> None: + external_payment = client.external_payments.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_method_reverse_with_all_params(self, client: Lithic) -> None: + external_payment = client.external_payments.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_raw_response_reverse(self, client: Lithic) -> None: + response = client.external_payments.with_raw_response.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_streaming_response_reverse(self, client: Lithic) -> None: + with client.external_payments.with_streaming_response.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_reverse(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + client.external_payments.with_raw_response.reverse( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + def test_method_settle(self, client: Lithic) -> None: + external_payment = client.external_payments.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_method_settle_with_all_params(self, client: Lithic) -> None: + external_payment = client.external_payments.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + progress_to="SETTLED", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_raw_response_settle(self, client: Lithic) -> None: + response = client.external_payments.with_raw_response.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + def test_streaming_response_settle(self, client: Lithic) -> None: + with client.external_payments.with_streaming_response.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_settle(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + client.external_payments.with_raw_response.settle( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) + + +class TestAsyncExternalPayments: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + memo="memo", + progress_to="SETTLED", + user_defined_id="user_defined_id", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.external_payments.with_raw_response.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.external_payments.with_streaming_response.create( + amount=0, + category="EXTERNAL_WIRE", + effective_date=parse_date("2019-12-27"), + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payment_type="DEPOSIT", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = await response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.external_payments.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.external_payments.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = await response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + await async_client.external_payments.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.list() + assert_matches_type(AsyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="EXTERNAL_WIRE", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + result="APPROVED", + starting_after="starting_after", + status="PENDING", + ) + assert_matches_type(AsyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.external_payments.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(AsyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.external_payments.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = await response.parse() + assert_matches_type(AsyncCursorPage[ExternalPayment], external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_cancel(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_method_cancel_with_all_params(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncLithic) -> None: + response = await async_client.external_payments.with_raw_response.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncLithic) -> None: + async with async_client.external_payments.with_streaming_response.cancel( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = await response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_cancel(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + await async_client.external_payments.with_raw_response.cancel( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + async def test_method_release(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_method_release_with_all_params(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_raw_response_release(self, async_client: AsyncLithic) -> None: + response = await async_client.external_payments.with_raw_response.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_streaming_response_release(self, async_client: AsyncLithic) -> None: + async with async_client.external_payments.with_streaming_response.release( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = await response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_release(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + await async_client.external_payments.with_raw_response.release( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + async def test_method_reverse(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_method_reverse_with_all_params(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_raw_response_reverse(self, async_client: AsyncLithic) -> None: + response = await async_client.external_payments.with_raw_response.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_streaming_response_reverse(self, async_client: AsyncLithic) -> None: + async with async_client.external_payments.with_streaming_response.reverse( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = await response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_reverse(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + await async_client.external_payments.with_raw_response.reverse( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) + + @parametrize + async def test_method_settle(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_method_settle_with_all_params(self, async_client: AsyncLithic) -> None: + external_payment = await async_client.external_payments.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + progress_to="SETTLED", + ) + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_raw_response_settle(self, async_client: AsyncLithic) -> None: + response = await async_client.external_payments.with_raw_response.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_payment = response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + @parametrize + async def test_streaming_response_settle(self, async_client: AsyncLithic) -> None: + async with async_client.external_payments.with_streaming_response.settle( + external_payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_payment = await response.parse() + assert_matches_type(ExternalPayment, external_payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_settle(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_payment_token` but received ''" + ): + await async_client.external_payments.with_raw_response.settle( + external_payment_token="", + effective_date=parse_date("2019-12-27"), + ) diff --git a/tests/api_resources/test_financial_accounts.py b/tests/api_resources/test_financial_accounts.py index 32d7e033..b490ddb3 100644 --- a/tests/api_resources/test_financial_accounts.py +++ b/tests/api_resources/test_financial_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -23,7 +23,7 @@ class TestFinancialAccounts: @parametrize def test_method_create(self, client: Lithic) -> None: financial_account = client.financial_accounts.create( - nickname="string", + nickname="nickname", type="OPERATING", ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @@ -31,16 +31,17 @@ def test_method_create(self, client: Lithic) -> None: @parametrize def test_method_create_with_all_params(self, client: Lithic) -> None: financial_account = client.financial_accounts.create( - nickname="string", + nickname="nickname", type="OPERATING", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + is_for_benefit_of=True, ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @parametrize def test_raw_response_create(self, client: Lithic) -> None: response = client.financial_accounts.with_raw_response.create( - nickname="string", + nickname="nickname", type="OPERATING", ) @@ -52,7 +53,7 @@ def test_raw_response_create(self, client: Lithic) -> None: @parametrize def test_streaming_response_create(self, client: Lithic) -> None: with client.financial_accounts.with_streaming_response.create( - nickname="string", + nickname="nickname", type="OPERATING", ) as response: assert not response.is_closed @@ -106,22 +107,22 @@ def test_path_params_retrieve(self, client: Lithic) -> None: @parametrize def test_method_update(self, client: Lithic) -> None: financial_account = client.financial_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: Lithic) -> None: financial_account = client.financial_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - nickname="string", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + nickname="nickname", ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @parametrize def test_raw_response_update(self, client: Lithic) -> None: response = client.financial_accounts.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -132,7 +133,7 @@ def test_raw_response_update(self, client: Lithic) -> None: @parametrize def test_streaming_response_update(self, client: Lithic) -> None: with client.financial_accounts.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -148,7 +149,7 @@ def test_path_params_update(self, client: Lithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): client.financial_accounts.with_raw_response.update( - "", + financial_account_token="", ) @parametrize @@ -185,14 +186,108 @@ def test_streaming_response_list(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_register_account_number(self, client: Lithic) -> None: + financial_account = client.financial_accounts.register_account_number( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_number="account_number", + ) + assert financial_account is None + + @parametrize + def test_raw_response_register_account_number(self, client: Lithic) -> None: + response = client.financial_accounts.with_raw_response.register_account_number( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_number="account_number", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + financial_account = response.parse() + assert financial_account is None + + @parametrize + def test_streaming_response_register_account_number(self, client: Lithic) -> None: + with client.financial_accounts.with_streaming_response.register_account_number( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_number="account_number", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + financial_account = response.parse() + assert financial_account is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_register_account_number(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.with_raw_response.register_account_number( + financial_account_token="", + account_number="account_number", + ) + + @parametrize + def test_method_update_status(self, client: Lithic) -> None: + financial_account = client.financial_accounts.update_status( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) + assert_matches_type(FinancialAccount, financial_account, path=["response"]) + + @parametrize + def test_raw_response_update_status(self, client: Lithic) -> None: + response = client.financial_accounts.with_raw_response.update_status( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + financial_account = response.parse() + assert_matches_type(FinancialAccount, financial_account, path=["response"]) + + @parametrize + def test_streaming_response_update_status(self, client: Lithic) -> None: + with client.financial_accounts.with_streaming_response.update_status( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + financial_account = response.parse() + assert_matches_type(FinancialAccount, financial_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_status(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + client.financial_accounts.with_raw_response.update_status( + financial_account_token="", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) + class TestAsyncFinancialAccounts: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: financial_account = await async_client.financial_accounts.create( - nickname="string", + nickname="nickname", type="OPERATING", ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @@ -200,16 +295,17 @@ async def test_method_create(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: financial_account = await async_client.financial_accounts.create( - nickname="string", + nickname="nickname", type="OPERATING", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + is_for_benefit_of=True, ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.with_raw_response.create( - nickname="string", + nickname="nickname", type="OPERATING", ) @@ -221,7 +317,7 @@ async def test_raw_response_create(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.with_streaming_response.create( - nickname="string", + nickname="nickname", type="OPERATING", ) as response: assert not response.is_closed @@ -275,22 +371,22 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_update(self, async_client: AsyncLithic) -> None: financial_account = await async_client.financial_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> None: financial_account = await async_client.financial_accounts.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - nickname="string", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + nickname="nickname", ) assert_matches_type(FinancialAccount, financial_account, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncLithic) -> None: response = await async_client.financial_accounts.with_raw_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert response.is_closed is True @@ -301,7 +397,7 @@ async def test_raw_response_update(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncLithic) -> None: async with async_client.financial_accounts.with_streaming_response.update( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -317,7 +413,7 @@ async def test_path_params_update(self, async_client: AsyncLithic) -> None: ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" ): await async_client.financial_accounts.with_raw_response.update( - "", + financial_account_token="", ) @parametrize @@ -353,3 +449,95 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: assert_matches_type(AsyncSinglePage[FinancialAccount], financial_account, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_register_account_number(self, async_client: AsyncLithic) -> None: + financial_account = await async_client.financial_accounts.register_account_number( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_number="account_number", + ) + assert financial_account is None + + @parametrize + async def test_raw_response_register_account_number(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.with_raw_response.register_account_number( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_number="account_number", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + financial_account = response.parse() + assert financial_account is None + + @parametrize + async def test_streaming_response_register_account_number(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.with_streaming_response.register_account_number( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + account_number="account_number", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + financial_account = await response.parse() + assert financial_account is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_register_account_number(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.with_raw_response.register_account_number( + financial_account_token="", + account_number="account_number", + ) + + @parametrize + async def test_method_update_status(self, async_client: AsyncLithic) -> None: + financial_account = await async_client.financial_accounts.update_status( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) + assert_matches_type(FinancialAccount, financial_account, path=["response"]) + + @parametrize + async def test_raw_response_update_status(self, async_client: AsyncLithic) -> None: + response = await async_client.financial_accounts.with_raw_response.update_status( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + financial_account = response.parse() + assert_matches_type(FinancialAccount, financial_account, path=["response"]) + + @parametrize + async def test_streaming_response_update_status(self, async_client: AsyncLithic) -> None: + async with async_client.financial_accounts.with_streaming_response.update_status( + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + financial_account = await response.parse() + assert_matches_type(FinancialAccount, financial_account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_status(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `financial_account_token` but received ''" + ): + await async_client.financial_accounts.with_raw_response.update_status( + financial_account_token="", + status="OPEN", + substatus="CHARGED_OFF_FRAUD", + ) diff --git a/tests/api_resources/test_funding_events.py b/tests/api_resources/test_funding_events.py new file mode 100644 index 00000000..fad60692 --- /dev/null +++ b/tests/api_resources/test_funding_events.py @@ -0,0 +1,245 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import FundingEvent, FundingEventRetrieveDetailsResponse +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestFundingEvents: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + funding_event = client.funding_events.retrieve( + "funding_event_token", + ) + assert_matches_type(FundingEvent, funding_event, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.funding_events.with_raw_response.retrieve( + "funding_event_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + funding_event = response.parse() + assert_matches_type(FundingEvent, funding_event, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.funding_events.with_streaming_response.retrieve( + "funding_event_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + funding_event = response.parse() + assert_matches_type(FundingEvent, funding_event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `funding_event_token` but received ''"): + client.funding_events.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + funding_event = client.funding_events.list() + assert_matches_type(SyncCursorPage[FundingEvent], funding_event, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + funding_event = client.funding_events.list( + ending_before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(SyncCursorPage[FundingEvent], funding_event, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.funding_events.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + funding_event = response.parse() + assert_matches_type(SyncCursorPage[FundingEvent], funding_event, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.funding_events.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + funding_event = response.parse() + assert_matches_type(SyncCursorPage[FundingEvent], funding_event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve_details(self, client: Lithic) -> None: + funding_event = client.funding_events.retrieve_details( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FundingEventRetrieveDetailsResponse, funding_event, path=["response"]) + + @parametrize + def test_raw_response_retrieve_details(self, client: Lithic) -> None: + response = client.funding_events.with_raw_response.retrieve_details( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + funding_event = response.parse() + assert_matches_type(FundingEventRetrieveDetailsResponse, funding_event, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_details(self, client: Lithic) -> None: + with client.funding_events.with_streaming_response.retrieve_details( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + funding_event = response.parse() + assert_matches_type(FundingEventRetrieveDetailsResponse, funding_event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_details(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `funding_event_token` but received ''"): + client.funding_events.with_raw_response.retrieve_details( + "", + ) + + +class TestAsyncFundingEvents: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + funding_event = await async_client.funding_events.retrieve( + "funding_event_token", + ) + assert_matches_type(FundingEvent, funding_event, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.funding_events.with_raw_response.retrieve( + "funding_event_token", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + funding_event = response.parse() + assert_matches_type(FundingEvent, funding_event, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.funding_events.with_streaming_response.retrieve( + "funding_event_token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + funding_event = await response.parse() + assert_matches_type(FundingEvent, funding_event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `funding_event_token` but received ''"): + await async_client.funding_events.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + funding_event = await async_client.funding_events.list() + assert_matches_type(AsyncCursorPage[FundingEvent], funding_event, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + funding_event = await async_client.funding_events.list( + ending_before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + starting_after="starting_after", + ) + assert_matches_type(AsyncCursorPage[FundingEvent], funding_event, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.funding_events.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + funding_event = response.parse() + assert_matches_type(AsyncCursorPage[FundingEvent], funding_event, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.funding_events.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + funding_event = await response.parse() + assert_matches_type(AsyncCursorPage[FundingEvent], funding_event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve_details(self, async_client: AsyncLithic) -> None: + funding_event = await async_client.funding_events.retrieve_details( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FundingEventRetrieveDetailsResponse, funding_event, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_details(self, async_client: AsyncLithic) -> None: + response = await async_client.funding_events.with_raw_response.retrieve_details( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + funding_event = response.parse() + assert_matches_type(FundingEventRetrieveDetailsResponse, funding_event, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_details(self, async_client: AsyncLithic) -> None: + async with async_client.funding_events.with_streaming_response.retrieve_details( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + funding_event = await response.parse() + assert_matches_type(FundingEventRetrieveDetailsResponse, funding_event, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_details(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `funding_event_token` but received ''"): + await async_client.funding_events.with_raw_response.retrieve_details( + "", + ) diff --git a/tests/api_resources/test_management_operations.py b/tests/api_resources/test_management_operations.py new file mode 100644 index 00000000..89e1e3c0 --- /dev/null +++ b/tests/api_resources/test_management_operations.py @@ -0,0 +1,422 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import ( + ManagementOperationTransaction, +) +from lithic._utils import parse_date, parse_datetime +from lithic.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestManagementOperations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Lithic) -> None: + management_operation = client.management_operations.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Lithic) -> None: + management_operation = client.management_operations.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + memo="memo", + on_closed_account="FAIL", + subtype="subtype", + user_defined_id="user_defined_id", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Lithic) -> None: + response = client.management_operations.with_raw_response.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Lithic) -> None: + with client.management_operations.with_streaming_response.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + management_operation = client.management_operations.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.management_operations.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.management_operations.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `management_operation_token` but received ''" + ): + client.management_operations.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + management_operation = client.management_operations.list() + assert_matches_type(SyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + management_operation = client.management_operations.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="MANAGEMENT_FEE", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + starting_after="starting_after", + status="PENDING", + ) + assert_matches_type(SyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.management_operations.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(SyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.management_operations.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = response.parse() + assert_matches_type(SyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_reverse(self, client: Lithic) -> None: + management_operation = client.management_operations.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_method_reverse_with_all_params(self, client: Lithic) -> None: + management_operation = client.management_operations.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_raw_response_reverse(self, client: Lithic) -> None: + response = client.management_operations.with_raw_response.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + def test_streaming_response_reverse(self, client: Lithic) -> None: + with client.management_operations.with_streaming_response.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_reverse(self, client: Lithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `management_operation_token` but received ''" + ): + client.management_operations.with_raw_response.reverse( + management_operation_token="", + effective_date=parse_date("2019-12-27"), + ) + + +class TestAsyncManagementOperations: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncLithic) -> None: + management_operation = await async_client.management_operations.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: + management_operation = await async_client.management_operations.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + memo="memo", + on_closed_account="FAIL", + subtype="subtype", + user_defined_id="user_defined_id", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncLithic) -> None: + response = await async_client.management_operations.with_raw_response.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: + async with async_client.management_operations.with_streaming_response.create( + amount=1, + category="MANAGEMENT_FEE", + direction="CREDIT", + effective_date=parse_date("2019-12-27"), + event_type="LOSS_WRITE_OFF", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = await response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + management_operation = await async_client.management_operations.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.management_operations.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.management_operations.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = await response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `management_operation_token` but received ''" + ): + await async_client.management_operations.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + management_operation = await async_client.management_operations.list() + assert_matches_type(AsyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + management_operation = await async_client.management_operations.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="MANAGEMENT_FEE", + end=parse_datetime("2019-12-27T18:11:19.117Z"), + ending_before="ending_before", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page_size=1, + starting_after="starting_after", + status="PENDING", + ) + assert_matches_type(AsyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.management_operations.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(AsyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.management_operations.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = await response.parse() + assert_matches_type( + AsyncCursorPage[ManagementOperationTransaction], management_operation, path=["response"] + ) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_reverse(self, async_client: AsyncLithic) -> None: + management_operation = await async_client.management_operations.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_method_reverse_with_all_params(self, async_client: AsyncLithic) -> None: + management_operation = await async_client.management_operations.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + memo="memo", + ) + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_raw_response_reverse(self, async_client: AsyncLithic) -> None: + response = await async_client.management_operations.with_raw_response.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + management_operation = response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + @parametrize + async def test_streaming_response_reverse(self, async_client: AsyncLithic) -> None: + async with async_client.management_operations.with_streaming_response.reverse( + management_operation_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + effective_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + management_operation = await response.parse() + assert_matches_type(ManagementOperationTransaction, management_operation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_reverse(self, async_client: AsyncLithic) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `management_operation_token` but received ''" + ): + await async_client.management_operations.with_raw_response.reverse( + management_operation_token="", + effective_date=parse_date("2019-12-27"), + ) diff --git a/tests/api_resources/test_network_programs.py b/tests/api_resources/test_network_programs.py new file mode 100644 index 00000000..f6cf21c2 --- /dev/null +++ b/tests/api_resources/test_network_programs.py @@ -0,0 +1,170 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types import NetworkProgram +from lithic._utils import parse_datetime +from lithic.pagination import SyncSinglePage, AsyncSinglePage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestNetworkPrograms: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + network_program = client.network_programs.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(NetworkProgram, network_program, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.network_programs.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_program = response.parse() + assert_matches_type(NetworkProgram, network_program, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.network_programs.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_program = response.parse() + assert_matches_type(NetworkProgram, network_program, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `network_program_token` but received ''"): + client.network_programs.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Lithic) -> None: + network_program = client.network_programs.list() + assert_matches_type(SyncSinglePage[NetworkProgram], network_program, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Lithic) -> None: + network_program = client.network_programs.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + end=parse_datetime("2019-12-27T18:11:19.117Z"), + page_size=1, + ) + assert_matches_type(SyncSinglePage[NetworkProgram], network_program, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Lithic) -> None: + response = client.network_programs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_program = response.parse() + assert_matches_type(SyncSinglePage[NetworkProgram], network_program, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Lithic) -> None: + with client.network_programs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_program = response.parse() + assert_matches_type(SyncSinglePage[NetworkProgram], network_program, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncNetworkPrograms: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + network_program = await async_client.network_programs.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(NetworkProgram, network_program, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.network_programs.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_program = response.parse() + assert_matches_type(NetworkProgram, network_program, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.network_programs.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_program = await response.parse() + assert_matches_type(NetworkProgram, network_program, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `network_program_token` but received ''"): + await async_client.network_programs.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncLithic) -> None: + network_program = await async_client.network_programs.list() + assert_matches_type(AsyncSinglePage[NetworkProgram], network_program, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: + network_program = await async_client.network_programs.list( + begin=parse_datetime("2019-12-27T18:11:19.117Z"), + end=parse_datetime("2019-12-27T18:11:19.117Z"), + page_size=1, + ) + assert_matches_type(AsyncSinglePage[NetworkProgram], network_program, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncLithic) -> None: + response = await async_client.network_programs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_program = response.parse() + assert_matches_type(AsyncSinglePage[NetworkProgram], network_program, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: + async with async_client.network_programs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_program = await response.parse() + assert_matches_type(AsyncSinglePage[NetworkProgram], network_program, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_payments.py b/tests/api_resources/test_payments.py index 72f04978..5a17ca96 100644 --- a/tests/api_resources/test_payments.py +++ b/tests/api_resources/test_payments.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -13,10 +13,12 @@ Payment, PaymentRetryResponse, PaymentCreateResponse, + PaymentSimulateActionResponse, PaymentSimulateReturnResponse, + PaymentSimulateReceiptResponse, PaymentSimulateReleaseResponse, ) -from lithic._utils import parse_datetime +from lithic._utils import parse_date, parse_datetime from lithic.pagination import SyncCursorPage, AsyncCursorPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -45,16 +47,14 @@ def test_method_create_with_all_params(self, client: Lithic) -> None: financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", method="ACH_NEXT_DAY", method_attributes={ - "company_id": "string", - "receipt_routing_number": "string", - "retries": 0, - "return_reason_code": "string", "sec_code": "CCD", + "ach_hold_period": 0, + "addenda": "addenda", }, type="COLLECTION", token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - memo="string", - user_defined_id="string", + memo="memo", + user_defined_id="user_defined_id", ) assert_matches_type(PaymentCreateResponse, payment, path=["response"]) @@ -138,13 +138,16 @@ def test_method_list(self, client: Lithic) -> None: @parametrize def test_method_list_with_all_params(self, client: Lithic) -> None: payment = client.payments.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="ACH", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", page_size=1, result="APPROVED", - starting_after="string", + starting_after="starting_after", status="DECLINED", ) assert_matches_type(SyncCursorPage[Payment], payment, path=["response"]) @@ -207,6 +210,169 @@ def test_path_params_retry(self, client: Lithic) -> None: "", ) + @parametrize + def test_method_return(self, client: Lithic) -> None: + payment = client.payments.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @parametrize + def test_method_return_with_all_params(self, client: Lithic) -> None: + payment = client.payments.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + addenda="addenda", + date_of_death=parse_date("2025-01-15"), + memo="memo", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @parametrize + def test_raw_response_return(self, client: Lithic) -> None: + response = client.payments.with_raw_response.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @parametrize + def test_streaming_response_return(self, client: Lithic) -> None: + with client.payments.with_streaming_response.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_return(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_token` but received ''"): + client.payments.with_raw_response.return_( + payment_token="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) + + @parametrize + def test_method_simulate_action(self, client: Lithic) -> None: + payment = client.payments.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + ) + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + @parametrize + def test_method_simulate_action_with_all_params(self, client: Lithic) -> None: + payment = client.payments.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + date_of_death=parse_date("2019-12-27"), + decline_reason="PROGRAM_TRANSACTION_LIMIT_EXCEEDED", + return_addenda="return_addenda", + return_reason_code="return_reason_code", + ) + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + @parametrize + def test_raw_response_simulate_action(self, client: Lithic) -> None: + response = client.payments.with_raw_response.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + @parametrize + def test_streaming_response_simulate_action(self, client: Lithic) -> None: + with client.payments.with_streaming_response.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_simulate_action(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_token` but received ''"): + client.payments.with_raw_response.simulate_action( + payment_token="", + event_type="ACH_ORIGINATION_REVIEWED", + ) + + @parametrize + def test_method_simulate_receipt(self, client: Lithic) -> None: + payment = client.payments.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + ) + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + @parametrize + def test_method_simulate_receipt_with_all_params(self, client: Lithic) -> None: + payment = client.payments.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + memo="memo", + ) + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + @parametrize + def test_raw_response_simulate_receipt(self, client: Lithic) -> None: + response = client.payments.with_raw_response.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + @parametrize + def test_streaming_response_simulate_receipt(self, client: Lithic) -> None: + with client.payments.with_streaming_response.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_simulate_release(self, client: Lithic) -> None: payment = client.payments.simulate_release( @@ -249,7 +415,7 @@ def test_method_simulate_return(self, client: Lithic) -> None: def test_method_simulate_return_with_all_params(self, client: Lithic) -> None: payment = client.payments.simulate_return( payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - return_reason_code="string", + return_reason_code="R12", ) assert_matches_type(PaymentSimulateReturnResponse, payment, path=["response"]) @@ -279,7 +445,9 @@ def test_streaming_response_simulate_return(self, client: Lithic) -> None: class TestAsyncPayments: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: @@ -301,16 +469,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", method="ACH_NEXT_DAY", method_attributes={ - "company_id": "string", - "receipt_routing_number": "string", - "retries": 0, - "return_reason_code": "string", "sec_code": "CCD", + "ach_hold_period": 0, + "addenda": "addenda", }, type="COLLECTION", token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - memo="string", - user_defined_id="string", + memo="memo", + user_defined_id="user_defined_id", ) assert_matches_type(PaymentCreateResponse, payment, path=["response"]) @@ -394,13 +560,16 @@ async def test_method_list(self, async_client: AsyncLithic) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> None: payment = await async_client.payments.list( + account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", begin=parse_datetime("2019-12-27T18:11:19.117Z"), + business_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + category="ACH", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", page_size=1, result="APPROVED", - starting_after="string", + starting_after="starting_after", status="DECLINED", ) assert_matches_type(AsyncCursorPage[Payment], payment, path=["response"]) @@ -463,6 +632,169 @@ async def test_path_params_retry(self, async_client: AsyncLithic) -> None: "", ) + @parametrize + async def test_method_return(self, async_client: AsyncLithic) -> None: + payment = await async_client.payments.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @parametrize + async def test_method_return_with_all_params(self, async_client: AsyncLithic) -> None: + payment = await async_client.payments.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + addenda="addenda", + date_of_death=parse_date("2025-01-15"), + memo="memo", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @parametrize + async def test_raw_response_return(self, async_client: AsyncLithic) -> None: + response = await async_client.payments.with_raw_response.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @parametrize + async def test_streaming_response_return(self, async_client: AsyncLithic) -> None: + async with async_client.payments.with_streaming_response.return_( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_return(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_token` but received ''"): + await async_client.payments.with_raw_response.return_( + payment_token="", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + return_reason_code="R01", + ) + + @parametrize + async def test_method_simulate_action(self, async_client: AsyncLithic) -> None: + payment = await async_client.payments.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + ) + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + @parametrize + async def test_method_simulate_action_with_all_params(self, async_client: AsyncLithic) -> None: + payment = await async_client.payments.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + date_of_death=parse_date("2019-12-27"), + decline_reason="PROGRAM_TRANSACTION_LIMIT_EXCEEDED", + return_addenda="return_addenda", + return_reason_code="return_reason_code", + ) + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + @parametrize + async def test_raw_response_simulate_action(self, async_client: AsyncLithic) -> None: + response = await async_client.payments.with_raw_response.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + @parametrize + async def test_streaming_response_simulate_action(self, async_client: AsyncLithic) -> None: + async with async_client.payments.with_streaming_response.simulate_action( + payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + event_type="ACH_ORIGINATION_REVIEWED", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(PaymentSimulateActionResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_simulate_action(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_token` but received ''"): + await async_client.payments.with_raw_response.simulate_action( + payment_token="", + event_type="ACH_ORIGINATION_REVIEWED", + ) + + @parametrize + async def test_method_simulate_receipt(self, async_client: AsyncLithic) -> None: + payment = await async_client.payments.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + ) + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + @parametrize + async def test_method_simulate_receipt_with_all_params(self, async_client: AsyncLithic) -> None: + payment = await async_client.payments.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + memo="memo", + ) + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + @parametrize + async def test_raw_response_simulate_receipt(self, async_client: AsyncLithic) -> None: + response = await async_client.payments.with_raw_response.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + @parametrize + async def test_streaming_response_simulate_receipt(self, async_client: AsyncLithic) -> None: + async with async_client.payments.with_streaming_response.simulate_receipt( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + amount=0, + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + receipt_type="RECEIPT_CREDIT", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(PaymentSimulateReceiptResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_simulate_release(self, async_client: AsyncLithic) -> None: payment = await async_client.payments.simulate_release( @@ -505,7 +837,7 @@ async def test_method_simulate_return(self, async_client: AsyncLithic) -> None: async def test_method_simulate_return_with_all_params(self, async_client: AsyncLithic) -> None: payment = await async_client.payments.simulate_return( payment_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - return_reason_code="string", + return_reason_code="R12", ) assert_matches_type(PaymentSimulateReturnResponse, payment, path=["response"]) diff --git a/tests/api_resources/test_responder_endpoints.py b/tests/api_resources/test_responder_endpoints.py index e5d9e506..beb88d7e 100644 --- a/tests/api_resources/test_responder_endpoints.py +++ b/tests/api_resources/test_responder_endpoints.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -120,7 +120,9 @@ def test_streaming_response_check_status(self, client: Lithic) -> None: class TestAsyncResponderEndpoints: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: diff --git a/tests/api_resources/test_tokenization_decisioning.py b/tests/api_resources/test_tokenization_decisioning.py index 7ae71bbc..5dae2a06 100644 --- a/tests/api_resources/test_tokenization_decisioning.py +++ b/tests/api_resources/test_tokenization_decisioning.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -71,7 +71,9 @@ def test_streaming_response_rotate_secret(self, client: Lithic) -> None: class TestAsyncTokenizationDecisioning: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve_secret(self, async_client: AsyncLithic) -> None: diff --git a/tests/api_resources/test_tokenizations.py b/tests/api_resources/test_tokenizations.py index 1d48cc9a..28ee7ed1 100644 --- a/tests/api_resources/test_tokenizations.py +++ b/tests/api_resources/test_tokenizations.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -11,8 +11,6 @@ from tests.utils import assert_matches_type from lithic.types import ( Tokenization, - TokenizationRetrieveResponse, - TokenizationSimulateResponse, ) from lithic._utils import parse_date from lithic.pagination import SyncCursorPage, AsyncCursorPage @@ -28,7 +26,7 @@ def test_method_retrieve(self, client: Lithic) -> None: tokenization = client.tokenizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(TokenizationRetrieveResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Lithic) -> None: @@ -39,7 +37,7 @@ def test_raw_response_retrieve(self, client: Lithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = response.parse() - assert_matches_type(TokenizationRetrieveResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Lithic) -> None: @@ -50,7 +48,7 @@ def test_streaming_response_retrieve(self, client: Lithic) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = response.parse() - assert_matches_type(TokenizationRetrieveResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) assert cast(Any, response.is_closed) is True @@ -73,9 +71,10 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: begin=parse_date("2019-12-27"), card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=parse_date("2019-12-27"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", + tokenization_channel="DIGITAL_WALLET", ) assert_matches_type(SyncCursorPage[Tokenization], tokenization, path=["response"]) @@ -99,6 +98,166 @@ def test_streaming_response_list(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_activate(self, client: Lithic) -> None: + tokenization = client.tokenizations.activate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + def test_raw_response_activate(self, client: Lithic) -> None: + response = client.tokenizations.with_raw_response.activate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + def test_streaming_response_activate(self, client: Lithic) -> None: + with client.tokenizations.with_streaming_response.activate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_activate(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + client.tokenizations.with_raw_response.activate( + "", + ) + + @parametrize + def test_method_deactivate(self, client: Lithic) -> None: + tokenization = client.tokenizations.deactivate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + def test_raw_response_deactivate(self, client: Lithic) -> None: + response = client.tokenizations.with_raw_response.deactivate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + def test_streaming_response_deactivate(self, client: Lithic) -> None: + with client.tokenizations.with_streaming_response.deactivate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_deactivate(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + client.tokenizations.with_raw_response.deactivate( + "", + ) + + @parametrize + def test_method_pause(self, client: Lithic) -> None: + tokenization = client.tokenizations.pause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + def test_raw_response_pause(self, client: Lithic) -> None: + response = client.tokenizations.with_raw_response.pause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + def test_streaming_response_pause(self, client: Lithic) -> None: + with client.tokenizations.with_streaming_response.pause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_pause(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + client.tokenizations.with_raw_response.pause( + "", + ) + + @parametrize + def test_method_resend_activation_code(self, client: Lithic) -> None: + tokenization = client.tokenizations.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + def test_method_resend_activation_code_with_all_params(self, client: Lithic) -> None: + tokenization = client.tokenizations.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + activation_method_type="TEXT_TO_CARDHOLDER_NUMBER", + ) + assert tokenization is None + + @parametrize + def test_raw_response_resend_activation_code(self, client: Lithic) -> None: + response = client.tokenizations.with_raw_response.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + def test_streaming_response_resend_activation_code(self, client: Lithic) -> None: + with client.tokenizations.with_streaming_response.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_resend_activation_code(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + client.tokenizations.with_raw_response.resend_activation_code( + tokenization_token="", + ) + @parametrize def test_method_simulate(self, client: Lithic) -> None: tokenization = client.tokenizations.simulate( @@ -107,7 +266,7 @@ def test_method_simulate(self, client: Lithic) -> None: pan="4111111289144142", tokenization_source="APPLE_PAY", ) - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize def test_method_simulate_with_all_params(self, client: Lithic) -> None: @@ -118,9 +277,10 @@ def test_method_simulate_with_all_params(self, client: Lithic) -> None: tokenization_source="APPLE_PAY", account_score=5, device_score=5, + entity="entity", wallet_recommended_decision="APPROVED", ) - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize def test_raw_response_simulate(self, client: Lithic) -> None: @@ -134,7 +294,7 @@ def test_raw_response_simulate(self, client: Lithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = response.parse() - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize def test_streaming_response_simulate(self, client: Lithic) -> None: @@ -148,20 +308,106 @@ def test_streaming_response_simulate(self, client: Lithic) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = response.parse() - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_unpause(self, client: Lithic) -> None: + tokenization = client.tokenizations.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + def test_raw_response_unpause(self, client: Lithic) -> None: + response = client.tokenizations.with_raw_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + def test_streaming_response_unpause(self, client: Lithic) -> None: + with client.tokenizations.with_streaming_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_unpause(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + client.tokenizations.with_raw_response.unpause( + "", + ) + + @parametrize + def test_method_update_digital_card_art(self, client: Lithic) -> None: + tokenization = client.tokenizations.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Tokenization, tokenization, path=["response"]) + + @parametrize + def test_method_update_digital_card_art_with_all_params(self, client: Lithic) -> None: + tokenization = client.tokenizations.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + digital_card_art_token="5e9483eb-8103-4e16-9794-2106111b2eca", + ) + assert_matches_type(Tokenization, tokenization, path=["response"]) + + @parametrize + def test_raw_response_update_digital_card_art(self, client: Lithic) -> None: + response = client.tokenizations.with_raw_response.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert_matches_type(Tokenization, tokenization, path=["response"]) + + @parametrize + def test_streaming_response_update_digital_card_art(self, client: Lithic) -> None: + with client.tokenizations.with_streaming_response.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = response.parse() + assert_matches_type(Tokenization, tokenization, path=["response"]) assert cast(Any, response.is_closed) is True + @parametrize + def test_path_params_update_digital_card_art(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + client.tokenizations.with_raw_response.update_digital_card_art( + tokenization_token="", + ) + class TestAsyncTokenizations: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: tokenization = await async_client.tokenizations.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(TokenizationRetrieveResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: @@ -172,7 +418,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = response.parse() - assert_matches_type(TokenizationRetrieveResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: @@ -183,7 +429,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = await response.parse() - assert_matches_type(TokenizationRetrieveResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) assert cast(Any, response.is_closed) is True @@ -206,9 +452,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N begin=parse_date("2019-12-27"), card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=parse_date("2019-12-27"), - ending_before="string", + ending_before="ending_before", page_size=1, - starting_after="string", + starting_after="starting_after", + tokenization_channel="DIGITAL_WALLET", ) assert_matches_type(AsyncCursorPage[Tokenization], tokenization, path=["response"]) @@ -232,6 +479,166 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_activate(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.activate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + async def test_raw_response_activate(self, async_client: AsyncLithic) -> None: + response = await async_client.tokenizations.with_raw_response.activate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + async def test_streaming_response_activate(self, async_client: AsyncLithic) -> None: + async with async_client.tokenizations.with_streaming_response.activate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = await response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_activate(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + await async_client.tokenizations.with_raw_response.activate( + "", + ) + + @parametrize + async def test_method_deactivate(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.deactivate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + async def test_raw_response_deactivate(self, async_client: AsyncLithic) -> None: + response = await async_client.tokenizations.with_raw_response.deactivate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + async def test_streaming_response_deactivate(self, async_client: AsyncLithic) -> None: + async with async_client.tokenizations.with_streaming_response.deactivate( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = await response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_deactivate(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + await async_client.tokenizations.with_raw_response.deactivate( + "", + ) + + @parametrize + async def test_method_pause(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.pause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + async def test_raw_response_pause(self, async_client: AsyncLithic) -> None: + response = await async_client.tokenizations.with_raw_response.pause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + async def test_streaming_response_pause(self, async_client: AsyncLithic) -> None: + async with async_client.tokenizations.with_streaming_response.pause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = await response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_pause(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + await async_client.tokenizations.with_raw_response.pause( + "", + ) + + @parametrize + async def test_method_resend_activation_code(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + async def test_method_resend_activation_code_with_all_params(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + activation_method_type="TEXT_TO_CARDHOLDER_NUMBER", + ) + assert tokenization is None + + @parametrize + async def test_raw_response_resend_activation_code(self, async_client: AsyncLithic) -> None: + response = await async_client.tokenizations.with_raw_response.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + async def test_streaming_response_resend_activation_code(self, async_client: AsyncLithic) -> None: + async with async_client.tokenizations.with_streaming_response.resend_activation_code( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = await response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_resend_activation_code(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + await async_client.tokenizations.with_raw_response.resend_activation_code( + tokenization_token="", + ) + @parametrize async def test_method_simulate(self, async_client: AsyncLithic) -> None: tokenization = await async_client.tokenizations.simulate( @@ -240,7 +647,7 @@ async def test_method_simulate(self, async_client: AsyncLithic) -> None: pan="4111111289144142", tokenization_source="APPLE_PAY", ) - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize async def test_method_simulate_with_all_params(self, async_client: AsyncLithic) -> None: @@ -251,9 +658,10 @@ async def test_method_simulate_with_all_params(self, async_client: AsyncLithic) tokenization_source="APPLE_PAY", account_score=5, device_score=5, + entity="entity", wallet_recommended_decision="APPROVED", ) - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize async def test_raw_response_simulate(self, async_client: AsyncLithic) -> None: @@ -267,7 +675,7 @@ async def test_raw_response_simulate(self, async_client: AsyncLithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = response.parse() - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) @parametrize async def test_streaming_response_simulate(self, async_client: AsyncLithic) -> None: @@ -281,6 +689,90 @@ async def test_streaming_response_simulate(self, async_client: AsyncLithic) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" tokenization = await response.parse() - assert_matches_type(TokenizationSimulateResponse, tokenization, path=["response"]) + assert_matches_type(Tokenization, tokenization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_unpause(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert tokenization is None + + @parametrize + async def test_raw_response_unpause(self, async_client: AsyncLithic) -> None: + response = await async_client.tokenizations.with_raw_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert tokenization is None + + @parametrize + async def test_streaming_response_unpause(self, async_client: AsyncLithic) -> None: + async with async_client.tokenizations.with_streaming_response.unpause( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = await response.parse() + assert tokenization is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_unpause(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + await async_client.tokenizations.with_raw_response.unpause( + "", + ) + + @parametrize + async def test_method_update_digital_card_art(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Tokenization, tokenization, path=["response"]) + + @parametrize + async def test_method_update_digital_card_art_with_all_params(self, async_client: AsyncLithic) -> None: + tokenization = await async_client.tokenizations.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + digital_card_art_token="5e9483eb-8103-4e16-9794-2106111b2eca", + ) + assert_matches_type(Tokenization, tokenization, path=["response"]) + + @parametrize + async def test_raw_response_update_digital_card_art(self, async_client: AsyncLithic) -> None: + response = await async_client.tokenizations.with_raw_response.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tokenization = response.parse() + assert_matches_type(Tokenization, tokenization, path=["response"]) + + @parametrize + async def test_streaming_response_update_digital_card_art(self, async_client: AsyncLithic) -> None: + async with async_client.tokenizations.with_streaming_response.update_digital_card_art( + tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tokenization = await response.parse() + assert_matches_type(Tokenization, tokenization, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_digital_card_art(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tokenization_token` but received ''"): + await async_client.tokenizations.with_raw_response.update_digital_card_art( + tokenization_token="", + ) diff --git a/tests/api_resources/test_transactions.py b/tests/api_resources/test_transactions.py index 4e7b9003..3f40b577 100644 --- a/tests/api_resources/test_transactions.py +++ b/tests/api_resources/test_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -18,10 +18,13 @@ TransactionSimulateReturnReversalResponse, TransactionSimulateAuthorizationAdviceResponse, TransactionSimulateCreditAuthorizationResponse, + TransactionSimulateCreditAuthorizationAdviceResponse, ) from lithic._utils import parse_datetime from lithic.pagination import SyncCursorPage, AsyncCursorPage +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -78,10 +81,11 @@ def test_method_list_with_all_params(self, client: Lithic) -> None: begin=parse_datetime("2019-12-27T18:11:19.117Z"), card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, result="APPROVED", - starting_after="string", + starting_after="starting_after", + status="PENDING", ) assert_matches_type(SyncCursorPage[Transaction], transaction, path=["response"]) @@ -105,6 +109,44 @@ def test_streaming_response_list(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_expire_authorization(self, client: Lithic) -> None: + transaction = client.transactions.expire_authorization( + "00000000-0000-0000-0000-000000000000", + ) + assert transaction is None + + @parametrize + def test_raw_response_expire_authorization(self, client: Lithic) -> None: + response = client.transactions.with_raw_response.expire_authorization( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert transaction is None + + @parametrize + def test_streaming_response_expire_authorization(self, client: Lithic) -> None: + with client.transactions.with_streaming_response.expire_authorization( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = response.parse() + assert transaction is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_expire_authorization(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + client.transactions.with_raw_response.expire_authorization( + "", + ) + @parametrize def test_method_simulate_authorization(self, client: Lithic) -> None: transaction = client.transactions.simulate_authorization( @@ -125,6 +167,7 @@ def test_method_simulate_authorization_with_all_params(self, client: Lithic) -> merchant_amount=0, merchant_currency="GBP", partial_approval_capable=True, + pin="1234", status="AUTHORIZATION", ) assert_matches_type(TransactionSimulateAuthorizationResponse, transaction, path=["response"]) @@ -232,27 +275,81 @@ def test_streaming_response_simulate_clearing(self, client: Lithic) -> None: @parametrize def test_method_simulate_credit_authorization(self, client: Lithic) -> None: - transaction = client.transactions.simulate_credit_authorization( + with pytest.warns(DeprecationWarning): + transaction = client.transactions.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + ) + + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + @parametrize + def test_method_simulate_credit_authorization_with_all_params(self, client: Lithic) -> None: + with pytest.warns(DeprecationWarning): + transaction = client.transactions.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + mcc="5812", + merchant_acceptor_id="XRKGDPOWEWQRRWU", + ) + + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + @parametrize + def test_raw_response_simulate_credit_authorization(self, client: Lithic) -> None: + with pytest.warns(DeprecationWarning): + response = client.transactions.with_raw_response.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + @parametrize + def test_streaming_response_simulate_credit_authorization(self, client: Lithic) -> None: + with pytest.warns(DeprecationWarning): + with client.transactions.with_streaming_response.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = response.parse() + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_simulate_credit_authorization_advice(self, client: Lithic) -> None: + transaction = client.transactions.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", ) - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) @parametrize - def test_method_simulate_credit_authorization_with_all_params(self, client: Lithic) -> None: - transaction = client.transactions.simulate_credit_authorization( + def test_method_simulate_credit_authorization_advice_with_all_params(self, client: Lithic) -> None: + transaction = client.transactions.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", mcc="5812", merchant_acceptor_id="XRKGDPOWEWQRRWU", ) - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) @parametrize - def test_raw_response_simulate_credit_authorization(self, client: Lithic) -> None: - response = client.transactions.with_raw_response.simulate_credit_authorization( + def test_raw_response_simulate_credit_authorization_advice(self, client: Lithic) -> None: + response = client.transactions.with_raw_response.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", @@ -261,11 +358,11 @@ def test_raw_response_simulate_credit_authorization(self, client: Lithic) -> Non assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transaction = response.parse() - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) @parametrize - def test_streaming_response_simulate_credit_authorization(self, client: Lithic) -> None: - with client.transactions.with_streaming_response.simulate_credit_authorization( + def test_streaming_response_simulate_credit_authorization_advice(self, client: Lithic) -> None: + with client.transactions.with_streaming_response.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", @@ -274,7 +371,7 @@ def test_streaming_response_simulate_credit_authorization(self, client: Lithic) assert response.http_request.headers.get("X-Stainless-Lang") == "python" transaction = response.parse() - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) assert cast(Any, response.is_closed) is True @@ -388,7 +485,9 @@ def test_streaming_response_simulate_void(self, client: Lithic) -> None: class TestAsyncTransactions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: @@ -440,10 +539,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncLithic) -> N begin=parse_datetime("2019-12-27T18:11:19.117Z"), card_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", end=parse_datetime("2019-12-27T18:11:19.117Z"), - ending_before="string", + ending_before="ending_before", page_size=1, result="APPROVED", - starting_after="string", + starting_after="starting_after", + status="PENDING", ) assert_matches_type(AsyncCursorPage[Transaction], transaction, path=["response"]) @@ -467,6 +567,44 @@ async def test_streaming_response_list(self, async_client: AsyncLithic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_expire_authorization(self, async_client: AsyncLithic) -> None: + transaction = await async_client.transactions.expire_authorization( + "00000000-0000-0000-0000-000000000000", + ) + assert transaction is None + + @parametrize + async def test_raw_response_expire_authorization(self, async_client: AsyncLithic) -> None: + response = await async_client.transactions.with_raw_response.expire_authorization( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert transaction is None + + @parametrize + async def test_streaming_response_expire_authorization(self, async_client: AsyncLithic) -> None: + async with async_client.transactions.with_streaming_response.expire_authorization( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = await response.parse() + assert transaction is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_expire_authorization(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + await async_client.transactions.with_raw_response.expire_authorization( + "", + ) + @parametrize async def test_method_simulate_authorization(self, async_client: AsyncLithic) -> None: transaction = await async_client.transactions.simulate_authorization( @@ -487,6 +625,7 @@ async def test_method_simulate_authorization_with_all_params(self, async_client: merchant_amount=0, merchant_currency="GBP", partial_approval_capable=True, + pin="1234", status="AUTHORIZATION", ) assert_matches_type(TransactionSimulateAuthorizationResponse, transaction, path=["response"]) @@ -594,27 +733,81 @@ async def test_streaming_response_simulate_clearing(self, async_client: AsyncLit @parametrize async def test_method_simulate_credit_authorization(self, async_client: AsyncLithic) -> None: - transaction = await async_client.transactions.simulate_credit_authorization( + with pytest.warns(DeprecationWarning): + transaction = await async_client.transactions.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + ) + + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + @parametrize + async def test_method_simulate_credit_authorization_with_all_params(self, async_client: AsyncLithic) -> None: + with pytest.warns(DeprecationWarning): + transaction = await async_client.transactions.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + mcc="5812", + merchant_acceptor_id="XRKGDPOWEWQRRWU", + ) + + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + @parametrize + async def test_raw_response_simulate_credit_authorization(self, async_client: AsyncLithic) -> None: + with pytest.warns(DeprecationWarning): + response = await async_client.transactions.with_raw_response.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + transaction = response.parse() + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + @parametrize + async def test_streaming_response_simulate_credit_authorization(self, async_client: AsyncLithic) -> None: + with pytest.warns(DeprecationWarning): + async with async_client.transactions.with_streaming_response.simulate_credit_authorization( + amount=3831, + descriptor="COFFEE SHOP", + pan="4111111289144142", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transaction = await response.parse() + assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_simulate_credit_authorization_advice(self, async_client: AsyncLithic) -> None: + transaction = await async_client.transactions.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", ) - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) @parametrize - async def test_method_simulate_credit_authorization_with_all_params(self, async_client: AsyncLithic) -> None: - transaction = await async_client.transactions.simulate_credit_authorization( + async def test_method_simulate_credit_authorization_advice_with_all_params(self, async_client: AsyncLithic) -> None: + transaction = await async_client.transactions.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", mcc="5812", merchant_acceptor_id="XRKGDPOWEWQRRWU", ) - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) @parametrize - async def test_raw_response_simulate_credit_authorization(self, async_client: AsyncLithic) -> None: - response = await async_client.transactions.with_raw_response.simulate_credit_authorization( + async def test_raw_response_simulate_credit_authorization_advice(self, async_client: AsyncLithic) -> None: + response = await async_client.transactions.with_raw_response.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", @@ -623,11 +816,11 @@ async def test_raw_response_simulate_credit_authorization(self, async_client: As assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transaction = response.parse() - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) @parametrize - async def test_streaming_response_simulate_credit_authorization(self, async_client: AsyncLithic) -> None: - async with async_client.transactions.with_streaming_response.simulate_credit_authorization( + async def test_streaming_response_simulate_credit_authorization_advice(self, async_client: AsyncLithic) -> None: + async with async_client.transactions.with_streaming_response.simulate_credit_authorization_advice( amount=3831, descriptor="COFFEE SHOP", pan="4111111289144142", @@ -636,7 +829,7 @@ async def test_streaming_response_simulate_credit_authorization(self, async_clie assert response.http_request.headers.get("X-Stainless-Lang") == "python" transaction = await response.parse() - assert_matches_type(TransactionSimulateCreditAuthorizationResponse, transaction, path=["response"]) + assert_matches_type(TransactionSimulateCreditAuthorizationAdviceResponse, transaction, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py index c3fc25b5..e48e221d 100644 --- a/tests/api_resources/test_webhooks.py +++ b/tests/api_resources/test_webhooks.py @@ -1,16 +1,14 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import os -import base64 -from typing import Any, cast -from datetime import datetime, timezone, timedelta +from datetime import datetime, timezone import pytest -import time_machine +import standardwebhooks -from lithic import Lithic, AsyncLithic +from lithic import Lithic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -18,194 +16,64 @@ class TestWebhooks: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - timestamp = "1676312382" - fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) - - payload = """{"card_token":"sit Lorem ipsum, accusantium repellendus possimus","created_at":"elit. placeat libero architecto molestias, sit","account_token":"elit.","issuer_decision":"magnam, libero esse Lorem ipsum magnam, magnam,","tokenization_attempt_id":"illum dolor repellendus libero esse accusantium","wallet_decisioning_info":{"device_score":"placeat architecto"},"digital_wallet_token_metadata":{"status":"reprehenderit dolor","token_requestor_id":"possimus","payment_account_info":{"account_holder_data":{"phone_number":"libero","email_address":"nobis molestias, veniam culpa! quas elit. quas libero esse architecto placeat"},"pan_unique_reference":"adipisicing odit magnam, odit"}}}""" - signature = "Dwa0AHInLL3XFo2sxcHamOQDrJNi7F654S3L6skMAOI=" - headers = { - "webhook-id": "msg_2Lh9KRb0pzN4LePd3XiA4v12Axj", - "webhook-timestamp": timestamp, - "webhook-signature": f"v1,{signature}", - } - secret = "whsec_zlFsbBZ8Xcodlpcu6NDTdSzZRLSdhkst" - - @time_machine.travel(fake_now) - def test_unwrap(self, client: Lithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - - client.webhooks.unwrap(payload, headers, secret=secret) - - @time_machine.travel(fake_now) - def test_verify_signature(self, client: Lithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - signature = self.signature - verify = client.webhooks.verify_signature - - assert verify(payload=payload, headers=headers, secret=secret) is None - - with pytest.raises(ValueError, match="Webhook timestamp is too old"): - with time_machine.travel(self.fake_now + timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - with pytest.raises(ValueError, match="Webhook timestamp is too new"): - with time_machine.travel(self.fake_now - timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - # wrong secret - with pytest.raises(ValueError, match=r"Bad secret"): - verify(payload=payload, headers=headers, secret="invalid secret") - - invalid_signature_message = "None of the given webhook signatures match the expected signature" - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers=headers, - secret=f"whsec_{base64.b64encode(b'foo').decode('utf-8')}", - ) - - # multiple signatures - invalid_signature = base64.b64encode(b"my_sig").decode("utf-8") - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v1,{invalid_signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # different signature version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature}"}, - secret=secret, - ) - - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # missing version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": signature}, - secret=secret, - ) - - # non-string payload - with pytest.raises(ValueError, match=r"Webhook body should be a string"): - verify( - payload=cast(Any, {"payload": payload}), - headers=headers, - secret=secret, - ) + def test_method_parsed(self, client: Lithic) -> None: + key = b"secret" + hook = standardwebhooks.Webhook(key) + + data = """{"event_type":"account_holder.created","token":"00000000-0000-0000-0000-000000000001","account_token":"00000000-0000-0000-0000-000000000001","created":"2019-12-27T18:11:19.117Z","required_documents":[{"entity_token":"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e","status_reasons":["string"],"valid_documents":["string"]}],"status":"ACCEPTED","status_reason":["string"]}""" + msg_id = "1" + timestamp = datetime.now(tz=timezone.utc) + sig = hook.sign(msg_id=msg_id, timestamp=timestamp, data=data) + headers = { + "webhook-id": msg_id, + "webhook-timestamp": str(int(timestamp.timestamp())), + "webhook-signature": sig, + } + + try: + _ = client.webhooks.parsed(data, headers=headers, key=key) + except standardwebhooks.WebhookVerificationError as e: + raise AssertionError("Failed to unwrap valid webhook") from e + + bad_headers = [ + {**headers, "webhook-signature": hook.sign(msg_id=msg_id, timestamp=timestamp, data="xxx")}, + {**headers, "webhook-id": "bad"}, + {**headers, "webhook-timestamp": "0"}, + ] + for bad_header in bad_headers: + with pytest.raises(standardwebhooks.WebhookVerificationError): + _ = client.webhooks.parsed(data, headers=bad_header, key=key) class TestAsyncWebhooks: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - timestamp = "1676312382" - fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) - - payload = """{"card_token":"sit Lorem ipsum, accusantium repellendus possimus","created_at":"elit. placeat libero architecto molestias, sit","account_token":"elit.","issuer_decision":"magnam, libero esse Lorem ipsum magnam, magnam,","tokenization_attempt_id":"illum dolor repellendus libero esse accusantium","wallet_decisioning_info":{"device_score":"placeat architecto"},"digital_wallet_token_metadata":{"status":"reprehenderit dolor","token_requestor_id":"possimus","payment_account_info":{"account_holder_data":{"phone_number":"libero","email_address":"nobis molestias, veniam culpa! quas elit. quas libero esse architecto placeat"},"pan_unique_reference":"adipisicing odit magnam, odit"}}}""" - signature = "Dwa0AHInLL3XFo2sxcHamOQDrJNi7F654S3L6skMAOI=" - headers = { - "webhook-id": "msg_2Lh9KRb0pzN4LePd3XiA4v12Axj", - "webhook-timestamp": timestamp, - "webhook-signature": f"v1,{signature}", - } - secret = "whsec_zlFsbBZ8Xcodlpcu6NDTdSzZRLSdhkst" - - @time_machine.travel(fake_now) - def test_unwrap(self, async_client: AsyncLithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - - async_client.webhooks.unwrap(payload, headers, secret=secret) - - @time_machine.travel(fake_now) - def test_verify_signature(self, async_client: Lithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - signature = self.signature - verify = async_client.webhooks.verify_signature - - assert verify(payload=payload, headers=headers, secret=secret) is None - - with pytest.raises(ValueError, match="Webhook timestamp is too old"): - with time_machine.travel(self.fake_now + timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - with pytest.raises(ValueError, match="Webhook timestamp is too new"): - with time_machine.travel(self.fake_now - timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - # wrong secret - with pytest.raises(ValueError, match=r"Bad secret"): - verify(payload=payload, headers=headers, secret="invalid secret") - - invalid_signature_message = "None of the given webhook signatures match the expected signature" - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers=headers, - secret=f"whsec_{base64.b64encode(b'foo').decode('utf-8')}", - ) - - # multiple signatures - invalid_signature = base64.b64encode(b"my_sig").decode("utf-8") - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v1,{invalid_signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # different signature version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature}"}, - secret=secret, - ) - - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # missing version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": signature}, - secret=secret, - ) - - # non-string payload - with pytest.raises(ValueError, match=r"Webhook body should be a string"): - verify( - payload=cast(Any, {"payload": payload}), - headers=headers, - secret=secret, - ) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + def test_method_parsed(self, client: Lithic) -> None: + key = b"secret" + hook = standardwebhooks.Webhook(key) + + data = """{"event_type":"account_holder.created","token":"00000000-0000-0000-0000-000000000001","account_token":"00000000-0000-0000-0000-000000000001","created":"2019-12-27T18:11:19.117Z","required_documents":[{"entity_token":"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e","status_reasons":["string"],"valid_documents":["string"]}],"status":"ACCEPTED","status_reason":["string"]}""" + msg_id = "1" + timestamp = datetime.now(tz=timezone.utc) + sig = hook.sign(msg_id=msg_id, timestamp=timestamp, data=data) + headers = { + "webhook-id": msg_id, + "webhook-timestamp": str(int(timestamp.timestamp())), + "webhook-signature": sig, + } + + try: + _ = client.webhooks.parsed(data, headers=headers, key=key) + except standardwebhooks.WebhookVerificationError as e: + raise AssertionError("Failed to unwrap valid webhook") from e + + bad_headers = [ + {**headers, "webhook-signature": hook.sign(msg_id=msg_id, timestamp=timestamp, data="xxx")}, + {**headers, "webhook-id": "bad"}, + {**headers, "webhook-timestamp": "0"}, + ] + for bad_header in bad_headers: + with pytest.raises(standardwebhooks.WebhookVerificationError): + _ = client.webhooks.parsed(data, headers=bad_header, key=key) diff --git a/tests/api_resources/three_ds/__init__.py b/tests/api_resources/three_ds/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/three_ds/__init__.py +++ b/tests/api_resources/three_ds/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/three_ds/test_authentication.py b/tests/api_resources/three_ds/test_authentication.py index f4a9d774..51b25d61 100644 --- a/tests/api_resources/three_ds/test_authentication.py +++ b/tests/api_resources/three_ds/test_authentication.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,8 +9,8 @@ from lithic import Lithic, AsyncLithic from tests.utils import assert_matches_type +from lithic.types import ThreeDSAuthentication from lithic.types.three_ds import ( - AuthenticationRetrieveResponse, AuthenticationSimulateResponse, ) @@ -25,7 +25,7 @@ def test_method_retrieve(self, client: Lithic) -> None: authentication = client.three_ds.authentication.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AuthenticationRetrieveResponse, authentication, path=["response"]) + assert_matches_type(ThreeDSAuthentication, authentication, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Lithic) -> None: @@ -36,7 +36,7 @@ def test_raw_response_retrieve(self, client: Lithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" authentication = response.parse() - assert_matches_type(AuthenticationRetrieveResponse, authentication, path=["response"]) + assert_matches_type(ThreeDSAuthentication, authentication, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Lithic) -> None: @@ -47,7 +47,7 @@ def test_streaming_response_retrieve(self, client: Lithic) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" authentication = response.parse() - assert_matches_type(AuthenticationRetrieveResponse, authentication, path=["response"]) + assert_matches_type(ThreeDSAuthentication, authentication, path=["response"]) assert cast(Any, response.is_closed) is True @@ -64,16 +64,34 @@ def test_path_params_retrieve(self, client: Lithic) -> None: def test_method_simulate(self, client: Lithic) -> None: authentication = client.three_ds.authentication.simulate( merchant={ + "id": "OODKZAPJVN4YS7O", "country": "USA", + "mcc": "5812", + "name": "COFFEE SHOP", + }, + pan="4111111289144142", + transaction={ + "amount": 0, + "currency": "GBP", + }, + ) + assert_matches_type(AuthenticationSimulateResponse, authentication, path=["response"]) + + @parametrize + def test_method_simulate_with_all_params(self, client: Lithic) -> None: + authentication = client.three_ds.authentication.simulate( + merchant={ "id": "OODKZAPJVN4YS7O", + "country": "USA", "mcc": "5812", "name": "COFFEE SHOP", }, pan="4111111289144142", transaction={ - "amount": 100, - "currency": "USD", + "amount": 0, + "currency": "GBP", }, + card_expiry_check="MATCH", ) assert_matches_type(AuthenticationSimulateResponse, authentication, path=["response"]) @@ -81,15 +99,15 @@ def test_method_simulate(self, client: Lithic) -> None: def test_raw_response_simulate(self, client: Lithic) -> None: response = client.three_ds.authentication.with_raw_response.simulate( merchant={ - "country": "USA", "id": "OODKZAPJVN4YS7O", + "country": "USA", "mcc": "5812", "name": "COFFEE SHOP", }, pan="4111111289144142", transaction={ - "amount": 100, - "currency": "USD", + "amount": 0, + "currency": "GBP", }, ) @@ -102,15 +120,15 @@ def test_raw_response_simulate(self, client: Lithic) -> None: def test_streaming_response_simulate(self, client: Lithic) -> None: with client.three_ds.authentication.with_streaming_response.simulate( merchant={ - "country": "USA", "id": "OODKZAPJVN4YS7O", + "country": "USA", "mcc": "5812", "name": "COFFEE SHOP", }, pan="4111111289144142", transaction={ - "amount": 100, - "currency": "USD", + "amount": 0, + "currency": "GBP", }, ) as response: assert not response.is_closed @@ -121,16 +139,52 @@ def test_streaming_response_simulate(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_simulate_otp_entry(self, client: Lithic) -> None: + authentication = client.three_ds.authentication.simulate_otp_entry( + token="fabd829d-7f7b-4432-a8f2-07ea4889aaac", + otp="123456", + ) + assert authentication is None + + @parametrize + def test_raw_response_simulate_otp_entry(self, client: Lithic) -> None: + response = client.three_ds.authentication.with_raw_response.simulate_otp_entry( + token="fabd829d-7f7b-4432-a8f2-07ea4889aaac", + otp="123456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + authentication = response.parse() + assert authentication is None + + @parametrize + def test_streaming_response_simulate_otp_entry(self, client: Lithic) -> None: + with client.three_ds.authentication.with_streaming_response.simulate_otp_entry( + token="fabd829d-7f7b-4432-a8f2-07ea4889aaac", + otp="123456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + authentication = response.parse() + assert authentication is None + + assert cast(Any, response.is_closed) is True + class TestAsyncAuthentication: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncLithic) -> None: authentication = await async_client.three_ds.authentication.retrieve( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - assert_matches_type(AuthenticationRetrieveResponse, authentication, path=["response"]) + assert_matches_type(ThreeDSAuthentication, authentication, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: @@ -141,7 +195,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" authentication = response.parse() - assert_matches_type(AuthenticationRetrieveResponse, authentication, path=["response"]) + assert_matches_type(ThreeDSAuthentication, authentication, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: @@ -152,7 +206,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" authentication = await response.parse() - assert_matches_type(AuthenticationRetrieveResponse, authentication, path=["response"]) + assert_matches_type(ThreeDSAuthentication, authentication, path=["response"]) assert cast(Any, response.is_closed) is True @@ -169,16 +223,34 @@ async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: async def test_method_simulate(self, async_client: AsyncLithic) -> None: authentication = await async_client.three_ds.authentication.simulate( merchant={ + "id": "OODKZAPJVN4YS7O", "country": "USA", + "mcc": "5812", + "name": "COFFEE SHOP", + }, + pan="4111111289144142", + transaction={ + "amount": 0, + "currency": "GBP", + }, + ) + assert_matches_type(AuthenticationSimulateResponse, authentication, path=["response"]) + + @parametrize + async def test_method_simulate_with_all_params(self, async_client: AsyncLithic) -> None: + authentication = await async_client.three_ds.authentication.simulate( + merchant={ "id": "OODKZAPJVN4YS7O", + "country": "USA", "mcc": "5812", "name": "COFFEE SHOP", }, pan="4111111289144142", transaction={ - "amount": 100, - "currency": "USD", + "amount": 0, + "currency": "GBP", }, + card_expiry_check="MATCH", ) assert_matches_type(AuthenticationSimulateResponse, authentication, path=["response"]) @@ -186,15 +258,15 @@ async def test_method_simulate(self, async_client: AsyncLithic) -> None: async def test_raw_response_simulate(self, async_client: AsyncLithic) -> None: response = await async_client.three_ds.authentication.with_raw_response.simulate( merchant={ - "country": "USA", "id": "OODKZAPJVN4YS7O", + "country": "USA", "mcc": "5812", "name": "COFFEE SHOP", }, pan="4111111289144142", transaction={ - "amount": 100, - "currency": "USD", + "amount": 0, + "currency": "GBP", }, ) @@ -207,15 +279,15 @@ async def test_raw_response_simulate(self, async_client: AsyncLithic) -> None: async def test_streaming_response_simulate(self, async_client: AsyncLithic) -> None: async with async_client.three_ds.authentication.with_streaming_response.simulate( merchant={ - "country": "USA", "id": "OODKZAPJVN4YS7O", + "country": "USA", "mcc": "5812", "name": "COFFEE SHOP", }, pan="4111111289144142", transaction={ - "amount": 100, - "currency": "USD", + "amount": 0, + "currency": "GBP", }, ) as response: assert not response.is_closed @@ -225,3 +297,37 @@ async def test_streaming_response_simulate(self, async_client: AsyncLithic) -> N assert_matches_type(AuthenticationSimulateResponse, authentication, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_simulate_otp_entry(self, async_client: AsyncLithic) -> None: + authentication = await async_client.three_ds.authentication.simulate_otp_entry( + token="fabd829d-7f7b-4432-a8f2-07ea4889aaac", + otp="123456", + ) + assert authentication is None + + @parametrize + async def test_raw_response_simulate_otp_entry(self, async_client: AsyncLithic) -> None: + response = await async_client.three_ds.authentication.with_raw_response.simulate_otp_entry( + token="fabd829d-7f7b-4432-a8f2-07ea4889aaac", + otp="123456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + authentication = response.parse() + assert authentication is None + + @parametrize + async def test_streaming_response_simulate_otp_entry(self, async_client: AsyncLithic) -> None: + async with async_client.three_ds.authentication.with_streaming_response.simulate_otp_entry( + token="fabd829d-7f7b-4432-a8f2-07ea4889aaac", + otp="123456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + authentication = await response.parse() + assert authentication is None + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/three_ds/test_decisioning.py b/tests/api_resources/three_ds/test_decisioning.py index cc15ea98..53a0bc0f 100644 --- a/tests/api_resources/three_ds/test_decisioning.py +++ b/tests/api_resources/three_ds/test_decisioning.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations @@ -9,7 +9,9 @@ from lithic import Lithic, AsyncLithic from tests.utils import assert_matches_type -from lithic.types.three_ds import DecisioningRetrieveSecretResponse +from lithic.types.three_ds import ( + DecisioningRetrieveSecretResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +19,40 @@ class TestDecisioning: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @parametrize + def test_method_challenge_response(self, client: Lithic) -> None: + decisioning = client.three_ds.decisioning.challenge_response( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + challenge_response="APPROVE", + ) + assert decisioning is None + + @parametrize + def test_raw_response_challenge_response(self, client: Lithic) -> None: + response = client.three_ds.decisioning.with_raw_response.challenge_response( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + challenge_response="APPROVE", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + decisioning = response.parse() + assert decisioning is None + + @parametrize + def test_streaming_response_challenge_response(self, client: Lithic) -> None: + with client.three_ds.decisioning.with_streaming_response.challenge_response( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + challenge_response="APPROVE", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + decisioning = response.parse() + assert decisioning is None + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve_secret(self, client: Lithic) -> None: decisioning = client.three_ds.decisioning.retrieve_secret() @@ -69,7 +105,43 @@ def test_streaming_response_rotate_secret(self, client: Lithic) -> None: class TestAsyncDecisioning: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_challenge_response(self, async_client: AsyncLithic) -> None: + decisioning = await async_client.three_ds.decisioning.challenge_response( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + challenge_response="APPROVE", + ) + assert decisioning is None + + @parametrize + async def test_raw_response_challenge_response(self, async_client: AsyncLithic) -> None: + response = await async_client.three_ds.decisioning.with_raw_response.challenge_response( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + challenge_response="APPROVE", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + decisioning = response.parse() + assert decisioning is None + + @parametrize + async def test_streaming_response_challenge_response(self, async_client: AsyncLithic) -> None: + async with async_client.three_ds.decisioning.with_streaming_response.challenge_response( + token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + challenge_response="APPROVE", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + decisioning = await response.parse() + assert decisioning is None + + assert cast(Any, response.is_closed) is True @parametrize async def test_method_retrieve_secret(self, async_client: AsyncLithic) -> None: diff --git a/tests/api_resources/transactions/__init__.py b/tests/api_resources/transactions/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/transactions/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/transactions/events/__init__.py b/tests/api_resources/transactions/events/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/transactions/events/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/transactions/events/test_enhanced_commercial_data.py b/tests/api_resources/transactions/events/test_enhanced_commercial_data.py new file mode 100644 index 00000000..bf35d51f --- /dev/null +++ b/tests/api_resources/transactions/events/test_enhanced_commercial_data.py @@ -0,0 +1,100 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types.transactions.events import EnhancedData + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEnhancedCommercialData: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + enhanced_commercial_data = client.transactions.events.enhanced_commercial_data.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + assert_matches_type(EnhancedData, enhanced_commercial_data, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.transactions.events.enhanced_commercial_data.with_raw_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + enhanced_commercial_data = response.parse() + assert_matches_type(EnhancedData, enhanced_commercial_data, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.transactions.events.enhanced_commercial_data.with_streaming_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + enhanced_commercial_data = response.parse() + assert_matches_type(EnhancedData, enhanced_commercial_data, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `event_token` but received ''"): + client.transactions.events.enhanced_commercial_data.with_raw_response.retrieve( + "", + ) + + +class TestAsyncEnhancedCommercialData: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + enhanced_commercial_data = await async_client.transactions.events.enhanced_commercial_data.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + assert_matches_type(EnhancedData, enhanced_commercial_data, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.transactions.events.enhanced_commercial_data.with_raw_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + enhanced_commercial_data = response.parse() + assert_matches_type(EnhancedData, enhanced_commercial_data, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.transactions.events.enhanced_commercial_data.with_streaming_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + enhanced_commercial_data = await response.parse() + assert_matches_type(EnhancedData, enhanced_commercial_data, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `event_token` but received ''"): + await async_client.transactions.events.enhanced_commercial_data.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/transactions/test_enhanced_commercial_data.py b/tests/api_resources/transactions/test_enhanced_commercial_data.py new file mode 100644 index 00000000..fe90ee11 --- /dev/null +++ b/tests/api_resources/transactions/test_enhanced_commercial_data.py @@ -0,0 +1,100 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from lithic import Lithic, AsyncLithic +from tests.utils import assert_matches_type +from lithic.types.transactions import EnhancedCommercialDataRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEnhancedCommercialData: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Lithic) -> None: + enhanced_commercial_data = client.transactions.enhanced_commercial_data.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + assert_matches_type(EnhancedCommercialDataRetrieveResponse, enhanced_commercial_data, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Lithic) -> None: + response = client.transactions.enhanced_commercial_data.with_raw_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + enhanced_commercial_data = response.parse() + assert_matches_type(EnhancedCommercialDataRetrieveResponse, enhanced_commercial_data, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Lithic) -> None: + with client.transactions.enhanced_commercial_data.with_streaming_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + enhanced_commercial_data = response.parse() + assert_matches_type(EnhancedCommercialDataRetrieveResponse, enhanced_commercial_data, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Lithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + client.transactions.enhanced_commercial_data.with_raw_response.retrieve( + "", + ) + + +class TestAsyncEnhancedCommercialData: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncLithic) -> None: + enhanced_commercial_data = await async_client.transactions.enhanced_commercial_data.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + assert_matches_type(EnhancedCommercialDataRetrieveResponse, enhanced_commercial_data, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLithic) -> None: + response = await async_client.transactions.enhanced_commercial_data.with_raw_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + enhanced_commercial_data = response.parse() + assert_matches_type(EnhancedCommercialDataRetrieveResponse, enhanced_commercial_data, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLithic) -> None: + async with async_client.transactions.enhanced_commercial_data.with_streaming_response.retrieve( + "00000000-0000-0000-0000-000000000000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + enhanced_commercial_data = await response.parse() + assert_matches_type(EnhancedCommercialDataRetrieveResponse, enhanced_commercial_data, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLithic) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `transaction_token` but received ''"): + await async_client.transactions.enhanced_commercial_data.with_raw_response.retrieve( + "", + ) diff --git a/tests/conftest.py b/tests/conftest.py index 029c962e..4a50dc5d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,27 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os -import asyncio import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest +from pytest_asyncio import is_async_test -from lithic import Lithic, AsyncLithic +from lithic import Lithic, AsyncLithic, DefaultAioHttpClient +from lithic._utils import is_dict if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") logging.getLogger("lithic").setLevel(logging.DEBUG) -@pytest.fixture(scope="session") -def event_loop() -> Iterator[asyncio.AbstractEventLoop]: - loop = asyncio.new_event_loop() - yield loop - loop.close() +# automatically add `pytest.mark.asyncio()` to all of our async tests +# so we don't have to add that boilerplate everywhere +def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: + pytest_asyncio_tests = (item for item in items if is_async_test(item)) + session_scope_marker = pytest.mark.asyncio(loop_scope="session") + for async_test in pytest_asyncio_tests: + async_test.add_marker(session_scope_marker, append=False) + + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -41,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[Lithic]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncLithic]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncLithic( + base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client + ) as client: yield client diff --git a/tests/sample_file.txt b/tests/sample_file.txt new file mode 100644 index 00000000..af5626b4 --- /dev/null +++ b/tests/sample_file.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/tests/test_client.py b/tests/test_client.py index 7c53d83b..2c78b5b1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,15 +1,17 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations import gc import os +import sys import json import asyncio import inspect import tracemalloc from typing import Any, Union, cast from unittest import mock +from typing_extensions import Literal import httpx import pytest @@ -17,11 +19,20 @@ from pydantic import ValidationError from lithic import Lithic, AsyncLithic, APIResponseValidationError -from lithic._client import Lithic, AsyncLithic +from lithic._types import Omit +from lithic._utils import asyncify from lithic._models import BaseModel, FinalRequestOptions -from lithic._constants import RAW_RESPONSE_HEADER from lithic._exceptions import LithicError, APIStatusError, APITimeoutError, APIResponseValidationError -from lithic._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options +from lithic._base_client import ( + DEFAULT_TIMEOUT, + HTTPX_DEFAULT_TIMEOUT, + BaseClient, + OtherPlatform, + DefaultHttpxClient, + DefaultAsyncHttpxClient, + get_platform, + make_request_options, +) from .utils import update_env @@ -48,51 +59,49 @@ def _get_open_connections(client: Lithic | AsyncLithic) -> int: class TestLithic: - client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter) -> None: + def test_raw_response(self, respx_mock: MockRouter, client: Lithic) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: + def test_raw_response_for_binary(self, respx_mock: MockRouter, client: Lithic) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + def test_copy(self, client: Lithic) -> None: + copied = client.copy() + assert id(copied) != id(client) - copied = self.client.copy(api_key="another My Lithic API Key") + copied = client.copy(api_key="another My Lithic API Key") assert copied.api_key == "another My Lithic API Key" - assert self.client.api_key == "My Lithic API Key" + assert client.api_key == "My Lithic API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, client: Lithic) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(client.timeout, httpx.Timeout) + copied = client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: client = Lithic( @@ -127,6 +136,7 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + client.close() def test_copy_default_query(self) -> None: client = Lithic( @@ -164,13 +174,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + client.close() + + def test_copy_signature(self, client: Lithic) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -180,12 +192,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, client: Lithic) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -242,14 +255,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + def test_request_timeout(self, client: Lithic) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( - FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) - ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(100.0) @@ -260,6 +271,8 @@ def test_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + client.close() + def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: @@ -271,6 +284,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + client.close() + # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = Lithic( @@ -281,6 +296,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + client.close() + # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = Lithic( @@ -291,15 +308,27 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + client.close() + + async def test_invalid_http_client(self) -> None: + with pytest.raises(TypeError, match="Invalid `http_client` arg"): + async with httpx.AsyncClient() as http_client: + Lithic( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) + def test_default_headers_option(self) -> None: - client = Lithic( + test_client = Lithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = Lithic( + test_client2 = Lithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -308,17 +337,21 @@ def test_default_headers_option(self) -> None: "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + test_client.close() + test_client2.close() + def test_validate_headers(self) -> None: client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("Authorization") == api_key with pytest.raises(LithicError): - client2 = Lithic(base_url=base_url, api_key=None, _strict_response_validation=True) + with update_env(**{"LITHIC_API_KEY": Omit()}): + client2 = Lithic(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: @@ -333,14 +366,16 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden"}, + params={"foo": "baz", "query_param": "overridden"}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden"} + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} + + client.close() - def test_request_extra_json(self) -> None: - request = self.client._build_request( + def test_request_extra_json(self, client: Lithic) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -351,7 +386,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -362,7 +397,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -373,8 +408,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: Lithic) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -384,7 +419,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -395,8 +430,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: Lithic) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -409,7 +444,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -423,7 +458,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -439,7 +474,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: Lithic) -> None: request = client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -466,7 +501,7 @@ def test_multipart_repeating_array(self, client: Lithic) -> None: ] @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter) -> None: + def test_basic_union_response(self, respx_mock: MockRouter, client: Lithic) -> None: class Model1(BaseModel): name: str @@ -475,12 +510,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + def test_union_response_different_types(self, respx_mock: MockRouter, client: Lithic) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -491,18 +526,18 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: Lithic) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -518,7 +553,7 @@ class Model(BaseModel): ) ) - response = self.client.get("/foo", cast_to=Model) + response = client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 @@ -530,6 +565,8 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" + client.close() + def test_base_url_env(self) -> None: with update_env(LITHIC_BASE_URL="http://localhost:5000/from/env"): client = Lithic(api_key=api_key, _strict_response_validation=True) @@ -541,7 +578,9 @@ def test_base_url_env(self) -> None: Lithic(api_key=api_key, _strict_response_validation=True, environment="production") client = Lithic(base_url=None, api_key=api_key, _strict_response_validation=True, environment="production") - assert str(client.base_url).startswith("https://api.lithic.com/v1") + assert str(client.base_url).startswith("https://api.lithic.com") + + client.close() @pytest.mark.parametrize( "client", @@ -565,6 +604,7 @@ def test_base_url_trailing_slash(self, client: Lithic) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -588,6 +628,7 @@ def test_base_url_no_trailing_slash(self, client: Lithic) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -611,129 +652,43 @@ def test_absolute_request_url(self, client: Lithic) -> None: ), ) assert request.url == "https://myapi.com/foo" - - def test_transport_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `transport` argument is deprecated. The `http_client` argument should be passed instead", - ): - transport = httpx.MockTransport( - lambda: None, # type: ignore - ) - - client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True, transport=transport) - - assert client._client._transport is transport - - def test_transport_option_mutually_exclusive_with_http_client(self) -> None: - with httpx.Client() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `transport`"): - with pytest.warns(DeprecationWarning): - Lithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - transport=httpx.MockTransport( - lambda: None, # type: ignore - ), - http_client=http_client, - ) - - def test_connection_pool_limits_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - ): - connection_pool_limits = httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ) - - client = Lithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - connection_pool_limits=connection_pool_limits, - ) - - assert isinstance(client._client._transport, httpx.HTTPTransport) - assert client._client._transport._pool._max_connections == 101 - assert client._client._transport._pool._max_keepalive_connections == 76 - assert client._client._transport._pool._keepalive_expiry == 23 - - def test_connection_pool_limits_option_mutually_exclusive_with_http_client(self) -> None: - with httpx.Client() as http_client: - with pytest.raises( - ValueError, match="The `http_client` argument is mutually exclusive with `connection_pool_limits`" - ): - with pytest.warns(DeprecationWarning): - Lithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - connection_pool_limits=httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ), - http_client=http_client, - ) - - def test_proxies_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - ): - proxies = "https://www.example.com/proxy" - - client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True, proxies=proxies) - - mounts = list(client._client._mounts.keys()) - assert len(mounts) == 1 - - pattern = mounts[0].pattern - assert pattern == "all://" - - def test_proxies_option_mutually_exclusive_with_http_client(self) -> None: - with httpx.Client() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `proxies`"): - with pytest.warns(DeprecationWarning): - Lithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - proxies="https://www.example.com/proxy", - http_client=http_client, - ) + client.close() def test_copied_client_does_not_close_http(self) -> None: - client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied - assert not client.is_closed() + assert not test_client.is_closed() def test_client_context_manager(self) -> None: - client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) - with client as c2: - assert c2 is client + test_client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) + with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + def test_client_response_validation_error(self, respx_mock: MockRouter, client: Lithic) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - self.client.get("/foo", cast_to=Model) + client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) + def test_client_max_retries_validation(self) -> None: + with pytest.raises(TypeError, match=r"max_retries cannot be None"): + Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)) + @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: class Model(BaseModel): @@ -746,11 +701,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=False) - response = client.get("/foo", cast_to=Model) + response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + strict_client.close() + non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -769,12 +727,13 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Lithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, client: Lithic + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) @@ -782,85 +741,221 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/cards").mock(side_effect=httpx.TimeoutException("Test timeout error")) + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Lithic) -> None: + respx_mock.post("/v1/cards").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/cards", - body=cast(object, dict(type="SINGLE_USE")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.cards.with_streaming_response.create(type="VIRTUAL").__enter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(client) == 0 @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/cards").mock(return_value=httpx.Response(500)) + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Lithic) -> None: + respx_mock.post("/v1/cards").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/cards", - body=cast(object, dict(type="SINGLE_USE")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.cards.with_streaming_response.create(type="VIRTUAL").__enter__() + assert _get_open_connections(client) == 0 - assert _get_open_connections(self.client) == 0 + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + @pytest.mark.parametrize("failure_mode", ["status", "exception"]) + def test_retries_taken( + self, + client: Lithic, + failures_before_success: int, + failure_mode: Literal["status", "exception"], + respx_mock: MockRouter, + ) -> None: + client = client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + if failure_mode == "exception": + raise RuntimeError("oops") + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + response = client.cards.with_raw_response.create(type="VIRTUAL") + + assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_omit_retry_count_header( + self, client: Lithic, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = client.with_options(max_retries=4) + nb_retries = 0 -class TestAsyncLithic: - client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + response = client.cards.with_raw_response.create( + type="VIRTUAL", extra_headers={"x-stainless-retry-count": Omit()} + ) + + assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_overwrite_retry_count_header( + self, client: Lithic, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = client.with_options(max_retries=4) + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + response = client.cards.with_raw_response.create( + type="VIRTUAL", extra_headers={"x-stainless-retry-count": "42"} + ) + + assert response.http_request.headers.get("x-stainless-retry-count") == "42" + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_retries_taken_new_response_class( + self, client: Lithic, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + with client.cards.with_streaming_response.create(type="VIRTUAL") as response: + assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter, client: Lithic) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Lithic) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" + + +class TestAsyncLithic: @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response(self, respx_mock: MockRouter) -> None: + async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: + async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = await self.client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + def test_copy(self, async_client: AsyncLithic) -> None: + copied = async_client.copy() + assert id(copied) != id(async_client) - copied = self.client.copy(api_key="another My Lithic API Key") + copied = async_client.copy(api_key="another My Lithic API Key") assert copied.api_key == "another My Lithic API Key" - assert self.client.api_key == "My Lithic API Key" + assert async_client.api_key == "My Lithic API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, async_client: AsyncLithic) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = async_client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert async_client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(async_client.timeout, httpx.Timeout) + copied = async_client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(async_client.timeout, httpx.Timeout) - def test_copy_default_headers(self) -> None: + async def test_copy_default_headers(self) -> None: client = AsyncLithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) @@ -893,8 +988,9 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + await client.close() - def test_copy_default_query(self) -> None: + async def test_copy_default_query(self) -> None: client = AsyncLithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) @@ -930,13 +1026,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + await client.close() + + def test_copy_signature(self, async_client: AsyncLithic) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + async_client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(async_client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -946,12 +1044,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, async_client: AsyncLithic) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = async_client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -1008,12 +1107,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - async def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + async def test_request_timeout(self, async_client: AsyncLithic) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( + request = async_client._build_request( FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) ) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1028,6 +1127,8 @@ async def test_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + await client.close() + async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: @@ -1039,6 +1140,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + await client.close() + # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncLithic( @@ -1049,6 +1152,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + await client.close() + # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncLithic( @@ -1059,15 +1164,27 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default - def test_default_headers_option(self) -> None: - client = AsyncLithic( + await client.close() + + def test_invalid_http_client(self) -> None: + with pytest.raises(TypeError, match="Invalid `http_client` arg"): + with httpx.Client() as http_client: + AsyncLithic( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) + + async def test_default_headers_option(self) -> None: + test_client = AsyncLithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncLithic( + test_client2 = AsyncLithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -1076,20 +1193,24 @@ def test_default_headers_option(self) -> None: "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + await test_client.close() + await test_client2.close() + def test_validate_headers(self) -> None: client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("Authorization") == api_key with pytest.raises(LithicError): - client2 = AsyncLithic(base_url=base_url, api_key=None, _strict_response_validation=True) + with update_env(**{"LITHIC_API_KEY": Omit()}): + client2 = AsyncLithic(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 - def test_default_query_option(self) -> None: + async def test_default_query_option(self) -> None: client = AsyncLithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) @@ -1101,14 +1222,16 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden"}, + params={"foo": "baz", "query_param": "overridden"}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden"} + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + await client.close() + + def test_request_extra_json(self, client: Lithic) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1119,7 +1242,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1130,7 +1253,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1141,8 +1264,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: Lithic) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1152,7 +1275,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1163,8 +1286,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: Lithic) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1177,7 +1300,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1191,7 +1314,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1207,7 +1330,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncLithic) -> None: request = async_client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -1234,7 +1357,7 @@ def test_multipart_repeating_array(self, async_client: AsyncLithic) -> None: ] @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter) -> None: + async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: class Model1(BaseModel): name: str @@ -1243,12 +1366,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1259,18 +1382,20 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - async def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + async def test_non_application_json_content_type_for_json_data( + self, respx_mock: MockRouter, async_client: AsyncLithic + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -1286,11 +1411,11 @@ class Model(BaseModel): ) ) - response = await self.client.get("/foo", cast_to=Model) + response = await async_client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 - def test_base_url_setter(self) -> None: + async def test_base_url_setter(self) -> None: client = AsyncLithic( base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True ) @@ -1300,7 +1425,9 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" - def test_base_url_env(self) -> None: + await client.close() + + async def test_base_url_env(self) -> None: with update_env(LITHIC_BASE_URL="http://localhost:5000/from/env"): client = AsyncLithic(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @@ -1313,7 +1440,9 @@ def test_base_url_env(self) -> None: client = AsyncLithic( base_url=None, api_key=api_key, _strict_response_validation=True, environment="production" ) - assert str(client.base_url).startswith("https://api.lithic.com/v1") + assert str(client.base_url).startswith("https://api.lithic.com") + + await client.close() @pytest.mark.parametrize( "client", @@ -1330,7 +1459,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncLithic) -> None: + async def test_base_url_trailing_slash(self, client: AsyncLithic) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1339,6 +1468,7 @@ def test_base_url_trailing_slash(self, client: AsyncLithic) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1355,7 +1485,7 @@ def test_base_url_trailing_slash(self, client: AsyncLithic) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncLithic) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncLithic) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1364,6 +1494,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncLithic) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1380,7 +1511,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncLithic) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncLithic) -> None: + async def test_absolute_request_url(self, client: AsyncLithic) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1389,135 +1520,47 @@ def test_absolute_request_url(self, client: AsyncLithic) -> None: ), ) assert request.url == "https://myapi.com/foo" - - def test_transport_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `transport` argument is deprecated. The `http_client` argument should be passed instead", - ): - transport = httpx.MockTransport( - lambda: None, # type: ignore - ) - - client = AsyncLithic( - base_url=base_url, api_key=api_key, _strict_response_validation=True, transport=transport - ) - - assert client._client._transport is transport - - async def test_transport_option_mutually_exclusive_with_http_client(self) -> None: - async with httpx.AsyncClient() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `transport`"): - with pytest.warns(DeprecationWarning): - AsyncLithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - transport=httpx.MockTransport( - lambda: None, # type: ignore - ), - http_client=http_client, - ) - - def test_connection_pool_limits_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - ): - connection_pool_limits = httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ) - - client = AsyncLithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - connection_pool_limits=connection_pool_limits, - ) - - assert isinstance(client._client._transport, httpx.AsyncHTTPTransport) - assert client._client._transport._pool._max_connections == 101 - assert client._client._transport._pool._max_keepalive_connections == 76 - assert client._client._transport._pool._keepalive_expiry == 23 - - async def test_connection_pool_limits_option_mutually_exclusive_with_http_client(self) -> None: - async with httpx.AsyncClient() as http_client: - with pytest.raises( - ValueError, match="The `http_client` argument is mutually exclusive with `connection_pool_limits`" - ): - with pytest.warns(DeprecationWarning): - AsyncLithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - connection_pool_limits=httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ), - http_client=http_client, - ) - - def test_proxies_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - ): - proxies = "https://www.example.com/proxy" - - client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True, proxies=proxies) - - mounts = list(client._client._mounts.keys()) - assert len(mounts) == 1 - - pattern = mounts[0].pattern - assert pattern == "all://" - - async def test_proxies_option_mutually_exclusive_with_http_client(self) -> None: - async with httpx.AsyncClient() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `proxies`"): - with pytest.warns(DeprecationWarning): - AsyncLithic( - base_url=base_url, - api_key=api_key, - _strict_response_validation=True, - proxies="https://www.example.com/proxy", - http_client=http_client, - ) + await client.close() async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied await asyncio.sleep(0.2) - assert not client.is_closed() + assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) - async with client as c2: - assert c2 is client + test_client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) + async with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - await self.client.get("/foo", cast_to=Model) + await async_client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) + async def test_client_max_retries_validation(self) -> None: + with pytest.raises(TypeError, match=r"max_retries cannot be None"): + AsyncLithic( + base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None) + ) + @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: class Model(BaseModel): name: str @@ -1529,11 +1572,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=False) - response = await client.get("/foo", cast_to=Model) + response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + await strict_client.close() + await non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -1552,44 +1598,190 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - @pytest.mark.asyncio - async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncLithic(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + async def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncLithic + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) - calculated = client._calculate_retry_timeout(remaining_retries, options, headers) + calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/cards").mock(side_effect=httpx.TimeoutException("Test timeout error")) + async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: + respx_mock.post("/v1/cards").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/cards", - body=cast(object, dict(type="SINGLE_USE")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + await async_client.cards.with_streaming_response.create(type="VIRTUAL").__aenter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(async_client) == 0 @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/cards").mock(return_value=httpx.Response(500)) + async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: + respx_mock.post("/v1/cards").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/cards", - body=cast(object, dict(type="SINGLE_USE")), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + await async_client.cards.with_streaming_response.create(type="VIRTUAL").__aenter__() + assert _get_open_connections(async_client) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + @pytest.mark.parametrize("failure_mode", ["status", "exception"]) + async def test_retries_taken( + self, + async_client: AsyncLithic, + failures_before_success: int, + failure_mode: Literal["status", "exception"], + respx_mock: MockRouter, + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + if failure_mode == "exception": + raise RuntimeError("oops") + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + response = await client.cards.with_raw_response.create(type="VIRTUAL") + + assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_omit_retry_count_header( + self, async_client: AsyncLithic, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + response = await client.cards.with_raw_response.create( + type="VIRTUAL", extra_headers={"x-stainless-retry-count": Omit()} + ) + + assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_overwrite_retry_count_header( + self, async_client: AsyncLithic, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + response = await client.cards.with_raw_response.create( + type="VIRTUAL", extra_headers={"x-stainless-retry-count": "42"} + ) + + assert response.http_request.headers.get("x-stainless-retry-count") == "42" + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("lithic._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_retries_taken_new_response_class( + self, async_client: AsyncLithic, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.post("/v1/cards").mock(side_effect=retry_handler) + + async with client.cards.with_streaming_response.create(type="VIRTUAL") as response: + assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) + + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncLithic) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await async_client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response ) - assert _get_open_connections(self.client) == 0 + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py index c4de8b11..b513e83a 100644 --- a/tests/test_deepcopy.py +++ b/tests/test_deepcopy.py @@ -41,8 +41,7 @@ def test_nested_list() -> None: assert_different_identities(obj1[1], obj2[1]) -class MyObject: - ... +class MyObject: ... def test_ignores_other_types() -> None: diff --git a/tests/test_legacy_response.py b/tests/test_legacy_response.py index 7bc981c5..e9a5972a 100644 --- a/tests/test_legacy_response.py +++ b/tests/test_legacy_response.py @@ -1,4 +1,6 @@ import json +from typing import Any, Union, cast +from typing_extensions import Annotated import httpx import pytest @@ -10,8 +12,7 @@ from lithic._legacy_response import LegacyAPIResponse -class PydanticModel(pydantic.BaseModel): - ... +class PydanticModel(pydantic.BaseModel): ... def test_response_parse_mismatched_basemodel(client: Lithic) -> None: @@ -31,6 +32,31 @@ def test_response_parse_mismatched_basemodel(client: Lithic) -> None: response.parse(to=PydanticModel) +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +def test_response_parse_bool(client: Lithic, content: str, expected: bool) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = response.parse(to=bool) + assert result is expected + + def test_response_parse_custom_stream(client: Lithic) -> None: response = LegacyAPIResponse( raw=httpx.Response(200, content=b"foo"), @@ -63,3 +89,40 @@ def test_response_parse_custom_model(client: Lithic) -> None: obj = response.parse(to=CustomModel) assert obj.foo == "hello!" assert obj.bar == 2 + + +def test_response_parse_annotated_type(client: Lithic) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +class OtherModel(pydantic.BaseModel): + a: str + + +@pytest.mark.parametrize("client", [False], indirect=True) # loose validation +def test_response_parse_expect_model_union_non_json_content(client: Lithic) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel])) + assert isinstance(obj, str) + assert obj == "foo" diff --git a/tests/test_models.py b/tests/test_models.py index 3c00abb2..a11c900d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,14 +1,15 @@ import json -from typing import Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal +from typing_extensions import Literal, Annotated, TypeAliasType import pytest import pydantic from pydantic import Field -from lithic._compat import PYDANTIC_V2, parse_obj, model_dump, model_json -from lithic._models import BaseModel +from lithic._utils import PropertyInfo +from lithic._compat import PYDANTIC_V1, parse_obj, model_dump, model_json +from lithic._models import DISCRIMINATOR_CACHE, BaseModel, construct_type class BasicModel(BaseModel): @@ -30,7 +31,7 @@ class NestedModel(BaseModel): # mismatched types m = NestedModel.construct(nested="hello!") - assert m.nested == "hello!" + assert cast(Any, m.nested) == "hello!" def test_optional_nested_model() -> None: @@ -47,7 +48,7 @@ class NestedModel(BaseModel): # mismatched types m3 = NestedModel.construct(nested={"foo"}) assert isinstance(cast(Any, m3.nested), set) - assert m3.nested == {"foo"} + assert cast(Any, m3.nested) == {"foo"} def test_list_nested_model() -> None: @@ -244,7 +245,7 @@ class Model(BaseModel): assert m.foo is True m = Model.construct(foo="CARD_HOLDER") - assert m.foo is "CARD_HOLDER" + assert m.foo == "CARD_HOLDER" m = Model.construct(foo={"bar": False}) assert isinstance(m.foo, Submodel1) @@ -293,12 +294,12 @@ class Model(BaseModel): assert cast(bool, m.foo) is True m = Model.construct(foo={"name": 3}) - if PYDANTIC_V2: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - else: + if PYDANTIC_V1: assert isinstance(m.foo, Submodel2) assert m.foo.name == "3" + else: + assert isinstance(m.foo, Submodel1) + assert m.foo.name == 3 # type: ignore def test_list_of_unions() -> None: @@ -322,7 +323,7 @@ class Model(BaseModel): assert len(m.items) == 2 assert isinstance(m.items[0], Submodel1) assert m.items[0].level == -1 - assert m.items[1] == 156 + assert cast(Any, m.items[1]) == 156 def test_union_of_lists() -> None: @@ -354,7 +355,7 @@ class Model(BaseModel): assert len(m.items) == 2 assert isinstance(m.items[0], SubModel1) assert m.items[0].level == -1 - assert m.items[1] == 156 + assert cast(Any, m.items[1]) == 156 def test_dict_of_union() -> None: @@ -425,10 +426,10 @@ class Model(BaseModel): expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - if PYDANTIC_V2: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - else: + if PYDANTIC_V1: expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + else: + expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' model = Model.construct(created_at="2019-12-27T18:11:19.117Z") assert model.created_at == expected @@ -491,15 +492,50 @@ class Model(BaseModel): resource_id: Optional[str] = None m = Model.construct() + assert m.resource_id is None assert "resource_id" not in m.model_fields_set m = Model.construct(resource_id=None) + assert m.resource_id is None assert "resource_id" in m.model_fields_set m = Model.construct(resource_id="foo") + assert m.resource_id == "foo" assert "resource_id" in m.model_fields_set +def test_to_dict() -> None: + class Model(BaseModel): + foo: Optional[str] = Field(alias="FOO", default=None) + + m = Model(FOO="hello") + assert m.to_dict() == {"FOO": "hello"} + assert m.to_dict(use_api_names=False) == {"foo": "hello"} + + m2 = Model() + assert m2.to_dict() == {} + assert m2.to_dict(exclude_unset=False) == {"FOO": None} + assert m2.to_dict(exclude_unset=False, exclude_none=True) == {} + assert m2.to_dict(exclude_unset=False, exclude_defaults=True) == {} + + m3 = Model(FOO=None) + assert m3.to_dict() == {"FOO": None} + assert m3.to_dict(exclude_none=True) == {} + assert m3.to_dict(exclude_defaults=True) == {} + + class Model2(BaseModel): + created_at: datetime + + time_str = "2024-03-21T11:39:01.275859" + m4 = Model2.construct(created_at=time_str) + assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} + assert m4.to_dict(mode="json") == {"created_at": time_str} + + if PYDANTIC_V1: + with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): + m.to_dict(warnings=False) + + def test_forwards_compat_model_dump_method() -> None: class Model(BaseModel): foo: Optional[str] = Field(alias="FOO", default=None) @@ -520,10 +556,7 @@ class Model(BaseModel): assert m3.model_dump() == {"foo": None} assert m3.model_dump(exclude_none=True) == {} - if not PYDANTIC_V2: - with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"): - m.model_dump(mode="json") - + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) @@ -531,6 +564,42 @@ class Model(BaseModel): m.model_dump(warnings=False) +def test_compat_method_no_error_for_warnings() -> None: + class Model(BaseModel): + foo: Optional[str] + + m = Model(foo="hello") + assert isinstance(model_dump(m, warnings=False), dict) + + +def test_to_json() -> None: + class Model(BaseModel): + foo: Optional[str] = Field(alias="FOO", default=None) + + m = Model(FOO="hello") + assert json.loads(m.to_json()) == {"FOO": "hello"} + assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"} + + if PYDANTIC_V1: + assert m.to_json(indent=None) == '{"FOO": "hello"}' + else: + assert m.to_json(indent=None) == '{"FOO":"hello"}' + + m2 = Model() + assert json.loads(m2.to_json()) == {} + assert json.loads(m2.to_json(exclude_unset=False)) == {"FOO": None} + assert json.loads(m2.to_json(exclude_unset=False, exclude_none=True)) == {} + assert json.loads(m2.to_json(exclude_unset=False, exclude_defaults=True)) == {} + + m3 = Model(FOO=None) + assert json.loads(m3.to_json()) == {"FOO": None} + assert json.loads(m3.to_json(exclude_none=True)) == {} + + if PYDANTIC_V1: + with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): + m.to_json(warnings=False) + + def test_forwards_compat_model_dump_json_method() -> None: class Model(BaseModel): foo: Optional[str] = Field(alias="FOO", default=None) @@ -553,7 +622,7 @@ class Model(BaseModel): assert json.loads(m3.model_dump_json()) == {"foo": None} assert json.loads(m3.model_dump_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump_json(round_trip=True) @@ -571,3 +640,324 @@ class OurModel(BaseModel): foo: Optional[str] = None takes_pydantic(OurModel()) + + +def test_annotated_types() -> None: + class Model(BaseModel): + value: str + + m = construct_type( + value={"value": "foo"}, + type_=cast(Any, Annotated[Model, "random metadata"]), + ) + assert isinstance(m, Model) + assert m.value == "foo" + + +def test_discriminated_unions_invalid_data() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "a", "data": 100}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, A) + assert m.type == "a" + if PYDANTIC_V1: + # pydantic v1 automatically converts inputs to strings + # if the expected type is a str + assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] + + +def test_discriminated_unions_unknown_variant() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + m = construct_type( + value={"type": "c", "data": None, "new_thing": "bar"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + + # just chooses the first variant + assert isinstance(m, A) + assert m.type == "c" # type: ignore[comparison-overlap] + assert m.data == None # type: ignore[unreachable] + assert m.new_thing == "bar" + + +def test_discriminated_unions_invalid_data_nested_unions() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + class C(BaseModel): + type: Literal["c"] + + data: bool + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "c", "data": "foo"}, + type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, C) + assert m.type == "c" + assert m.data == "foo" # type: ignore[comparison-overlap] + + +def test_discriminated_unions_with_aliases_invalid_data() -> None: + class A(BaseModel): + foo_type: Literal["a"] = Field(alias="type") + + data: str + + class B(BaseModel): + foo_type: Literal["b"] = Field(alias="type") + + data: int + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), + ) + assert isinstance(m, B) + assert m.foo_type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "a", "data": 100}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), + ) + assert isinstance(m, A) + assert m.foo_type == "a" + if PYDANTIC_V1: + # pydantic v1 automatically converts inputs to strings + # if the expected type is a str + assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] + + +def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["a"] + + data: int + + m = construct_type( + value={"type": "a", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "a" + assert m.data == "foo" # type: ignore[comparison-overlap] + + +def test_discriminated_unions_invalid_data_uses_cache() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + UnionType = cast(Any, Union[A, B]) + + assert not DISCRIMINATOR_CACHE.get(UnionType) + + m = construct_type( + value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + discriminator = DISCRIMINATOR_CACHE.get(UnionType) + assert discriminator is not None + + m = construct_type( + value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + # if the discriminator details object stays the same between invocations then + # we hit the cache + assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator + + +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") +def test_type_alias_type() -> None: + Alias = TypeAliasType("Alias", str) # pyright: ignore + + class Model(BaseModel): + alias: Alias + union: Union[int, Alias] + + m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.alias, str) + assert m.alias == "foo" + assert isinstance(m.union, str) + assert m.union == "bar" + + +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") +def test_field_named_cls() -> None: + class Model(BaseModel): + cls: str + + m = construct_type(value={"cls": "foo"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.cls, str) + + +def test_discriminated_union_case() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["b"] + + data: List[Union[A, object]] + + class ModelA(BaseModel): + type: Literal["modelA"] + + data: int + + class ModelB(BaseModel): + type: Literal["modelB"] + + required: str + + data: Union[A, B] + + # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required` + m = construct_type( + value={"type": "modelB", "data": {"type": "a", "data": True}}, + type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]), + ) + + assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now") +def test_extra_properties() -> None: + class Item(BaseModel): + prop: int + + class Model(BaseModel): + __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + other: str + + if TYPE_CHECKING: + + def __getattr__(self, attr: str) -> Item: ... + + model = construct_type( + type_=Model, + value={ + "a": {"prop": 1}, + "other": "foo", + }, + ) + assert isinstance(model, Model) + assert model.a.prop == 1 + assert isinstance(model.a, Item) + assert model.other == "foo" diff --git a/tests/test_response.py b/tests/test_response.py index ae1587a7..5c0709ed 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,5 +1,6 @@ import json -from typing import List +from typing import Any, List, Union, cast +from typing_extensions import Annotated import httpx import pytest @@ -18,16 +19,13 @@ from lithic._base_client import FinalRequestOptions -class ConcreteBaseAPIResponse(APIResponse[bytes]): - ... +class ConcreteBaseAPIResponse(APIResponse[bytes]): ... -class ConcreteAPIResponse(APIResponse[List[str]]): - ... +class ConcreteAPIResponse(APIResponse[List[str]]): ... -class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): - ... +class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ... def test_extract_response_type_direct_classes() -> None: @@ -55,8 +53,7 @@ def test_extract_response_type_binary_response() -> None: assert extract_response_type(AsyncBinaryAPIResponse) == bytes -class PydanticModel(pydantic.BaseModel): - ... +class PydanticModel(pydantic.BaseModel): ... def test_response_parse_mismatched_basemodel(client: Lithic) -> None: @@ -157,3 +154,124 @@ async def test_async_response_parse_custom_model(async_client: AsyncLithic) -> N obj = await response.parse(to=CustomModel) assert obj.foo == "hello!" assert obj.bar == 2 + + +def test_response_parse_annotated_type(client: Lithic) -> None: + response = APIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +async def test_async_response_parse_annotated_type(async_client: AsyncLithic) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +def test_response_parse_bool(client: Lithic, content: str, expected: bool) -> None: + response = APIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = response.parse(to=bool) + assert result is expected + + +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +async def test_async_response_parse_bool(client: AsyncLithic, content: str, expected: bool) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = await response.parse(to=bool) + assert result is expected + + +class OtherModel(BaseModel): + a: str + + +@pytest.mark.parametrize("client", [False], indirect=True) # loose validation +def test_response_parse_expect_model_union_non_json_content(client: Lithic) -> None: + response = APIResponse( + raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel])) + assert isinstance(obj, str) + assert obj == "foo" + + +@pytest.mark.asyncio +@pytest.mark.parametrize("async_client", [False], indirect=True) # loose validation +async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncLithic) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse(to=cast(Any, Union[CustomModel, OtherModel])) + assert isinstance(obj, str) + assert obj == "foo" diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 088a7677..2e09be02 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -1,104 +1,248 @@ +from __future__ import annotations + from typing import Iterator, AsyncIterator +import httpx import pytest -from lithic._streaming import SSEDecoder +from lithic import Lithic, AsyncLithic +from lithic._streaming import Stream, AsyncStream, ServerSentEvent @pytest.mark.asyncio -async def test_basic_async() -> None: - async def body() -> AsyncIterator[str]: - yield "event: completion" - yield 'data: {"foo":true}' - yield "" - - async for sse in SSEDecoder().aiter(body()): - assert sse.event == "completion" - assert sse.json() == {"foo": True} +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_basic(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b"event: completion\n" + yield b'data: {"foo":true}\n' + yield b"\n" + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) -def test_basic() -> None: - def body() -> Iterator[str]: - yield "event: completion" - yield 'data: {"foo":true}' - yield "" - - it = SSEDecoder().iter(body()) - sse = next(it) + sse = await iter_next(iterator) assert sse.event == "completion" assert sse.json() == {"foo": True} - with pytest.raises(StopIteration): - next(it) + await assert_empty_iter(iterator) -def test_data_missing_event() -> None: - def body() -> Iterator[str]: - yield 'data: {"foo":true}' - yield "" +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_data_missing_event(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b'data: {"foo":true}\n' + yield b"\n" - it = SSEDecoder().iter(body()) - sse = next(it) + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) assert sse.event is None assert sse.json() == {"foo": True} - with pytest.raises(StopIteration): - next(it) + await assert_empty_iter(iterator) + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_event_missing_data(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"\n" -def test_event_missing_data() -> None: - def body() -> Iterator[str]: - yield "event: ping" - yield "" + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - it = SSEDecoder().iter(body()) - sse = next(it) + sse = await iter_next(iterator) assert sse.event == "ping" assert sse.data == "" - with pytest.raises(StopIteration): - next(it) + await assert_empty_iter(iterator) -def test_multiple_events() -> None: - def body() -> Iterator[str]: - yield "event: ping" - yield "" - yield "event: completion" - yield "" +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_events(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"\n" + yield b"event: completion\n" + yield b"\n" - it = SSEDecoder().iter(body()) + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) - sse = next(it) + sse = await iter_next(iterator) assert sse.event == "ping" assert sse.data == "" - sse = next(it) + sse = await iter_next(iterator) assert sse.event == "completion" assert sse.data == "" - with pytest.raises(StopIteration): - next(it) - - -def test_multiple_events_with_data() -> None: - def body() -> Iterator[str]: - yield "event: ping" - yield 'data: {"foo":true}' - yield "" - yield "event: completion" - yield 'data: {"bar":false}' - yield "" + await assert_empty_iter(iterator) - it = SSEDecoder().iter(body()) - sse = next(it) +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_events_with_data(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b'data: {"foo":true}\n' + yield b"\n" + yield b"event: completion\n" + yield b'data: {"bar":false}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) assert sse.event == "ping" assert sse.json() == {"foo": True} - sse = next(it) + sse = await iter_next(iterator) assert sse.event == "completion" assert sse.json() == {"bar": False} - with pytest.raises(StopIteration): - next(it) + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_data_lines_with_empty_line(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"data: {\n" + yield b'data: "foo":\n' + yield b"data: \n" + yield b"data:\n" + yield b"data: true}\n" + yield b"\n\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.json() == {"foo": True} + assert sse.data == '{\n"foo":\n\n\ntrue}' + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_data_json_escaped_double_new_line(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b'data: {"foo": "my long\\n\\ncontent"}' + yield b"\n\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.json() == {"foo": "my long\n\ncontent"} + + await assert_empty_iter(iterator) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multiple_data_lines(sync: bool, client: Lithic, async_client: AsyncLithic) -> None: + def body() -> Iterator[bytes]: + yield b"event: ping\n" + yield b"data: {\n" + yield b'data: "foo":\n' + yield b"data: true}\n" + yield b"\n\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event == "ping" + assert sse.json() == {"foo": True} + + await assert_empty_iter(iterator) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_special_new_line_character( + sync: bool, + client: Lithic, + async_client: AsyncLithic, +) -> None: + def body() -> Iterator[bytes]: + yield b'data: {"content":" culpa"}\n' + yield b"\n" + yield b'data: {"content":" \xe2\x80\xa8"}\n' + yield b"\n" + yield b'data: {"content":"foo"}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": " culpa"} + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": " 
"} + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": "foo"} + + await assert_empty_iter(iterator) + + +@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) +async def test_multi_byte_character_multiple_chunks( + sync: bool, + client: Lithic, + async_client: AsyncLithic, +) -> None: + def body() -> Iterator[bytes]: + yield b'data: {"content":"' + # bytes taken from the string 'известни' and arbitrarily split + # so that some multi-byte characters span multiple chunks + yield b"\xd0" + yield b"\xb8\xd0\xb7\xd0" + yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8" + yield b'"}\n' + yield b"\n" + + iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client) + + sse = await iter_next(iterator) + assert sse.event is None + assert sse.json() == {"content": "известни"} + + +async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]: + for chunk in iter: + yield chunk + + +async def iter_next(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> ServerSentEvent: + if isinstance(iter, AsyncIterator): + return await iter.__anext__() + + return next(iter) + + +async def assert_empty_iter(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> None: + with pytest.raises((StopAsyncIteration, RuntimeError)): + await iter_next(iter) + + +def make_event_iterator( + content: Iterator[bytes], + *, + sync: bool, + client: Lithic, + async_client: AsyncLithic, +) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: + if sync: + return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() + + return AsyncStream( + cast_to=object, client=async_client, response=httpx.Response(200, content=to_aiter(content)) + )._iter_events() diff --git a/tests/test_transform.py b/tests/test_transform.py index c57a6172..5c132ac3 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1,22 +1,50 @@ from __future__ import annotations -from typing import Any, List, Union, Iterable, Optional, cast +import io +import pathlib +from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict import pytest -from lithic._utils import PropertyInfo, transform, parse_datetime -from lithic._compat import PYDANTIC_V2 +from lithic._types import Base64FileInput, omit, not_given +from lithic._utils import ( + PropertyInfo, + transform as _transform, + parse_datetime, + async_transform as _async_transform, +) +from lithic._compat import PYDANTIC_V1 from lithic._models import BaseModel +_T = TypeVar("_T") + +SAMPLE_FILE_PATH = pathlib.Path(__file__).parent.joinpath("sample_file.txt") + + +async def transform( + data: _T, + expected_type: object, + use_async: bool, +) -> _T: + if use_async: + return await _async_transform(data, expected_type=expected_type) + + return _transform(data, expected_type=expected_type) + + +parametrize = pytest.mark.parametrize("use_async", [False, True], ids=["sync", "async"]) + class Foo1(TypedDict): foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] -def test_top_level_alias() -> None: - assert transform({"foo_bar": "hello"}, expected_type=Foo1) == {"fooBar": "hello"} +@parametrize +@pytest.mark.asyncio +async def test_top_level_alias(use_async: bool) -> None: + assert await transform({"foo_bar": "hello"}, expected_type=Foo1, use_async=use_async) == {"fooBar": "hello"} class Foo2(TypedDict): @@ -32,9 +60,11 @@ class Baz2(TypedDict): my_baz: Annotated[str, PropertyInfo(alias="myBaz")] -def test_recursive_typeddict() -> None: - assert transform({"bar": {"this_thing": 1}}, Foo2) == {"bar": {"this__thing": 1}} - assert transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2) == {"bar": {"Baz": {"myBaz": "foo"}}} +@parametrize +@pytest.mark.asyncio +async def test_recursive_typeddict(use_async: bool) -> None: + assert await transform({"bar": {"this_thing": 1}}, Foo2, use_async) == {"bar": {"this__thing": 1}} + assert await transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2, use_async) == {"bar": {"Baz": {"myBaz": "foo"}}} class Foo3(TypedDict): @@ -45,8 +75,10 @@ class Bar3(TypedDict): my_field: Annotated[str, PropertyInfo(alias="myField")] -def test_list_of_typeddict() -> None: - result = transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, expected_type=Foo3) +@parametrize +@pytest.mark.asyncio +async def test_list_of_typeddict(use_async: bool) -> None: + result = await transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, Foo3, use_async) assert result == {"things": [{"myField": "foo"}, {"myField": "foo2"}]} @@ -62,10 +94,14 @@ class Baz4(TypedDict): foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] -def test_union_of_typeddict() -> None: - assert transform({"foo": {"foo_bar": "bar"}}, Foo4) == {"foo": {"fooBar": "bar"}} - assert transform({"foo": {"foo_baz": "baz"}}, Foo4) == {"foo": {"fooBaz": "baz"}} - assert transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4) == {"foo": {"fooBaz": "baz", "fooBar": "bar"}} +@parametrize +@pytest.mark.asyncio +async def test_union_of_typeddict(use_async: bool) -> None: + assert await transform({"foo": {"foo_bar": "bar"}}, Foo4, use_async) == {"foo": {"fooBar": "bar"}} + assert await transform({"foo": {"foo_baz": "baz"}}, Foo4, use_async) == {"foo": {"fooBaz": "baz"}} + assert await transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4, use_async) == { + "foo": {"fooBaz": "baz", "fooBar": "bar"} + } class Foo5(TypedDict): @@ -80,9 +116,11 @@ class Baz5(TypedDict): foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] -def test_union_of_list() -> None: - assert transform({"foo": {"foo_bar": "bar"}}, Foo5) == {"FOO": {"fooBar": "bar"}} - assert transform( +@parametrize +@pytest.mark.asyncio +async def test_union_of_list(use_async: bool) -> None: + assert await transform({"foo": {"foo_bar": "bar"}}, Foo5, use_async) == {"FOO": {"fooBar": "bar"}} + assert await transform( { "foo": [ {"foo_baz": "baz"}, @@ -90,6 +128,7 @@ def test_union_of_list() -> None: ] }, Foo5, + use_async, ) == {"FOO": [{"fooBaz": "baz"}, {"fooBaz": "baz"}]} @@ -97,8 +136,10 @@ class Foo6(TypedDict): bar: Annotated[str, PropertyInfo(alias="Bar")] -def test_includes_unknown_keys() -> None: - assert transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6) == { +@parametrize +@pytest.mark.asyncio +async def test_includes_unknown_keys(use_async: bool) -> None: + assert await transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6, use_async) == { "Bar": "bar", "baz_": {"FOO": 1}, } @@ -113,9 +154,11 @@ class Bar7(TypedDict): foo: str -def test_ignores_invalid_input() -> None: - assert transform({"bar": ""}, Foo7) == {"bAr": ""} - assert transform({"foo": ""}, Foo7) == {"foo": ""} +@parametrize +@pytest.mark.asyncio +async def test_ignores_invalid_input(use_async: bool) -> None: + assert await transform({"bar": ""}, Foo7, use_async) == {"bAr": ""} + assert await transform({"foo": ""}, Foo7, use_async) == {"foo": ""} class DatetimeDict(TypedDict, total=False): @@ -134,52 +177,81 @@ class DateDict(TypedDict, total=False): foo: Annotated[date, PropertyInfo(format="iso8601")] -def test_iso8601_format() -> None: +class DatetimeModel(BaseModel): + foo: datetime + + +class DateModel(BaseModel): + foo: Optional[date] + + +@parametrize +@pytest.mark.asyncio +async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"foo": dt}, DatetimeDict) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + tz = "+00:00" if PYDANTIC_V1 else "Z" + assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] dt = dt.replace(tzinfo=None) - assert transform({"foo": dt}, DatetimeDict) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] - - assert transform({"foo": None}, DateDict) == {"foo": None} # type: ignore[comparison-overlap] - assert transform({"foo": date.fromisoformat("2023-02-23")}, DateDict) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] + assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] + assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] + + assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None} # type: ignore + assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == { + "foo": "2023-02-23" + } # type: ignore[comparison-overlap] -def test_optional_iso8601_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_optional_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"bar": dt}, DatetimeDict) == {"bar": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform({"bar": dt}, DatetimeDict, use_async) == {"bar": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] - assert transform({"bar": None}, DatetimeDict) == {"bar": None} + assert await transform({"bar": None}, DatetimeDict, use_async) == {"bar": None} -def test_required_iso8601_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_required_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"required": dt}, DatetimeDict) == {"required": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform({"required": dt}, DatetimeDict, use_async) == { + "required": "2023-02-23T14:16:36.337692+00:00" + } # type: ignore[comparison-overlap] - assert transform({"required": None}, DatetimeDict) == {"required": None} + assert await transform({"required": None}, DatetimeDict, use_async) == {"required": None} -def test_union_datetime() -> None: +@parametrize +@pytest.mark.asyncio +async def test_union_datetime(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"union": dt}, DatetimeDict) == { # type: ignore[comparison-overlap] + assert await transform({"union": dt}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] "union": "2023-02-23T14:16:36.337692+00:00" } - assert transform({"union": "foo"}, DatetimeDict) == {"union": "foo"} + assert await transform({"union": "foo"}, DatetimeDict, use_async) == {"union": "foo"} -def test_nested_list_iso6801_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_nested_list_iso6801_format(use_async: bool) -> None: dt1 = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") dt2 = parse_datetime("2022-01-15T06:34:23Z") - assert transform({"list_": [dt1, dt2]}, DatetimeDict) == { # type: ignore[comparison-overlap] + assert await transform({"list_": [dt1, dt2]}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] "list_": ["2023-02-23T14:16:36.337692+00:00", "2022-01-15T06:34:23+00:00"] } -def test_datetime_custom_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_datetime_custom_format(use_async: bool) -> None: dt = parse_datetime("2022-01-15T06:34:23Z") - result = transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")]) + result = await transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")], use_async) assert result == "06" # type: ignore[comparison-overlap] @@ -187,58 +259,74 @@ class DateDictWithRequiredAlias(TypedDict, total=False): required_prop: Required[Annotated[date, PropertyInfo(format="iso8601", alias="prop")]] -def test_datetime_with_alias() -> None: - assert transform({"required_prop": None}, DateDictWithRequiredAlias) == {"prop": None} # type: ignore[comparison-overlap] - assert transform({"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias) == { - "prop": "2023-02-23" - } # type: ignore[comparison-overlap] +@parametrize +@pytest.mark.asyncio +async def test_datetime_with_alias(use_async: bool) -> None: + assert await transform({"required_prop": None}, DateDictWithRequiredAlias, use_async) == {"prop": None} # type: ignore[comparison-overlap] + assert await transform( + {"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias, use_async + ) == {"prop": "2023-02-23"} # type: ignore[comparison-overlap] class MyModel(BaseModel): foo: str -def test_pydantic_model_to_dictionary() -> None: - assert transform(MyModel(foo="hi!"), Any) == {"foo": "hi!"} - assert transform(MyModel.construct(foo="hi!"), Any) == {"foo": "hi!"} +@parametrize +@pytest.mark.asyncio +async def test_pydantic_model_to_dictionary(use_async: bool) -> None: + assert cast(Any, await transform(MyModel(foo="hi!"), Any, use_async)) == {"foo": "hi!"} + assert cast(Any, await transform(MyModel.construct(foo="hi!"), Any, use_async)) == {"foo": "hi!"} -def test_pydantic_empty_model() -> None: - assert transform(MyModel.construct(), Any) == {} +@parametrize +@pytest.mark.asyncio +async def test_pydantic_empty_model(use_async: bool) -> None: + assert cast(Any, await transform(MyModel.construct(), Any, use_async)) == {} -def test_pydantic_unknown_field() -> None: - assert transform(MyModel.construct(my_untyped_field=True), Any) == {"my_untyped_field": True} +@parametrize +@pytest.mark.asyncio +async def test_pydantic_unknown_field(use_async: bool) -> None: + assert cast(Any, await transform(MyModel.construct(my_untyped_field=True), Any, use_async)) == { + "my_untyped_field": True + } -def test_pydantic_mismatched_types() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_mismatched_types(use_async: bool) -> None: model = MyModel.construct(foo=True) - if PYDANTIC_V2: - with pytest.warns(UserWarning): - params = transform(model, Any) + if PYDANTIC_V1: + params = await transform(model, Any, use_async) else: - params = transform(model, Any) - assert params == {"foo": True} + with pytest.warns(UserWarning): + params = await transform(model, Any, use_async) + assert cast(Any, params) == {"foo": True} -def test_pydantic_mismatched_object_type() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_mismatched_object_type(use_async: bool) -> None: model = MyModel.construct(foo=MyModel.construct(hello="world")) - if PYDANTIC_V2: - with pytest.warns(UserWarning): - params = transform(model, Any) + if PYDANTIC_V1: + params = await transform(model, Any, use_async) else: - params = transform(model, Any) - assert params == {"foo": {"hello": "world"}} + with pytest.warns(UserWarning): + params = await transform(model, Any, use_async) + assert cast(Any, params) == {"foo": {"hello": "world"}} class ModelNestedObjects(BaseModel): nested: MyModel -def test_pydantic_nested_objects() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_nested_objects(use_async: bool) -> None: model = ModelNestedObjects.construct(nested={"foo": "stainless"}) assert isinstance(model.nested, MyModel) - assert transform(model, Any) == {"nested": {"foo": "stainless"}} + assert cast(Any, await transform(model, Any, use_async)) == {"nested": {"foo": "stainless"}} class ModelWithDefaultField(BaseModel): @@ -247,24 +335,26 @@ class ModelWithDefaultField(BaseModel): with_str_default: str = "foo" -def test_pydantic_default_field() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_default_field(use_async: bool) -> None: # should be excluded when defaults are used model = ModelWithDefaultField.construct() assert model.with_none_default is None assert model.with_str_default == "foo" - assert transform(model, Any) == {} + assert cast(Any, await transform(model, Any, use_async)) == {} # should be included when the default value is explicitly given model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo") assert model.with_none_default is None assert model.with_str_default == "foo" - assert transform(model, Any) == {"with_none_default": None, "with_str_default": "foo"} + assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": None, "with_str_default": "foo"} # should be included when a non-default value is explicitly given model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz") assert model.with_none_default == "bar" assert model.with_str_default == "baz" - assert transform(model, Any) == {"with_none_default": "bar", "with_str_default": "baz"} + assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"} class TypedDictIterableUnion(TypedDict): @@ -279,21 +369,92 @@ class Baz8(TypedDict): foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] -def test_iterable_of_dictionaries() -> None: - assert transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion) == {"FOO": [{"fooBaz": "bar"}]} - assert cast(Any, transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion)) == {"FOO": [{"fooBaz": "bar"}]} +@parametrize +@pytest.mark.asyncio +async def test_iterable_of_dictionaries(use_async: bool) -> None: + assert await transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion, use_async) == { + "FOO": [{"fooBaz": "bar"}] + } + assert cast(Any, await transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion, use_async)) == { + "FOO": [{"fooBaz": "bar"}] + } def my_iter() -> Iterable[Baz8]: yield {"foo_baz": "hello"} yield {"foo_baz": "world"} - assert transform({"foo": my_iter()}, TypedDictIterableUnion) == {"FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}]} + assert await transform({"foo": my_iter()}, TypedDictIterableUnion, use_async) == { + "FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}] + } + + +@parametrize +@pytest.mark.asyncio +async def test_dictionary_items(use_async: bool) -> None: + class DictItems(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}} class TypedDictIterableUnionStr(TypedDict): foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")] -def test_iterable_union_str() -> None: - assert transform({"foo": "bar"}, TypedDictIterableUnionStr) == {"FOO": "bar"} - assert cast(Any, transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]])) == [{"fooBaz": "bar"}] +@parametrize +@pytest.mark.asyncio +async def test_iterable_union_str(use_async: bool) -> None: + assert await transform({"foo": "bar"}, TypedDictIterableUnionStr, use_async) == {"FOO": "bar"} + assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [ + {"fooBaz": "bar"} + ] + + +class TypedDictBase64Input(TypedDict): + foo: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] + + +@parametrize +@pytest.mark.asyncio +async def test_base64_file_input(use_async: bool) -> None: + # strings are left as-is + assert await transform({"foo": "bar"}, TypedDictBase64Input, use_async) == {"foo": "bar"} + + # pathlib.Path is automatically converted to base64 + assert await transform({"foo": SAMPLE_FILE_PATH}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQo=" + } # type: ignore[comparison-overlap] + + # io instances are automatically converted to base64 + assert await transform({"foo": io.StringIO("Hello, world!")}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQ==" + } # type: ignore[comparison-overlap] + assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQ==" + } # type: ignore[comparison-overlap] + + +@parametrize +@pytest.mark.asyncio +async def test_transform_skipping(use_async: bool) -> None: + # lists of ints are left as-is + data = [1, 2, 3] + assert await transform(data, List[int], use_async) is data + + # iterables of ints are converted to a list + data = iter([1, 2, 3]) + assert await transform(data, Iterable[int], use_async) == [1, 2, 3] + + +@parametrize +@pytest.mark.asyncio +async def test_strips_notgiven(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py new file mode 100644 index 00000000..81044b9a --- /dev/null +++ b/tests/test_utils/test_datetime_parse.py @@ -0,0 +1,110 @@ +""" +Copied from https://github.com/pydantic/pydantic/blob/v1.10.22/tests/test_datetime_parse.py +with modifications so it works without pydantic v1 imports. +""" + +from typing import Type, Union +from datetime import date, datetime, timezone, timedelta + +import pytest + +from lithic._utils import parse_date, parse_datetime + + +def create_tz(minutes: int) -> timezone: + return timezone(timedelta(minutes=minutes)) + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + ("1494012444.883309", date(2017, 5, 5)), + (b"1494012444.883309", date(2017, 5, 5)), + (1_494_012_444.883_309, date(2017, 5, 5)), + ("1494012444", date(2017, 5, 5)), + (1_494_012_444, date(2017, 5, 5)), + (0, date(1970, 1, 1)), + ("2012-04-23", date(2012, 4, 23)), + (b"2012-04-23", date(2012, 4, 23)), + ("2012-4-9", date(2012, 4, 9)), + (date(2012, 4, 9), date(2012, 4, 9)), + (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), + # Invalid inputs + ("x20120423", ValueError), + ("2012-04-56", ValueError), + (19_999_999_999, date(2603, 10, 11)), # just before watershed + (20_000_000_001, date(1970, 8, 20)), # just after watershed + (1_549_316_052, date(2019, 2, 4)), # nowish in s + (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms + (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs + (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns + ("infinity", date(9999, 12, 31)), + ("inf", date(9999, 12, 31)), + (float("inf"), date(9999, 12, 31)), + ("infinity ", date(9999, 12, 31)), + (int("1" + "0" * 100), date(9999, 12, 31)), + (1e1000, date(9999, 12, 31)), + ("-infinity", date(1, 1, 1)), + ("-inf", date(1, 1, 1)), + ("nan", ValueError), + ], +) +def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_date(value) + else: + assert parse_date(value) == result + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + # values in seconds + ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + # values in ms + ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), + ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), + (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)), + ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)), + ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), + ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), + ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), + ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), + ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (datetime(2017, 5, 5), datetime(2017, 5, 5)), + (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), + # Invalid inputs + ("x20120423091500", ValueError), + ("2012-04-56T09:15:90", ValueError), + ("2012-04-23T11:05:00-25:00", ValueError), + (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed + (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed + (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s + (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms + (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs + (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns + ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)), + (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)), + (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("-infinity", datetime(1, 1, 1, 0, 0)), + ("-inf", datetime(1, 1, 1, 0, 0)), + ("nan", ValueError), + ], +) +def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_datetime(value) + else: + assert parse_datetime(value) == result diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index de5da111..1b0c9c0f 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -21,3 +21,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class AlwaysErrorProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise RuntimeError("Mocking missing dependency") + + proxy = AlwaysErrorProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy) diff --git a/tests/test_utils/test_typing.py b/tests/test_utils/test_typing.py index a75ffb27..35bbef91 100644 --- a/tests/test_utils/test_typing.py +++ b/tests/test_utils/test_typing.py @@ -9,24 +9,19 @@ _T3 = TypeVar("_T3") -class BaseGeneric(Generic[_T]): - ... +class BaseGeneric(Generic[_T]): ... -class SubclassGeneric(BaseGeneric[_T]): - ... +class SubclassGeneric(BaseGeneric[_T]): ... -class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): - ... +class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): ... -class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): - ... +class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): ... -class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): - ... +class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): ... def test_extract_type_var() -> None: diff --git a/tests/utils.py b/tests/utils.py index 4f4ed1ec..94ec814d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,18 +4,22 @@ import inspect import traceback import contextlib -from typing import Any, TypeVar, Iterator, cast +from typing import Any, TypeVar, Iterator, Sequence, cast from datetime import date, datetime from typing_extensions import Literal, get_args, get_origin, assert_type -from lithic._types import NoneType +from lithic._types import Omit, NoneType from lithic._utils import ( is_dict, is_list, is_list_type, is_union_type, + extract_type_arg, + is_sequence_type, + is_annotated_type, + is_type_alias_type, ) -from lithic._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from lithic._compat import PYDANTIC_V1, field_outer_type, get_model_fields from lithic._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) @@ -24,12 +28,12 @@ def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool: for name, field in get_model_fields(model).items(): field_value = getattr(value, name) - if PYDANTIC_V2: - allow_none = False - else: + if PYDANTIC_V1: # in v1 nullability was structured differently # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields allow_none = getattr(field, "allow_none", False) + else: + allow_none = False assert_matches_type( field_outer_type(field), @@ -49,6 +53,13 @@ def assert_matches_type( path: list[str], allow_none: bool = False, ) -> None: + if is_type_alias_type(type_): + type_ = type_.__value__ + + # unwrap `Annotated[T, ...]` -> `T` + if is_annotated_type(type_): + type_ = extract_type_arg(type_, 0) + if allow_none and value is None: return @@ -61,6 +72,13 @@ def assert_matches_type( if is_list_type(type_): return _assert_list_type(type_, value) + if is_sequence_type(type_): + assert isinstance(value, Sequence) + inner_type = get_args(type_)[0] + for entry in value: # type: ignore + assert_type(inner_type, entry) # type: ignore + return + if origin == str: assert isinstance(value, str) elif origin == int: @@ -91,7 +109,22 @@ def assert_matches_type( assert_matches_type(key_type, key, path=[*path, ""]) assert_matches_type(items_type, item, path=[*path, ""]) elif is_union_type(type_): - for i, variant in enumerate(get_args(type_)): + variants = get_args(type_) + + try: + none_index = variants.index(type(None)) + except ValueError: + pass + else: + # special case Optional[T] for better error messages + if len(variants) == 2: + if value is None: + # valid + return + + return assert_matches_type(type_=variants[not none_index], value=value, path=path) + + for i, variant in enumerate(variants): try: assert_matches_type(variant, value, path=[*path, f"variant {i}"]) return @@ -118,11 +151,15 @@ def _assert_list_type(type_: type[object], value: object) -> None: @contextlib.contextmanager -def update_env(**new_env: str) -> Iterator[None]: +def update_env(**new_env: str | Omit) -> Iterator[None]: old = os.environ.copy() try: - os.environ.update(new_env) + for name, value in new_env.items(): + if isinstance(value, Omit): + os.environ.pop(name, None) + else: + os.environ[name] = value yield None finally: