diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 40293964..c8a8a4f7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,6 @@ jobs:
lint:
name: lint
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
@@ -30,6 +29,7 @@ jobs:
- name: Run lints
run: ./scripts/lint
+
test:
name: test
runs-on: ubuntu-latest
@@ -50,4 +50,3 @@ jobs:
- name: Run tests
run: ./scripts/test
-
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 288087c1..c5fe8ab6 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.24.0"
+ ".": "2.25.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index efeca04e..ae0299a7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 101
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-7fca89ba5a0b4997358c25e6cdfb616a1d8b93a6820e25078f3fa5f61110bfe6.yml
+configured_endpoints: 103
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-326205df28a52e9ad57c34d7ed1ec85fadd67f9a041df2882ebaa65f6de09930.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1983c92..1e0b2a97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 2.25.0 (2025-01-23)
+
+Full Changelog: [v2.24.0...v2.25.0](https://github.com/orbcorp/orb-python/compare/v2.24.0...v2.25.0)
+
+### Features
+
+* **api:** api update ([#511](https://github.com/orbcorp/orb-python/issues/511)) ([052d15d](https://github.com/orbcorp/orb-python/commit/052d15d9822356db28a4a42ea085133a29b183aa))
+
+
+### Chores
+
+* **internal:** minor formatting changes ([#509](https://github.com/orbcorp/orb-python/issues/509)) ([15852c1](https://github.com/orbcorp/orb-python/commit/15852c155fa49397b3184cd648603070fbf59a87))
+
## 2.24.0 (2025-01-21)
Full Changelog: [v2.23.1...v2.24.0](https://github.com/orbcorp/orb-python/compare/v2.23.1...v2.24.0)
diff --git a/api.md b/api.md
index c6fdb8fa..be3ecaf3 100644
--- a/api.md
+++ b/api.md
@@ -75,6 +75,8 @@ Methods:
- client.customers.delete(customer_id) -> None
- client.customers.fetch(customer_id) -> Customer
- client.customers.fetch_by_external_id(external_customer_id) -> Customer
+- client.customers.sync_payment_methods_from_gateway(external_customer_id) -> None
+- client.customers.sync_payment_methods_from_gateway_by_external_customer_id(customer_id) -> None
- client.customers.update_by_external_id(id, \*\*params) -> Customer
## Costs
diff --git a/pyproject.toml b/pyproject.toml
index 6cd22fef..c8851608 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "orb-billing"
-version = "2.24.0"
+version = "2.25.0"
description = "The official Python library for the orb API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/scripts/bootstrap b/scripts/bootstrap
index 8c5c60eb..e84fe62c 100755
--- a/scripts/bootstrap
+++ b/scripts/bootstrap
@@ -4,7 +4,7 @@ set -e
cd "$(dirname "$0")/.."
-if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
+if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then
brew bundle check >/dev/null 2>&1 || {
echo "==> Installing Homebrew dependencies…"
brew bundle
diff --git a/scripts/lint b/scripts/lint
index 446e36fd..38b82fa1 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -9,4 +9,3 @@ rye run lint
echo "==> Making sure it imports"
rye run python -c 'import orb'
-
diff --git a/src/orb/_version.py b/src/orb/_version.py
index 46f0c987..4ce2f17c 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.24.0" # x-release-please-version
+__version__ = "2.25.0" # x-release-please-version
diff --git a/src/orb/resources/customers/customers.py b/src/orb/resources/customers/customers.py
index b02118f5..f131d20c 100644
--- a/src/orb/resources/customers/customers.py
+++ b/src/orb/resources/customers/customers.py
@@ -732,6 +732,100 @@ def fetch_by_external_id(
cast_to=Customer,
)
+ def sync_payment_methods_from_gateway(
+ self,
+ external_customer_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:
+ """
+ Sync Orb's payment methods for the customer with their gateway.
+
+ This method can be called before taking an action that may cause the customer to
+ be charged, ensuring that the most up-to-date payment method is charged.
+
+ **Note**: This functionality is currently only available for Stripe.
+
+ 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 external_customer_id:
+ raise ValueError(
+ f"Expected a non-empty value for `external_customer_id` but received {external_customer_id!r}"
+ )
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ f"/customers/external_customer_id/{external_customer_id}/sync_payment_methods_from_gateway",
+ 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 sync_payment_methods_from_gateway_by_external_customer_id(
+ self,
+ customer_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:
+ """
+ Sync Orb's payment methods for the customer with their gateway.
+
+ This method can be called before taking an action that may cause the customer to
+ be charged, ensuring that the most up-to-date payment method is charged.
+
+ **Note**: This functionality is currently only available for Stripe.
+
+ 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 customer_id:
+ raise ValueError(f"Expected a non-empty value for `customer_id` but received {customer_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ f"/customers/{customer_id}/sync_payment_methods_from_gateway",
+ 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 update_by_external_id(
self,
id: str,
@@ -1632,6 +1726,100 @@ async def fetch_by_external_id(
cast_to=Customer,
)
+ async def sync_payment_methods_from_gateway(
+ self,
+ external_customer_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:
+ """
+ Sync Orb's payment methods for the customer with their gateway.
+
+ This method can be called before taking an action that may cause the customer to
+ be charged, ensuring that the most up-to-date payment method is charged.
+
+ **Note**: This functionality is currently only available for Stripe.
+
+ 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 external_customer_id:
+ raise ValueError(
+ f"Expected a non-empty value for `external_customer_id` but received {external_customer_id!r}"
+ )
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ f"/customers/external_customer_id/{external_customer_id}/sync_payment_methods_from_gateway",
+ 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 sync_payment_methods_from_gateway_by_external_customer_id(
+ self,
+ customer_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:
+ """
+ Sync Orb's payment methods for the customer with their gateway.
+
+ This method can be called before taking an action that may cause the customer to
+ be charged, ensuring that the most up-to-date payment method is charged.
+
+ **Note**: This functionality is currently only available for Stripe.
+
+ 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 customer_id:
+ raise ValueError(f"Expected a non-empty value for `customer_id` but received {customer_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ f"/customers/{customer_id}/sync_payment_methods_from_gateway",
+ 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 update_by_external_id(
self,
id: str,
@@ -1876,6 +2064,12 @@ def __init__(self, customers: Customers) -> None:
self.fetch_by_external_id = _legacy_response.to_raw_response_wrapper(
customers.fetch_by_external_id,
)
+ self.sync_payment_methods_from_gateway = _legacy_response.to_raw_response_wrapper(
+ customers.sync_payment_methods_from_gateway,
+ )
+ self.sync_payment_methods_from_gateway_by_external_customer_id = _legacy_response.to_raw_response_wrapper(
+ customers.sync_payment_methods_from_gateway_by_external_customer_id,
+ )
self.update_by_external_id = _legacy_response.to_raw_response_wrapper(
customers.update_by_external_id,
)
@@ -1915,6 +2109,12 @@ def __init__(self, customers: AsyncCustomers) -> None:
self.fetch_by_external_id = _legacy_response.async_to_raw_response_wrapper(
customers.fetch_by_external_id,
)
+ self.sync_payment_methods_from_gateway = _legacy_response.async_to_raw_response_wrapper(
+ customers.sync_payment_methods_from_gateway,
+ )
+ self.sync_payment_methods_from_gateway_by_external_customer_id = _legacy_response.async_to_raw_response_wrapper(
+ customers.sync_payment_methods_from_gateway_by_external_customer_id,
+ )
self.update_by_external_id = _legacy_response.async_to_raw_response_wrapper(
customers.update_by_external_id,
)
@@ -1954,6 +2154,12 @@ def __init__(self, customers: Customers) -> None:
self.fetch_by_external_id = to_streamed_response_wrapper(
customers.fetch_by_external_id,
)
+ self.sync_payment_methods_from_gateway = to_streamed_response_wrapper(
+ customers.sync_payment_methods_from_gateway,
+ )
+ self.sync_payment_methods_from_gateway_by_external_customer_id = to_streamed_response_wrapper(
+ customers.sync_payment_methods_from_gateway_by_external_customer_id,
+ )
self.update_by_external_id = to_streamed_response_wrapper(
customers.update_by_external_id,
)
@@ -1993,6 +2199,12 @@ def __init__(self, customers: AsyncCustomers) -> None:
self.fetch_by_external_id = async_to_streamed_response_wrapper(
customers.fetch_by_external_id,
)
+ self.sync_payment_methods_from_gateway = async_to_streamed_response_wrapper(
+ customers.sync_payment_methods_from_gateway,
+ )
+ self.sync_payment_methods_from_gateway_by_external_customer_id = async_to_streamed_response_wrapper(
+ customers.sync_payment_methods_from_gateway_by_external_customer_id,
+ )
self.update_by_external_id = async_to_streamed_response_wrapper(
customers.update_by_external_id,
)
diff --git a/src/orb/types/invoice.py b/src/orb/types/invoice.py
index 6703f65b..e3c51e72 100644
--- a/src/orb/types/invoice.py
+++ b/src/orb/types/invoice.py
@@ -706,8 +706,11 @@ class Invoice(BaseModel):
discounts: List[InvoiceLevelDiscount]
- due_date: datetime
- """When the invoice payment is due."""
+ 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
"""
diff --git a/src/orb/types/invoice_fetch_upcoming_response.py b/src/orb/types/invoice_fetch_upcoming_response.py
index 4b576e98..eafb79b5 100644
--- a/src/orb/types/invoice_fetch_upcoming_response.py
+++ b/src/orb/types/invoice_fetch_upcoming_response.py
@@ -706,8 +706,11 @@ class InvoiceFetchUpcomingResponse(BaseModel):
discounts: List[InvoiceLevelDiscount]
- due_date: datetime
- """When the invoice payment is due."""
+ 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
"""
diff --git a/tests/api_resources/test_customers.py b/tests/api_resources/test_customers.py
index 38419de3..abedef0b 100644
--- a/tests/api_resources/test_customers.py
+++ b/tests/api_resources/test_customers.py
@@ -350,6 +350,82 @@ def test_path_params_fetch_by_external_id(self, client: Orb) -> None:
"",
)
+ @parametrize
+ def test_method_sync_payment_methods_from_gateway(self, client: Orb) -> None:
+ customer = client.customers.sync_payment_methods_from_gateway(
+ "external_customer_id",
+ )
+ assert customer is None
+
+ @parametrize
+ def test_raw_response_sync_payment_methods_from_gateway(self, client: Orb) -> None:
+ response = client.customers.with_raw_response.sync_payment_methods_from_gateway(
+ "external_customer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ customer = response.parse()
+ assert customer is None
+
+ @parametrize
+ def test_streaming_response_sync_payment_methods_from_gateway(self, client: Orb) -> None:
+ with client.customers.with_streaming_response.sync_payment_methods_from_gateway(
+ "external_customer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ customer = response.parse()
+ assert customer is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_sync_payment_methods_from_gateway(self, client: Orb) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_customer_id` but received ''"):
+ client.customers.with_raw_response.sync_payment_methods_from_gateway(
+ "",
+ )
+
+ @parametrize
+ def test_method_sync_payment_methods_from_gateway_by_external_customer_id(self, client: Orb) -> None:
+ customer = client.customers.sync_payment_methods_from_gateway_by_external_customer_id(
+ "customer_id",
+ )
+ assert customer is None
+
+ @parametrize
+ def test_raw_response_sync_payment_methods_from_gateway_by_external_customer_id(self, client: Orb) -> None:
+ response = client.customers.with_raw_response.sync_payment_methods_from_gateway_by_external_customer_id(
+ "customer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ customer = response.parse()
+ assert customer is None
+
+ @parametrize
+ def test_streaming_response_sync_payment_methods_from_gateway_by_external_customer_id(self, client: Orb) -> None:
+ with client.customers.with_streaming_response.sync_payment_methods_from_gateway_by_external_customer_id(
+ "customer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ customer = response.parse()
+ assert customer is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_sync_payment_methods_from_gateway_by_external_customer_id(self, client: Orb) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `customer_id` but received ''"):
+ client.customers.with_raw_response.sync_payment_methods_from_gateway_by_external_customer_id(
+ "",
+ )
+
@parametrize
def test_method_update_by_external_id(self, client: Orb) -> None:
customer = client.customers.update_by_external_id(
@@ -775,6 +851,92 @@ async def test_path_params_fetch_by_external_id(self, async_client: AsyncOrb) ->
"",
)
+ @parametrize
+ async def test_method_sync_payment_methods_from_gateway(self, async_client: AsyncOrb) -> None:
+ customer = await async_client.customers.sync_payment_methods_from_gateway(
+ "external_customer_id",
+ )
+ assert customer is None
+
+ @parametrize
+ async def test_raw_response_sync_payment_methods_from_gateway(self, async_client: AsyncOrb) -> None:
+ response = await async_client.customers.with_raw_response.sync_payment_methods_from_gateway(
+ "external_customer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ customer = response.parse()
+ assert customer is None
+
+ @parametrize
+ async def test_streaming_response_sync_payment_methods_from_gateway(self, async_client: AsyncOrb) -> None:
+ async with async_client.customers.with_streaming_response.sync_payment_methods_from_gateway(
+ "external_customer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ customer = await response.parse()
+ assert customer is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_sync_payment_methods_from_gateway(self, async_client: AsyncOrb) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_customer_id` but received ''"):
+ await async_client.customers.with_raw_response.sync_payment_methods_from_gateway(
+ "",
+ )
+
+ @parametrize
+ async def test_method_sync_payment_methods_from_gateway_by_external_customer_id(
+ self, async_client: AsyncOrb
+ ) -> None:
+ customer = await async_client.customers.sync_payment_methods_from_gateway_by_external_customer_id(
+ "customer_id",
+ )
+ assert customer is None
+
+ @parametrize
+ async def test_raw_response_sync_payment_methods_from_gateway_by_external_customer_id(
+ self, async_client: AsyncOrb
+ ) -> None:
+ response = (
+ await async_client.customers.with_raw_response.sync_payment_methods_from_gateway_by_external_customer_id(
+ "customer_id",
+ )
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ customer = response.parse()
+ assert customer is None
+
+ @parametrize
+ async def test_streaming_response_sync_payment_methods_from_gateway_by_external_customer_id(
+ self, async_client: AsyncOrb
+ ) -> None:
+ async with async_client.customers.with_streaming_response.sync_payment_methods_from_gateway_by_external_customer_id(
+ "customer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ customer = await response.parse()
+ assert customer is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_sync_payment_methods_from_gateway_by_external_customer_id(
+ self, async_client: AsyncOrb
+ ) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `customer_id` but received ''"):
+ await async_client.customers.with_raw_response.sync_payment_methods_from_gateway_by_external_customer_id(
+ "",
+ )
+
@parametrize
async def test_method_update_by_external_id(self, async_client: AsyncOrb) -> None:
customer = await async_client.customers.update_by_external_id(