diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index cff01f26..b2585653 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.15.0"
+ ".": "2.16.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 3114caa3..e670c774 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 96
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-1345a8e288e34d5477b0e189877225f83939a59078c22fbb5367712e376c5d19.yml
+configured_endpoints: 97
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-bf3e71b33372f4a9307f4b6cb689ea46b3cf583ecc5d79eee9601cd0b0467c9a.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c74492bf..0efd74be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 2.16.0 (2024-11-15)
+
+Full Changelog: [v2.15.0...v2.16.0](https://github.com/orbcorp/orb-python/compare/v2.15.0...v2.16.0)
+
+### Features
+
+* **api:** api update ([#430](https://github.com/orbcorp/orb-python/issues/430)) ([5457ac2](https://github.com/orbcorp/orb-python/commit/5457ac24adc55caee65382214e0cbf9739e963f3))
+
+
+### Chores
+
+* rebuild project due to codegen change ([#427](https://github.com/orbcorp/orb-python/issues/427)) ([bf7c315](https://github.com/orbcorp/orb-python/commit/bf7c315a84c6b959369bfc1f255f9353ad5c3c6f))
+* rebuild project due to codegen change ([#429](https://github.com/orbcorp/orb-python/issues/429)) ([2bfa0a2](https://github.com/orbcorp/orb-python/commit/2bfa0a206c28724761471ed98d5c619d33a2506b))
+
## 2.15.0 (2024-11-06)
Full Changelog: [v2.14.0...v2.15.0](https://github.com/orbcorp/orb-python/compare/v2.14.0...v2.15.0)
diff --git a/README.md b/README.md
index 3936ac2c..1bc81f80 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,7 @@ import os
from orb import Orb
client = Orb(
- # This is the default and can be omitted
- api_key=os.environ.get("ORB_API_KEY"),
+ api_key=os.environ.get("ORB_API_KEY"), # This is the default and can be omitted
)
customer = client.customers.create(
@@ -52,8 +51,7 @@ import asyncio
from orb import AsyncOrb
client = AsyncOrb(
- # This is the default and can be omitted
- api_key=os.environ.get("ORB_API_KEY"),
+ api_key=os.environ.get("ORB_API_KEY"), # This is the default and can be omitted
)
diff --git a/api.md b/api.md
index c441ce56..7d1e5f7a 100644
--- a/api.md
+++ b/api.md
@@ -220,6 +220,7 @@ Methods:
- client.invoices.fetch_upcoming(\*\*params) -> InvoiceFetchUpcomingResponse
- client.invoices.issue(invoice_id) -> Invoice
- client.invoices.mark_paid(invoice_id, \*\*params) -> Invoice
+- client.invoices.pay(invoice_id) -> Invoice
- client.invoices.void(invoice_id) -> Invoice
# Items
diff --git a/pyproject.toml b/pyproject.toml
index 527302b0..43bff382 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "orb-billing"
-version = "2.15.0"
+version = "2.16.0"
description = "The official Python library for the orb API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/orb/_utils/_transform.py b/src/orb/_utils/_transform.py
index 47e262a5..71f35299 100644
--- a/src/orb/_utils/_transform.py
+++ b/src/orb/_utils/_transform.py
@@ -311,6 +311,11 @@ async def _async_transform_recursive(
# Iterable[T]
or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
):
+ # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
+ # intended as an iterable, so we don't transform it.
+ if isinstance(data, dict):
+ return cast(object, data)
+
inner_type = extract_type_arg(stripped_type, 0)
return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
diff --git a/src/orb/_version.py b/src/orb/_version.py
index 95fab6f1..5826d194 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__ = "2.15.0" # x-release-please-version
+__version__ = "2.16.0" # x-release-please-version
diff --git a/src/orb/resources/invoices.py b/src/orb/resources/invoices.py
index f522e06f..6c40a39f 100644
--- a/src/orb/resources/invoices.py
+++ b/src/orb/resources/invoices.py
@@ -451,6 +451,47 @@ def mark_paid(
cast_to=Invoice,
)
+ def pay(
+ self,
+ 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,
+ ) -> Invoice:
+ """
+ This endpoint collects payment for an invoice using the customer's default
+ payment method. This action can only be taken on invoices with status "issued".
+
+ 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}")
+ return self._post(
+ f"/invoices/{invoice_id}/pay",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=Invoice,
+ )
+
def void(
self,
invoice_id: str,
@@ -917,6 +958,47 @@ async def mark_paid(
cast_to=Invoice,
)
+ async def pay(
+ self,
+ 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,
+ ) -> Invoice:
+ """
+ This endpoint collects payment for an invoice using the customer's default
+ payment method. This action can only be taken on invoices with status "issued".
+
+ 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}")
+ return await self._post(
+ f"/invoices/{invoice_id}/pay",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ idempotency_key=idempotency_key,
+ ),
+ cast_to=Invoice,
+ )
+
async def void(
self,
invoice_id: str,
@@ -990,6 +1072,9 @@ def __init__(self, invoices: Invoices) -> None:
self.mark_paid = _legacy_response.to_raw_response_wrapper(
invoices.mark_paid,
)
+ self.pay = _legacy_response.to_raw_response_wrapper(
+ invoices.pay,
+ )
self.void = _legacy_response.to_raw_response_wrapper(
invoices.void,
)
@@ -1020,6 +1105,9 @@ def __init__(self, invoices: AsyncInvoices) -> None:
self.mark_paid = _legacy_response.async_to_raw_response_wrapper(
invoices.mark_paid,
)
+ self.pay = _legacy_response.async_to_raw_response_wrapper(
+ invoices.pay,
+ )
self.void = _legacy_response.async_to_raw_response_wrapper(
invoices.void,
)
@@ -1050,6 +1138,9 @@ def __init__(self, invoices: Invoices) -> None:
self.mark_paid = to_streamed_response_wrapper(
invoices.mark_paid,
)
+ self.pay = to_streamed_response_wrapper(
+ invoices.pay,
+ )
self.void = to_streamed_response_wrapper(
invoices.void,
)
@@ -1080,6 +1171,9 @@ def __init__(self, invoices: AsyncInvoices) -> None:
self.mark_paid = async_to_streamed_response_wrapper(
invoices.mark_paid,
)
+ self.pay = async_to_streamed_response_wrapper(
+ invoices.pay,
+ )
self.void = async_to_streamed_response_wrapper(
invoices.void,
)
diff --git a/src/orb/types/invoice.py b/src/orb/types/invoice.py
index 5781f30d..4019c548 100644
--- a/src/orb/types/invoice.py
+++ b/src/orb/types/invoice.py
@@ -34,6 +34,7 @@
"LineItemTaxAmount",
"Maximum",
"Minimum",
+ "PaymentAttempt",
"ShippingAddress",
"Subscription",
]
@@ -743,6 +744,26 @@ class Minimum(BaseModel):
"""Minimum amount applied"""
+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."""
+
+ succeeded: bool
+ """Whether the payment attempt succeeded."""
+
+
class ShippingAddress(BaseModel):
city: Optional[str] = None
@@ -970,6 +991,9 @@ class Invoice(BaseModel):
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
diff --git a/src/orb/types/invoice_fetch_upcoming_response.py b/src/orb/types/invoice_fetch_upcoming_response.py
index f1398c16..f59a6eda 100644
--- a/src/orb/types/invoice_fetch_upcoming_response.py
+++ b/src/orb/types/invoice_fetch_upcoming_response.py
@@ -34,6 +34,7 @@
"LineItemTaxAmount",
"Maximum",
"Minimum",
+ "PaymentAttempt",
"ShippingAddress",
"Subscription",
]
@@ -743,6 +744,26 @@ class Minimum(BaseModel):
"""Minimum amount applied"""
+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."""
+
+ succeeded: bool
+ """Whether the payment attempt succeeded."""
+
+
class ShippingAddress(BaseModel):
city: Optional[str] = None
@@ -967,6 +988,9 @@ class InvoiceFetchUpcomingResponse(BaseModel):
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
diff --git a/tests/api_resources/test_invoices.py b/tests/api_resources/test_invoices.py
index 16fd1453..7121b809 100644
--- a/tests/api_resources/test_invoices.py
+++ b/tests/api_resources/test_invoices.py
@@ -379,6 +379,44 @@ def test_path_params_mark_paid(self, client: Orb) -> None:
payment_received_date=parse_date("2023-09-22"),
)
+ @parametrize
+ def test_method_pay(self, client: Orb) -> None:
+ invoice = client.invoices.pay(
+ "invoice_id",
+ )
+ assert_matches_type(Invoice, invoice, path=["response"])
+
+ @parametrize
+ def test_raw_response_pay(self, client: Orb) -> None:
+ response = client.invoices.with_raw_response.pay(
+ "invoice_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ invoice = response.parse()
+ assert_matches_type(Invoice, invoice, path=["response"])
+
+ @parametrize
+ def test_streaming_response_pay(self, client: Orb) -> None:
+ with client.invoices.with_streaming_response.pay(
+ "invoice_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ invoice = response.parse()
+ assert_matches_type(Invoice, invoice, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_pay(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.pay(
+ "",
+ )
+
@parametrize
def test_method_void(self, client: Orb) -> None:
invoice = client.invoices.void(
@@ -778,6 +816,44 @@ async def test_path_params_mark_paid(self, async_client: AsyncOrb) -> None:
payment_received_date=parse_date("2023-09-22"),
)
+ @parametrize
+ async def test_method_pay(self, async_client: AsyncOrb) -> None:
+ invoice = await async_client.invoices.pay(
+ "invoice_id",
+ )
+ assert_matches_type(Invoice, invoice, path=["response"])
+
+ @parametrize
+ async def test_raw_response_pay(self, async_client: AsyncOrb) -> None:
+ response = await async_client.invoices.with_raw_response.pay(
+ "invoice_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ invoice = response.parse()
+ assert_matches_type(Invoice, invoice, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_pay(self, async_client: AsyncOrb) -> None:
+ async with async_client.invoices.with_streaming_response.pay(
+ "invoice_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ invoice = await response.parse()
+ assert_matches_type(Invoice, invoice, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_pay(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.pay(
+ "",
+ )
+
@parametrize
async def test_method_void(self, async_client: AsyncOrb) -> None:
invoice = await async_client.invoices.void(