diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 9183c177..7553ced8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.47.0" + ".": "4.48.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 8f4293ee..df8db457 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 118 +configured_endpoints: 126 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-a5a28a58483355d3cc3da7ac5c452d548ee17183324318198052968121ca7dba.yml openapi_spec_hash: a317931a99e6d4a122919135a0363e40 -config_hash: 05c94c0e6dbeab2c9b554c2e0d6371a0 +config_hash: bcf82bddb691f6be773ac6cae8c03b9a diff --git a/CHANGELOG.md b/CHANGELOG.md index 775a94da..af9f7bf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 4.48.0 (2026-01-08) + +Full Changelog: [v4.47.0...v4.48.0](https://github.com/orbcorp/orb-python/compare/v4.47.0...v4.48.0) + +### Features + +* **api:** manual updates ([19e971e](https://github.com/orbcorp/orb-python/commit/19e971ed6f2da625b7d29101aa3ae8aab99e4401)) + ## 4.47.0 (2026-01-06) Full Changelog: [v4.46.3...v4.47.0](https://github.com/orbcorp/orb-python/compare/v4.46.3...v4.47.0) diff --git a/api.md b/api.md index 5081e822..cff6ca7b 100644 --- a/api.md +++ b/api.md @@ -389,7 +389,7 @@ Methods: Types: ```python -from orb.types import InvoiceFetchUpcomingResponse +from orb.types import InvoiceFetchUpcomingResponse, InvoiceListSummaryResponse ``` Methods: @@ -397,9 +397,11 @@ Methods: - client.invoices.create(\*\*params) -> Invoice - client.invoices.update(invoice_id, \*\*params) -> Invoice - client.invoices.list(\*\*params) -> SyncPage[Invoice] +- client.invoices.delete_line_item(line_item_id, \*, invoice_id) -> None - client.invoices.fetch(invoice_id) -> Invoice - client.invoices.fetch_upcoming(\*\*params) -> InvoiceFetchUpcomingResponse - client.invoices.issue(invoice_id, \*\*params) -> Invoice +- client.invoices.list_summary(\*\*params) -> SyncPage[InvoiceListSummaryResponse] - client.invoices.mark_paid(invoice_id, \*\*params) -> Invoice - client.invoices.pay(invoice_id) -> Invoice - client.invoices.void(invoice_id) -> Invoice @@ -457,6 +459,24 @@ Methods: - client.plans.external_plan_id.update(other_external_plan_id, \*\*params) -> Plan - client.plans.external_plan_id.fetch(external_plan_id) -> Plan +## Migrations + +Types: + +```python +from orb.types.plans import ( + MigrationRetrieveResponse, + MigrationListResponse, + MigrationCancelResponse, +) +``` + +Methods: + +- client.plans.migrations.retrieve(migration_id, \*, plan_id) -> MigrationRetrieveResponse +- client.plans.migrations.list(plan_id, \*\*params) -> SyncPage[MigrationListResponse] +- client.plans.migrations.cancel(migration_id, \*, plan_id) -> MigrationCancelResponse + # Prices Types: @@ -603,6 +623,7 @@ Types: from orb.types import ( MutatedSubscription, SubscriptionChangeRetrieveResponse, + SubscriptionChangeListResponse, SubscriptionChangeApplyResponse, SubscriptionChangeCancelResponse, ) @@ -611,5 +632,19 @@ from orb.types import ( Methods: - client.subscription_changes.retrieve(subscription_change_id) -> SubscriptionChangeRetrieveResponse +- client.subscription_changes.list(\*\*params) -> SyncPage[SubscriptionChangeListResponse] - client.subscription_changes.apply(subscription_change_id, \*\*params) -> SubscriptionChangeApplyResponse - client.subscription_changes.cancel(subscription_change_id) -> SubscriptionChangeCancelResponse + +# CreditBlocks + +Types: + +```python +from orb.types import CreditBlockRetrieveResponse +``` + +Methods: + +- client.credit_blocks.retrieve(block_id) -> CreditBlockRetrieveResponse +- client.credit_blocks.delete(block_id) -> None diff --git a/pyproject.toml b/pyproject.toml index f8f576e3..e783c6e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "orb-billing" -version = "4.47.0" +version = "4.48.0" description = "The official Python library for the orb API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/orb/_client.py b/src/orb/_client.py index 988bde1a..63d77496 100644 --- a/src/orb/_client.py +++ b/src/orb/_client.py @@ -49,6 +49,7 @@ customers, top_level, credit_notes, + credit_blocks, subscriptions, invoice_line_items, subscription_changes, @@ -62,6 +63,7 @@ from .resources.top_level import TopLevel, AsyncTopLevel from .resources.plans.plans import Plans, AsyncPlans from .resources.credit_notes import CreditNotes, AsyncCreditNotes + from .resources.credit_blocks import CreditBlocks, AsyncCreditBlocks from .resources.events.events import Events, AsyncEvents from .resources.prices.prices import Prices, AsyncPrices from .resources.subscriptions import Subscriptions, AsyncSubscriptions @@ -244,6 +246,12 @@ def webhooks(self) -> webhooks.Webhooks: return Webhooks(self) + @cached_property + def credit_blocks(self) -> CreditBlocks: + from .resources.credit_blocks import CreditBlocks + + return CreditBlocks(self) + @cached_property def with_raw_response(self) -> OrbWithRawResponse: return OrbWithRawResponse(self) @@ -574,6 +582,12 @@ def webhooks(self) -> webhooks.AsyncWebhooks: return AsyncWebhooks(self) + @cached_property + def credit_blocks(self) -> AsyncCreditBlocks: + from .resources.credit_blocks import AsyncCreditBlocks + + return AsyncCreditBlocks(self) + @cached_property def with_raw_response(self) -> AsyncOrbWithRawResponse: return AsyncOrbWithRawResponse(self) @@ -839,6 +853,12 @@ def subscription_changes(self) -> subscription_changes.SubscriptionChangesWithRa return SubscriptionChangesWithRawResponse(self._client.subscription_changes) + @cached_property + def credit_blocks(self) -> credit_blocks.CreditBlocksWithRawResponse: + from .resources.credit_blocks import CreditBlocksWithRawResponse + + return CreditBlocksWithRawResponse(self._client.credit_blocks) + class AsyncOrbWithRawResponse: _client: AsyncOrb @@ -942,6 +962,12 @@ def subscription_changes(self) -> subscription_changes.AsyncSubscriptionChangesW return AsyncSubscriptionChangesWithRawResponse(self._client.subscription_changes) + @cached_property + def credit_blocks(self) -> credit_blocks.AsyncCreditBlocksWithRawResponse: + from .resources.credit_blocks import AsyncCreditBlocksWithRawResponse + + return AsyncCreditBlocksWithRawResponse(self._client.credit_blocks) + class OrbWithStreamedResponse: _client: Orb @@ -1045,6 +1071,12 @@ def subscription_changes(self) -> subscription_changes.SubscriptionChangesWithSt return SubscriptionChangesWithStreamingResponse(self._client.subscription_changes) + @cached_property + def credit_blocks(self) -> credit_blocks.CreditBlocksWithStreamingResponse: + from .resources.credit_blocks import CreditBlocksWithStreamingResponse + + return CreditBlocksWithStreamingResponse(self._client.credit_blocks) + class AsyncOrbWithStreamedResponse: _client: AsyncOrb @@ -1148,6 +1180,12 @@ def subscription_changes(self) -> subscription_changes.AsyncSubscriptionChangesW return AsyncSubscriptionChangesWithStreamingResponse(self._client.subscription_changes) + @cached_property + def credit_blocks(self) -> credit_blocks.AsyncCreditBlocksWithStreamingResponse: + from .resources.credit_blocks import AsyncCreditBlocksWithStreamingResponse + + return AsyncCreditBlocksWithStreamingResponse(self._client.credit_blocks) + Client = Orb diff --git a/src/orb/_version.py b/src/orb/_version.py index 114fe4d7..1a499960 100644 --- a/src/orb/_version.py +++ b/src/orb/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "orb" -__version__ = "4.47.0" # x-release-please-version +__version__ = "4.48.0" # x-release-please-version diff --git a/src/orb/resources/__init__.py b/src/orb/resources/__init__.py index 09ab61de..581b66b6 100644 --- a/src/orb/resources/__init__.py +++ b/src/orb/resources/__init__.py @@ -100,6 +100,14 @@ CreditNotesWithStreamingResponse, AsyncCreditNotesWithStreamingResponse, ) +from .credit_blocks import ( + CreditBlocks, + AsyncCreditBlocks, + CreditBlocksWithRawResponse, + AsyncCreditBlocksWithRawResponse, + CreditBlocksWithStreamingResponse, + AsyncCreditBlocksWithStreamingResponse, +) from .subscriptions import ( Subscriptions, AsyncSubscriptions, @@ -232,4 +240,10 @@ "AsyncSubscriptionChangesWithRawResponse", "SubscriptionChangesWithStreamingResponse", "AsyncSubscriptionChangesWithStreamingResponse", + "CreditBlocks", + "AsyncCreditBlocks", + "CreditBlocksWithRawResponse", + "AsyncCreditBlocksWithRawResponse", + "CreditBlocksWithStreamingResponse", + "AsyncCreditBlocksWithStreamingResponse", ] diff --git a/src/orb/resources/credit_blocks.py b/src/orb/resources/credit_blocks.py new file mode 100644 index 00000000..5dbec7f4 --- /dev/null +++ b/src/orb/resources/credit_blocks.py @@ -0,0 +1,281 @@ +# 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 +from ..types.credit_block_retrieve_response import CreditBlockRetrieveResponse + +__all__ = ["CreditBlocks", "AsyncCreditBlocks"] + + +class CreditBlocks(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CreditBlocksWithRawResponse: + """ + 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/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return CreditBlocksWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CreditBlocksWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return CreditBlocksWithStreamingResponse(self) + + def retrieve( + self, + block_id: 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, + ) -> CreditBlockRetrieveResponse: + """ + This endpoint returns a credit block identified by its block_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 block_id: + raise ValueError(f"Expected a non-empty value for `block_id` but received {block_id!r}") + return self._get( + f"/credit_blocks/{block_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CreditBlockRetrieveResponse, + ) + + def delete( + self, + block_id: 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, + idempotency_key: str | None = None, + ) -> None: + """ + This endpoint deletes a credit block by its ID. + + When a credit block is deleted: + + - The block is removed from the customer's credit ledger. + - Any usage of the credit block is reversed, and the ledger is replayed as if + the block never existed. + - If invoices were generated from the purchase of the credit block, they will be + deleted if in draft status, voided if issued, or a credit note will be issued + if the invoice is paid. + + + Issued invoices that had credits applied from this block will not be regenerated, but the ledger will + reflect the state as if credits from the deleted block were never applied. + + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not block_id: + raise ValueError(f"Expected a non-empty value for `block_id` but received {block_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/credit_blocks/{block_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=NoneType, + ) + + +class AsyncCreditBlocks(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCreditBlocksWithRawResponse: + """ + 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/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return AsyncCreditBlocksWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCreditBlocksWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return AsyncCreditBlocksWithStreamingResponse(self) + + async def retrieve( + self, + block_id: 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, + ) -> CreditBlockRetrieveResponse: + """ + This endpoint returns a credit block identified by its block_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 block_id: + raise ValueError(f"Expected a non-empty value for `block_id` but received {block_id!r}") + return await self._get( + f"/credit_blocks/{block_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CreditBlockRetrieveResponse, + ) + + async def delete( + self, + block_id: 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, + idempotency_key: str | None = None, + ) -> None: + """ + This endpoint deletes a credit block by its ID. + + When a credit block is deleted: + + - The block is removed from the customer's credit ledger. + - Any usage of the credit block is reversed, and the ledger is replayed as if + the block never existed. + - If invoices were generated from the purchase of the credit block, they will be + deleted if in draft status, voided if issued, or a credit note will be issued + if the invoice is paid. + + + Issued invoices that had credits applied from this block will not be regenerated, but the ledger will + reflect the state as if credits from the deleted block were never applied. + + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not block_id: + raise ValueError(f"Expected a non-empty value for `block_id` but received {block_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/credit_blocks/{block_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=NoneType, + ) + + +class CreditBlocksWithRawResponse: + def __init__(self, credit_blocks: CreditBlocks) -> None: + self._credit_blocks = credit_blocks + + self.retrieve = _legacy_response.to_raw_response_wrapper( + credit_blocks.retrieve, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + credit_blocks.delete, + ) + + +class AsyncCreditBlocksWithRawResponse: + def __init__(self, credit_blocks: AsyncCreditBlocks) -> None: + self._credit_blocks = credit_blocks + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + credit_blocks.retrieve, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + credit_blocks.delete, + ) + + +class CreditBlocksWithStreamingResponse: + def __init__(self, credit_blocks: CreditBlocks) -> None: + self._credit_blocks = credit_blocks + + self.retrieve = to_streamed_response_wrapper( + credit_blocks.retrieve, + ) + self.delete = to_streamed_response_wrapper( + credit_blocks.delete, + ) + + +class AsyncCreditBlocksWithStreamingResponse: + def __init__(self, credit_blocks: AsyncCreditBlocks) -> None: + self._credit_blocks = credit_blocks + + self.retrieve = async_to_streamed_response_wrapper( + credit_blocks.retrieve, + ) + self.delete = async_to_streamed_response_wrapper( + credit_blocks.delete, + ) diff --git a/src/orb/resources/invoices.py b/src/orb/resources/invoices.py index 6ac4f58a..63fa9ab4 100644 --- a/src/orb/resources/invoices.py +++ b/src/orb/resources/invoices.py @@ -15,9 +15,10 @@ invoice_create_params, invoice_update_params, invoice_mark_paid_params, + invoice_list_summary_params, invoice_fetch_upcoming_params, ) -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +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 @@ -26,6 +27,7 @@ from .._base_client import AsyncPaginator, make_request_options from ..types.shared.invoice import Invoice from ..types.shared_params.discount import Discount +from ..types.invoice_list_summary_response import InvoiceListSummaryResponse from ..types.invoice_fetch_upcoming_response import InvoiceFetchUpcomingResponse __all__ = ["Invoices", "AsyncInvoices"] @@ -322,6 +324,54 @@ def list( model=Invoice, ) + def delete_line_item( + self, + line_item_id: str, + *, + invoice_id: 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, + idempotency_key: str | None = None, + ) -> None: + """ + This endpoint deletes an invoice line item from a draft invoice. + + This endpoint only allows deletion of one-off line items (not subscription-based + line items). The invoice must be in a draft status for this operation to + succeed. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not invoice_id: + raise ValueError(f"Expected a non-empty value for `invoice_id` but received {invoice_id!r}") + if not line_item_id: + raise ValueError(f"Expected a non-empty value for `line_item_id` but received {line_item_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/invoices/{invoice_id}/invoice_line_items/{line_item_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=NoneType, + ) + def fetch( self, invoice_id: str, @@ -449,6 +499,108 @@ def issue( cast_to=Invoice, ) + def list_summary( + self, + *, + amount: Optional[str] | Omit = omit, + amount_gt: Optional[str] | Omit = omit, + amount_lt: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + customer_id: Optional[str] | Omit = omit, + date_type: Optional[Literal["due_date", "invoice_date"]] | Omit = omit, + due_date: Union[str, date, None] | Omit = omit, + due_date_window: Optional[str] | Omit = omit, + due_date_gt: Union[str, date, None] | Omit = omit, + due_date_lt: Union[str, date, None] | Omit = omit, + external_customer_id: Optional[str] | Omit = omit, + invoice_date_gt: Union[str, datetime, None] | Omit = omit, + invoice_date_gte: Union[str, datetime, None] | Omit = omit, + invoice_date_lt: Union[str, datetime, None] | Omit = omit, + invoice_date_lte: Union[str, datetime, None] | Omit = omit, + is_recurring: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + status: Optional[Literal["draft", "issued", "paid", "synced", "void"]] | Omit = omit, + subscription_id: 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, + ) -> SyncPage[InvoiceListSummaryResponse]: + """ + This is a lighter-weight endpoint that returns a list of all + [`Invoice`](/core-concepts#invoice) summaries for an account in a list format. + + These invoice summaries do not include line item details, minimums, maximums, + and discounts, making this endpoint more efficient. + + The list of invoices is ordered starting from the most recently issued invoice + date. The response also includes + [`pagination_metadata`](/api-reference/pagination), which lets the caller + retrieve the next page of results if they exist. + + By default, this only returns invoices that are `issued`, `paid`, or `synced`. + + When fetching any `draft` invoices, this returns the last-computed invoice + values for each draft invoice, which may not always be up-to-date since Orb + regularly refreshes invoices asynchronously. + + Args: + cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned + from the initial request. + + due_date_window: Filters invoices by their due dates within a specific time range in the past. + Specify the range as a number followed by 'd' (days) or 'm' (months). For + example, '7d' filters invoices due in the last 7 days, and '2m' filters those + due in the last 2 months. + + limit: The number of items to fetch. Defaults to 20. + + 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( + "/invoices/summary", + page=SyncPage[InvoiceListSummaryResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "amount": amount, + "amount_gt": amount_gt, + "amount_lt": amount_lt, + "cursor": cursor, + "customer_id": customer_id, + "date_type": date_type, + "due_date": due_date, + "due_date_window": due_date_window, + "due_date_gt": due_date_gt, + "due_date_lt": due_date_lt, + "external_customer_id": external_customer_id, + "invoice_date_gt": invoice_date_gt, + "invoice_date_gte": invoice_date_gte, + "invoice_date_lt": invoice_date_lt, + "invoice_date_lte": invoice_date_lte, + "is_recurring": is_recurring, + "limit": limit, + "status": status, + "subscription_id": subscription_id, + }, + invoice_list_summary_params.InvoiceListSummaryParams, + ), + ), + model=InvoiceListSummaryResponse, + ) + def mark_paid( self, invoice_id: str, @@ -892,6 +1044,54 @@ def list( model=Invoice, ) + async def delete_line_item( + self, + line_item_id: str, + *, + invoice_id: 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, + idempotency_key: str | None = None, + ) -> None: + """ + This endpoint deletes an invoice line item from a draft invoice. + + This endpoint only allows deletion of one-off line items (not subscription-based + line items). The invoice must be in a draft status for this operation to + succeed. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not invoice_id: + raise ValueError(f"Expected a non-empty value for `invoice_id` but received {invoice_id!r}") + if not line_item_id: + raise ValueError(f"Expected a non-empty value for `line_item_id` but received {line_item_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/invoices/{invoice_id}/invoice_line_items/{line_item_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=NoneType, + ) + async def fetch( self, invoice_id: str, @@ -1019,6 +1219,108 @@ async def issue( cast_to=Invoice, ) + def list_summary( + self, + *, + amount: Optional[str] | Omit = omit, + amount_gt: Optional[str] | Omit = omit, + amount_lt: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + customer_id: Optional[str] | Omit = omit, + date_type: Optional[Literal["due_date", "invoice_date"]] | Omit = omit, + due_date: Union[str, date, None] | Omit = omit, + due_date_window: Optional[str] | Omit = omit, + due_date_gt: Union[str, date, None] | Omit = omit, + due_date_lt: Union[str, date, None] | Omit = omit, + external_customer_id: Optional[str] | Omit = omit, + invoice_date_gt: Union[str, datetime, None] | Omit = omit, + invoice_date_gte: Union[str, datetime, None] | Omit = omit, + invoice_date_lt: Union[str, datetime, None] | Omit = omit, + invoice_date_lte: Union[str, datetime, None] | Omit = omit, + is_recurring: Optional[bool] | Omit = omit, + limit: int | Omit = omit, + status: Optional[Literal["draft", "issued", "paid", "synced", "void"]] | Omit = omit, + subscription_id: 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, + ) -> AsyncPaginator[InvoiceListSummaryResponse, AsyncPage[InvoiceListSummaryResponse]]: + """ + This is a lighter-weight endpoint that returns a list of all + [`Invoice`](/core-concepts#invoice) summaries for an account in a list format. + + These invoice summaries do not include line item details, minimums, maximums, + and discounts, making this endpoint more efficient. + + The list of invoices is ordered starting from the most recently issued invoice + date. The response also includes + [`pagination_metadata`](/api-reference/pagination), which lets the caller + retrieve the next page of results if they exist. + + By default, this only returns invoices that are `issued`, `paid`, or `synced`. + + When fetching any `draft` invoices, this returns the last-computed invoice + values for each draft invoice, which may not always be up-to-date since Orb + regularly refreshes invoices asynchronously. + + Args: + cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned + from the initial request. + + due_date_window: Filters invoices by their due dates within a specific time range in the past. + Specify the range as a number followed by 'd' (days) or 'm' (months). For + example, '7d' filters invoices due in the last 7 days, and '2m' filters those + due in the last 2 months. + + limit: The number of items to fetch. Defaults to 20. + + 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( + "/invoices/summary", + page=AsyncPage[InvoiceListSummaryResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "amount": amount, + "amount_gt": amount_gt, + "amount_lt": amount_lt, + "cursor": cursor, + "customer_id": customer_id, + "date_type": date_type, + "due_date": due_date, + "due_date_window": due_date_window, + "due_date_gt": due_date_gt, + "due_date_lt": due_date_lt, + "external_customer_id": external_customer_id, + "invoice_date_gt": invoice_date_gt, + "invoice_date_gte": invoice_date_gte, + "invoice_date_lt": invoice_date_lt, + "invoice_date_lte": invoice_date_lte, + "is_recurring": is_recurring, + "limit": limit, + "status": status, + "subscription_id": subscription_id, + }, + invoice_list_summary_params.InvoiceListSummaryParams, + ), + ), + model=InvoiceListSummaryResponse, + ) + async def mark_paid( self, invoice_id: str, @@ -1184,6 +1486,9 @@ def __init__(self, invoices: Invoices) -> None: self.list = _legacy_response.to_raw_response_wrapper( invoices.list, ) + self.delete_line_item = _legacy_response.to_raw_response_wrapper( + invoices.delete_line_item, + ) self.fetch = _legacy_response.to_raw_response_wrapper( invoices.fetch, ) @@ -1193,6 +1498,9 @@ def __init__(self, invoices: Invoices) -> None: self.issue = _legacy_response.to_raw_response_wrapper( invoices.issue, ) + self.list_summary = _legacy_response.to_raw_response_wrapper( + invoices.list_summary, + ) self.mark_paid = _legacy_response.to_raw_response_wrapper( invoices.mark_paid, ) @@ -1217,6 +1525,9 @@ def __init__(self, invoices: AsyncInvoices) -> None: self.list = _legacy_response.async_to_raw_response_wrapper( invoices.list, ) + self.delete_line_item = _legacy_response.async_to_raw_response_wrapper( + invoices.delete_line_item, + ) self.fetch = _legacy_response.async_to_raw_response_wrapper( invoices.fetch, ) @@ -1226,6 +1537,9 @@ def __init__(self, invoices: AsyncInvoices) -> None: self.issue = _legacy_response.async_to_raw_response_wrapper( invoices.issue, ) + self.list_summary = _legacy_response.async_to_raw_response_wrapper( + invoices.list_summary, + ) self.mark_paid = _legacy_response.async_to_raw_response_wrapper( invoices.mark_paid, ) @@ -1250,6 +1564,9 @@ def __init__(self, invoices: Invoices) -> None: self.list = to_streamed_response_wrapper( invoices.list, ) + self.delete_line_item = to_streamed_response_wrapper( + invoices.delete_line_item, + ) self.fetch = to_streamed_response_wrapper( invoices.fetch, ) @@ -1259,6 +1576,9 @@ def __init__(self, invoices: Invoices) -> None: self.issue = to_streamed_response_wrapper( invoices.issue, ) + self.list_summary = to_streamed_response_wrapper( + invoices.list_summary, + ) self.mark_paid = to_streamed_response_wrapper( invoices.mark_paid, ) @@ -1283,6 +1603,9 @@ def __init__(self, invoices: AsyncInvoices) -> None: self.list = async_to_streamed_response_wrapper( invoices.list, ) + self.delete_line_item = async_to_streamed_response_wrapper( + invoices.delete_line_item, + ) self.fetch = async_to_streamed_response_wrapper( invoices.fetch, ) @@ -1292,6 +1615,9 @@ def __init__(self, invoices: AsyncInvoices) -> None: self.issue = async_to_streamed_response_wrapper( invoices.issue, ) + self.list_summary = async_to_streamed_response_wrapper( + invoices.list_summary, + ) self.mark_paid = async_to_streamed_response_wrapper( invoices.mark_paid, ) diff --git a/src/orb/resources/plans/__init__.py b/src/orb/resources/plans/__init__.py index fa461833..5af74622 100644 --- a/src/orb/resources/plans/__init__.py +++ b/src/orb/resources/plans/__init__.py @@ -8,6 +8,14 @@ PlansWithStreamingResponse, AsyncPlansWithStreamingResponse, ) +from .migrations import ( + Migrations, + AsyncMigrations, + MigrationsWithRawResponse, + AsyncMigrationsWithRawResponse, + MigrationsWithStreamingResponse, + AsyncMigrationsWithStreamingResponse, +) from .external_plan_id import ( ExternalPlanID, AsyncExternalPlanID, @@ -24,6 +32,12 @@ "AsyncExternalPlanIDWithRawResponse", "ExternalPlanIDWithStreamingResponse", "AsyncExternalPlanIDWithStreamingResponse", + "Migrations", + "AsyncMigrations", + "MigrationsWithRawResponse", + "AsyncMigrationsWithRawResponse", + "MigrationsWithStreamingResponse", + "AsyncMigrationsWithStreamingResponse", "Plans", "AsyncPlans", "PlansWithRawResponse", diff --git a/src/orb/resources/plans/migrations.py b/src/orb/resources/plans/migrations.py new file mode 100644 index 00000000..593a7e10 --- /dev/null +++ b/src/orb/resources/plans/migrations.py @@ -0,0 +1,392 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +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 SyncPage, AsyncPage +from ...types.plans import migration_list_params +from ..._base_client import AsyncPaginator, make_request_options +from ...types.plans.migration_list_response import MigrationListResponse +from ...types.plans.migration_cancel_response import MigrationCancelResponse +from ...types.plans.migration_retrieve_response import MigrationRetrieveResponse + +__all__ = ["Migrations", "AsyncMigrations"] + + +class Migrations(SyncAPIResource): + @cached_property + def with_raw_response(self) -> MigrationsWithRawResponse: + """ + 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/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return MigrationsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> MigrationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return MigrationsWithStreamingResponse(self) + + def retrieve( + self, + migration_id: str, + *, + plan_id: 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, + ) -> MigrationRetrieveResponse: + """ + Fetch migration + + 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 plan_id: + raise ValueError(f"Expected a non-empty value for `plan_id` but received {plan_id!r}") + if not migration_id: + raise ValueError(f"Expected a non-empty value for `migration_id` but received {migration_id!r}") + return self._get( + f"/plans/{plan_id}/migrations/{migration_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MigrationRetrieveResponse, + ) + + def list( + self, + plan_id: str, + *, + cursor: Optional[str] | Omit = omit, + limit: 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, + ) -> SyncPage[MigrationListResponse]: + """This endpoint returns a list of all migrations for a plan. + + The list of + migrations is ordered starting from the most recently created migration. The + response also includes pagination_metadata, which lets the caller retrieve the + next page of results if they exist. + + Args: + cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned + from the initial request. + + limit: The number of items to fetch. Defaults to 20. + + 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 plan_id: + raise ValueError(f"Expected a non-empty value for `plan_id` but received {plan_id!r}") + return self._get_api_list( + f"/plans/{plan_id}/migrations", + page=SyncPage[MigrationListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "limit": limit, + }, + migration_list_params.MigrationListParams, + ), + ), + model=MigrationListResponse, + ) + + def cancel( + self, + migration_id: str, + *, + plan_id: 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, + idempotency_key: str | None = None, + ) -> MigrationCancelResponse: + """ + This endpoint cancels a migration. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not plan_id: + raise ValueError(f"Expected a non-empty value for `plan_id` but received {plan_id!r}") + if not migration_id: + raise ValueError(f"Expected a non-empty value for `migration_id` but received {migration_id!r}") + return self._post( + f"/plans/{plan_id}/migrations/{migration_id}/cancel", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=MigrationCancelResponse, + ) + + +class AsyncMigrations(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncMigrationsWithRawResponse: + """ + 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/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return AsyncMigrationsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMigrationsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return AsyncMigrationsWithStreamingResponse(self) + + async def retrieve( + self, + migration_id: str, + *, + plan_id: 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, + ) -> MigrationRetrieveResponse: + """ + Fetch migration + + 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 plan_id: + raise ValueError(f"Expected a non-empty value for `plan_id` but received {plan_id!r}") + if not migration_id: + raise ValueError(f"Expected a non-empty value for `migration_id` but received {migration_id!r}") + return await self._get( + f"/plans/{plan_id}/migrations/{migration_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MigrationRetrieveResponse, + ) + + def list( + self, + plan_id: str, + *, + cursor: Optional[str] | Omit = omit, + limit: 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[MigrationListResponse, AsyncPage[MigrationListResponse]]: + """This endpoint returns a list of all migrations for a plan. + + The list of + migrations is ordered starting from the most recently created migration. The + response also includes pagination_metadata, which lets the caller retrieve the + next page of results if they exist. + + Args: + cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned + from the initial request. + + limit: The number of items to fetch. Defaults to 20. + + 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 plan_id: + raise ValueError(f"Expected a non-empty value for `plan_id` but received {plan_id!r}") + return self._get_api_list( + f"/plans/{plan_id}/migrations", + page=AsyncPage[MigrationListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "limit": limit, + }, + migration_list_params.MigrationListParams, + ), + ), + model=MigrationListResponse, + ) + + async def cancel( + self, + migration_id: str, + *, + plan_id: 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, + idempotency_key: str | None = None, + ) -> MigrationCancelResponse: + """ + This endpoint cancels a migration. + + 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 + + idempotency_key: Specify a custom idempotency key for this request + """ + if not plan_id: + raise ValueError(f"Expected a non-empty value for `plan_id` but received {plan_id!r}") + if not migration_id: + raise ValueError(f"Expected a non-empty value for `migration_id` but received {migration_id!r}") + return await self._post( + f"/plans/{plan_id}/migrations/{migration_id}/cancel", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=MigrationCancelResponse, + ) + + +class MigrationsWithRawResponse: + def __init__(self, migrations: Migrations) -> None: + self._migrations = migrations + + self.retrieve = _legacy_response.to_raw_response_wrapper( + migrations.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + migrations.list, + ) + self.cancel = _legacy_response.to_raw_response_wrapper( + migrations.cancel, + ) + + +class AsyncMigrationsWithRawResponse: + def __init__(self, migrations: AsyncMigrations) -> None: + self._migrations = migrations + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + migrations.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + migrations.list, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + migrations.cancel, + ) + + +class MigrationsWithStreamingResponse: + def __init__(self, migrations: Migrations) -> None: + self._migrations = migrations + + self.retrieve = to_streamed_response_wrapper( + migrations.retrieve, + ) + self.list = to_streamed_response_wrapper( + migrations.list, + ) + self.cancel = to_streamed_response_wrapper( + migrations.cancel, + ) + + +class AsyncMigrationsWithStreamingResponse: + def __init__(self, migrations: AsyncMigrations) -> None: + self._migrations = migrations + + self.retrieve = async_to_streamed_response_wrapper( + migrations.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + migrations.list, + ) + self.cancel = async_to_streamed_response_wrapper( + migrations.cancel, + ) diff --git a/src/orb/resources/plans/plans.py b/src/orb/resources/plans/plans.py index 257f1774..64d0bad8 100644 --- a/src/orb/resources/plans/plans.py +++ b/src/orb/resources/plans/plans.py @@ -13,6 +13,14 @@ 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 .migrations import ( + Migrations, + AsyncMigrations, + MigrationsWithRawResponse, + AsyncMigrationsWithRawResponse, + MigrationsWithStreamingResponse, + AsyncMigrationsWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncPage, AsyncPage @@ -35,6 +43,10 @@ class Plans(SyncAPIResource): def external_plan_id(self) -> ExternalPlanID: return ExternalPlanID(self._client) + @cached_property + def migrations(self) -> Migrations: + return Migrations(self._client) + @cached_property def with_raw_response(self) -> PlansWithRawResponse: """ @@ -320,6 +332,10 @@ class AsyncPlans(AsyncAPIResource): def external_plan_id(self) -> AsyncExternalPlanID: return AsyncExternalPlanID(self._client) + @cached_property + def migrations(self) -> AsyncMigrations: + return AsyncMigrations(self._client) + @cached_property def with_raw_response(self) -> AsyncPlansWithRawResponse: """ @@ -621,6 +637,10 @@ def __init__(self, plans: Plans) -> None: def external_plan_id(self) -> ExternalPlanIDWithRawResponse: return ExternalPlanIDWithRawResponse(self._plans.external_plan_id) + @cached_property + def migrations(self) -> MigrationsWithRawResponse: + return MigrationsWithRawResponse(self._plans.migrations) + class AsyncPlansWithRawResponse: def __init__(self, plans: AsyncPlans) -> None: @@ -643,6 +663,10 @@ def __init__(self, plans: AsyncPlans) -> None: def external_plan_id(self) -> AsyncExternalPlanIDWithRawResponse: return AsyncExternalPlanIDWithRawResponse(self._plans.external_plan_id) + @cached_property + def migrations(self) -> AsyncMigrationsWithRawResponse: + return AsyncMigrationsWithRawResponse(self._plans.migrations) + class PlansWithStreamingResponse: def __init__(self, plans: Plans) -> None: @@ -665,6 +689,10 @@ def __init__(self, plans: Plans) -> None: def external_plan_id(self) -> ExternalPlanIDWithStreamingResponse: return ExternalPlanIDWithStreamingResponse(self._plans.external_plan_id) + @cached_property + def migrations(self) -> MigrationsWithStreamingResponse: + return MigrationsWithStreamingResponse(self._plans.migrations) + class AsyncPlansWithStreamingResponse: def __init__(self, plans: AsyncPlans) -> None: @@ -686,3 +714,7 @@ def __init__(self, plans: AsyncPlans) -> None: @cached_property def external_plan_id(self) -> AsyncExternalPlanIDWithStreamingResponse: return AsyncExternalPlanIDWithStreamingResponse(self._plans.external_plan_id) + + @cached_property + def migrations(self) -> AsyncMigrationsWithStreamingResponse: + return AsyncMigrationsWithStreamingResponse(self._plans.migrations) diff --git a/src/orb/resources/subscription_changes.py b/src/orb/resources/subscription_changes.py index 0709fa58..5adbb9e3 100644 --- a/src/orb/resources/subscription_changes.py +++ b/src/orb/resources/subscription_changes.py @@ -4,17 +4,20 @@ from typing import Union, Optional from datetime import date +from typing_extensions import Literal import httpx from .. import _legacy_response -from ..types import subscription_change_apply_params +from ..types import subscription_change_list_params, subscription_change_apply_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 .._base_client import make_request_options +from ..pagination import SyncPage, AsyncPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.subscription_change_list_response import SubscriptionChangeListResponse from ..types.subscription_change_apply_response import SubscriptionChangeApplyResponse from ..types.subscription_change_cancel_response import SubscriptionChangeCancelResponse from ..types.subscription_change_retrieve_response import SubscriptionChangeRetrieveResponse @@ -85,6 +88,63 @@ def retrieve( cast_to=SubscriptionChangeRetrieveResponse, ) + def list( + self, + *, + cursor: Optional[str] | Omit = omit, + customer_id: Optional[str] | Omit = omit, + external_customer_id: Optional[str] | Omit = omit, + limit: int | Omit = omit, + status: Optional[Literal["pending", "applied", "cancelled"]] | 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, + ) -> SyncPage[SubscriptionChangeListResponse]: + """This endpoint returns a list of pending subscription changes for a customer. + + Use + the [Fetch Subscription Change](fetch-subscription-change) endpoint to retrieve + the expected subscription state after the pending change is applied. + + Args: + cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned + from the initial request. + + limit: The number of items to fetch. Defaults to 20. + + 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( + "/subscription_changes", + page=SyncPage[SubscriptionChangeListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "customer_id": customer_id, + "external_customer_id": external_customer_id, + "limit": limit, + "status": status, + }, + subscription_change_list_params.SubscriptionChangeListParams, + ), + ), + model=SubscriptionChangeListResponse, + ) + def apply( self, subscription_change_id: str, @@ -272,6 +332,63 @@ async def retrieve( cast_to=SubscriptionChangeRetrieveResponse, ) + def list( + self, + *, + cursor: Optional[str] | Omit = omit, + customer_id: Optional[str] | Omit = omit, + external_customer_id: Optional[str] | Omit = omit, + limit: int | Omit = omit, + status: Optional[Literal["pending", "applied", "cancelled"]] | 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[SubscriptionChangeListResponse, AsyncPage[SubscriptionChangeListResponse]]: + """This endpoint returns a list of pending subscription changes for a customer. + + Use + the [Fetch Subscription Change](fetch-subscription-change) endpoint to retrieve + the expected subscription state after the pending change is applied. + + Args: + cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned + from the initial request. + + limit: The number of items to fetch. Defaults to 20. + + 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( + "/subscription_changes", + page=AsyncPage[SubscriptionChangeListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "customer_id": customer_id, + "external_customer_id": external_customer_id, + "limit": limit, + "status": status, + }, + subscription_change_list_params.SubscriptionChangeListParams, + ), + ), + model=SubscriptionChangeListResponse, + ) + async def apply( self, subscription_change_id: str, @@ -403,6 +520,9 @@ def __init__(self, subscription_changes: SubscriptionChanges) -> None: self.retrieve = _legacy_response.to_raw_response_wrapper( subscription_changes.retrieve, ) + self.list = _legacy_response.to_raw_response_wrapper( + subscription_changes.list, + ) self.apply = _legacy_response.to_raw_response_wrapper( subscription_changes.apply, ) @@ -418,6 +538,9 @@ def __init__(self, subscription_changes: AsyncSubscriptionChanges) -> None: self.retrieve = _legacy_response.async_to_raw_response_wrapper( subscription_changes.retrieve, ) + self.list = _legacy_response.async_to_raw_response_wrapper( + subscription_changes.list, + ) self.apply = _legacy_response.async_to_raw_response_wrapper( subscription_changes.apply, ) @@ -433,6 +556,9 @@ def __init__(self, subscription_changes: SubscriptionChanges) -> None: self.retrieve = to_streamed_response_wrapper( subscription_changes.retrieve, ) + self.list = to_streamed_response_wrapper( + subscription_changes.list, + ) self.apply = to_streamed_response_wrapper( subscription_changes.apply, ) @@ -448,6 +574,9 @@ def __init__(self, subscription_changes: AsyncSubscriptionChanges) -> None: self.retrieve = async_to_streamed_response_wrapper( subscription_changes.retrieve, ) + self.list = async_to_streamed_response_wrapper( + subscription_changes.list, + ) self.apply = async_to_streamed_response_wrapper( subscription_changes.apply, ) diff --git a/src/orb/types/__init__.py b/src/orb/types/__init__.py index 59ff1e6c..57a93dd5 100644 --- a/src/orb/types/__init__.py +++ b/src/orb/types/__init__.py @@ -195,7 +195,10 @@ from .subscription_cancel_params import SubscriptionCancelParams as SubscriptionCancelParams from .subscription_create_params import SubscriptionCreateParams as SubscriptionCreateParams from .subscription_update_params import SubscriptionUpdateParams as SubscriptionUpdateParams +from .invoice_list_summary_params import InvoiceListSummaryParams as InvoiceListSummaryParams from .invoice_fetch_upcoming_params import InvoiceFetchUpcomingParams as InvoiceFetchUpcomingParams +from .invoice_list_summary_response import InvoiceListSummaryResponse as InvoiceListSummaryResponse +from .credit_block_retrieve_response import CreditBlockRetrieveResponse as CreditBlockRetrieveResponse from .new_sphere_configuration_param import NewSphereConfigurationParam as NewSphereConfigurationParam from .price_evaluate_multiple_params import PriceEvaluateMultipleParams as PriceEvaluateMultipleParams from .beta_create_plan_version_params import BetaCreatePlanVersionParams as BetaCreatePlanVersionParams @@ -203,6 +206,7 @@ from .invoice_fetch_upcoming_response import InvoiceFetchUpcomingResponse as InvoiceFetchUpcomingResponse from .invoice_line_item_create_params import InvoiceLineItemCreateParams as InvoiceLineItemCreateParams from .new_tax_jar_configuration_param import NewTaxJarConfigurationParam as NewTaxJarConfigurationParam +from .subscription_change_list_params import SubscriptionChangeListParams as SubscriptionChangeListParams from .subscription_fetch_costs_params import SubscriptionFetchCostsParams as SubscriptionFetchCostsParams from .subscription_fetch_usage_params import SubscriptionFetchUsageParams as SubscriptionFetchUsageParams from .accounting_provider_config_param import AccountingProviderConfigParam as AccountingProviderConfigParam @@ -214,6 +218,7 @@ from .new_reporting_configuration_param import NewReportingConfigurationParam as NewReportingConfigurationParam from .new_subscription_bulk_price_param import NewSubscriptionBulkPriceParam as NewSubscriptionBulkPriceParam from .new_subscription_unit_price_param import NewSubscriptionUnitPriceParam as NewSubscriptionUnitPriceParam +from .subscription_change_list_response import SubscriptionChangeListResponse as SubscriptionChangeListResponse from .subscription_fetch_costs_response import SubscriptionFetchCostsResponse as SubscriptionFetchCostsResponse from .subscription_redeem_coupon_params import SubscriptionRedeemCouponParams as SubscriptionRedeemCouponParams from .subscription_trigger_phase_params import SubscriptionTriggerPhaseParams as SubscriptionTriggerPhaseParams diff --git a/src/orb/types/credit_block_retrieve_response.py b/src/orb/types/credit_block_retrieve_response.py new file mode 100644 index 00000000..b931d83b --- /dev/null +++ b/src/orb/types/credit_block_retrieve_response.py @@ -0,0 +1,40 @@ +# 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__ = ["CreditBlockRetrieveResponse", "Filter"] + + +class Filter(BaseModel): + field: Literal["price_id", "item_id", "price_type", "currency", "pricing_unit_id"] + """The property of the price to filter on.""" + + operator: Literal["includes", "excludes"] + """Should prices that match the filter be included or excluded.""" + + values: List[str] + """The IDs or values that match this filter.""" + + +class CreditBlockRetrieveResponse(BaseModel): + """The Credit Block resource models prepaid credits within Orb.""" + + id: str + + balance: float + + effective_date: Optional[datetime] = None + + expiry_date: Optional[datetime] = None + + filters: List[Filter] + + maximum_initial_balance: Optional[float] = None + + per_unit_cost_basis: Optional[str] = None + + status: Literal["active", "pending_payment"] diff --git a/src/orb/types/invoice_list_summary_params.py b/src/orb/types/invoice_list_summary_params.py new file mode 100644 index 00000000..41b86e65 --- /dev/null +++ b/src/orb/types/invoice_list_summary_params.py @@ -0,0 +1,63 @@ +# 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, datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["InvoiceListSummaryParams"] + + +class InvoiceListSummaryParams(TypedDict, total=False): + amount: Optional[str] + + amount_gt: Annotated[Optional[str], PropertyInfo(alias="amount[gt]")] + + amount_lt: Annotated[Optional[str], PropertyInfo(alias="amount[lt]")] + + cursor: Optional[str] + """Cursor for pagination. + + This can be populated by the `next_cursor` value returned from the initial + request. + """ + + customer_id: Optional[str] + + date_type: Optional[Literal["due_date", "invoice_date"]] + + due_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")] + + due_date_window: Optional[str] + """Filters invoices by their due dates within a specific time range in the past. + + Specify the range as a number followed by 'd' (days) or 'm' (months). For + example, '7d' filters invoices due in the last 7 days, and '2m' filters those + due in the last 2 months. + """ + + due_date_gt: Annotated[Union[str, date, None], PropertyInfo(alias="due_date[gt]", format="iso8601")] + + due_date_lt: Annotated[Union[str, date, None], PropertyInfo(alias="due_date[lt]", format="iso8601")] + + external_customer_id: Optional[str] + + invoice_date_gt: Annotated[Union[str, datetime, None], PropertyInfo(alias="invoice_date[gt]", format="iso8601")] + + invoice_date_gte: Annotated[Union[str, datetime, None], PropertyInfo(alias="invoice_date[gte]", format="iso8601")] + + invoice_date_lt: Annotated[Union[str, datetime, None], PropertyInfo(alias="invoice_date[lt]", format="iso8601")] + + invoice_date_lte: Annotated[Union[str, datetime, None], PropertyInfo(alias="invoice_date[lte]", format="iso8601")] + + is_recurring: Optional[bool] + + limit: int + """The number of items to fetch. Defaults to 20.""" + + status: Optional[Literal["draft", "issued", "paid", "synced", "void"]] + + subscription_id: Optional[str] diff --git a/src/orb/types/invoice_list_summary_response.py b/src/orb/types/invoice_list_summary_response.py new file mode 100644 index 00000000..553979b6 --- /dev/null +++ b/src/orb/types/invoice_list_summary_response.py @@ -0,0 +1,425 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.address import Address +from .shared.invoice_tiny import InvoiceTiny +from .shared.customer_tax_id import CustomerTaxID +from .shared.credit_note_tiny import CreditNoteTiny +from .shared.customer_minified import CustomerMinified +from .shared.subscription_minified import SubscriptionMinified + +__all__ = ["InvoiceListSummaryResponse", "AutoCollection", "CreditNote", "CustomerBalanceTransaction", "PaymentAttempt"] + + +class AutoCollection(BaseModel): + enabled: Optional[bool] = None + """True only if auto-collection is enabled for this invoice.""" + + next_attempt_at: Optional[datetime] = None + """ + If the invoice is scheduled for auto-collection, this field will reflect when + the next attempt will occur. If dunning has been exhausted, or auto-collection + is not enabled for this invoice, this field will be `null`. + """ + + num_attempts: Optional[int] = None + """Number of auto-collection payment attempts.""" + + previously_attempted_at: Optional[datetime] = None + """ + If Orb has ever attempted payment auto-collection for this invoice, this field + will reflect when that attempt occurred. In conjunction with `next_attempt_at`, + this can be used to tell whether the invoice is currently in dunning (that is, + `previously_attempted_at` is non-null, and `next_attempt_time` is non-null), or + if dunning has been exhausted (`previously_attempted_at` is non-null, but + `next_attempt_time` is null). + """ + + +class CreditNote(BaseModel): + id: str + + credit_note_number: str + + memo: Optional[str] = None + """An optional memo supplied on the credit note.""" + + reason: str + + total: str + + type: str + + voided_at: Optional[datetime] = None + """ + If the credit note has a status of `void`, this gives a timestamp when the + credit note was voided. + """ + + +class CustomerBalanceTransaction(BaseModel): + id: str + """A unique id for this transaction.""" + + action: Literal[ + "applied_to_invoice", + "manual_adjustment", + "prorated_refund", + "revert_prorated_refund", + "return_from_voiding", + "credit_note_applied", + "credit_note_voided", + "overpayment_refund", + "external_payment", + "small_invoice_carryover", + ] + + amount: str + """The value of the amount changed in the transaction.""" + + created_at: datetime + """The creation time of this transaction.""" + + credit_note: Optional[CreditNoteTiny] = None + + description: Optional[str] = None + """An optional description provided for manual customer balance adjustments.""" + + ending_balance: str + """ + The new value of the customer's balance prior to the transaction, in the + customer's currency. + """ + + invoice: Optional[InvoiceTiny] = None + + starting_balance: str + """ + The original value of the customer's balance prior to the transaction, in the + customer's currency. + """ + + type: Literal["increment", "decrement"] + + +class PaymentAttempt(BaseModel): + id: str + """The ID of the payment attempt.""" + + amount: str + """The amount of the payment attempt.""" + + created_at: datetime + """The time at which the payment attempt was created.""" + + payment_provider: Optional[Literal["stripe"]] = None + """The payment provider that attempted to collect the payment.""" + + payment_provider_id: Optional[str] = None + """The ID of the payment attempt in the payment provider.""" + + receipt_pdf: Optional[str] = None + """URL to the downloadable PDF version of the receipt. + + This field will be `null` for payment attempts that did not succeed. + """ + + succeeded: bool + """Whether the payment attempt succeeded.""" + + +class InvoiceListSummaryResponse(BaseModel): + """#InvoiceApiResourceWithoutLineItems""" + + id: str + + amount_due: str + """ + This is the final amount required to be charged to the customer and reflects the + application of the customer balance to the `total` of the invoice. + """ + + auto_collection: AutoCollection + + billing_address: Optional[Address] = None + + created_at: datetime + """The creation time of the resource in Orb.""" + + credit_notes: List[CreditNote] + """A list of credit notes associated with the invoice""" + + currency: str + """An ISO 4217 currency string or `credits`""" + + customer: CustomerMinified + + customer_balance_transactions: List[CustomerBalanceTransaction] + + customer_tax_id: Optional[CustomerTaxID] = None + """ + Tax IDs are commonly required to be displayed on customer invoices, which are + added to the headers of invoices. + + ### Supported Tax ID Countries and Types + + | Country | Type | Description | + | ---------------------- | ------------ | ------------------------------------------------------------------------------------------------------- | + | Albania | `al_tin` | Albania Tax Identification Number | + | Andorra | `ad_nrt` | Andorran NRT Number | + | Angola | `ao_tin` | Angola Tax Identification Number | + | Argentina | `ar_cuit` | Argentinian Tax ID Number | + | Armenia | `am_tin` | Armenia Tax Identification Number | + | Aruba | `aw_tin` | Aruba Tax Identification Number | + | Australia | `au_abn` | Australian Business Number (AU ABN) | + | Australia | `au_arn` | Australian Taxation Office Reference Number | + | Austria | `eu_vat` | European VAT Number | + | Azerbaijan | `az_tin` | Azerbaijan Tax Identification Number | + | Bahamas | `bs_tin` | Bahamas Tax Identification Number | + | Bahrain | `bh_vat` | Bahraini VAT Number | + | Bangladesh | `bd_bin` | Bangladesh Business Identification Number | + | Barbados | `bb_tin` | Barbados Tax Identification Number | + | Belarus | `by_tin` | Belarus TIN Number | + | Belgium | `eu_vat` | European VAT Number | + | Benin | `bj_ifu` | Benin Tax Identification Number (Identifiant Fiscal Unique) | + | Bolivia | `bo_tin` | Bolivian Tax ID | + | Bosnia and Herzegovina | `ba_tin` | Bosnia and Herzegovina Tax Identification Number | + | Brazil | `br_cnpj` | Brazilian CNPJ Number | + | Brazil | `br_cpf` | Brazilian CPF Number | + | Bulgaria | `bg_uic` | Bulgaria Unified Identification Code | + | Bulgaria | `eu_vat` | European VAT Number | + | Burkina Faso | `bf_ifu` | Burkina Faso Tax Identification Number (Numéro d'Identifiant Fiscal Unique) | + | Cambodia | `kh_tin` | Cambodia Tax Identification Number | + | Cameroon | `cm_niu` | Cameroon Tax Identification Number (Numéro d'Identifiant fiscal Unique) | + | Canada | `ca_bn` | Canadian BN | + | Canada | `ca_gst_hst` | Canadian GST/HST Number | + | Canada | `ca_pst_bc` | Canadian PST Number (British Columbia) | + | Canada | `ca_pst_mb` | Canadian PST Number (Manitoba) | + | Canada | `ca_pst_sk` | Canadian PST Number (Saskatchewan) | + | Canada | `ca_qst` | Canadian QST Number (Québec) | + | Cape Verde | `cv_nif` | Cape Verde Tax Identification Number (Número de Identificação Fiscal) | + | Chile | `cl_tin` | Chilean TIN | + | China | `cn_tin` | Chinese Tax ID | + | Colombia | `co_nit` | Colombian NIT Number | + | Congo-Kinshasa | `cd_nif` | Congo (DR) Tax Identification Number (Número de Identificação Fiscal) | + | Costa Rica | `cr_tin` | Costa Rican Tax ID | + | Croatia | `eu_vat` | European VAT Number | + | Croatia | `hr_oib` | Croatian Personal Identification Number (OIB) | + | Cyprus | `eu_vat` | European VAT Number | + | Czech Republic | `eu_vat` | European VAT Number | + | Denmark | `eu_vat` | European VAT Number | + | Dominican Republic | `do_rcn` | Dominican RCN Number | + | Ecuador | `ec_ruc` | Ecuadorian RUC Number | + | Egypt | `eg_tin` | Egyptian Tax Identification Number | + | El Salvador | `sv_nit` | El Salvadorian NIT Number | + | Estonia | `eu_vat` | European VAT Number | + | Ethiopia | `et_tin` | Ethiopia Tax Identification Number | + | European Union | `eu_oss_vat` | European One Stop Shop VAT Number for non-Union scheme | + | Finland | `eu_vat` | European VAT Number | + | France | `eu_vat` | European VAT Number | + | Georgia | `ge_vat` | Georgian VAT | + | Germany | `de_stn` | German Tax Number (Steuernummer) | + | Germany | `eu_vat` | European VAT Number | + | Greece | `eu_vat` | European VAT Number | + | Guinea | `gn_nif` | Guinea Tax Identification Number (Número de Identificação Fiscal) | + | Hong Kong | `hk_br` | Hong Kong BR Number | + | Hungary | `eu_vat` | European VAT Number | + | Hungary | `hu_tin` | Hungary Tax Number (adószám) | + | Iceland | `is_vat` | Icelandic VAT | + | India | `in_gst` | Indian GST Number | + | Indonesia | `id_npwp` | Indonesian NPWP Number | + | Ireland | `eu_vat` | European VAT Number | + | Israel | `il_vat` | Israel VAT | + | Italy | `eu_vat` | European VAT Number | + | Japan | `jp_cn` | Japanese Corporate Number (_Hōjin Bangō_) | + | Japan | `jp_rn` | Japanese Registered Foreign Businesses' Registration Number (_Tōroku Kokugai Jigyōsha no Tōroku Bangō_) | + | Japan | `jp_trn` | Japanese Tax Registration Number (_Tōroku Bangō_) | + | Kazakhstan | `kz_bin` | Kazakhstani Business Identification Number | + | Kenya | `ke_pin` | Kenya Revenue Authority Personal Identification Number | + | Kyrgyzstan | `kg_tin` | Kyrgyzstan Tax Identification Number | + | Laos | `la_tin` | Laos Tax Identification Number | + | Latvia | `eu_vat` | European VAT Number | + | Liechtenstein | `li_uid` | Liechtensteinian UID Number | + | Liechtenstein | `li_vat` | Liechtenstein VAT Number | + | Lithuania | `eu_vat` | European VAT Number | + | Luxembourg | `eu_vat` | European VAT Number | + | Malaysia | `my_frp` | Malaysian FRP Number | + | Malaysia | `my_itn` | Malaysian ITN | + | Malaysia | `my_sst` | Malaysian SST Number | + | Malta | `eu_vat` | European VAT Number | + | Mauritania | `mr_nif` | Mauritania Tax Identification Number (Número de Identificação Fiscal) | + | Mexico | `mx_rfc` | Mexican RFC Number | + | Moldova | `md_vat` | Moldova VAT Number | + | Montenegro | `me_pib` | Montenegro PIB Number | + | Morocco | `ma_vat` | Morocco VAT Number | + | Nepal | `np_pan` | Nepal PAN Number | + | Netherlands | `eu_vat` | European VAT Number | + | New Zealand | `nz_gst` | New Zealand GST Number | + | Nigeria | `ng_tin` | Nigerian Tax Identification Number | + | North Macedonia | `mk_vat` | North Macedonia VAT Number | + | Northern Ireland | `eu_vat` | Northern Ireland VAT Number | + | Norway | `no_vat` | Norwegian VAT Number | + | Norway | `no_voec` | Norwegian VAT on e-commerce Number | + | Oman | `om_vat` | Omani VAT Number | + | Peru | `pe_ruc` | Peruvian RUC Number | + | Philippines | `ph_tin` | Philippines Tax Identification Number | + | Poland | `eu_vat` | European VAT Number | + | Portugal | `eu_vat` | European VAT Number | + | Romania | `eu_vat` | European VAT Number | + | Romania | `ro_tin` | Romanian Tax ID Number | + | Russia | `ru_inn` | Russian INN | + | Russia | `ru_kpp` | Russian KPP | + | Saudi Arabia | `sa_vat` | Saudi Arabia VAT | + | Senegal | `sn_ninea` | Senegal NINEA Number | + | Serbia | `rs_pib` | Serbian PIB Number | + | Singapore | `sg_gst` | Singaporean GST | + | Singapore | `sg_uen` | Singaporean UEN | + | Slovakia | `eu_vat` | European VAT Number | + | Slovenia | `eu_vat` | European VAT Number | + | Slovenia | `si_tin` | Slovenia Tax Number (davčna številka) | + | South Africa | `za_vat` | South African VAT Number | + | South Korea | `kr_brn` | Korean BRN | + | Spain | `es_cif` | Spanish NIF Number (previously Spanish CIF Number) | + | Spain | `eu_vat` | European VAT Number | + | Suriname | `sr_fin` | Suriname FIN Number | + | Sweden | `eu_vat` | European VAT Number | + | Switzerland | `ch_uid` | Switzerland UID Number | + | Switzerland | `ch_vat` | Switzerland VAT Number | + | Taiwan | `tw_vat` | Taiwanese VAT | + | Tajikistan | `tj_tin` | Tajikistan Tax Identification Number | + | Tanzania | `tz_vat` | Tanzania VAT Number | + | Thailand | `th_vat` | Thai VAT | + | Turkey | `tr_tin` | Turkish Tax Identification Number | + | Uganda | `ug_tin` | Uganda Tax Identification Number | + | Ukraine | `ua_vat` | Ukrainian VAT | + | United Arab Emirates | `ae_trn` | United Arab Emirates TRN | + | United Kingdom | `gb_vat` | United Kingdom VAT Number | + | United States | `us_ein` | United States EIN | + | Uruguay | `uy_ruc` | Uruguayan RUC Number | + | Uzbekistan | `uz_tin` | Uzbekistan TIN Number | + | Uzbekistan | `uz_vat` | Uzbekistan VAT Number | + | Venezuela | `ve_rif` | Venezuelan RIF Number | + | Vietnam | `vn_tin` | Vietnamese Tax ID Number | + | Zambia | `zm_tin` | Zambia Tax Identification Number | + | Zimbabwe | `zw_tin` | Zimbabwe Tax Identification Number | + """ + + due_date: Optional[datetime] = None + """When the invoice payment is due. + + The due date is null if the invoice is not yet finalized. + """ + + eligible_to_issue_at: Optional[datetime] = None + """ + If the invoice has a status of `draft`, this will be the time that the invoice + will be eligible to be issued, otherwise it will be `null`. If `auto-issue` is + true, the invoice will automatically begin issuing at this time. + """ + + hosted_invoice_url: Optional[str] = None + """A URL for the customer-facing invoice portal. + + This URL expires 30 days after the invoice's due date, or 60 days after being + re-generated through the UI. + """ + + invoice_date: datetime + """The scheduled date of the invoice""" + + invoice_number: str + """Automatically generated invoice number to help track and reconcile invoices. + + Invoice numbers have a prefix such as `RFOBWG`. These can be sequential per + account or customer. + """ + + invoice_pdf: Optional[str] = None + """The link to download the PDF representation of the `Invoice`.""" + + invoice_source: Literal["subscription", "partial", "one_off"] + + issue_failed_at: Optional[datetime] = None + """ + If the invoice failed to issue, this will be the last time it failed to issue + (even if it is now in a different state.) + """ + + issued_at: Optional[datetime] = None + """ + If the invoice has been issued, this will be the time it transitioned to + `issued` (even if it is now in a different state.) + """ + + memo: Optional[str] = None + """ + Free-form text which is available on the invoice PDF and the Orb invoice portal. + """ + + metadata: Dict[str, str] + """User specified key-value pairs for the resource. + + If not present, this defaults to an empty dictionary. Individual keys can be + removed by setting the value to `null`, and the entire metadata mapping can be + cleared by setting `metadata` to `null`. + """ + + paid_at: Optional[datetime] = None + """ + If the invoice has a status of `paid`, this gives a timestamp when the invoice + was paid. + """ + + payment_attempts: List[PaymentAttempt] + """A list of payment attempts associated with the invoice""" + + payment_failed_at: Optional[datetime] = None + """ + If payment was attempted on this invoice but failed, this will be the time of + the most recent attempt. + """ + + payment_started_at: Optional[datetime] = None + """ + If payment was attempted on this invoice, this will be the start time of the + most recent attempt. This field is especially useful for delayed-notification + payment mechanisms (like bank transfers), where payment can take 3 days or more. + """ + + scheduled_issue_at: Optional[datetime] = None + """ + If the invoice is in draft, this timestamp will reflect when the invoice is + scheduled to be issued. + """ + + shipping_address: Optional[Address] = None + + status: Literal["issued", "paid", "synced", "void", "draft"] + + subscription: Optional[SubscriptionMinified] = None + + sync_failed_at: Optional[datetime] = None + """ + If the invoice failed to sync, this will be the last time an external invoicing + provider sync was attempted. This field will always be `null` for invoices using + Orb Invoicing. + """ + + total: str + """The total after any minimums and discounts have been applied.""" + + voided_at: Optional[datetime] = None + """ + If the invoice has a status of `void`, this gives a timestamp when the invoice + was voided. + """ + + will_auto_issue: bool + """ + This is true if the invoice will be automatically issued in the future, and + false otherwise. + """ diff --git a/src/orb/types/plans/__init__.py b/src/orb/types/plans/__init__.py index 6108b59a..37f6dbd3 100644 --- a/src/orb/types/plans/__init__.py +++ b/src/orb/types/plans/__init__.py @@ -2,4 +2,8 @@ from __future__ import annotations +from .migration_list_params import MigrationListParams as MigrationListParams +from .migration_list_response import MigrationListResponse as MigrationListResponse +from .migration_cancel_response import MigrationCancelResponse as MigrationCancelResponse +from .migration_retrieve_response import MigrationRetrieveResponse as MigrationRetrieveResponse from .external_plan_id_update_params import ExternalPlanIDUpdateParams as ExternalPlanIDUpdateParams diff --git a/src/orb/types/plans/migration_cancel_response.py b/src/orb/types/plans/migration_cancel_response.py new file mode 100644 index 00000000..e95010cd --- /dev/null +++ b/src/orb/types/plans/migration_cancel_response.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from datetime import date, datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MigrationCancelResponse"] + + +class MigrationCancelResponse(BaseModel): + id: str + + effective_time: Union[date, datetime, Literal["end_of_term"], None] = None + + plan_id: str + + status: Literal["not_started", "in_progress", "completed", "action_needed", "canceled"] diff --git a/src/orb/types/plans/migration_list_params.py b/src/orb/types/plans/migration_list_params.py new file mode 100644 index 00000000..aecb3f1d --- /dev/null +++ b/src/orb/types/plans/migration_list_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 import Optional +from typing_extensions import TypedDict + +__all__ = ["MigrationListParams"] + + +class MigrationListParams(TypedDict, total=False): + cursor: Optional[str] + """Cursor for pagination. + + This can be populated by the `next_cursor` value returned from the initial + request. + """ + + limit: int + """The number of items to fetch. Defaults to 20.""" diff --git a/src/orb/types/plans/migration_list_response.py b/src/orb/types/plans/migration_list_response.py new file mode 100644 index 00000000..3ebf0fc7 --- /dev/null +++ b/src/orb/types/plans/migration_list_response.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from datetime import date, datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MigrationListResponse"] + + +class MigrationListResponse(BaseModel): + id: str + + effective_time: Union[date, datetime, Literal["end_of_term"], None] = None + + plan_id: str + + status: Literal["not_started", "in_progress", "completed", "action_needed", "canceled"] diff --git a/src/orb/types/plans/migration_retrieve_response.py b/src/orb/types/plans/migration_retrieve_response.py new file mode 100644 index 00000000..aed370c0 --- /dev/null +++ b/src/orb/types/plans/migration_retrieve_response.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from datetime import date, datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MigrationRetrieveResponse"] + + +class MigrationRetrieveResponse(BaseModel): + id: str + + effective_time: Union[date, datetime, Literal["end_of_term"], None] = None + + plan_id: str + + status: Literal["not_started", "in_progress", "completed", "action_needed", "canceled"] diff --git a/src/orb/types/subscription_change_list_params.py b/src/orb/types/subscription_change_list_params.py new file mode 100644 index 00000000..3d52e7ab --- /dev/null +++ b/src/orb/types/subscription_change_list_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 import Optional +from typing_extensions import Literal, TypedDict + +__all__ = ["SubscriptionChangeListParams"] + + +class SubscriptionChangeListParams(TypedDict, total=False): + cursor: Optional[str] + """Cursor for pagination. + + This can be populated by the `next_cursor` value returned from the initial + request. + """ + + customer_id: Optional[str] + + external_customer_id: Optional[str] + + limit: int + """The number of items to fetch. Defaults to 20.""" + + status: Optional[Literal["pending", "applied", "cancelled"]] diff --git a/src/orb/types/subscription_change_list_response.py b/src/orb/types/subscription_change_list_response.py new file mode 100644 index 00000000..1a706ddf --- /dev/null +++ b/src/orb/types/subscription_change_list_response.py @@ -0,0 +1,28 @@ +# 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__ = ["SubscriptionChangeListResponse"] + + +class SubscriptionChangeListResponse(BaseModel): + id: str + + expiration_time: datetime + """ + Subscription change will be cancelled at this time and can no longer be applied. + """ + + status: Literal["pending", "applied", "cancelled"] + + subscription_id: Optional[str] = None + + applied_at: Optional[datetime] = None + """When this change was applied.""" + + cancelled_at: Optional[datetime] = None + """When this change was cancelled.""" diff --git a/tests/api_resources/plans/test_migrations.py b/tests/api_resources/plans/test_migrations.py new file mode 100644 index 00000000..d680314d --- /dev/null +++ b/tests/api_resources/plans/test_migrations.py @@ -0,0 +1,315 @@ +# 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 orb import Orb, AsyncOrb +from tests.utils import assert_matches_type +from orb.pagination import SyncPage, AsyncPage +from orb.types.plans import ( + MigrationListResponse, + MigrationCancelResponse, + MigrationRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestMigrations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Orb) -> None: + migration = client.plans.migrations.retrieve( + migration_id="migration_id", + plan_id="plan_id", + ) + assert_matches_type(MigrationRetrieveResponse, migration, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Orb) -> None: + response = client.plans.migrations.with_raw_response.retrieve( + migration_id="migration_id", + plan_id="plan_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + migration = response.parse() + assert_matches_type(MigrationRetrieveResponse, migration, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Orb) -> None: + with client.plans.migrations.with_streaming_response.retrieve( + migration_id="migration_id", + plan_id="plan_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + migration = response.parse() + assert_matches_type(MigrationRetrieveResponse, migration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Orb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `plan_id` but received ''"): + client.plans.migrations.with_raw_response.retrieve( + migration_id="migration_id", + plan_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `migration_id` but received ''"): + client.plans.migrations.with_raw_response.retrieve( + migration_id="", + plan_id="plan_id", + ) + + @parametrize + def test_method_list(self, client: Orb) -> None: + migration = client.plans.migrations.list( + plan_id="plan_id", + ) + assert_matches_type(SyncPage[MigrationListResponse], migration, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Orb) -> None: + migration = client.plans.migrations.list( + plan_id="plan_id", + cursor="cursor", + limit=1, + ) + assert_matches_type(SyncPage[MigrationListResponse], migration, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Orb) -> None: + response = client.plans.migrations.with_raw_response.list( + plan_id="plan_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + migration = response.parse() + assert_matches_type(SyncPage[MigrationListResponse], migration, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Orb) -> None: + with client.plans.migrations.with_streaming_response.list( + plan_id="plan_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + migration = response.parse() + assert_matches_type(SyncPage[MigrationListResponse], migration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Orb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `plan_id` but received ''"): + client.plans.migrations.with_raw_response.list( + plan_id="", + ) + + @parametrize + def test_method_cancel(self, client: Orb) -> None: + migration = client.plans.migrations.cancel( + migration_id="migration_id", + plan_id="plan_id", + ) + assert_matches_type(MigrationCancelResponse, migration, path=["response"]) + + @parametrize + def test_raw_response_cancel(self, client: Orb) -> None: + response = client.plans.migrations.with_raw_response.cancel( + migration_id="migration_id", + plan_id="plan_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + migration = response.parse() + assert_matches_type(MigrationCancelResponse, migration, path=["response"]) + + @parametrize + def test_streaming_response_cancel(self, client: Orb) -> None: + with client.plans.migrations.with_streaming_response.cancel( + migration_id="migration_id", + plan_id="plan_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + migration = response.parse() + assert_matches_type(MigrationCancelResponse, migration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_cancel(self, client: Orb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `plan_id` but received ''"): + client.plans.migrations.with_raw_response.cancel( + migration_id="migration_id", + plan_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `migration_id` but received ''"): + client.plans.migrations.with_raw_response.cancel( + migration_id="", + plan_id="plan_id", + ) + + +class TestAsyncMigrations: + 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: AsyncOrb) -> None: + migration = await async_client.plans.migrations.retrieve( + migration_id="migration_id", + plan_id="plan_id", + ) + assert_matches_type(MigrationRetrieveResponse, migration, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOrb) -> None: + response = await async_client.plans.migrations.with_raw_response.retrieve( + migration_id="migration_id", + plan_id="plan_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + migration = response.parse() + assert_matches_type(MigrationRetrieveResponse, migration, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOrb) -> None: + async with async_client.plans.migrations.with_streaming_response.retrieve( + migration_id="migration_id", + plan_id="plan_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + migration = await response.parse() + assert_matches_type(MigrationRetrieveResponse, migration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOrb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `plan_id` but received ''"): + await async_client.plans.migrations.with_raw_response.retrieve( + migration_id="migration_id", + plan_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `migration_id` but received ''"): + await async_client.plans.migrations.with_raw_response.retrieve( + migration_id="", + plan_id="plan_id", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOrb) -> None: + migration = await async_client.plans.migrations.list( + plan_id="plan_id", + ) + assert_matches_type(AsyncPage[MigrationListResponse], migration, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOrb) -> None: + migration = await async_client.plans.migrations.list( + plan_id="plan_id", + cursor="cursor", + limit=1, + ) + assert_matches_type(AsyncPage[MigrationListResponse], migration, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOrb) -> None: + response = await async_client.plans.migrations.with_raw_response.list( + plan_id="plan_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + migration = response.parse() + assert_matches_type(AsyncPage[MigrationListResponse], migration, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOrb) -> None: + async with async_client.plans.migrations.with_streaming_response.list( + plan_id="plan_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + migration = await response.parse() + assert_matches_type(AsyncPage[MigrationListResponse], migration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOrb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `plan_id` but received ''"): + await async_client.plans.migrations.with_raw_response.list( + plan_id="", + ) + + @parametrize + async def test_method_cancel(self, async_client: AsyncOrb) -> None: + migration = await async_client.plans.migrations.cancel( + migration_id="migration_id", + plan_id="plan_id", + ) + assert_matches_type(MigrationCancelResponse, migration, path=["response"]) + + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncOrb) -> None: + response = await async_client.plans.migrations.with_raw_response.cancel( + migration_id="migration_id", + plan_id="plan_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + migration = response.parse() + assert_matches_type(MigrationCancelResponse, migration, path=["response"]) + + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncOrb) -> None: + async with async_client.plans.migrations.with_streaming_response.cancel( + migration_id="migration_id", + plan_id="plan_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + migration = await response.parse() + assert_matches_type(MigrationCancelResponse, migration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_cancel(self, async_client: AsyncOrb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `plan_id` but received ''"): + await async_client.plans.migrations.with_raw_response.cancel( + migration_id="migration_id", + plan_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `migration_id` but received ''"): + await async_client.plans.migrations.with_raw_response.cancel( + migration_id="", + plan_id="plan_id", + ) diff --git a/tests/api_resources/test_credit_blocks.py b/tests/api_resources/test_credit_blocks.py new file mode 100644 index 00000000..1d5f30ae --- /dev/null +++ b/tests/api_resources/test_credit_blocks.py @@ -0,0 +1,176 @@ +# 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 orb import Orb, AsyncOrb +from orb.types import CreditBlockRetrieveResponse +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCreditBlocks: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Orb) -> None: + credit_block = client.credit_blocks.retrieve( + "block_id", + ) + assert_matches_type(CreditBlockRetrieveResponse, credit_block, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Orb) -> None: + response = client.credit_blocks.with_raw_response.retrieve( + "block_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_block = response.parse() + assert_matches_type(CreditBlockRetrieveResponse, credit_block, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Orb) -> None: + with client.credit_blocks.with_streaming_response.retrieve( + "block_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_block = response.parse() + assert_matches_type(CreditBlockRetrieveResponse, credit_block, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Orb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `block_id` but received ''"): + client.credit_blocks.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_delete(self, client: Orb) -> None: + credit_block = client.credit_blocks.delete( + "block_id", + ) + assert credit_block is None + + @parametrize + def test_raw_response_delete(self, client: Orb) -> None: + response = client.credit_blocks.with_raw_response.delete( + "block_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_block = response.parse() + assert credit_block is None + + @parametrize + def test_streaming_response_delete(self, client: Orb) -> None: + with client.credit_blocks.with_streaming_response.delete( + "block_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_block = response.parse() + assert credit_block is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Orb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `block_id` but received ''"): + client.credit_blocks.with_raw_response.delete( + "", + ) + + +class TestAsyncCreditBlocks: + 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: AsyncOrb) -> None: + credit_block = await async_client.credit_blocks.retrieve( + "block_id", + ) + assert_matches_type(CreditBlockRetrieveResponse, credit_block, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOrb) -> None: + response = await async_client.credit_blocks.with_raw_response.retrieve( + "block_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_block = response.parse() + assert_matches_type(CreditBlockRetrieveResponse, credit_block, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOrb) -> None: + async with async_client.credit_blocks.with_streaming_response.retrieve( + "block_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_block = await response.parse() + assert_matches_type(CreditBlockRetrieveResponse, credit_block, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOrb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `block_id` but received ''"): + await async_client.credit_blocks.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOrb) -> None: + credit_block = await async_client.credit_blocks.delete( + "block_id", + ) + assert credit_block is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOrb) -> None: + response = await async_client.credit_blocks.with_raw_response.delete( + "block_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credit_block = response.parse() + assert credit_block is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOrb) -> None: + async with async_client.credit_blocks.with_streaming_response.delete( + "block_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credit_block = await response.parse() + assert credit_block is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOrb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `block_id` but received ''"): + await async_client.credit_blocks.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_invoices.py b/tests/api_resources/test_invoices.py index db64ecc2..38ef17ea 100644 --- a/tests/api_resources/test_invoices.py +++ b/tests/api_resources/test_invoices.py @@ -9,6 +9,7 @@ from orb import Orb, AsyncOrb from orb.types import ( + InvoiceListSummaryResponse, InvoiceFetchUpcomingResponse, ) from orb._utils import parse_date, parse_datetime @@ -230,6 +231,54 @@ def test_streaming_response_list(self, client: Orb) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_delete_line_item(self, client: Orb) -> None: + invoice = client.invoices.delete_line_item( + line_item_id="line_item_id", + invoice_id="invoice_id", + ) + assert invoice is None + + @parametrize + def test_raw_response_delete_line_item(self, client: Orb) -> None: + response = client.invoices.with_raw_response.delete_line_item( + line_item_id="line_item_id", + invoice_id="invoice_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invoice = response.parse() + assert invoice is None + + @parametrize + def test_streaming_response_delete_line_item(self, client: Orb) -> None: + with client.invoices.with_streaming_response.delete_line_item( + line_item_id="line_item_id", + invoice_id="invoice_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invoice = response.parse() + assert invoice is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete_line_item(self, client: Orb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invoice_id` but received ''"): + client.invoices.with_raw_response.delete_line_item( + line_item_id="line_item_id", + invoice_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `line_item_id` but received ''"): + client.invoices.with_raw_response.delete_line_item( + line_item_id="", + invoice_id="invoice_id", + ) + @parametrize def test_method_fetch(self, client: Orb) -> None: invoice = client.invoices.fetch( @@ -345,6 +394,56 @@ def test_path_params_issue(self, client: Orb) -> None: invoice_id="", ) + @parametrize + def test_method_list_summary(self, client: Orb) -> None: + invoice = client.invoices.list_summary() + assert_matches_type(SyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + @parametrize + def test_method_list_summary_with_all_params(self, client: Orb) -> None: + invoice = client.invoices.list_summary( + amount="amount", + amount_gt="amount[gt]", + amount_lt="amount[lt]", + cursor="cursor", + customer_id="customer_id", + date_type="due_date", + due_date=parse_date("2019-12-27"), + due_date_window="due_date_window", + due_date_gt=parse_date("2019-12-27"), + due_date_lt=parse_date("2019-12-27"), + external_customer_id="external_customer_id", + invoice_date_gt=parse_datetime("2019-12-27T18:11:19.117Z"), + invoice_date_gte=parse_datetime("2019-12-27T18:11:19.117Z"), + invoice_date_lt=parse_datetime("2019-12-27T18:11:19.117Z"), + invoice_date_lte=parse_datetime("2019-12-27T18:11:19.117Z"), + is_recurring=True, + limit=1, + status="draft", + subscription_id="subscription_id", + ) + assert_matches_type(SyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + @parametrize + def test_raw_response_list_summary(self, client: Orb) -> None: + response = client.invoices.with_raw_response.list_summary() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invoice = response.parse() + assert_matches_type(SyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + @parametrize + def test_streaming_response_list_summary(self, client: Orb) -> None: + with client.invoices.with_streaming_response.list_summary() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invoice = response.parse() + assert_matches_type(SyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_mark_paid(self, client: Orb) -> None: invoice = client.invoices.mark_paid( @@ -687,6 +786,54 @@ async def test_streaming_response_list(self, async_client: AsyncOrb) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_delete_line_item(self, async_client: AsyncOrb) -> None: + invoice = await async_client.invoices.delete_line_item( + line_item_id="line_item_id", + invoice_id="invoice_id", + ) + assert invoice is None + + @parametrize + async def test_raw_response_delete_line_item(self, async_client: AsyncOrb) -> None: + response = await async_client.invoices.with_raw_response.delete_line_item( + line_item_id="line_item_id", + invoice_id="invoice_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invoice = response.parse() + assert invoice is None + + @parametrize + async def test_streaming_response_delete_line_item(self, async_client: AsyncOrb) -> None: + async with async_client.invoices.with_streaming_response.delete_line_item( + line_item_id="line_item_id", + invoice_id="invoice_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invoice = await response.parse() + assert invoice is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete_line_item(self, async_client: AsyncOrb) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invoice_id` but received ''"): + await async_client.invoices.with_raw_response.delete_line_item( + line_item_id="line_item_id", + invoice_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `line_item_id` but received ''"): + await async_client.invoices.with_raw_response.delete_line_item( + line_item_id="", + invoice_id="invoice_id", + ) + @parametrize async def test_method_fetch(self, async_client: AsyncOrb) -> None: invoice = await async_client.invoices.fetch( @@ -802,6 +949,56 @@ async def test_path_params_issue(self, async_client: AsyncOrb) -> None: invoice_id="", ) + @parametrize + async def test_method_list_summary(self, async_client: AsyncOrb) -> None: + invoice = await async_client.invoices.list_summary() + assert_matches_type(AsyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + @parametrize + async def test_method_list_summary_with_all_params(self, async_client: AsyncOrb) -> None: + invoice = await async_client.invoices.list_summary( + amount="amount", + amount_gt="amount[gt]", + amount_lt="amount[lt]", + cursor="cursor", + customer_id="customer_id", + date_type="due_date", + due_date=parse_date("2019-12-27"), + due_date_window="due_date_window", + due_date_gt=parse_date("2019-12-27"), + due_date_lt=parse_date("2019-12-27"), + external_customer_id="external_customer_id", + invoice_date_gt=parse_datetime("2019-12-27T18:11:19.117Z"), + invoice_date_gte=parse_datetime("2019-12-27T18:11:19.117Z"), + invoice_date_lt=parse_datetime("2019-12-27T18:11:19.117Z"), + invoice_date_lte=parse_datetime("2019-12-27T18:11:19.117Z"), + is_recurring=True, + limit=1, + status="draft", + subscription_id="subscription_id", + ) + assert_matches_type(AsyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + @parametrize + async def test_raw_response_list_summary(self, async_client: AsyncOrb) -> None: + response = await async_client.invoices.with_raw_response.list_summary() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invoice = response.parse() + assert_matches_type(AsyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + @parametrize + async def test_streaming_response_list_summary(self, async_client: AsyncOrb) -> None: + async with async_client.invoices.with_streaming_response.list_summary() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invoice = await response.parse() + assert_matches_type(AsyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_mark_paid(self, async_client: AsyncOrb) -> None: invoice = await async_client.invoices.mark_paid( diff --git a/tests/api_resources/test_subscription_changes.py b/tests/api_resources/test_subscription_changes.py index 5b6fc868..0a5d6dae 100644 --- a/tests/api_resources/test_subscription_changes.py +++ b/tests/api_resources/test_subscription_changes.py @@ -9,12 +9,14 @@ from orb import Orb, AsyncOrb from orb.types import ( + SubscriptionChangeListResponse, SubscriptionChangeApplyResponse, SubscriptionChangeCancelResponse, SubscriptionChangeRetrieveResponse, ) from orb._utils import parse_date from tests.utils import assert_matches_type +from orb.pagination import SyncPage, AsyncPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -62,6 +64,42 @@ def test_path_params_retrieve(self, client: Orb) -> None: "", ) + @parametrize + def test_method_list(self, client: Orb) -> None: + subscription_change = client.subscription_changes.list() + assert_matches_type(SyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Orb) -> None: + subscription_change = client.subscription_changes.list( + cursor="cursor", + customer_id="customer_id", + external_customer_id="external_customer_id", + limit=1, + status="pending", + ) + assert_matches_type(SyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Orb) -> None: + response = client.subscription_changes.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + subscription_change = response.parse() + assert_matches_type(SyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Orb) -> None: + with client.subscription_changes.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + subscription_change = response.parse() + assert_matches_type(SyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_apply(self, client: Orb) -> None: subscription_change = client.subscription_changes.apply( @@ -201,6 +239,42 @@ async def test_path_params_retrieve(self, async_client: AsyncOrb) -> None: "", ) + @parametrize + async def test_method_list(self, async_client: AsyncOrb) -> None: + subscription_change = await async_client.subscription_changes.list() + assert_matches_type(AsyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOrb) -> None: + subscription_change = await async_client.subscription_changes.list( + cursor="cursor", + customer_id="customer_id", + external_customer_id="external_customer_id", + limit=1, + status="pending", + ) + assert_matches_type(AsyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOrb) -> None: + response = await async_client.subscription_changes.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + subscription_change = response.parse() + assert_matches_type(AsyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOrb) -> None: + async with async_client.subscription_changes.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + subscription_change = await response.parse() + assert_matches_type(AsyncPage[SubscriptionChangeListResponse], subscription_change, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_apply(self, async_client: AsyncOrb) -> None: subscription_change = await async_client.subscription_changes.apply(