diff --git a/api.md b/api.md index 68de1f6..3e58d8f 100644 --- a/api.md +++ b/api.md @@ -10,7 +10,7 @@ A list method returns its first page, such as a `AddOnsListPage`: a subclass of model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, `get_next_page()`, `iter_pages()` and `body`, which iterates every item. The page classes are in `meteroid.api`. -[Add ons](#add-ons) · [Add ons entitlements](#add-ons-entitlements) · [Batch jobs](#batch-jobs) · [Checkout sessions](#checkout-sessions) · [Connect](#connect) · [Coupons](#coupons) · [Credit notes](#credit-notes) · [Custom properties](#custom-properties) · [Customers](#customers) · [Entitlements](#entitlements) · [Events](#events) · [Features](#features) · [Invoices](#invoices) · [Metrics](#metrics) · [Oauth](#oauth) · [Oauth apps](#oauth-apps) · [Plans](#plans) · [Plans versions](#plans-versions) · [Product families](#product-families) · [Products](#products) · [Products entitlements](#products-entitlements) · [Subscriptions](#subscriptions) · [Usage](#usage) · [Webhook endpoints](#webhook-endpoints) · [Webhook endpoints endpoints](#webhook-endpoints-endpoints) +[Add ons](#add-ons) · [Add ons entitlements](#add-ons-entitlements) · [Batch jobs](#batch-jobs) · [Budgets](#budgets) · [Checkout sessions](#checkout-sessions) · [Checks](#checks) · [Connect](#connect) · [Coupons](#coupons) · [Credit notes](#credit-notes) · [Credit units](#credit-units) · [Credit units prices](#credit-units-prices) · [Credits](#credits) · [Credits grants](#credits-grants) · [Credits wallets](#credits-wallets) · [Custom properties](#custom-properties) · [Customers](#customers) · [Entitlements](#entitlements) · [Events](#events) · [Features](#features) · [Invoices](#invoices) · [Metrics](#metrics) · [Oauth](#oauth) · [Oauth apps](#oauth-apps) · [Plans](#plans) · [Plans versions](#plans-versions) · [Product families](#product-families) · [Products](#products) · [Products entitlements](#products-entitlements) · [Subscriptions](#subscriptions) · [Usage](#usage) · [Usage customer](#usage-customer) · [Usage costs](#usage-costs) · [Usage costs customer](#usage-costs-customer) · [Usage costs subscription](#usage-costs-subscription) · [Webhook endpoints](#webhook-endpoints) · [Webhook endpoints endpoints](#webhook-endpoints-endpoints) ## Add ons @@ -44,6 +44,17 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | `client.batch_jobs.retrieve(batch_job_id: str) -> BatchJobDetailResponse` | `GET /api/v1/batch-jobs/{batch_job_id}` | [`BatchJobDetailResponse`](meteroid/models/batch_job_detail_response.py) | | `client.batch_jobs.list_failures(batch_job_id: str, *, chunk_id=..., limit=..., offset=...) -> BatchJobsListFailuresPage` | `GET /api/v1/batch-jobs/{batch_job_id}/failures` | [`BatchJobFailuresResponse`](meteroid/models/batch_job_failures_response.py) pages of [`BatchJobItemFailureResponse`](meteroid/models/batch_job_item_failure_response.py) | +## Budgets + +[`client.budgets`](meteroid/api/budgets.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.budgets.list(id_or_alias: str, *, include_archived=..., page=..., per_page=...) -> BudgetsListPage` | `GET /api/v1/customers/{id_or_alias}/budgets` | [`BudgetListResponse`](meteroid/models/budget_list_response.py) pages of [`Budget`](meteroid/models/budget.py) | +| `client.budgets.create(id_or_alias: str, *, amount: int, idempotency_key: str, behavior=..., credit_unit=..., currency=..., subscription_id=..., thresholds=...) -> Budget` | `POST /api/v1/customers/{id_or_alias}/budgets` | [`Budget`](meteroid/models/budget.py) | +| `client.budgets.update(id_or_alias: str, budget_id: str, *, expected_version: int, idempotency_key: str, amount=..., behavior=..., thresholds=...) -> Budget` | `PATCH /api/v1/customers/{id_or_alias}/budgets/{budget_id}` | [`Budget`](meteroid/models/budget.py) | +| `client.budgets.archive(id_or_alias: str, budget_id: str, *, idempotency_key: str) -> Budget` | `POST /api/v1/customers/{id_or_alias}/budgets/{budget_id}/archive` | [`Budget`](meteroid/models/budget.py) | + ## Checkout sessions [`client.checkout_sessions`](meteroid/api/checkout_sessions.py) @@ -55,6 +66,14 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | `client.checkout_sessions.retrieve(id: str) -> GetCheckoutSessionResponse` | `GET /api/v1/checkout-sessions/{id}` | [`GetCheckoutSessionResponse`](meteroid/models/get_checkout_session_response.py) | | `client.checkout_sessions.cancel(id: str) -> CancelCheckoutSessionResponse` | `POST /api/v1/checkout-sessions/{id}/cancel` | [`CancelCheckoutSessionResponse`](meteroid/models/cancel_checkout_session_response.py) | +## Checks + +[`client.checks`](meteroid/api/checks.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.checks.check_entitlement(id_or_alias: str, feature_id_or_code: str, *, requested=..., subscription_id=..., group=...) -> EntitlementCheck` | `GET /api/v1/customers/{id_or_alias}/entitlements/{feature_id_or_code}/check` | [`EntitlementCheck`](meteroid/models/entitlement_check.py) | + ## Connect [`client.connect`](meteroid/api/connect.py) @@ -62,7 +81,7 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | Method | Request | Returns | | --- | --- | --- | | `client.connect.list_connected_accounts() -> ConnectedAccountsResponse` | `GET /api/v1/connected-accounts` | [`ConnectedAccountsResponse`](meteroid/models/connected_accounts_response.py) | -| `client.connect.create_connected_account(*, connected_organization_id: UUID, connection_type=..., metadata=..., platform_customer_id=...) -> ConnectedAccount` | `POST /api/v1/connected-accounts` | [`ConnectedAccount`](meteroid/models/connected_account.py) | +| `client.connect.create_connected_account(*, email: str, organization_name: str, country=..., metadata=..., mode=..., redirect_url=...) -> CreateConnectedAccountResponse` | `POST /api/v1/connected-accounts` | [`CreateConnectedAccountResponse`](meteroid/models/create_connected_account_response.py) | | `client.connect.retrieve_connected_account(id: str) -> ConnectedAccount` | `GET /api/v1/connected-accounts/{id}` | [`ConnectedAccount`](meteroid/models/connected_account.py) | | `client.connect.disconnect_account(id: str) -> None` | `DELETE /api/v1/connected-accounts/{id}` | nothing | | `client.connect.create_onboarding_link(id: str, *, redirect_url: str) -> OnboardingLinkResponse` | `POST /api/v1/connected-accounts/{id}/onboarding` | [`OnboardingLinkResponse`](meteroid/models/onboarding_link_response.py) | @@ -94,6 +113,55 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | `client.credit_notes.download(credit_note_id: str) -> bytes` | `GET /api/v1/credit-notes/{credit_note_id}/download` | bytes | | `client.credit_notes.download_xml(credit_note_id: str) -> bytes` | `GET /api/v1/credit-notes/{credit_note_id}/xml` | bytes | +## Credit units + +[`client.credit_units`](meteroid/api/credit_units.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.credit_units.list(*, include_archived=...) -> CreditUnitListResponse` | `GET /api/v1/credit-units` | [`CreditUnitListResponse`](meteroid/models/credit_unit_list_response.py) | +| `client.credit_units.create(*, code: str, name: str, plural_name: str, precision: int, symbol=...) -> CreditUnit` | `POST /api/v1/credit-units` | [`CreditUnit`](meteroid/models/credit_unit.py) | +| `client.credit_units.retrieve(id: str) -> CreditUnit` | `GET /api/v1/credit-units/{id}` | [`CreditUnit`](meteroid/models/credit_unit.py) | +| `client.credit_units.update(id: str, *, archived=..., name=..., plural_name=..., product_id=..., symbol=...) -> CreditUnit` | `PATCH /api/v1/credit-units/{id}` | [`CreditUnit`](meteroid/models/credit_unit.py) | + +### Credit units prices + +[`client.credit_units.prices`](meteroid/api/credit_units_prices.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.credit_units.prices.list(id: str, *, include_archived=...) -> CreditUnitPriceListResponse` | `GET /api/v1/credit-units/{id}/prices` | [`CreditUnitPriceListResponse`](meteroid/models/credit_unit_price_list_response.py) | +| `client.credit_units.prices.update(id: str, currency: str, *, unit_price: str) -> CreditUnitPrice` | `PUT /api/v1/credit-units/{id}/prices/{currency}` | [`CreditUnitPrice`](meteroid/models/credit_unit_price.py) | +| `client.credit_units.prices.archive(id: str, currency: str) -> CreditUnitPrice` | `DELETE /api/v1/credit-units/{id}/prices/{currency}` | [`CreditUnitPrice`](meteroid/models/credit_unit_price.py) | + +## Credits + +[`client.credits`](meteroid/api/credits.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.credits.list_customer_credit_ledger(id_or_alias: str, *, currency=..., credit_unit=..., page=..., per_page=...) -> CreditsListCustomerCreditLedgerPage` | `GET /api/v1/customers/{id_or_alias}/credit-ledger` | [`LedgerListResponse`](meteroid/models/ledger_list_response.py) pages of [`LedgerEntry`](meteroid/models/ledger_entry.py) | +| `client.credits.create_customer_top_up(id_or_alias: str, *, amount: int, idempotency_key: str, collection=..., credit_unit=..., currency=..., expires_at=..., payment_method_id=..., unit_price=...) -> TopUpResponse` | `POST /api/v1/customers/{id_or_alias}/top-ups` | [`TopUpResponse`](meteroid/models/top_up_response.py) | + +### Credits grants + +[`client.credits.grants`](meteroid/api/credits_grants.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.credits.grants.list(id_or_alias: str, *, currency=..., credit_unit=..., status=..., subscription_id=..., usable_by_subscription_id=..., page=..., per_page=...) -> CreditsGrantsListPage` | `GET /api/v1/customers/{id_or_alias}/credit-grants` | [`CreditGrantListResponse`](meteroid/models/credit_grant_list_response.py) pages of [`CreditGrant`](meteroid/models/credit_grant.py) | +| `client.credits.grants.create(id_or_alias: str, *, amount: int, type: CreditGrantType, idempotency_key: str, applicable_product_ids=..., applies_to=..., cost=..., credit_unit=..., currency=..., expires_at=..., priority=..., reason=..., reference=..., subscription_id=...) -> CreditGrant` | `POST /api/v1/customers/{id_or_alias}/credit-grants` | [`CreditGrant`](meteroid/models/credit_grant.py) | + +### Credits wallets + +[`client.credits.wallets`](meteroid/api/credits_wallets.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.credits.wallets.list(id_or_alias: str, *, subscription_id=...) -> WalletListResponse` | `GET /api/v1/customers/{id_or_alias}/wallets` | [`WalletListResponse`](meteroid/models/wallet_list_response.py) | +| `client.credits.wallets.check(id_or_alias: str, *, currency=..., credit_unit=..., product_id=..., subscription_id=...) -> WalletCheck` | `GET /api/v1/customers/{id_or_alias}/wallets/check` | [`WalletCheck`](meteroid/models/wallet_check.py) | +| `client.credits.wallets.update(id_or_alias: str, currency: str, *, idempotency_key: str, auto_top_up=..., low_balance_thresholds=..., unit_price=..., usage_at_zero=...) -> Wallet` | `PATCH /api/v1/customers/{id_or_alias}/wallets/{currency}` | [`Wallet`](meteroid/models/wallet.py) | + ## Custom properties [`client.custom_properties`](meteroid/api/custom_properties.py) @@ -173,9 +241,9 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | Method | Request | Returns | | --- | --- | --- | | `client.metrics.list(*, product_family_id=..., search=..., order_by=..., page=..., per_page=...) -> MetricsListPage` | `GET /api/v1/metrics` | [`MetricListResponse`](meteroid/models/metric_list_response.py) pages of [`MetricSummary`](meteroid/models/metric_summary.py) | -| `client.metrics.create(*, aggregation_type: BillingMetricAggregateEnum, code: str, name: str, product_family_id: ProductFamilyId, aggregation_key=..., description=..., filters=..., product_id=..., segmentation_matrix=..., unit_conversion=..., usage_group_key=...) -> Metric` | `POST /api/v1/metrics` | [`Metric`](meteroid/models/metric.py) | +| `client.metrics.create(*, aggregation_type: BillingMetricAggregateEnum, code: str, name: str, product_family_id: ProductFamilyId, aggregation_key=..., description=..., filters=..., product_id=..., segmentation_matrix=..., unit_conversion=..., unit_plural=..., unit_singular=..., usage_group_key=...) -> Metric` | `POST /api/v1/metrics` | [`Metric`](meteroid/models/metric.py) | | `client.metrics.retrieve(metric_id: str) -> Metric` | `GET /api/v1/metrics/{metric_id}` | [`Metric`](meteroid/models/metric.py) | -| `client.metrics.update(metric_id: str, *, description=..., filters=..., name=..., segmentation_matrix=..., unit_conversion=...) -> Metric` | `PATCH /api/v1/metrics/{metric_id}` | [`Metric`](meteroid/models/metric.py) | +| `client.metrics.update(metric_id: str, *, description=..., filters=..., name=..., segmentation_matrix=..., unit_conversion=..., unit_plural=..., unit_singular=...) -> Metric` | `PATCH /api/v1/metrics/{metric_id}` | [`Metric`](meteroid/models/metric.py) | | `client.metrics.archive(metric_id: str) -> None` | `POST /api/v1/metrics/{metric_id}/archive` | nothing | | `client.metrics.unarchive(metric_id: str) -> None` | `POST /api/v1/metrics/{metric_id}/unarchive` | nothing | @@ -210,9 +278,9 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | `client.plans.list_plan_version_entitlements(plan_version_id: str) -> ResolvedEntitlementListResponse` | `GET /api/v1/plan-versions/{plan_version_id}/entitlements` | [`ResolvedEntitlementListResponse`](meteroid/models/resolved_entitlement_list_response.py) | | `client.plans.create_plan_version_entitlement(plan_version_id: str, *, entitlements: list[EntitlementSpecRequest]) -> EntitlementListResponse` | `POST /api/v1/plan-versions/{plan_version_id}/entitlements` | [`EntitlementListResponse`](meteroid/models/entitlement_list_response.py) | | `client.plans.list(*, product_family_id=..., search=..., status=..., plan_type=..., order_by=..., page=..., per_page=...) -> PlansListPage` | `GET /api/v1/plans` | [`PlanListResponse`](meteroid/models/plan_list_response.py) pages of [`Plan`](meteroid/models/plan.py) | -| `client.plans.create(*, components: list[PriceComponentInput], currency: str, name: str, plan_type: PlanTypeEnum, product_family_id: ProductFamilyId, status: PlanStatusEnum, add_ons=..., billing=..., description=..., entitlements=..., self_service_rank=..., tax_inclusive=..., trial=...) -> Plan` | `POST /api/v1/plans` | [`Plan`](meteroid/models/plan.py) | +| `client.plans.create(*, components: list[PriceComponentInput], currency: str, name: str, plan_type: PlanTypeEnum, product_family_id: ProductFamilyId, status: PlanStatusEnum, add_ons=..., billing=..., credit_exhausted_rule=..., description=..., entitlements=..., self_service_rank=..., tax_inclusive=..., trial=...) -> Plan` | `POST /api/v1/plans` | [`Plan`](meteroid/models/plan.py) | | `client.plans.retrieve(plan_id: str, *, version=...) -> Plan` | `GET /api/v1/plans/{plan_id}` | [`Plan`](meteroid/models/plan.py) | -| `client.plans.replace(plan_id: str, *, components: list[PriceComponentInput], currency: str, name: str, add_ons=..., billing=..., description=..., entitlements=..., minimum_commitment=..., status=..., tax_inclusive=..., trial=...) -> Plan` | `PUT /api/v1/plans/{plan_id}` | [`Plan`](meteroid/models/plan.py) | +| `client.plans.replace(plan_id: str, *, components: list[PriceComponentInput], currency: str, name: str, add_ons=..., billing=..., credit_exhausted_rule=..., description=..., entitlements=..., minimum_commitment=..., status=..., tax_inclusive=..., trial=...) -> Plan` | `PUT /api/v1/plans/{plan_id}` | [`Plan`](meteroid/models/plan.py) | | `client.plans.update(plan_id: str, *, description=..., name=..., self_service_rank=...) -> Plan` | `PATCH /api/v1/plans/{plan_id}` | [`Plan`](meteroid/models/plan.py) | | `client.plans.archive(plan_id: str) -> None` | `POST /api/v1/plans/{plan_id}/archive` | nothing | | `client.plans.publish(plan_id: str) -> Plan` | `POST /api/v1/plans/{plan_id}/publish` | [`Plan`](meteroid/models/plan.py) | @@ -267,9 +335,9 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | Method | Request | Returns | | --- | --- | --- | | `client.subscriptions.list(*, customer_id=..., plan_id=..., statuses=..., order_by=..., page=..., per_page=...) -> SubscriptionsListPage` | `GET /api/v1/subscriptions` | [`SubscriptionListResponse`](meteroid/models/subscription_list_response.py) pages of [`Subscription`](meteroid/models/subscription.py) | -| `client.subscriptions.create(*, activation_condition: SubscriptionActivationConditionEnum, customer_id_or_alias: str, plan_id: PlanId, start_date: date, add_ons=..., auto_advance_invoices=..., backdate_invoices=..., billing_day_anchor=..., charge_automatically=..., coupon_codes=..., custom_properties=..., end_date=..., invoice_memo=..., net_terms=..., payment_methods_config=..., price_components=..., purchase_order=..., skip_past_invoices=..., trial_days=..., version=...) -> SubscriptionDetails` | `POST /api/v1/subscriptions` | [`SubscriptionDetails`](meteroid/models/subscription_details.py) | +| `client.subscriptions.create(*, activation_condition: SubscriptionActivationConditionEnum, customer_id_or_alias: str, plan_id: PlanId, start_date: date, add_ons=..., auto_advance_invoices=..., backdate_invoices=..., billing_day_anchor=..., charge_automatically=..., coupon_codes=..., custom_properties=..., end_date=..., invoice_memo=..., net_terms=..., payment_methods_config=..., price_components=..., purchase_order=..., skip_past_invoices=..., trial_days=..., usage_filter=..., version=...) -> SubscriptionDetails` | `POST /api/v1/subscriptions` | [`SubscriptionDetails`](meteroid/models/subscription_details.py) | | `client.subscriptions.retrieve(subscription_id: str) -> SubscriptionDetails` | `GET /api/v1/subscriptions/{subscription_id}` | [`SubscriptionDetails`](meteroid/models/subscription_details.py) | -| `client.subscriptions.update(subscription_id: str, *, auto_advance_invoices=..., charge_automatically=..., custom_properties=..., invoice_memo=..., net_terms=..., payment_methods_config=..., purchase_order=...) -> SubscriptionUpdateResponse` | `PATCH /api/v1/subscriptions/{subscription_id}` | [`SubscriptionUpdateResponse`](meteroid/models/subscription_update_response.py) | +| `client.subscriptions.update(subscription_id: str, *, auto_advance_invoices=..., charge_automatically=..., custom_properties=..., invoice_memo=..., net_terms=..., payment_methods_config=..., purchase_order=..., usage_filter=...) -> SubscriptionUpdateResponse` | `PATCH /api/v1/subscriptions/{subscription_id}` | [`SubscriptionUpdateResponse`](meteroid/models/subscription_update_response.py) | | `client.subscriptions.cancel(subscription_id: str, *, effective_date=..., reason=...) -> CancelSubscriptionResponse` | `POST /api/v1/subscriptions/{subscription_id}/cancel` | [`CancelSubscriptionResponse`](meteroid/models/cancel_subscription_response.py) | | `client.subscriptions.list_entitlements(subscription_id: str) -> EffectiveEntitlementListResponse` | `GET /api/v1/subscriptions/{subscription_id}/entitlements` | [`EffectiveEntitlementListResponse`](meteroid/models/effective_entitlement_list_response.py) | | `client.subscriptions.retrieve_summary(subscription_id: str) -> Subscription` | `GET /api/v1/subscriptions/{subscription_id}/summary` | [`Subscription`](meteroid/models/subscription.py) | @@ -280,10 +348,43 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | Method | Request | Returns | | --- | --- | --- | -| `client.usage.retrieve_customer(customer_id: str, *, start_date: date, end_date: date, metric_id=...) -> UsageResponse` | `GET /api/v1/usage/customer/{customer_id}` | [`UsageResponse`](meteroid/models/usage_response.py) | | `client.usage.retrieve_subscription(subscription_id: str, *, start_date=..., end_date=..., metric_id=...) -> UsageResponse` | `GET /api/v1/usage/subscription/{subscription_id}` | [`UsageResponse`](meteroid/models/usage_response.py) | | `client.usage.retrieve_summary(*, start_date: date, end_date: date, metric_id=...) -> UsageResponse` | `GET /api/v1/usage/summary` | [`UsageResponse`](meteroid/models/usage_response.py) | +### Usage customer + +[`client.usage.customer`](meteroid/api/usage_customer.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.usage.customer.retrieve(customer_id: str, *, start_date: date, end_date: date, metric_id=...) -> UsageResponse` | `GET /api/v1/usage/customer/{customer_id}` | [`UsageResponse`](meteroid/models/usage_response.py) | +| `client.usage.customer.retrieve_unattributed(customer_id: str, *, start_date=..., end_date=...) -> UnattributedUsageResponse` | `GET /api/v1/usage/customer/{customer_id}/unattributed` | [`UnattributedUsageResponse`](meteroid/models/unattributed_usage_response.py) | + +## Usage costs + +[`client.usage_costs`](meteroid/api/usage_costs.py) + +| Method | Request | Returns | +| --- | --- | --- | + +### Usage costs customer + +[`client.usage_costs.customer`](meteroid/api/usage_costs_customer.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.usage_costs.customer.list_costs(customer_id: str, *, metric: str, granularity: ListCustomerUsageCostsGranularity, from_: datetime, to: datetime, group_by=...) -> UsageCostListResponse` | `GET /api/v1/usage/customer/{customer_id}/costs` | [`UsageCostListResponse`](meteroid/models/usage_cost_list_response.py) | +| `client.usage_costs.customer.list_events(customer_id: str, *, metric: str, from_: datetime, to: datetime, cursor=..., limit=...) -> UsageEventListResponse` | `GET /api/v1/usage/customer/{customer_id}/events` | [`UsageEventListResponse`](meteroid/models/usage_event_list_response.py) | + +### Usage costs subscription + +[`client.usage_costs.subscription`](meteroid/api/usage_costs_subscription.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.usage_costs.subscription.list_costs(subscription_id: str, *, metric: str, granularity: ListSubscriptionUsageCostsGranularity, from_: datetime, to: datetime, group_by=...) -> UsageCostListResponse` | `GET /api/v1/usage/subscription/{subscription_id}/costs` | [`UsageCostListResponse`](meteroid/models/usage_cost_list_response.py) | +| `client.usage_costs.subscription.list_events(subscription_id: str, *, metric: str, from_: datetime, to: datetime, cursor=..., limit=...) -> UsageEventListResponse` | `GET /api/v1/usage/subscription/{subscription_id}/events` | [`UsageEventListResponse`](meteroid/models/usage_event_list_response.py) | + ## Webhook endpoints [`client.webhook_endpoints`](meteroid/api/webhook_endpoints.py) diff --git a/meteroid/api/__init__.py b/meteroid/api/__init__.py index 93a4158..5efa1f4 100644 --- a/meteroid/api/__init__.py +++ b/meteroid/api/__init__.py @@ -43,8 +43,11 @@ AsyncAddOnsListPage, AsyncBatchJobsListFailuresPage, AsyncBatchJobsListPage, + AsyncBudgetsListPage, AsyncCouponsListPage, AsyncCreditNotesListPage, + AsyncCreditsGrantsListPage, + AsyncCreditsListCustomerCreditLedgerPage, AsyncCustomersListPage, AsyncCustomPropertiesListCustomPropertyDefinitionsPage, AsyncFeaturesListPage, @@ -58,8 +61,11 @@ AsyncWebhookEndpointsEndpointsListDeliveriesPage, BatchJobsListFailuresPage, BatchJobsListPage, + BudgetsListPage, CouponsListPage, CreditNotesListPage, + CreditsGrantsListPage, + CreditsListCustomerCreditLedgerPage, CustomersListPage, CustomPropertiesListCustomPropertyDefinitionsPage, FeaturesListPage, @@ -75,10 +81,17 @@ from .add_ons import AddOns, AsyncAddOns from .add_ons_entitlements import AddOnsEntitlements, AsyncAddOnsEntitlements from .batch_jobs import AsyncBatchJobs, BatchJobs + from .budgets import AsyncBudgets, Budgets from .checkout_sessions import AsyncCheckoutSessions, CheckoutSessions + from .checks import AsyncChecks, Checks from .connect import AsyncConnect, Connect from .coupons import AsyncCoupons, Coupons from .credit_notes import AsyncCreditNotes, CreditNotes + from .credit_units import AsyncCreditUnits, CreditUnits + from .credit_units_prices import AsyncCreditUnitsPrices, CreditUnitsPrices + from .credits import AsyncCredits, Credits + from .credits_grants import AsyncCreditsGrants, CreditsGrants + from .credits_wallets import AsyncCreditsWallets, CreditsWallets from .custom_properties import AsyncCustomProperties, CustomProperties from .customers import AsyncCustomers, Customers from .entitlements import AsyncEntitlements, Entitlements @@ -95,6 +108,13 @@ from .products_entitlements import AsyncProductsEntitlements, ProductsEntitlements from .subscriptions import AsyncSubscriptions, Subscriptions from .usage import AsyncUsage, Usage + from .usage_costs import AsyncUsageCosts, UsageCosts + from .usage_costs_customer import AsyncUsageCostsCustomer, UsageCostsCustomer + from .usage_costs_subscription import ( + AsyncUsageCostsSubscription, + UsageCostsSubscription, + ) + from .usage_customer import AsyncUsageCustomer, UsageCustomer from .webhook_endpoints import AsyncWebhookEndpoints, WebhookEndpoints from .webhook_endpoints_endpoints import ( AsyncWebhookEndpointsEndpoints, @@ -109,14 +129,28 @@ "AddOnsEntitlements": "add_ons_entitlements", "AsyncBatchJobs": "batch_jobs", "BatchJobs": "batch_jobs", + "AsyncBudgets": "budgets", + "Budgets": "budgets", "AsyncCheckoutSessions": "checkout_sessions", "CheckoutSessions": "checkout_sessions", + "AsyncChecks": "checks", + "Checks": "checks", "AsyncConnect": "connect", "Connect": "connect", "AsyncCoupons": "coupons", "Coupons": "coupons", "AsyncCreditNotes": "credit_notes", "CreditNotes": "credit_notes", + "AsyncCreditUnits": "credit_units", + "CreditUnits": "credit_units", + "AsyncCreditUnitsPrices": "credit_units_prices", + "CreditUnitsPrices": "credit_units_prices", + "AsyncCredits": "credits", + "Credits": "credits", + "AsyncCreditsGrants": "credits_grants", + "CreditsGrants": "credits_grants", + "AsyncCreditsWallets": "credits_wallets", + "CreditsWallets": "credits_wallets", "AsyncCustomProperties": "custom_properties", "CustomProperties": "custom_properties", "AsyncCustomers": "customers", @@ -149,6 +183,14 @@ "Subscriptions": "subscriptions", "AsyncUsage": "usage", "Usage": "usage", + "AsyncUsageCustomer": "usage_customer", + "UsageCustomer": "usage_customer", + "AsyncUsageCosts": "usage_costs", + "UsageCosts": "usage_costs", + "AsyncUsageCostsCustomer": "usage_costs_customer", + "UsageCostsCustomer": "usage_costs_customer", + "AsyncUsageCostsSubscription": "usage_costs_subscription", + "UsageCostsSubscription": "usage_costs_subscription", "AsyncWebhookEndpoints": "webhook_endpoints", "WebhookEndpoints": "webhook_endpoints", "AsyncWebhookEndpointsEndpoints": "webhook_endpoints_endpoints", @@ -159,10 +201,16 @@ "BatchJobsListPage": "_pages", "AsyncBatchJobsListFailuresPage": "_pages", "BatchJobsListFailuresPage": "_pages", + "AsyncBudgetsListPage": "_pages", + "BudgetsListPage": "_pages", "AsyncCouponsListPage": "_pages", "CouponsListPage": "_pages", "AsyncCreditNotesListPage": "_pages", "CreditNotesListPage": "_pages", + "AsyncCreditsListCustomerCreditLedgerPage": "_pages", + "CreditsListCustomerCreditLedgerPage": "_pages", + "AsyncCreditsGrantsListPage": "_pages", + "CreditsGrantsListPage": "_pages", "AsyncCustomPropertiesListCustomPropertyDefinitionsPage": "_pages", "CustomPropertiesListCustomPropertyDefinitionsPage": "_pages", "AsyncCustomersListPage": "_pages", @@ -235,14 +283,28 @@ def __dir__() -> list[str]: "AddOnsEntitlements", "AsyncBatchJobs", "BatchJobs", + "AsyncBudgets", + "Budgets", "AsyncCheckoutSessions", "CheckoutSessions", + "AsyncChecks", + "Checks", "AsyncConnect", "Connect", "AsyncCoupons", "Coupons", "AsyncCreditNotes", "CreditNotes", + "AsyncCreditUnits", + "CreditUnits", + "AsyncCreditUnitsPrices", + "CreditUnitsPrices", + "AsyncCredits", + "Credits", + "AsyncCreditsGrants", + "CreditsGrants", + "AsyncCreditsWallets", + "CreditsWallets", "AsyncCustomProperties", "CustomProperties", "AsyncCustomers", @@ -275,6 +337,14 @@ def __dir__() -> list[str]: "Subscriptions", "AsyncUsage", "Usage", + "AsyncUsageCustomer", + "UsageCustomer", + "AsyncUsageCosts", + "UsageCosts", + "AsyncUsageCostsCustomer", + "UsageCostsCustomer", + "AsyncUsageCostsSubscription", + "UsageCostsSubscription", "AsyncWebhookEndpoints", "WebhookEndpoints", "AsyncWebhookEndpointsEndpoints", @@ -285,10 +355,16 @@ def __dir__() -> list[str]: "BatchJobsListPage", "AsyncBatchJobsListFailuresPage", "BatchJobsListFailuresPage", + "AsyncBudgetsListPage", + "BudgetsListPage", "AsyncCouponsListPage", "CouponsListPage", "AsyncCreditNotesListPage", "CreditNotesListPage", + "AsyncCreditsListCustomerCreditLedgerPage", + "CreditsListCustomerCreditLedgerPage", + "AsyncCreditsGrantsListPage", + "CreditsGrantsListPage", "AsyncCustomPropertiesListCustomPropertyDefinitionsPage", "CustomPropertiesListCustomPropertyDefinitionsPage", "AsyncCustomersListPage", diff --git a/meteroid/api/_pages.py b/meteroid/api/_pages.py index c9dfc69..63de7fc 100644 --- a/meteroid/api/_pages.py +++ b/meteroid/api/_pages.py @@ -10,8 +10,12 @@ BatchJobItemFailureResponse, BatchJobListResponse, BatchJobResponse, + Budget, + BudgetListResponse, Coupon, CouponListResponse, + CreditGrant, + CreditGrantListResponse, CreditNote, CreditNoteListResponse, Customer, @@ -22,6 +26,8 @@ FeatureListResponse, Invoice, InvoiceListResponse, + LedgerEntry, + LedgerListResponse, MetricListResponse, MetricSummary, Plan, @@ -46,10 +52,16 @@ "BatchJobsListPage", "AsyncBatchJobsListFailuresPage", "BatchJobsListFailuresPage", + "AsyncBudgetsListPage", + "BudgetsListPage", "AsyncCouponsListPage", "CouponsListPage", "AsyncCreditNotesListPage", "CreditNotesListPage", + "AsyncCreditsListCustomerCreditLedgerPage", + "CreditsListCustomerCreditLedgerPage", + "AsyncCreditsGrantsListPage", + "CreditsGrantsListPage", "AsyncCustomPropertiesListCustomPropertyDefinitionsPage", "CustomPropertiesListCustomPropertyDefinitionsPage", "AsyncCustomersListPage", @@ -109,6 +121,14 @@ class AsyncBatchJobsListFailuresPage( """A page of :class:`BatchJobItemFailureResponse`, and the :class:`BatchJobFailuresResponse` response of its request.""" +class BudgetsListPage(SyncPage[Budget, BudgetListResponse], BudgetListResponse): + """A page of :class:`Budget`, and the :class:`BudgetListResponse` response of its request.""" + + +class AsyncBudgetsListPage(AsyncPage[Budget, BudgetListResponse], BudgetListResponse): + """A page of :class:`Budget`, and the :class:`BudgetListResponse` response of its request.""" + + class CouponsListPage(SyncPage[Coupon, CouponListResponse], CouponListResponse): """A page of :class:`Coupon`, and the :class:`CouponListResponse` response of its request.""" @@ -129,6 +149,30 @@ class AsyncCreditNotesListPage( """A page of :class:`CreditNote`, and the :class:`CreditNoteListResponse` response of its request.""" +class CreditsListCustomerCreditLedgerPage( + SyncPage[LedgerEntry, LedgerListResponse], LedgerListResponse +): + """A page of :class:`LedgerEntry`, and the :class:`LedgerListResponse` response of its request.""" + + +class AsyncCreditsListCustomerCreditLedgerPage( + AsyncPage[LedgerEntry, LedgerListResponse], LedgerListResponse +): + """A page of :class:`LedgerEntry`, and the :class:`LedgerListResponse` response of its request.""" + + +class CreditsGrantsListPage( + SyncPage[CreditGrant, CreditGrantListResponse], CreditGrantListResponse +): + """A page of :class:`CreditGrant`, and the :class:`CreditGrantListResponse` response of its request.""" + + +class AsyncCreditsGrantsListPage( + AsyncPage[CreditGrant, CreditGrantListResponse], CreditGrantListResponse +): + """A page of :class:`CreditGrant`, and the :class:`CreditGrantListResponse` response of its request.""" + + class CustomPropertiesListCustomPropertyDefinitionsPage( SyncPage[CustomPropertyDefinition, CustomPropertyDefinitionListResponse], CustomPropertyDefinitionListResponse, diff --git a/meteroid/api/budgets.py b/meteroid/api/budgets.py new file mode 100644 index 0000000..c649a17 --- /dev/null +++ b/meteroid/api/budgets.py @@ -0,0 +1,515 @@ +# this file is @generated +"""Budgets API.""" + +from __future__ import annotations + +import builtins +import typing as t + +from .. import models as _models +from ..models import ( + Budget, + BudgetBehavior, + BudgetBehaviorLiteral, + BudgetListResponse, + CreateBudgetRequest, + Currency, + CurrencyLiteral, + PatchBudgetRequest, +) +from ..serialization import UNSET, Unset, to_json_value +from ._pages import ( + AsyncBudgetsListPage, + BudgetsListPage, +) +from ._pagination import ( + AsyncPaginator, + PagePaging, + step, +) +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + header_value, + serialize_query_params, +) + + +class AsyncBudgets(ApiBaseAsync): + """Budgets API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncBudgetsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncBudgetsWithRawResponse(self) + + def list( + self, + id_or_alias: str, + *, + include_archived: bool | None = None, + page: int | None = None, + per_page: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> AsyncPaginator[Budget, AsyncBudgetsListPage]: + """List budgets + + A customer's budgets, oldest first, with their estimated spend in the current billing period. + Archived ones are left out unless requested. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param include_archived: Defaults to false. + :param page: Page number (0-indexed) + :param per_page: Number of items per page""" + + async def fetch(page_param: int | None) -> BudgetListResponse: + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/budgets", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "include_archived": include_archived, + "page": page_param, + "per_page": per_page, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, BudgetListResponse) + + paging = PagePaging[BudgetListResponse, Budget]( + items=lambda body: body.data, + total_pages=lambda body: step( + body.pagination_meta, lambda v: v.total_pages + ), + first_page=0, + ) + return AsyncPaginator(lambda: AsyncBudgetsListPage._first(fetch, page, paging)) + + async def create( + self, + id_or_alias: str, + *, + amount: int, + idempotency_key: str, + behavior: BudgetBehavior | BudgetBehaviorLiteral | None | Unset = UNSET, + credit_unit: str | None | Unset = UNSET, + currency: Currency | CurrencyLiteral | None | Unset = UNSET, + subscription_id: str | None | Unset = UNSET, + thresholds: builtins.list[int] | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Budget: + """Create a budget + + Sets a spending limit for one currency the customer is billed in, with alerts at percentages + of it, on the customer's whole spend or on one subscription's. A customer has one active + budget per currency, and per subscription. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param amount: In minor units of the currency or credit unit. + :param credit_unit: A credit unit (its code): the budget counts the usage priced in that unit, in its minor units. + :param subscription_id: An active subscription of the customer (`sub_…`): the budget then counts only that subscription's spend, in its currency. Absent counts the whole customer's. + :param thresholds: Percentages of the amount (1 to 100, at most 10). Defaults to 50, 80 and 100.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/budgets", + path_params={ + "id_or_alias": id_or_alias, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + CreateBudgetRequest( + amount=amount, + behavior=t.cast("BudgetBehavior | None | Unset", behavior), + credit_unit=credit_unit, + currency=t.cast("Currency | None | Unset", currency), + subscription_id=subscription_id, + thresholds=thresholds, + ), + CreateBudgetRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Budget) + + async def update( + self, + id_or_alias: str, + budget_id: str, + *, + expected_version: int, + idempotency_key: str, + amount: int | None | Unset = UNSET, + behavior: BudgetBehavior | BudgetBehaviorLiteral | None | Unset = UNSET, + thresholds: builtins.list[int] | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Budget: + """Update a budget + + Changes the amount, thresholds or behavior. Send the `version` you read as `expected_version`; a newer + stored version, for instance after the customer edited it, is refused with 409. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param expected_version: The `version` you read; a newer stored version is refused with 409.""" + response = await self._request( + ApiRequest( + method="patch", + path="/api/v1/customers/{id_or_alias}/budgets/{budget_id}", + path_params={ + "id_or_alias": id_or_alias, + "budget_id": budget_id, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + PatchBudgetRequest( + amount=amount, + behavior=t.cast("BudgetBehavior | None | Unset", behavior), + expected_version=expected_version, + thresholds=thresholds, + ), + PatchBudgetRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Budget) + + async def archive( + self, + id_or_alias: str, + budget_id: str, + *, + idempotency_key: str, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Budget: + """Archive a budget + + Stops the budget from sending alerts and, for a HARD budget, from refusing access. Archiving an + archived budget returns it unchanged. + + Beta: the request and response shapes of this endpoint may change before general availability.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/budgets/{budget_id}/archive", + path_params={ + "id_or_alias": id_or_alias, + "budget_id": budget_id, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Budget) + + +class AsyncBudgetsWithRawResponse: + """The methods of :class:`AsyncBudgets`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncBudgets) -> None: + self.list = async_to_raw_response_wrapper(resource.list) + self.create = async_to_raw_response_wrapper(resource.create) + self.update = async_to_raw_response_wrapper(resource.update) + self.archive = async_to_raw_response_wrapper(resource.archive) + + +class Budgets(ApiBaseSync): + """Budgets API.""" + + @property + def with_raw_response(self) -> BudgetsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return BudgetsWithRawResponse(self) + + def list( + self, + id_or_alias: str, + *, + include_archived: bool | None = None, + page: int | None = None, + per_page: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> BudgetsListPage: + """List budgets + + A customer's budgets, oldest first, with their estimated spend in the current billing period. + Archived ones are left out unless requested. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param include_archived: Defaults to false. + :param page: Page number (0-indexed) + :param per_page: Number of items per page""" + + def fetch(page_param: int | None) -> BudgetListResponse: + response = self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/budgets", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "include_archived": include_archived, + "page": page_param, + "per_page": per_page, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, BudgetListResponse) + + paging = PagePaging[BudgetListResponse, Budget]( + items=lambda body: body.data, + total_pages=lambda body: step( + body.pagination_meta, lambda v: v.total_pages + ), + first_page=0, + ) + return BudgetsListPage._first(fetch, page, paging) + + def create( + self, + id_or_alias: str, + *, + amount: int, + idempotency_key: str, + behavior: BudgetBehavior | BudgetBehaviorLiteral | None | Unset = UNSET, + credit_unit: str | None | Unset = UNSET, + currency: Currency | CurrencyLiteral | None | Unset = UNSET, + subscription_id: str | None | Unset = UNSET, + thresholds: builtins.list[int] | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Budget: + """Create a budget + + Sets a spending limit for one currency the customer is billed in, with alerts at percentages + of it, on the customer's whole spend or on one subscription's. A customer has one active + budget per currency, and per subscription. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param amount: In minor units of the currency or credit unit. + :param credit_unit: A credit unit (its code): the budget counts the usage priced in that unit, in its minor units. + :param subscription_id: An active subscription of the customer (`sub_…`): the budget then counts only that subscription's spend, in its currency. Absent counts the whole customer's. + :param thresholds: Percentages of the amount (1 to 100, at most 10). Defaults to 50, 80 and 100.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/budgets", + path_params={ + "id_or_alias": id_or_alias, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + CreateBudgetRequest( + amount=amount, + behavior=t.cast("BudgetBehavior | None | Unset", behavior), + credit_unit=credit_unit, + currency=t.cast("Currency | None | Unset", currency), + subscription_id=subscription_id, + thresholds=thresholds, + ), + CreateBudgetRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Budget) + + def update( + self, + id_or_alias: str, + budget_id: str, + *, + expected_version: int, + idempotency_key: str, + amount: int | None | Unset = UNSET, + behavior: BudgetBehavior | BudgetBehaviorLiteral | None | Unset = UNSET, + thresholds: builtins.list[int] | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Budget: + """Update a budget + + Changes the amount, thresholds or behavior. Send the `version` you read as `expected_version`; a newer + stored version, for instance after the customer edited it, is refused with 409. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param expected_version: The `version` you read; a newer stored version is refused with 409.""" + response = self._request( + ApiRequest( + method="patch", + path="/api/v1/customers/{id_or_alias}/budgets/{budget_id}", + path_params={ + "id_or_alias": id_or_alias, + "budget_id": budget_id, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + PatchBudgetRequest( + amount=amount, + behavior=t.cast("BudgetBehavior | None | Unset", behavior), + expected_version=expected_version, + thresholds=thresholds, + ), + PatchBudgetRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Budget) + + def archive( + self, + id_or_alias: str, + budget_id: str, + *, + idempotency_key: str, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Budget: + """Archive a budget + + Stops the budget from sending alerts and, for a HARD budget, from refusing access. Archiving an + archived budget returns it unchanged. + + Beta: the request and response shapes of this endpoint may change before general availability.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/budgets/{budget_id}/archive", + path_params={ + "id_or_alias": id_or_alias, + "budget_id": budget_id, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Budget) + + +class BudgetsWithRawResponse: + """The methods of :class:`Budgets`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: Budgets) -> None: + self.list = to_raw_response_wrapper(resource.list) + self.create = to_raw_response_wrapper(resource.create) + self.update = to_raw_response_wrapper(resource.update) + self.archive = to_raw_response_wrapper(resource.archive) diff --git a/meteroid/api/checkout_sessions.py b/meteroid/api/checkout_sessions.py index 2499f6b..8068fb4 100644 --- a/meteroid/api/checkout_sessions.py +++ b/meteroid/api/checkout_sessions.py @@ -112,6 +112,7 @@ async def create( :param auto_advance_invoices: If false, invoices will stay in Draft until manually reviewed and finalized. Default is true. :param cancel_url: Absolute http(s) URL offered to the customer to leave the checkout without paying. :param charge_automatically: Automatically try to charge the customer's configured payment method on finalize. Default is true. + :param coupon_code: Code of the coupon to apply, used only when `coupon_ids` is empty. It names the coupon with exactly that code, else the only live coupon equal to it ignoring the case of ASCII letters. An unknown or ambiguous code is refused with 400. If the code no longer names a usable coupon when the customer pays, the payment is refused and nothing is charged. :param customer_id: Customer ID or alias :param expires_in_hours: Session expiry time in hours. Default is 1 hour for self-serve checkout. :param success_url: Absolute http(s) URL the customer is sent to after a successful checkout. `checkout_session_id` is appended as a query parameter. Without it the customer stays on the hosted confirmation page.""" @@ -305,6 +306,7 @@ def create( :param auto_advance_invoices: If false, invoices will stay in Draft until manually reviewed and finalized. Default is true. :param cancel_url: Absolute http(s) URL offered to the customer to leave the checkout without paying. :param charge_automatically: Automatically try to charge the customer's configured payment method on finalize. Default is true. + :param coupon_code: Code of the coupon to apply, used only when `coupon_ids` is empty. It names the coupon with exactly that code, else the only live coupon equal to it ignoring the case of ASCII letters. An unknown or ambiguous code is refused with 400. If the code no longer names a usable coupon when the customer pays, the payment is refused and nothing is charged. :param customer_id: Customer ID or alias :param expires_in_hours: Session expiry time in hours. Default is 1 hour for self-serve checkout. :param success_url: Absolute http(s) URL the customer is sent to after a successful checkout. `checkout_session_id` is appended as a query parameter. Without it the customer stays on the hosted confirmation page.""" diff --git a/meteroid/api/checks.py b/meteroid/api/checks.py new file mode 100644 index 0000000..3aad24b --- /dev/null +++ b/meteroid/api/checks.py @@ -0,0 +1,186 @@ +# this file is @generated +"""Checks API.""" + +from __future__ import annotations + +import typing as t +from decimal import Decimal + +from .. import models as _models +from ..models import ( + EntitlementCheck, +) +from ..serialization import UNSET, Unset +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + serialize_query_params, +) + + +class AsyncChecks(ApiBaseAsync): + """Checks API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncChecksWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncChecksWithRawResponse(self) + + async def check_entitlement( + self, + id_or_alias: str, + feature_id_or_code: str, + *, + requested: Decimal | None = None, + subscription_id: str | None = None, + group: dict[str, str] | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> EntitlementCheck: + """Check customer entitlement + + Whether the customer may consume `requested` more of a feature right now, given their + entitlement, the hard budgets that count the feature's usage, and their prepaid credit. + Boolean features answer from their enabled state; config features grant access and return + their `value`. An `ALLOW` limit never refuses: `in_overage` tells when the requested units go + beyond it. Sliding windows send no threshold webhooks. Pass the group as + `group[]=`: it is required when the entitlement is grouped. Pass + `subscription_id` when the usage belongs to one subscription, so that a hard budget on another + subscription does not refuse it. + + The answer reflects usage up to `as_of`: typically less than a minute old, and less than 10 + seconds old once `near_limit` is true. A hard limit can therefore be exceeded by the usage of + those few seconds. While `near_limit` is true, check before every request with its actual + `requested` and do not cache the answer; `retry_after_ms` says when to check again. Use the + check to gate access, not to bill. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param requested: Units the customer is about to consume. Defaults to 1. + :param subscription_id: The subscription (`sub_…`) the usage belongs to: only its own budgets and the customer-wide ones apply. Without it, a budget on one subscription refuses only once every subscription charging the feature's metric is refused by its own budget. + :param group: The group to check, as `group[]=`: required when the entitlement is grouped""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/entitlements/{feature_id_or_code}/check", + path_params={ + "id_or_alias": id_or_alias, + "feature_id_or_code": feature_id_or_code, + }, + query_params=serialize_query_params( + { + "requested": requested, + "subscription_id": subscription_id, + "group": group, + }, + structured=("group",), + deep_object=("group",), + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, EntitlementCheck) + + +class AsyncChecksWithRawResponse: + """The methods of :class:`AsyncChecks`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncChecks) -> None: + self.check_entitlement = async_to_raw_response_wrapper( + resource.check_entitlement + ) + + +class Checks(ApiBaseSync): + """Checks API.""" + + @property + def with_raw_response(self) -> ChecksWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return ChecksWithRawResponse(self) + + def check_entitlement( + self, + id_or_alias: str, + feature_id_or_code: str, + *, + requested: Decimal | None = None, + subscription_id: str | None = None, + group: dict[str, str] | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> EntitlementCheck: + """Check customer entitlement + + Whether the customer may consume `requested` more of a feature right now, given their + entitlement, the hard budgets that count the feature's usage, and their prepaid credit. + Boolean features answer from their enabled state; config features grant access and return + their `value`. An `ALLOW` limit never refuses: `in_overage` tells when the requested units go + beyond it. Sliding windows send no threshold webhooks. Pass the group as + `group[]=`: it is required when the entitlement is grouped. Pass + `subscription_id` when the usage belongs to one subscription, so that a hard budget on another + subscription does not refuse it. + + The answer reflects usage up to `as_of`: typically less than a minute old, and less than 10 + seconds old once `near_limit` is true. A hard limit can therefore be exceeded by the usage of + those few seconds. While `near_limit` is true, check before every request with its actual + `requested` and do not cache the answer; `retry_after_ms` says when to check again. Use the + check to gate access, not to bill. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param requested: Units the customer is about to consume. Defaults to 1. + :param subscription_id: The subscription (`sub_…`) the usage belongs to: only its own budgets and the customer-wide ones apply. Without it, a budget on one subscription refuses only once every subscription charging the feature's metric is refused by its own budget. + :param group: The group to check, as `group[]=`: required when the entitlement is grouped""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/entitlements/{feature_id_or_code}/check", + path_params={ + "id_or_alias": id_or_alias, + "feature_id_or_code": feature_id_or_code, + }, + query_params=serialize_query_params( + { + "requested": requested, + "subscription_id": subscription_id, + "group": group, + }, + structured=("group",), + deep_object=("group",), + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, EntitlementCheck) + + +class ChecksWithRawResponse: + """The methods of :class:`Checks`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: Checks) -> None: + self.check_entitlement = to_raw_response_wrapper(resource.check_entitlement) diff --git a/meteroid/api/client.py b/meteroid/api/client.py index 8ebe456..48fcaf1 100644 --- a/meteroid/api/client.py +++ b/meteroid/api/client.py @@ -29,12 +29,24 @@ BatchJobs, BatchJobsWithRawResponse, ) + from .budgets import ( + AsyncBudgets, + AsyncBudgetsWithRawResponse, + Budgets, + BudgetsWithRawResponse, + ) from .checkout_sessions import ( AsyncCheckoutSessions, AsyncCheckoutSessionsWithRawResponse, CheckoutSessions, CheckoutSessionsWithRawResponse, ) + from .checks import ( + AsyncChecks, + AsyncChecksWithRawResponse, + Checks, + ChecksWithRawResponse, + ) from .connect import ( AsyncConnect, AsyncConnectWithRawResponse, @@ -53,6 +65,18 @@ CreditNotes, CreditNotesWithRawResponse, ) + from .credit_units import ( + AsyncCreditUnits, + AsyncCreditUnitsWithRawResponse, + CreditUnits, + CreditUnitsWithRawResponse, + ) + from .credits import ( + AsyncCredits, + AsyncCreditsWithRawResponse, + Credits, + CreditsWithRawResponse, + ) from .custom_properties import ( AsyncCustomProperties, AsyncCustomPropertiesWithRawResponse, @@ -137,6 +161,12 @@ Usage, UsageWithRawResponse, ) + from .usage_costs import ( + AsyncUsageCosts, + AsyncUsageCostsWithRawResponse, + UsageCosts, + UsageCostsWithRawResponse, + ) from .webhook_endpoints import ( AsyncWebhookEndpoints, AsyncWebhookEndpointsWithRawResponse, @@ -304,6 +334,13 @@ def batch_jobs(self) -> BatchJobs: return BatchJobs(self._cfg, self._httpx_client) + @functools.cached_property + def budgets(self) -> Budgets: + """The budgets API.""" + from .budgets import Budgets + + return Budgets(self._cfg, self._httpx_client) + @functools.cached_property def checkout_sessions(self) -> CheckoutSessions: """The checkout sessions API.""" @@ -311,6 +348,13 @@ def checkout_sessions(self) -> CheckoutSessions: return CheckoutSessions(self._cfg, self._httpx_client) + @functools.cached_property + def checks(self) -> Checks: + """The checks API.""" + from .checks import Checks + + return Checks(self._cfg, self._httpx_client) + @functools.cached_property def connect(self) -> Connect: """The connect API.""" @@ -332,6 +376,20 @@ def credit_notes(self) -> CreditNotes: return CreditNotes(self._cfg, self._httpx_client) + @functools.cached_property + def credit_units(self) -> CreditUnits: + """The credit units API.""" + from .credit_units import CreditUnits + + return CreditUnits(self._cfg, self._httpx_client) + + @functools.cached_property + def credits(self) -> Credits: + """The credits API.""" + from .credits import Credits + + return Credits(self._cfg, self._httpx_client) + @functools.cached_property def custom_properties(self) -> CustomProperties: """The custom properties API.""" @@ -430,6 +488,13 @@ def usage(self) -> Usage: return Usage(self._cfg, self._httpx_client) + @functools.cached_property + def usage_costs(self) -> UsageCosts: + """The usage costs API.""" + from .usage_costs import UsageCosts + + return UsageCosts(self._cfg, self._httpx_client) + @functools.cached_property def webhook_endpoints(self) -> WebhookEndpoints: """The webhook endpoints API.""" @@ -458,6 +523,13 @@ def batch_jobs(self) -> BatchJobsWithRawResponse: return BatchJobsWithRawResponse(self._client.batch_jobs) + @property + def budgets(self) -> BudgetsWithRawResponse: + """The budgets API.""" + from .budgets import BudgetsWithRawResponse + + return BudgetsWithRawResponse(self._client.budgets) + @property def checkout_sessions(self) -> CheckoutSessionsWithRawResponse: """The checkout sessions API.""" @@ -465,6 +537,13 @@ def checkout_sessions(self) -> CheckoutSessionsWithRawResponse: return CheckoutSessionsWithRawResponse(self._client.checkout_sessions) + @property + def checks(self) -> ChecksWithRawResponse: + """The checks API.""" + from .checks import ChecksWithRawResponse + + return ChecksWithRawResponse(self._client.checks) + @property def connect(self) -> ConnectWithRawResponse: """The connect API.""" @@ -486,6 +565,20 @@ def credit_notes(self) -> CreditNotesWithRawResponse: return CreditNotesWithRawResponse(self._client.credit_notes) + @property + def credit_units(self) -> CreditUnitsWithRawResponse: + """The credit units API.""" + from .credit_units import CreditUnitsWithRawResponse + + return CreditUnitsWithRawResponse(self._client.credit_units) + + @property + def credits(self) -> CreditsWithRawResponse: + """The credits API.""" + from .credits import CreditsWithRawResponse + + return CreditsWithRawResponse(self._client.credits) + @property def custom_properties(self) -> CustomPropertiesWithRawResponse: """The custom properties API.""" @@ -584,6 +677,13 @@ def usage(self) -> UsageWithRawResponse: return UsageWithRawResponse(self._client.usage) + @property + def usage_costs(self) -> UsageCostsWithRawResponse: + """The usage costs API.""" + from .usage_costs import UsageCostsWithRawResponse + + return UsageCostsWithRawResponse(self._client.usage_costs) + @property def webhook_endpoints(self) -> WebhookEndpointsWithRawResponse: """The webhook endpoints API.""" @@ -712,6 +812,13 @@ def batch_jobs(self) -> AsyncBatchJobs: return AsyncBatchJobs(self._cfg, self._httpx_client) + @functools.cached_property + def budgets(self) -> AsyncBudgets: + """The budgets API.""" + from .budgets import AsyncBudgets + + return AsyncBudgets(self._cfg, self._httpx_client) + @functools.cached_property def checkout_sessions(self) -> AsyncCheckoutSessions: """The checkout sessions API.""" @@ -719,6 +826,13 @@ def checkout_sessions(self) -> AsyncCheckoutSessions: return AsyncCheckoutSessions(self._cfg, self._httpx_client) + @functools.cached_property + def checks(self) -> AsyncChecks: + """The checks API.""" + from .checks import AsyncChecks + + return AsyncChecks(self._cfg, self._httpx_client) + @functools.cached_property def connect(self) -> AsyncConnect: """The connect API.""" @@ -740,6 +854,20 @@ def credit_notes(self) -> AsyncCreditNotes: return AsyncCreditNotes(self._cfg, self._httpx_client) + @functools.cached_property + def credit_units(self) -> AsyncCreditUnits: + """The credit units API.""" + from .credit_units import AsyncCreditUnits + + return AsyncCreditUnits(self._cfg, self._httpx_client) + + @functools.cached_property + def credits(self) -> AsyncCredits: + """The credits API.""" + from .credits import AsyncCredits + + return AsyncCredits(self._cfg, self._httpx_client) + @functools.cached_property def custom_properties(self) -> AsyncCustomProperties: """The custom properties API.""" @@ -838,6 +966,13 @@ def usage(self) -> AsyncUsage: return AsyncUsage(self._cfg, self._httpx_client) + @functools.cached_property + def usage_costs(self) -> AsyncUsageCosts: + """The usage costs API.""" + from .usage_costs import AsyncUsageCosts + + return AsyncUsageCosts(self._cfg, self._httpx_client) + @functools.cached_property def webhook_endpoints(self) -> AsyncWebhookEndpoints: """The webhook endpoints API.""" @@ -866,6 +1001,13 @@ def batch_jobs(self) -> AsyncBatchJobsWithRawResponse: return AsyncBatchJobsWithRawResponse(self._client.batch_jobs) + @property + def budgets(self) -> AsyncBudgetsWithRawResponse: + """The budgets API.""" + from .budgets import AsyncBudgetsWithRawResponse + + return AsyncBudgetsWithRawResponse(self._client.budgets) + @property def checkout_sessions(self) -> AsyncCheckoutSessionsWithRawResponse: """The checkout sessions API.""" @@ -873,6 +1015,13 @@ def checkout_sessions(self) -> AsyncCheckoutSessionsWithRawResponse: return AsyncCheckoutSessionsWithRawResponse(self._client.checkout_sessions) + @property + def checks(self) -> AsyncChecksWithRawResponse: + """The checks API.""" + from .checks import AsyncChecksWithRawResponse + + return AsyncChecksWithRawResponse(self._client.checks) + @property def connect(self) -> AsyncConnectWithRawResponse: """The connect API.""" @@ -894,6 +1043,20 @@ def credit_notes(self) -> AsyncCreditNotesWithRawResponse: return AsyncCreditNotesWithRawResponse(self._client.credit_notes) + @property + def credit_units(self) -> AsyncCreditUnitsWithRawResponse: + """The credit units API.""" + from .credit_units import AsyncCreditUnitsWithRawResponse + + return AsyncCreditUnitsWithRawResponse(self._client.credit_units) + + @property + def credits(self) -> AsyncCreditsWithRawResponse: + """The credits API.""" + from .credits import AsyncCreditsWithRawResponse + + return AsyncCreditsWithRawResponse(self._client.credits) + @property def custom_properties(self) -> AsyncCustomPropertiesWithRawResponse: """The custom properties API.""" @@ -992,6 +1155,13 @@ def usage(self) -> AsyncUsageWithRawResponse: return AsyncUsageWithRawResponse(self._client.usage) + @property + def usage_costs(self) -> AsyncUsageCostsWithRawResponse: + """The usage costs API.""" + from .usage_costs import AsyncUsageCostsWithRawResponse + + return AsyncUsageCostsWithRawResponse(self._client.usage_costs) + @property def webhook_endpoints(self) -> AsyncWebhookEndpointsWithRawResponse: """The webhook endpoints API.""" diff --git a/meteroid/api/connect.py b/meteroid/api/connect.py index 856dbec..03eadbd 100644 --- a/meteroid/api/connect.py +++ b/meteroid/api/connect.py @@ -4,18 +4,18 @@ from __future__ import annotations import typing as t -from uuid import UUID from .. import models as _models from ..models import ( ConnectedAccount, ConnectedAccountsResponse, - ConnectionType, - ConnectionTypeLiteral, + CountryCode, CreateConnectedAccountRequest, + CreateConnectedAccountResponse, CreateOnboardingLinkRequest, - CustomerId, OnboardingLinkResponse, + OnboardingMode, + OnboardingModeLiteral, ) from ..serialization import UNSET, Unset, to_json_value from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper @@ -67,32 +67,34 @@ async def list_connected_accounts( async def create_connected_account( self, *, - connected_organization_id: UUID, - connection_type: ConnectionType | ConnectionTypeLiteral | None | Unset = UNSET, + email: str, + organization_name: str, + country: CountryCode | None | Unset = UNSET, metadata: t.Any = None, - platform_customer_id: CustomerId | None | Unset = UNSET, + mode: OnboardingMode | OnboardingModeLiteral | None | Unset = UNSET, + redirect_url: str | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, extra_body: t.Mapping[str, object] | None = None, timeout: Timeout | Unset = UNSET, max_retries: int | None = None, - ) -> ConnectedAccount: + ) -> CreateConnectedAccountResponse: """Create connected account - Create a new connected account (Express flow). Returns the account - and an onboarding link for the user to complete setup.""" + Create a new pending connected account (Express flow). Returns the + account and an onboarding link for the user to complete setup.""" response = await self._request( ApiRequest( method="post", path="/api/v1/connected-accounts", json_body=to_json_value( CreateConnectedAccountRequest( - connected_organization_id=connected_organization_id, - connection_type=t.cast( - "ConnectionType | None | Unset", connection_type - ), + country=country, + email=email, metadata=metadata, - platform_customer_id=platform_customer_id, + mode=t.cast("OnboardingMode | None | Unset", mode), + organization_name=organization_name, + redirect_url=redirect_url, ), CreateConnectedAccountRequest, ), @@ -106,7 +108,7 @@ async def create_connected_account( max_retries=max_retries, ) ) - return decode_response(response, ConnectedAccount) + return decode_response(response, CreateConnectedAccountResponse) async def retrieve_connected_account( self, @@ -272,32 +274,34 @@ def list_connected_accounts( def create_connected_account( self, *, - connected_organization_id: UUID, - connection_type: ConnectionType | ConnectionTypeLiteral | None | Unset = UNSET, + email: str, + organization_name: str, + country: CountryCode | None | Unset = UNSET, metadata: t.Any = None, - platform_customer_id: CustomerId | None | Unset = UNSET, + mode: OnboardingMode | OnboardingModeLiteral | None | Unset = UNSET, + redirect_url: str | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, extra_body: t.Mapping[str, object] | None = None, timeout: Timeout | Unset = UNSET, max_retries: int | None = None, - ) -> ConnectedAccount: + ) -> CreateConnectedAccountResponse: """Create connected account - Create a new connected account (Express flow). Returns the account - and an onboarding link for the user to complete setup.""" + Create a new pending connected account (Express flow). Returns the + account and an onboarding link for the user to complete setup.""" response = self._request( ApiRequest( method="post", path="/api/v1/connected-accounts", json_body=to_json_value( CreateConnectedAccountRequest( - connected_organization_id=connected_organization_id, - connection_type=t.cast( - "ConnectionType | None | Unset", connection_type - ), + country=country, + email=email, metadata=metadata, - platform_customer_id=platform_customer_id, + mode=t.cast("OnboardingMode | None | Unset", mode), + organization_name=organization_name, + redirect_url=redirect_url, ), CreateConnectedAccountRequest, ), @@ -311,7 +315,7 @@ def create_connected_account( max_retries=max_retries, ) ) - return decode_response(response, ConnectedAccount) + return decode_response(response, CreateConnectedAccountResponse) def retrieve_connected_account( self, diff --git a/meteroid/api/coupons.py b/meteroid/api/coupons.py index a49055e..85dbf64 100644 --- a/meteroid/api/coupons.py +++ b/meteroid/api/coupons.py @@ -118,7 +118,10 @@ async def create( timeout: Timeout | Unset = UNSET, max_retries: int | None = None, ) -> Coupon: - """Create a coupon""" + """Create a coupon + + :param code: 1 to 64 ASCII letters, digits, hyphens or underscores. A promotional-credit (`CREDIT`) code is stored upper-case. Codes are matched as described on `Coupon.code`. + :param reusable: Whether one customer may redeem the coupon more than once. Must be false for a promotional-credit (`CREDIT`) coupon.""" response = await self._request( ApiRequest( method="post", @@ -430,7 +433,10 @@ def create( timeout: Timeout | Unset = UNSET, max_retries: int | None = None, ) -> Coupon: - """Create a coupon""" + """Create a coupon + + :param code: 1 to 64 ASCII letters, digits, hyphens or underscores. A promotional-credit (`CREDIT`) code is stored upper-case. Codes are matched as described on `Coupon.code`. + :param reusable: Whether one customer may redeem the coupon more than once. Must be false for a promotional-credit (`CREDIT`) coupon.""" response = self._request( ApiRequest( method="post", diff --git a/meteroid/api/credit_units.py b/meteroid/api/credit_units.py new file mode 100644 index 0000000..0d276fe --- /dev/null +++ b/meteroid/api/credit_units.py @@ -0,0 +1,441 @@ +# this file is @generated +"""Credit units API.""" + +from __future__ import annotations + +import functools +import typing as t + +from .. import models as _models +from ..models import ( + CreditUnit, + CreditUnitCreateRequest, + CreditUnitListResponse, + CreditUnitUpdateRequest, +) +from ..serialization import UNSET, Unset, to_json_value +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + serialize_query_params, +) +from .credit_units_prices import ( + AsyncCreditUnitsPrices, + AsyncCreditUnitsPricesWithRawResponse, + CreditUnitsPrices, + CreditUnitsPricesWithRawResponse, +) + + +class AsyncCreditUnits(ApiBaseAsync): + """Credit units API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncCreditUnitsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncCreditUnitsWithRawResponse(self) + + @functools.cached_property + def prices(self) -> AsyncCreditUnitsPrices: + """The prices API.""" + return AsyncCreditUnitsPrices(self._cfg, self._httpx_client) + + async def list( + self, + *, + include_archived: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitListResponse: + """List credit units + + The tenant's credit units (e.g. "Halyard credits"): denominations usage can be priced and + credit granted in, instead of money, each with its selling prices. + + :param include_archived: Include archived units. Defaults to false.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/credit-units", + query_params=serialize_query_params( + { + "include_archived": include_archived, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitListResponse) + + async def create( + self, + *, + code: str, + name: str, + plural_name: str, + precision: int, + symbol: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnit: + """Create a credit unit + + `code` and `precision` are immutable once created. Price usage in the unit with a plan fee's + `credit_unit`, grant it with a credit pack or a credit grant, and sell it with a top-up once + it has a `product_id` and a selling price. + + :param code: Immutable once created: 4 to 16 characters, A-Z/0-9/_, starting with a letter. Never an ISO currency code (those are always 3 letters). + :param name: Singular display name, 1 to 64 characters, e.g. "Credit". + :param plural_name: Plural display name, 1 to 64 characters, e.g. "Credits". + :param precision: Immutable once created: 0 to 6. + :param symbol: Short display symbol, at most 8 characters.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/credit-units", + json_body=to_json_value( + CreditUnitCreateRequest( + code=code, + name=name, + plural_name=plural_name, + precision=precision, + symbol=symbol, + ), + CreditUnitCreateRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnit) + + async def retrieve( + self, + id: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnit: + """Get a credit unit + + The unit with its active selling prices.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/credit-units/{id}", + path_params={ + "id": id, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnit) + + async def update( + self, + id: str, + *, + archived: bool | None | Unset = UNSET, + name: str | None | Unset = UNSET, + plural_name: str | None | Unset = UNSET, + product_id: str | None | Unset = UNSET, + symbol: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnit: + """Update a credit unit + + Partial update: an absent field is left unchanged. `code` and `precision` are immutable and + cannot be changed here. Setting `archived: true` blocks the unit from new prices, packs and + grants; existing ones keep working. + + :param archived: Blocks the unit from new prices, packs and grants; existing ones keep working. + :param name: Singular display name, 1 to 64 characters. + :param plural_name: Plural display name, 1 to 64 characters. + :param product_id: The product sales of the unit are booked under; `null` clears it, which stops the unit from being sold. + :param symbol: Validated by the store (trimmed, at most 8 characters) rather than here: `validator` has no built-in support for a double `Option`.""" + response = await self._request( + ApiRequest( + method="patch", + path="/api/v1/credit-units/{id}", + path_params={ + "id": id, + }, + json_body=to_json_value( + CreditUnitUpdateRequest( + archived=archived, + name=name, + plural_name=plural_name, + product_id=product_id, + symbol=symbol, + ), + CreditUnitUpdateRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnit) + + +class AsyncCreditUnitsWithRawResponse: + """The methods of :class:`AsyncCreditUnits`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncCreditUnits) -> None: + self._resource = resource + self.list = async_to_raw_response_wrapper(resource.list) + self.create = async_to_raw_response_wrapper(resource.create) + self.retrieve = async_to_raw_response_wrapper(resource.retrieve) + self.update = async_to_raw_response_wrapper(resource.update) + + @property + def prices(self) -> AsyncCreditUnitsPricesWithRawResponse: + """The prices API.""" + return AsyncCreditUnitsPricesWithRawResponse(self._resource.prices) + + +class CreditUnits(ApiBaseSync): + """Credit units API.""" + + @property + def with_raw_response(self) -> CreditUnitsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return CreditUnitsWithRawResponse(self) + + @functools.cached_property + def prices(self) -> CreditUnitsPrices: + """The prices API.""" + return CreditUnitsPrices(self._cfg, self._httpx_client) + + def list( + self, + *, + include_archived: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitListResponse: + """List credit units + + The tenant's credit units (e.g. "Halyard credits"): denominations usage can be priced and + credit granted in, instead of money, each with its selling prices. + + :param include_archived: Include archived units. Defaults to false.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/credit-units", + query_params=serialize_query_params( + { + "include_archived": include_archived, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitListResponse) + + def create( + self, + *, + code: str, + name: str, + plural_name: str, + precision: int, + symbol: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnit: + """Create a credit unit + + `code` and `precision` are immutable once created. Price usage in the unit with a plan fee's + `credit_unit`, grant it with a credit pack or a credit grant, and sell it with a top-up once + it has a `product_id` and a selling price. + + :param code: Immutable once created: 4 to 16 characters, A-Z/0-9/_, starting with a letter. Never an ISO currency code (those are always 3 letters). + :param name: Singular display name, 1 to 64 characters, e.g. "Credit". + :param plural_name: Plural display name, 1 to 64 characters, e.g. "Credits". + :param precision: Immutable once created: 0 to 6. + :param symbol: Short display symbol, at most 8 characters.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/credit-units", + json_body=to_json_value( + CreditUnitCreateRequest( + code=code, + name=name, + plural_name=plural_name, + precision=precision, + symbol=symbol, + ), + CreditUnitCreateRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnit) + + def retrieve( + self, + id: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnit: + """Get a credit unit + + The unit with its active selling prices.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/credit-units/{id}", + path_params={ + "id": id, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnit) + + def update( + self, + id: str, + *, + archived: bool | None | Unset = UNSET, + name: str | None | Unset = UNSET, + plural_name: str | None | Unset = UNSET, + product_id: str | None | Unset = UNSET, + symbol: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnit: + """Update a credit unit + + Partial update: an absent field is left unchanged. `code` and `precision` are immutable and + cannot be changed here. Setting `archived: true` blocks the unit from new prices, packs and + grants; existing ones keep working. + + :param archived: Blocks the unit from new prices, packs and grants; existing ones keep working. + :param name: Singular display name, 1 to 64 characters. + :param plural_name: Plural display name, 1 to 64 characters. + :param product_id: The product sales of the unit are booked under; `null` clears it, which stops the unit from being sold. + :param symbol: Validated by the store (trimmed, at most 8 characters) rather than here: `validator` has no built-in support for a double `Option`.""" + response = self._request( + ApiRequest( + method="patch", + path="/api/v1/credit-units/{id}", + path_params={ + "id": id, + }, + json_body=to_json_value( + CreditUnitUpdateRequest( + archived=archived, + name=name, + plural_name=plural_name, + product_id=product_id, + symbol=symbol, + ), + CreditUnitUpdateRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnit) + + +class CreditUnitsWithRawResponse: + """The methods of :class:`CreditUnits`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: CreditUnits) -> None: + self._resource = resource + self.list = to_raw_response_wrapper(resource.list) + self.create = to_raw_response_wrapper(resource.create) + self.retrieve = to_raw_response_wrapper(resource.retrieve) + self.update = to_raw_response_wrapper(resource.update) + + @property + def prices(self) -> CreditUnitsPricesWithRawResponse: + """The prices API.""" + return CreditUnitsPricesWithRawResponse(self._resource.prices) diff --git a/meteroid/api/credit_units_prices.py b/meteroid/api/credit_units_prices.py new file mode 100644 index 0000000..c485f23 --- /dev/null +++ b/meteroid/api/credit_units_prices.py @@ -0,0 +1,299 @@ +# this file is @generated +"""Credit units prices API.""" + +from __future__ import annotations + +import typing as t + +from .. import models as _models +from ..models import ( + CreditUnitPrice, + CreditUnitPriceListResponse, + CreditUnitPriceSetRequest, +) +from ..serialization import UNSET, Unset, to_json_value +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + serialize_query_params, +) + + +class AsyncCreditUnitsPrices(ApiBaseAsync): + """Credit units prices API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncCreditUnitsPricesWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncCreditUnitsPricesWithRawResponse(self) + + async def list( + self, + id: str, + *, + include_archived: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitPriceListResponse: + """List a credit unit's selling prices + + The active price per currency, and archived ones when asked, newest first. + + :param include_archived: Include archived prices. Defaults to false.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/credit-units/{id}/prices", + path_params={ + "id": id, + }, + query_params=serialize_query_params( + { + "include_archived": include_archived, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitPriceListResponse) + + async def update( + self, + id: str, + currency: str, + *, + unit_price: str, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitPrice: + """Set a credit unit's selling price + + The price of one credit in a currency, used to sell the unit through top-ups and automatic + top-ups (taxed at purchase under the unit's `product_id`). Replaces the active price in that + currency, which is archived: a top-up already priced keeps its terms. + + :param unit_price: Major units of the currency per credit, as a decimal string: positive, at most 12 decimal places.""" + response = await self._request( + ApiRequest( + method="put", + path="/api/v1/credit-units/{id}/prices/{currency}", + path_params={ + "id": id, + "currency": currency, + }, + json_body=to_json_value( + CreditUnitPriceSetRequest( + unit_price=unit_price, + ), + CreditUnitPriceSetRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitPrice) + + async def archive( + self, + id: str, + currency: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitPrice: + """Remove a credit unit's selling price + + Archives the active price in a currency: the unit can no longer be sold in it (top-ups and + automatic top-ups included) until a new price is set.""" + response = await self._request( + ApiRequest( + method="delete", + path="/api/v1/credit-units/{id}/prices/{currency}", + path_params={ + "id": id, + "currency": currency, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitPrice) + + +class AsyncCreditUnitsPricesWithRawResponse: + """The methods of :class:`AsyncCreditUnitsPrices`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncCreditUnitsPrices) -> None: + self.list = async_to_raw_response_wrapper(resource.list) + self.update = async_to_raw_response_wrapper(resource.update) + self.archive = async_to_raw_response_wrapper(resource.archive) + + +class CreditUnitsPrices(ApiBaseSync): + """Credit units prices API.""" + + @property + def with_raw_response(self) -> CreditUnitsPricesWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return CreditUnitsPricesWithRawResponse(self) + + def list( + self, + id: str, + *, + include_archived: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitPriceListResponse: + """List a credit unit's selling prices + + The active price per currency, and archived ones when asked, newest first. + + :param include_archived: Include archived prices. Defaults to false.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/credit-units/{id}/prices", + path_params={ + "id": id, + }, + query_params=serialize_query_params( + { + "include_archived": include_archived, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitPriceListResponse) + + def update( + self, + id: str, + currency: str, + *, + unit_price: str, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitPrice: + """Set a credit unit's selling price + + The price of one credit in a currency, used to sell the unit through top-ups and automatic + top-ups (taxed at purchase under the unit's `product_id`). Replaces the active price in that + currency, which is archived: a top-up already priced keeps its terms. + + :param unit_price: Major units of the currency per credit, as a decimal string: positive, at most 12 decimal places.""" + response = self._request( + ApiRequest( + method="put", + path="/api/v1/credit-units/{id}/prices/{currency}", + path_params={ + "id": id, + "currency": currency, + }, + json_body=to_json_value( + CreditUnitPriceSetRequest( + unit_price=unit_price, + ), + CreditUnitPriceSetRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitPrice) + + def archive( + self, + id: str, + currency: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditUnitPrice: + """Remove a credit unit's selling price + + Archives the active price in a currency: the unit can no longer be sold in it (top-ups and + automatic top-ups included) until a new price is set.""" + response = self._request( + ApiRequest( + method="delete", + path="/api/v1/credit-units/{id}/prices/{currency}", + path_params={ + "id": id, + "currency": currency, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditUnitPrice) + + +class CreditUnitsPricesWithRawResponse: + """The methods of :class:`CreditUnitsPrices`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: CreditUnitsPrices) -> None: + self.list = to_raw_response_wrapper(resource.list) + self.update = to_raw_response_wrapper(resource.update) + self.archive = to_raw_response_wrapper(resource.archive) diff --git a/meteroid/api/credits.py b/meteroid/api/credits.py new file mode 100644 index 0000000..10c719e --- /dev/null +++ b/meteroid/api/credits.py @@ -0,0 +1,396 @@ +# this file is @generated +"""Credits API.""" + +from __future__ import annotations + +import functools +import typing as t +from datetime import datetime + +from .. import models as _models +from ..models import ( + CreateTopUpRequest, + Currency, + CurrencyLiteral, + LedgerEntry, + LedgerListResponse, + TopUpCollection, + TopUpCollectionLiteral, + TopUpResponse, +) +from ..serialization import UNSET, Unset, to_json_value +from ._pages import ( + AsyncCreditsListCustomerCreditLedgerPage, + CreditsListCustomerCreditLedgerPage, +) +from ._pagination import ( + AsyncPaginator, + PagePaging, + step, +) +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + header_value, + serialize_query_params, +) +from .credits_grants import ( + AsyncCreditsGrants, + AsyncCreditsGrantsWithRawResponse, + CreditsGrants, + CreditsGrantsWithRawResponse, +) +from .credits_wallets import ( + AsyncCreditsWallets, + AsyncCreditsWalletsWithRawResponse, + CreditsWallets, + CreditsWalletsWithRawResponse, +) + + +class AsyncCredits(ApiBaseAsync): + """Credits API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncCreditsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncCreditsWithRawResponse(self) + + @functools.cached_property + def grants(self) -> AsyncCreditsGrants: + """The grants API.""" + return AsyncCreditsGrants(self._cfg, self._httpx_client) + + @functools.cached_property + def wallets(self) -> AsyncCreditsWallets: + """The wallets API.""" + return AsyncCreditsWallets(self._cfg, self._httpx_client) + + def list_customer_credit_ledger( + self, + id_or_alias: str, + *, + currency: Currency | None = None, + credit_unit: str | None = None, + page: int | None = None, + per_page: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> AsyncPaginator[LedgerEntry, AsyncCreditsListCustomerCreditLedgerPage]: + """List credit ledger entries + + Every movement of a customer's credit in one currency or credit unit, newest first, with the + credit on account after it: credit granted, applied to invoices, given back, expired or + rolled over. + + :param currency: Defaults to the customer's currency. + :param credit_unit: The ledger of this credit unit (its code) instead of a currency; excludes `currency`. + :param page: Page number (0-indexed) + :param per_page: Number of items per page""" + + async def fetch(page_param: int | None) -> LedgerListResponse: + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/credit-ledger", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "currency": currency, + "credit_unit": credit_unit, + "page": page_param, + "per_page": per_page, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LedgerListResponse) + + paging = PagePaging[LedgerListResponse, LedgerEntry]( + items=lambda body: body.data, + total_pages=lambda body: step( + body.pagination_meta, lambda v: v.total_pages + ), + first_page=0, + ) + return AsyncPaginator( + lambda: AsyncCreditsListCustomerCreditLedgerPage._first(fetch, page, paging) + ) + + async def create_customer_top_up( + self, + id_or_alias: str, + *, + amount: int, + idempotency_key: str, + collection: TopUpCollection | TopUpCollectionLiteral | None = None, + credit_unit: str | None | Unset = UNSET, + currency: Currency | CurrencyLiteral | None | Unset = UNSET, + expires_at: datetime | None | Unset = UNSET, + payment_method_id: str | None | Unset = UNSET, + unit_price: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> TopUpResponse: + """Create a top-up + + Sells credit: CHARGE bills a saved payment method now, SEND_INVOICE grants it once paid. The + credit is PAID and applies to ALL charges. Currency credit is sold one for one, untaxed, as + its usage is taxed when invoiced. With `credit_unit`, sells credits of that unit at its + selling price in the customer's currency, taxed at purchase under the unit's product; refused + when the unit has no price in that currency or no product. Without a saved payment method, + CHARGE returns a hosted `checkout_url` where the customer pays. + + :param amount: Credit to buy, positive, applying to ALL charges. In minor units of `currency`, charged one for one without tax: the usage it pays is taxed on the invoice that bills it. With `credit_unit`, in minor units of that unit (e.g. 500000 is 5,000.00 credits of a unit with precision 2), charged at the unit's price per credit in the customer's currency, plus tax under the unit's product. + :param collection: CHARGE (default) charges a saved payment method now; SEND_INVOICE issues an invoice and grants the credit once it is paid. + :param credit_unit: Buy credits of this credit unit (its code) instead of money; excludes `currency`. They are charged in the customer's currency at the customer's agreed price for the unit (the wallet's `unit_price`), else its list price, plus tax, under the unit's product. + :param expires_at: Absolute expiry of the purchased credit; unset never expires. + :param payment_method_id: CHARGE only; defaults to the customer's default payment method. + :param unit_price: With `credit_unit`: the negotiated price of one credit for this purchase, in major units of the customer's currency, as a decimal string (e.g. "0.0008"). Defaults to the wallet's `unit_price` when set, else the list price; the unit must still have a list price in that currency.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/top-ups", + path_params={ + "id_or_alias": id_or_alias, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + CreateTopUpRequest( + amount=amount, + collection=t.cast("TopUpCollection | None", collection), + credit_unit=credit_unit, + currency=t.cast("Currency | None | Unset", currency), + expires_at=expires_at, + payment_method_id=payment_method_id, + unit_price=unit_price, + ), + CreateTopUpRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, TopUpResponse) + + +class AsyncCreditsWithRawResponse: + """The methods of :class:`AsyncCredits`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncCredits) -> None: + self._resource = resource + self.list_customer_credit_ledger = async_to_raw_response_wrapper( + resource.list_customer_credit_ledger + ) + self.create_customer_top_up = async_to_raw_response_wrapper( + resource.create_customer_top_up + ) + + @property + def grants(self) -> AsyncCreditsGrantsWithRawResponse: + """The grants API.""" + return AsyncCreditsGrantsWithRawResponse(self._resource.grants) + + @property + def wallets(self) -> AsyncCreditsWalletsWithRawResponse: + """The wallets API.""" + return AsyncCreditsWalletsWithRawResponse(self._resource.wallets) + + +class Credits(ApiBaseSync): + """Credits API.""" + + @property + def with_raw_response(self) -> CreditsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return CreditsWithRawResponse(self) + + @functools.cached_property + def grants(self) -> CreditsGrants: + """The grants API.""" + return CreditsGrants(self._cfg, self._httpx_client) + + @functools.cached_property + def wallets(self) -> CreditsWallets: + """The wallets API.""" + return CreditsWallets(self._cfg, self._httpx_client) + + def list_customer_credit_ledger( + self, + id_or_alias: str, + *, + currency: Currency | None = None, + credit_unit: str | None = None, + page: int | None = None, + per_page: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditsListCustomerCreditLedgerPage: + """List credit ledger entries + + Every movement of a customer's credit in one currency or credit unit, newest first, with the + credit on account after it: credit granted, applied to invoices, given back, expired or + rolled over. + + :param currency: Defaults to the customer's currency. + :param credit_unit: The ledger of this credit unit (its code) instead of a currency; excludes `currency`. + :param page: Page number (0-indexed) + :param per_page: Number of items per page""" + + def fetch(page_param: int | None) -> LedgerListResponse: + response = self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/credit-ledger", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "currency": currency, + "credit_unit": credit_unit, + "page": page_param, + "per_page": per_page, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LedgerListResponse) + + paging = PagePaging[LedgerListResponse, LedgerEntry]( + items=lambda body: body.data, + total_pages=lambda body: step( + body.pagination_meta, lambda v: v.total_pages + ), + first_page=0, + ) + return CreditsListCustomerCreditLedgerPage._first(fetch, page, paging) + + def create_customer_top_up( + self, + id_or_alias: str, + *, + amount: int, + idempotency_key: str, + collection: TopUpCollection | TopUpCollectionLiteral | None = None, + credit_unit: str | None | Unset = UNSET, + currency: Currency | CurrencyLiteral | None | Unset = UNSET, + expires_at: datetime | None | Unset = UNSET, + payment_method_id: str | None | Unset = UNSET, + unit_price: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> TopUpResponse: + """Create a top-up + + Sells credit: CHARGE bills a saved payment method now, SEND_INVOICE grants it once paid. The + credit is PAID and applies to ALL charges. Currency credit is sold one for one, untaxed, as + its usage is taxed when invoiced. With `credit_unit`, sells credits of that unit at its + selling price in the customer's currency, taxed at purchase under the unit's product; refused + when the unit has no price in that currency or no product. Without a saved payment method, + CHARGE returns a hosted `checkout_url` where the customer pays. + + :param amount: Credit to buy, positive, applying to ALL charges. In minor units of `currency`, charged one for one without tax: the usage it pays is taxed on the invoice that bills it. With `credit_unit`, in minor units of that unit (e.g. 500000 is 5,000.00 credits of a unit with precision 2), charged at the unit's price per credit in the customer's currency, plus tax under the unit's product. + :param collection: CHARGE (default) charges a saved payment method now; SEND_INVOICE issues an invoice and grants the credit once it is paid. + :param credit_unit: Buy credits of this credit unit (its code) instead of money; excludes `currency`. They are charged in the customer's currency at the customer's agreed price for the unit (the wallet's `unit_price`), else its list price, plus tax, under the unit's product. + :param expires_at: Absolute expiry of the purchased credit; unset never expires. + :param payment_method_id: CHARGE only; defaults to the customer's default payment method. + :param unit_price: With `credit_unit`: the negotiated price of one credit for this purchase, in major units of the customer's currency, as a decimal string (e.g. "0.0008"). Defaults to the wallet's `unit_price` when set, else the list price; the unit must still have a list price in that currency.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/top-ups", + path_params={ + "id_or_alias": id_or_alias, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + CreateTopUpRequest( + amount=amount, + collection=t.cast("TopUpCollection | None", collection), + credit_unit=credit_unit, + currency=t.cast("Currency | None | Unset", currency), + expires_at=expires_at, + payment_method_id=payment_method_id, + unit_price=unit_price, + ), + CreateTopUpRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, TopUpResponse) + + +class CreditsWithRawResponse: + """The methods of :class:`Credits`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: Credits) -> None: + self._resource = resource + self.list_customer_credit_ledger = to_raw_response_wrapper( + resource.list_customer_credit_ledger + ) + self.create_customer_top_up = to_raw_response_wrapper( + resource.create_customer_top_up + ) + + @property + def grants(self) -> CreditsGrantsWithRawResponse: + """The grants API.""" + return CreditsGrantsWithRawResponse(self._resource.grants) + + @property + def wallets(self) -> CreditsWalletsWithRawResponse: + """The wallets API.""" + return CreditsWalletsWithRawResponse(self._resource.wallets) diff --git a/meteroid/api/credits_grants.py b/meteroid/api/credits_grants.py new file mode 100644 index 0000000..b177aa5 --- /dev/null +++ b/meteroid/api/credits_grants.py @@ -0,0 +1,380 @@ +# this file is @generated +"""Credits grants API.""" + +from __future__ import annotations + +import builtins +import typing as t +from datetime import datetime + +from .. import models as _models +from ..models import ( + CreateCreditGrantRequest, + CreditAppliesTo, + CreditAppliesToLiteral, + CreditCostRequest, + CreditGrant, + CreditGrantListResponse, + CreditGrantStatus, + CreditGrantType, + CreditGrantTypeLiteral, + Currency, + CurrencyLiteral, + ProductId, +) +from ..serialization import UNSET, Unset, to_json_value +from ._pages import ( + AsyncCreditsGrantsListPage, + CreditsGrantsListPage, +) +from ._pagination import ( + AsyncPaginator, + PagePaging, + step, +) +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + header_value, + serialize_query_params, +) + + +class AsyncCreditsGrants(ApiBaseAsync): + """Credits grants API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncCreditsGrantsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncCreditsGrantsWithRawResponse(self) + + def list( + self, + id_or_alias: str, + *, + currency: Currency | None = None, + credit_unit: str | None = None, + status: CreditGrantStatus | None = None, + subscription_id: str | None = None, + usable_by_subscription_id: str | None = None, + page: int | None = None, + per_page: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> AsyncPaginator[CreditGrant, AsyncCreditsGrantsListPage]: + """List credit grants + + A customer's credit grants, newest first, in every currency and credit unit unless filtered. + + :param currency: Only grants in this currency. + :param credit_unit: Only grants in this credit unit (its code); excludes `currency`. + :param subscription_id: Only the grants that belong to this subscription (`sub_…`); excludes `usable_by_subscription_id`. + :param usable_by_subscription_id: Only the credit this subscription (`sub_…`) can use: its own grants and the customer's grants that belong to no subscription. Excludes `subscription_id`. + :param page: Page number (0-indexed) + :param per_page: Number of items per page""" + + async def fetch(page_param: int | None) -> CreditGrantListResponse: + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/credit-grants", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "currency": currency, + "credit_unit": credit_unit, + "status": status, + "subscription_id": subscription_id, + "usable_by_subscription_id": usable_by_subscription_id, + "page": page_param, + "per_page": per_page, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditGrantListResponse) + + paging = PagePaging[CreditGrantListResponse, CreditGrant]( + items=lambda body: body.data, + total_pages=lambda body: step( + body.pagination_meta, lambda v: v.total_pages + ), + first_page=0, + ) + return AsyncPaginator( + lambda: AsyncCreditsGrantsListPage._first(fetch, page, paging) + ) + + async def create( + self, + id_or_alias: str, + *, + amount: int, + type: CreditGrantType | CreditGrantTypeLiteral, + idempotency_key: str, + applicable_product_ids: builtins.list[ProductId] | None | Unset = UNSET, + applies_to: CreditAppliesTo | CreditAppliesToLiteral | None = None, + cost: CreditCostRequest | None | Unset = UNSET, + credit_unit: str | None | Unset = UNSET, + currency: Currency | CurrencyLiteral | None | Unset = UNSET, + expires_at: datetime | None | Unset = UNSET, + priority: int | None | Unset = UNSET, + reason: str | None | Unset = UNSET, + reference: str | None | Unset = UNSET, + subscription_id: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditGrant: + """Create a credit grant + + Grants credit without charging or invoicing: PROMOTIONAL credit for free, or PAID credit the + customer paid for outside Meteroid ("Paid outside Meteroid"). A PAID currency grant whose + `cost` is below its `amount` is split into a PAID grant for the cost and a PROMOTIONAL bonus + grant for the rest, returned as `bonus_grant_id`; a `cost` above the `amount` is refused. + For a customer of a French invoicing entity, PAID credit that cost more than zero needs a + `reference` to the invoice or receipt of its payment. Sell credit through Meteroid with a + top-up. + + :param amount: Credit granted, in minor units of `currency`, or of `credit_unit` when set (e.g. 500000 is 5,000.00 credits of a unit with precision 2). Positive. + :param applicable_product_ids: Restricts the credit to charges of these products; absent applies to every product. Combined with `applies_to`. Not available for credit units. + :param applies_to: ALL pays any charge on an invoice; USAGE only usage charges. + :param credit_unit: Grants in this credit unit (its code) instead of a currency; excludes `currency`. Credit in a credit unit pays the usage priced in that unit, so `applies_to` must stay ALL. + :param expires_at: When unspent credit lapses; absent never expires. + :param priority: Lower values are drawn first among grants of the same type that pay the same charges; defaults to 100. + :param reason: Shown on the credit ledger. + :param reference: Your own reference, e.g. a contract or support ticket. Required for PAID credit that cost more than zero when the customer's invoicing entity is in France: the number of the invoice or receipt for the payment, as French law requires a supporting document for every accounting entry. Refused with 400 when missing. + :param subscription_id: The customer's subscription (`sub_…`) the credit belongs to: it pays only that subscription's charges and lapses when the subscription ends. It must not have ended, and its currency must be the grant's. Absent pays any of the customer's subscriptions. + :param type: PROMOTIONAL gives credit for free (a goodwill or marketing grant). PAID records credit the customer paid for outside of Meteroid: nothing is charged or invoiced, and the grant is shown as "Paid outside Meteroid". Usage paid with PAID currency credit is taxed on the invoice that bills it, so do not charge tax on the external sale of currency credit, or the customer is taxed twice. Credit in a credit unit is the exception: its usage is billed in credits, untaxed, so tax its sale as you would any other. Use a top-up to sell credit through Meteroid.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/credit-grants", + path_params={ + "id_or_alias": id_or_alias, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + CreateCreditGrantRequest( + amount=amount, + applicable_product_ids=applicable_product_ids, + applies_to=t.cast("CreditAppliesTo | None", applies_to), + cost=cost, + credit_unit=credit_unit, + currency=t.cast("Currency | None | Unset", currency), + expires_at=expires_at, + priority=priority, + reason=reason, + reference=reference, + subscription_id=subscription_id, + type=t.cast("CreditGrantType", type), + ), + CreateCreditGrantRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditGrant) + + +class AsyncCreditsGrantsWithRawResponse: + """The methods of :class:`AsyncCreditsGrants`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncCreditsGrants) -> None: + self.list = async_to_raw_response_wrapper(resource.list) + self.create = async_to_raw_response_wrapper(resource.create) + + +class CreditsGrants(ApiBaseSync): + """Credits grants API.""" + + @property + def with_raw_response(self) -> CreditsGrantsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return CreditsGrantsWithRawResponse(self) + + def list( + self, + id_or_alias: str, + *, + currency: Currency | None = None, + credit_unit: str | None = None, + status: CreditGrantStatus | None = None, + subscription_id: str | None = None, + usable_by_subscription_id: str | None = None, + page: int | None = None, + per_page: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditsGrantsListPage: + """List credit grants + + A customer's credit grants, newest first, in every currency and credit unit unless filtered. + + :param currency: Only grants in this currency. + :param credit_unit: Only grants in this credit unit (its code); excludes `currency`. + :param subscription_id: Only the grants that belong to this subscription (`sub_…`); excludes `usable_by_subscription_id`. + :param usable_by_subscription_id: Only the credit this subscription (`sub_…`) can use: its own grants and the customer's grants that belong to no subscription. Excludes `subscription_id`. + :param page: Page number (0-indexed) + :param per_page: Number of items per page""" + + def fetch(page_param: int | None) -> CreditGrantListResponse: + response = self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/credit-grants", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "currency": currency, + "credit_unit": credit_unit, + "status": status, + "subscription_id": subscription_id, + "usable_by_subscription_id": usable_by_subscription_id, + "page": page_param, + "per_page": per_page, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditGrantListResponse) + + paging = PagePaging[CreditGrantListResponse, CreditGrant]( + items=lambda body: body.data, + total_pages=lambda body: step( + body.pagination_meta, lambda v: v.total_pages + ), + first_page=0, + ) + return CreditsGrantsListPage._first(fetch, page, paging) + + def create( + self, + id_or_alias: str, + *, + amount: int, + type: CreditGrantType | CreditGrantTypeLiteral, + idempotency_key: str, + applicable_product_ids: builtins.list[ProductId] | None | Unset = UNSET, + applies_to: CreditAppliesTo | CreditAppliesToLiteral | None = None, + cost: CreditCostRequest | None | Unset = UNSET, + credit_unit: str | None | Unset = UNSET, + currency: Currency | CurrencyLiteral | None | Unset = UNSET, + expires_at: datetime | None | Unset = UNSET, + priority: int | None | Unset = UNSET, + reason: str | None | Unset = UNSET, + reference: str | None | Unset = UNSET, + subscription_id: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> CreditGrant: + """Create a credit grant + + Grants credit without charging or invoicing: PROMOTIONAL credit for free, or PAID credit the + customer paid for outside Meteroid ("Paid outside Meteroid"). A PAID currency grant whose + `cost` is below its `amount` is split into a PAID grant for the cost and a PROMOTIONAL bonus + grant for the rest, returned as `bonus_grant_id`; a `cost` above the `amount` is refused. + For a customer of a French invoicing entity, PAID credit that cost more than zero needs a + `reference` to the invoice or receipt of its payment. Sell credit through Meteroid with a + top-up. + + :param amount: Credit granted, in minor units of `currency`, or of `credit_unit` when set (e.g. 500000 is 5,000.00 credits of a unit with precision 2). Positive. + :param applicable_product_ids: Restricts the credit to charges of these products; absent applies to every product. Combined with `applies_to`. Not available for credit units. + :param applies_to: ALL pays any charge on an invoice; USAGE only usage charges. + :param credit_unit: Grants in this credit unit (its code) instead of a currency; excludes `currency`. Credit in a credit unit pays the usage priced in that unit, so `applies_to` must stay ALL. + :param expires_at: When unspent credit lapses; absent never expires. + :param priority: Lower values are drawn first among grants of the same type that pay the same charges; defaults to 100. + :param reason: Shown on the credit ledger. + :param reference: Your own reference, e.g. a contract or support ticket. Required for PAID credit that cost more than zero when the customer's invoicing entity is in France: the number of the invoice or receipt for the payment, as French law requires a supporting document for every accounting entry. Refused with 400 when missing. + :param subscription_id: The customer's subscription (`sub_…`) the credit belongs to: it pays only that subscription's charges and lapses when the subscription ends. It must not have ended, and its currency must be the grant's. Absent pays any of the customer's subscriptions. + :param type: PROMOTIONAL gives credit for free (a goodwill or marketing grant). PAID records credit the customer paid for outside of Meteroid: nothing is charged or invoiced, and the grant is shown as "Paid outside Meteroid". Usage paid with PAID currency credit is taxed on the invoice that bills it, so do not charge tax on the external sale of currency credit, or the customer is taxed twice. Credit in a credit unit is the exception: its usage is billed in credits, untaxed, so tax its sale as you would any other. Use a top-up to sell credit through Meteroid.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/customers/{id_or_alias}/credit-grants", + path_params={ + "id_or_alias": id_or_alias, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + CreateCreditGrantRequest( + amount=amount, + applicable_product_ids=applicable_product_ids, + applies_to=t.cast("CreditAppliesTo | None", applies_to), + cost=cost, + credit_unit=credit_unit, + currency=t.cast("Currency | None | Unset", currency), + expires_at=expires_at, + priority=priority, + reason=reason, + reference=reference, + subscription_id=subscription_id, + type=t.cast("CreditGrantType", type), + ), + CreateCreditGrantRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, CreditGrant) + + +class CreditsGrantsWithRawResponse: + """The methods of :class:`CreditsGrants`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: CreditsGrants) -> None: + self.list = to_raw_response_wrapper(resource.list) + self.create = to_raw_response_wrapper(resource.create) diff --git a/meteroid/api/credits_wallets.py b/meteroid/api/credits_wallets.py new file mode 100644 index 0000000..81f3829 --- /dev/null +++ b/meteroid/api/credits_wallets.py @@ -0,0 +1,384 @@ +# this file is @generated +"""Credits wallets API.""" + +from __future__ import annotations + +import builtins +import typing as t + +from .. import models as _models +from ..models import ( + AutoTopUpWriteRequest, + Currency, + PatchWalletRequest, + ProductId, + SubscriptionId, + UsageAtZero, + UsageAtZeroLiteral, + Wallet, + WalletCheck, + WalletListResponse, +) +from ..serialization import UNSET, Unset, to_json_value +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + header_value, + serialize_query_params, +) + + +class AsyncCreditsWallets(ApiBaseAsync): + """Credits wallets API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncCreditsWalletsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncCreditsWalletsWithRawResponse(self) + + async def list( + self, + id_or_alias: str, + *, + subscription_id: SubscriptionId | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> WalletListResponse: + """List wallets + + A customer's credit per currency and credit unit: the credit on account, the usage not + invoiced yet and the balance left after it, with the wallet's alerts and automatic top-up. The + customer's own currency comes first, even when empty. With `subscription_id`, each wallet + counts only the credit that subscription can use. + + :param subscription_id: Only the credit this subscription (`sub_…`) can use: the credit that belongs to it and the customer's credit that belongs to no subscription. Every wallet is still listed.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/wallets", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "subscription_id": subscription_id, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, WalletListResponse) + + async def check( + self, + id_or_alias: str, + *, + currency: Currency | None = None, + credit_unit: str | None = None, + product_id: ProductId | None = None, + subscription_id: SubscriptionId | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> WalletCheck: + """Check wallet access + + Whether the customer's prepaid credit in a currency or credit unit still covers them, net of + usage not invoiced yet. Denies only when the wallet blocks usage once its credit is used up; + otherwise access stays allowed and `exhausted` signals the empty balance on its own. With + `subscription_id`, only the credit that subscription can use counts: pass it when the + customer has several subscriptions, since one subscription's credit cannot pay another's + usage. The balance follows usage within a few seconds near zero or a threshold, within a + minute otherwise, and the answer is cached for one second: use it to gate consumption, not + to bill. + + :param currency: Defaults to the customer's currency. + :param credit_unit: Checks the wallet of this credit unit (its code) instead of a currency. + :param product_id: Checks only the credit that can pay for this product: credit not restricted to products, and credit restricted to it. Absent checks all credit. + :param subscription_id: Checks only the credit this subscription (`sub_…`) can spend, net of its own usage no credit pays: the credit that belongs to it and the customer's credit that belongs to no subscription, once every subscription's unbilled usage is paid. `has_access` is then the decision the access gate makes for that subscription's usage. Absent checks the customer's credit as a whole.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/wallets/check", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "currency": currency, + "credit_unit": credit_unit, + "product_id": product_id, + "subscription_id": subscription_id, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, WalletCheck) + + async def update( + self, + id_or_alias: str, + currency: str, + *, + idempotency_key: str, + auto_top_up: AutoTopUpWriteRequest | None | Unset = UNSET, + low_balance_thresholds: builtins.list[int] | None | Unset = UNSET, + unit_price: str | None | Unset = UNSET, + usage_at_zero: UsageAtZero | UsageAtZeroLiteral | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Wallet: + """Configure a wallet + + Settings of a customer's wallet in one currency or credit unit: whether usage is refused + once its credit is used up, the balances that fire `wallet.balance_low`, and its automatic + top-up. Opens the wallet if it does not exist yet. Partial update: omit a field to leave it + unchanged. + + :param low_balance_thresholds: Ascending, distinct, at most 5, non-negative. Omit to leave unchanged; an empty list clears every threshold. + :param unit_price: Credit unit wallets: the price this customer agreed for one credit, in major units of the customer's currency, as a decimal string (positive, at most 12 decimal places). Purchases and automatic top-ups default to it instead of the list price. Omit to leave it unchanged; `null` returns the customer to the list price. A change applies to purchases quoted after it. An automatic top-up keeps running at the new price when it is at or below the price its terms were authorized at, and stops until they are confirmed again when it is above.""" + response = await self._request( + ApiRequest( + method="patch", + path="/api/v1/customers/{id_or_alias}/wallets/{currency}", + path_params={ + "id_or_alias": id_or_alias, + "currency": currency, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + PatchWalletRequest( + auto_top_up=auto_top_up, + low_balance_thresholds=low_balance_thresholds, + unit_price=unit_price, + usage_at_zero=t.cast( + "UsageAtZero | None | Unset", usage_at_zero + ), + ), + PatchWalletRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Wallet) + + +class AsyncCreditsWalletsWithRawResponse: + """The methods of :class:`AsyncCreditsWallets`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncCreditsWallets) -> None: + self.list = async_to_raw_response_wrapper(resource.list) + self.check = async_to_raw_response_wrapper(resource.check) + self.update = async_to_raw_response_wrapper(resource.update) + + +class CreditsWallets(ApiBaseSync): + """Credits wallets API.""" + + @property + def with_raw_response(self) -> CreditsWalletsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return CreditsWalletsWithRawResponse(self) + + def list( + self, + id_or_alias: str, + *, + subscription_id: SubscriptionId | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> WalletListResponse: + """List wallets + + A customer's credit per currency and credit unit: the credit on account, the usage not + invoiced yet and the balance left after it, with the wallet's alerts and automatic top-up. The + customer's own currency comes first, even when empty. With `subscription_id`, each wallet + counts only the credit that subscription can use. + + :param subscription_id: Only the credit this subscription (`sub_…`) can use: the credit that belongs to it and the customer's credit that belongs to no subscription. Every wallet is still listed.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/wallets", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "subscription_id": subscription_id, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, WalletListResponse) + + def check( + self, + id_or_alias: str, + *, + currency: Currency | None = None, + credit_unit: str | None = None, + product_id: ProductId | None = None, + subscription_id: SubscriptionId | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> WalletCheck: + """Check wallet access + + Whether the customer's prepaid credit in a currency or credit unit still covers them, net of + usage not invoiced yet. Denies only when the wallet blocks usage once its credit is used up; + otherwise access stays allowed and `exhausted` signals the empty balance on its own. With + `subscription_id`, only the credit that subscription can use counts: pass it when the + customer has several subscriptions, since one subscription's credit cannot pay another's + usage. The balance follows usage within a few seconds near zero or a threshold, within a + minute otherwise, and the answer is cached for one second: use it to gate consumption, not + to bill. + + :param currency: Defaults to the customer's currency. + :param credit_unit: Checks the wallet of this credit unit (its code) instead of a currency. + :param product_id: Checks only the credit that can pay for this product: credit not restricted to products, and credit restricted to it. Absent checks all credit. + :param subscription_id: Checks only the credit this subscription (`sub_…`) can spend, net of its own usage no credit pays: the credit that belongs to it and the customer's credit that belongs to no subscription, once every subscription's unbilled usage is paid. `has_access` is then the decision the access gate makes for that subscription's usage. Absent checks the customer's credit as a whole.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/customers/{id_or_alias}/wallets/check", + path_params={ + "id_or_alias": id_or_alias, + }, + query_params=serialize_query_params( + { + "currency": currency, + "credit_unit": credit_unit, + "product_id": product_id, + "subscription_id": subscription_id, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, WalletCheck) + + def update( + self, + id_or_alias: str, + currency: str, + *, + idempotency_key: str, + auto_top_up: AutoTopUpWriteRequest | None | Unset = UNSET, + low_balance_thresholds: builtins.list[int] | None | Unset = UNSET, + unit_price: str | None | Unset = UNSET, + usage_at_zero: UsageAtZero | UsageAtZeroLiteral | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> Wallet: + """Configure a wallet + + Settings of a customer's wallet in one currency or credit unit: whether usage is refused + once its credit is used up, the balances that fire `wallet.balance_low`, and its automatic + top-up. Opens the wallet if it does not exist yet. Partial update: omit a field to leave it + unchanged. + + :param low_balance_thresholds: Ascending, distinct, at most 5, non-negative. Omit to leave unchanged; an empty list clears every threshold. + :param unit_price: Credit unit wallets: the price this customer agreed for one credit, in major units of the customer's currency, as a decimal string (positive, at most 12 decimal places). Purchases and automatic top-ups default to it instead of the list price. Omit to leave it unchanged; `null` returns the customer to the list price. A change applies to purchases quoted after it. An automatic top-up keeps running at the new price when it is at or below the price its terms were authorized at, and stops until they are confirmed again when it is above.""" + response = self._request( + ApiRequest( + method="patch", + path="/api/v1/customers/{id_or_alias}/wallets/{currency}", + path_params={ + "id_or_alias": id_or_alias, + "currency": currency, + }, + header_params={ + "Idempotency-Key": header_value(idempotency_key), + }, + json_body=to_json_value( + PatchWalletRequest( + auto_top_up=auto_top_up, + low_balance_thresholds=low_balance_thresholds, + unit_price=unit_price, + usage_at_zero=t.cast( + "UsageAtZero | None | Unset", usage_at_zero + ), + ), + PatchWalletRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, Wallet) + + +class CreditsWalletsWithRawResponse: + """The methods of :class:`CreditsWallets`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: CreditsWallets) -> None: + self.list = to_raw_response_wrapper(resource.list) + self.check = to_raw_response_wrapper(resource.check) + self.update = to_raw_response_wrapper(resource.update) diff --git a/meteroid/api/features.py b/meteroid/api/features.py index b746231..2f22cae 100644 --- a/meteroid/api/features.py +++ b/meteroid/api/features.py @@ -234,7 +234,8 @@ async def archive( ) -> None: """Archive a feature - Keeps the feature and its entitlements but hides them from resolution.""" + Turns the feature off and hides it and its entitlements until it is unarchived, and frees its + code for a new feature.""" await self._request( ApiRequest( method="post", @@ -263,7 +264,10 @@ async def unarchive( timeout: Timeout | Unset = UNSET, max_retries: int | None = None, ) -> None: - """Unarchive a feature""" + """Unarchive a feature + + Makes the feature active again with all its entitlements. Fails while another feature uses its + code.""" await self._request( ApiRequest( method="post", @@ -491,7 +495,8 @@ def archive( ) -> None: """Archive a feature - Keeps the feature and its entitlements but hides them from resolution.""" + Turns the feature off and hides it and its entitlements until it is unarchived, and frees its + code for a new feature.""" self._request( ApiRequest( method="post", @@ -520,7 +525,10 @@ def unarchive( timeout: Timeout | Unset = UNSET, max_retries: int | None = None, ) -> None: - """Unarchive a feature""" + """Unarchive a feature + + Makes the feature active again with all its entitlements. Fails while another feature uses its + code.""" self._request( ApiRequest( method="post", diff --git a/meteroid/api/metrics.py b/meteroid/api/metrics.py index 9f9991e..52298e4 100644 --- a/meteroid/api/metrics.py +++ b/meteroid/api/metrics.py @@ -120,6 +120,8 @@ async def create( product_id: ProductId | None | Unset = UNSET, segmentation_matrix: MetricSegmentationMatrix | None | Unset = UNSET, unit_conversion: UnitConversion | None | Unset = UNSET, + unit_plural: str | None | Unset = UNSET, + unit_singular: str | None | Unset = UNSET, usage_group_key: str | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, @@ -129,7 +131,9 @@ async def create( ) -> Metric: """Create a billable metric - :param filters: Pre-aggregation property filters. Optional and backward-compatible; omit for none.""" + :param filters: Pre-aggregation property filters. Optional and backward-compatible; omit for none. + :param unit_plural: What several units of the metric are called, e.g. `tokens`. Set together with `unit_singular`. Trimmed; at most 32 characters. + :param unit_singular: What one unit of the metric is called, e.g. `token`, used to label quantities and prices ("$12.00 per 1M tokens"). Set both `unit_singular` and `unit_plural`, or neither. Trimmed; at most 32 characters.""" response = await self._request( ApiRequest( method="post", @@ -148,6 +152,8 @@ async def create( product_id=product_id, segmentation_matrix=segmentation_matrix, unit_conversion=unit_conversion, + unit_plural=unit_plural, + unit_singular=unit_singular, usage_group_key=usage_group_key, ), CreateMetricRequest, @@ -203,6 +209,8 @@ async def update( name: str | None | Unset = UNSET, segmentation_matrix: MetricSegmentationMatrix | None | Unset = UNSET, unit_conversion: UnitConversion | None | Unset = UNSET, + unit_plural: str | None | Unset = UNSET, + unit_singular: str | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, extra_body: t.Mapping[str, object] | None = None, @@ -213,7 +221,9 @@ async def update( Partially update metric fields. Code and aggregation_type are immutable. - :param filters: Absent = leave filters untouched; present (even empty) = replace them.""" + :param filters: Absent = leave filters untouched; present (even empty) = replace them. + :param unit_plural: What several units of the metric are called, e.g. `tokens`. See `unit_singular`. + :param unit_singular: What one unit of the metric is called, e.g. `token`. Send `unit_singular` and `unit_plural` together; omit both to leave the label unchanged, or send both as empty strings to remove it. Trimmed; at most 32 characters.""" response = await self._request( ApiRequest( method="patch", @@ -228,6 +238,8 @@ async def update( name=name, segmentation_matrix=segmentation_matrix, unit_conversion=unit_conversion, + unit_plural=unit_plural, + unit_singular=unit_singular, ), UpdateMetricRequest, ), @@ -392,6 +404,8 @@ def create( product_id: ProductId | None | Unset = UNSET, segmentation_matrix: MetricSegmentationMatrix | None | Unset = UNSET, unit_conversion: UnitConversion | None | Unset = UNSET, + unit_plural: str | None | Unset = UNSET, + unit_singular: str | None | Unset = UNSET, usage_group_key: str | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, @@ -401,7 +415,9 @@ def create( ) -> Metric: """Create a billable metric - :param filters: Pre-aggregation property filters. Optional and backward-compatible; omit for none.""" + :param filters: Pre-aggregation property filters. Optional and backward-compatible; omit for none. + :param unit_plural: What several units of the metric are called, e.g. `tokens`. Set together with `unit_singular`. Trimmed; at most 32 characters. + :param unit_singular: What one unit of the metric is called, e.g. `token`, used to label quantities and prices ("$12.00 per 1M tokens"). Set both `unit_singular` and `unit_plural`, or neither. Trimmed; at most 32 characters.""" response = self._request( ApiRequest( method="post", @@ -420,6 +436,8 @@ def create( product_id=product_id, segmentation_matrix=segmentation_matrix, unit_conversion=unit_conversion, + unit_plural=unit_plural, + unit_singular=unit_singular, usage_group_key=usage_group_key, ), CreateMetricRequest, @@ -475,6 +493,8 @@ def update( name: str | None | Unset = UNSET, segmentation_matrix: MetricSegmentationMatrix | None | Unset = UNSET, unit_conversion: UnitConversion | None | Unset = UNSET, + unit_plural: str | None | Unset = UNSET, + unit_singular: str | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, extra_body: t.Mapping[str, object] | None = None, @@ -485,7 +505,9 @@ def update( Partially update metric fields. Code and aggregation_type are immutable. - :param filters: Absent = leave filters untouched; present (even empty) = replace them.""" + :param filters: Absent = leave filters untouched; present (even empty) = replace them. + :param unit_plural: What several units of the metric are called, e.g. `tokens`. See `unit_singular`. + :param unit_singular: What one unit of the metric is called, e.g. `token`. Send `unit_singular` and `unit_plural` together; omit both to leave the label unchanged, or send both as empty strings to remove it. Trimmed; at most 32 characters.""" response = self._request( ApiRequest( method="patch", @@ -500,6 +522,8 @@ def update( name=name, segmentation_matrix=segmentation_matrix, unit_conversion=unit_conversion, + unit_plural=unit_plural, + unit_singular=unit_singular, ), UpdateMetricRequest, ), diff --git a/meteroid/api/plans.py b/meteroid/api/plans.py index 3ca3af6..81a0e9b 100644 --- a/meteroid/api/plans.py +++ b/meteroid/api/plans.py @@ -12,6 +12,7 @@ BillingConfig, CreateEntitlementsRequest, CreatePlanRequest, + CreditExhaustedRule, EntitlementListResponse, EntitlementSpecRequest, MinimumCommitmentInput, @@ -211,6 +212,7 @@ async def create( status: PlanStatusEnum | PlanStatusEnumLiteral, add_ons: builtins.list[PlanAddOnInput] | None = None, billing: BillingConfig | None | Unset = UNSET, + credit_exhausted_rule: CreditExhaustedRule | None | Unset = UNSET, description: str | None | Unset = UNSET, entitlements: builtins.list[EntitlementSpecRequest] | None = None, self_service_rank: int | None | Unset = UNSET, @@ -238,6 +240,7 @@ async def create( add_ons=add_ons, billing=billing, components=components, + credit_exhausted_rule=credit_exhausted_rule, currency=currency, description=description, entitlements=entitlements, @@ -313,6 +316,7 @@ async def replace( name: str, add_ons: builtins.list[PlanAddOnInput] | None = None, billing: BillingConfig | None | Unset = UNSET, + credit_exhausted_rule: CreditExhaustedRule | None | Unset = UNSET, description: str | None | Unset = UNSET, entitlements: builtins.list[EntitlementSpecRequest] | None = None, minimum_commitment: MinimumCommitmentInput | None | Unset = UNSET, @@ -329,7 +333,9 @@ async def replace( Full replacement of a plan's version. On a draft plan, updates in-place. On a published plan, creates a new version. Set `status` to `DRAFT` to - stage as a new draft without publishing. + stage as a new draft without publishing. A usage or capacity component priced in a credit + unit must be sent with its `credit_unit`, or the request is refused: omitting it would bill + its credits as money. :param entitlements: Entitlements to attach to this plan's version. Replacing a published plan creates a new version, and entitlements belong to a version, so passing them here keeps them attached to whichever version the call produces. :param tax_inclusive: The plan's amounts are quoted tax-included ("9.99 incl. VAT"): tax is carved out of them at invoice time instead of being added on top, so the customer pays the quoted price whatever rate applies. A customer who bears no tax (reverse charge, exempt, export) still pays it in full. Defaults to `false`.""" @@ -345,6 +351,7 @@ async def replace( add_ons=add_ons, billing=billing, components=components, + credit_exhausted_rule=credit_exhausted_rule, currency=currency, description=description, entitlements=entitlements, @@ -684,6 +691,7 @@ def create( status: PlanStatusEnum | PlanStatusEnumLiteral, add_ons: builtins.list[PlanAddOnInput] | None = None, billing: BillingConfig | None | Unset = UNSET, + credit_exhausted_rule: CreditExhaustedRule | None | Unset = UNSET, description: str | None | Unset = UNSET, entitlements: builtins.list[EntitlementSpecRequest] | None = None, self_service_rank: int | None | Unset = UNSET, @@ -711,6 +719,7 @@ def create( add_ons=add_ons, billing=billing, components=components, + credit_exhausted_rule=credit_exhausted_rule, currency=currency, description=description, entitlements=entitlements, @@ -786,6 +795,7 @@ def replace( name: str, add_ons: builtins.list[PlanAddOnInput] | None = None, billing: BillingConfig | None | Unset = UNSET, + credit_exhausted_rule: CreditExhaustedRule | None | Unset = UNSET, description: str | None | Unset = UNSET, entitlements: builtins.list[EntitlementSpecRequest] | None = None, minimum_commitment: MinimumCommitmentInput | None | Unset = UNSET, @@ -802,7 +812,9 @@ def replace( Full replacement of a plan's version. On a draft plan, updates in-place. On a published plan, creates a new version. Set `status` to `DRAFT` to - stage as a new draft without publishing. + stage as a new draft without publishing. A usage or capacity component priced in a credit + unit must be sent with its `credit_unit`, or the request is refused: omitting it would bill + its credits as money. :param entitlements: Entitlements to attach to this plan's version. Replacing a published plan creates a new version, and entitlements belong to a version, so passing them here keeps them attached to whichever version the call produces. :param tax_inclusive: The plan's amounts are quoted tax-included ("9.99 incl. VAT"): tax is carved out of them at invoice time instead of being added on top, so the customer pays the quoted price whatever rate applies. A customer who bears no tax (reverse charge, exempt, export) still pays it in full. Defaults to `false`.""" @@ -818,6 +830,7 @@ def replace( add_ons=add_ons, billing=billing, components=components, + credit_exhausted_rule=credit_exhausted_rule, currency=currency, description=description, entitlements=entitlements, diff --git a/meteroid/api/subscriptions.py b/meteroid/api/subscriptions.py index 68ab93f..5f0bf7e 100644 --- a/meteroid/api/subscriptions.py +++ b/meteroid/api/subscriptions.py @@ -25,6 +25,7 @@ SubscriptionStatusEnum, SubscriptionUpdateRequest, SubscriptionUpdateResponse, + UsageFilter, ) from ..serialization import UNSET, Unset, to_json_value from ._pages import ( @@ -138,6 +139,7 @@ async def create( purchase_order: str | None | Unset = UNSET, skip_past_invoices: bool | None = None, trial_days: int | None = None, + usage_filter: UsageFilter | None = None, version: int | None = None, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, @@ -150,9 +152,11 @@ async def create( Create a new subscription for a customer with a specific plan. :param backdate_invoices: Historical import mode: when true, invoices finalized for this subscription keep their billing-period date as the invoice date instead of being stamped with the emission date. + :param coupon_codes: Codes of the coupons to apply. A code matches whatever the case of its ASCII letters; when several coupons differ from it only in that case, the one with exactly that code applies. A code that names no live coupon, or several differing only in case, refuses the request with 400 listing the codes (unknown codes used to be ignored silently). :param custom_properties: User-defined custom property values, keyed by definition `key`. Validated against the tenant's subscription definitions. :param payment_methods_config: Payment methods configuration. If not specified, inherits from the invoicing entity. - :param skip_past_invoices: Migration mode: when true with a past start_date, skip creating invoices for past cycles. The subscription will be set to the current billing period with correct cycle_index.""" + :param skip_past_invoices: Migration mode: when true with a past start_date, skip creating invoices for past cycles. The subscription will be set to the current billing period with correct cycle_index. + :param usage_filter: Bill only the customer's events whose property has this value. Omit to bill the events no usage filter of the customer's other subscriptions matches.""" response = await self._request( ApiRequest( method="post", @@ -180,6 +184,7 @@ async def create( skip_past_invoices=skip_past_invoices, start_date=start_date, trial_days=trial_days, + usage_filter=usage_filter, version=version, ), SubscriptionCreateRequest, @@ -239,6 +244,7 @@ async def update( net_terms: int | None | Unset = UNSET, payment_methods_config: PaymentMethodsConfig | None | Unset = UNSET, purchase_order: str | None | Unset = UNSET, + usage_filter: UsageFilter | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, extra_body: t.Mapping[str, object] | None = None, @@ -269,6 +275,7 @@ async def update( net_terms=net_terms, payment_methods_config=payment_methods_config, purchase_order=purchase_order, + usage_filter=usage_filter, ), SubscriptionUpdateRequest, ), @@ -495,6 +502,7 @@ def create( purchase_order: str | None | Unset = UNSET, skip_past_invoices: bool | None = None, trial_days: int | None = None, + usage_filter: UsageFilter | None = None, version: int | None = None, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, @@ -507,9 +515,11 @@ def create( Create a new subscription for a customer with a specific plan. :param backdate_invoices: Historical import mode: when true, invoices finalized for this subscription keep their billing-period date as the invoice date instead of being stamped with the emission date. + :param coupon_codes: Codes of the coupons to apply. A code matches whatever the case of its ASCII letters; when several coupons differ from it only in that case, the one with exactly that code applies. A code that names no live coupon, or several differing only in case, refuses the request with 400 listing the codes (unknown codes used to be ignored silently). :param custom_properties: User-defined custom property values, keyed by definition `key`. Validated against the tenant's subscription definitions. :param payment_methods_config: Payment methods configuration. If not specified, inherits from the invoicing entity. - :param skip_past_invoices: Migration mode: when true with a past start_date, skip creating invoices for past cycles. The subscription will be set to the current billing period with correct cycle_index.""" + :param skip_past_invoices: Migration mode: when true with a past start_date, skip creating invoices for past cycles. The subscription will be set to the current billing period with correct cycle_index. + :param usage_filter: Bill only the customer's events whose property has this value. Omit to bill the events no usage filter of the customer's other subscriptions matches.""" response = self._request( ApiRequest( method="post", @@ -537,6 +547,7 @@ def create( skip_past_invoices=skip_past_invoices, start_date=start_date, trial_days=trial_days, + usage_filter=usage_filter, version=version, ), SubscriptionCreateRequest, @@ -596,6 +607,7 @@ def update( net_terms: int | None | Unset = UNSET, payment_methods_config: PaymentMethodsConfig | None | Unset = UNSET, purchase_order: str | None | Unset = UNSET, + usage_filter: UsageFilter | None | Unset = UNSET, extra_headers: t.Mapping[str, str] | None = None, extra_query: t.Mapping[str, object] | None = None, extra_body: t.Mapping[str, object] | None = None, @@ -626,6 +638,7 @@ def update( net_terms=net_terms, payment_methods_config=payment_methods_config, purchase_order=purchase_order, + usage_filter=usage_filter, ), SubscriptionUpdateRequest, ), diff --git a/meteroid/api/usage.py b/meteroid/api/usage.py index 30bc8f3..ae540a1 100644 --- a/meteroid/api/usage.py +++ b/meteroid/api/usage.py @@ -3,6 +3,7 @@ from __future__ import annotations +import functools import typing as t from datetime import date @@ -21,6 +22,12 @@ decode_response, serialize_query_params, ) +from .usage_customer import ( + AsyncUsageCustomer, + AsyncUsageCustomerWithRawResponse, + UsageCustomer, + UsageCustomerWithRawResponse, +) class AsyncUsage(ApiBaseAsync): @@ -31,47 +38,10 @@ def with_raw_response(self) -> AsyncUsageWithRawResponse: """These methods, returning an :class:`APIResponse` with the status and headers.""" return AsyncUsageWithRawResponse(self) - async def retrieve_customer( - self, - customer_id: str, - *, - start_date: date, - end_date: date, - metric_id: BillableMetricId | None = None, - extra_headers: t.Mapping[str, str] | None = None, - extra_query: t.Mapping[str, object] | None = None, - extra_body: t.Mapping[str, object] | None = None, - timeout: Timeout | Unset = UNSET, - max_retries: int | None = None, - ) -> UsageResponse: - """Get customer usage - - Retrieve aggregated usage data for a customer over a specified period.""" - response = await self._request( - ApiRequest( - method="get", - path="/api/v1/usage/customer/{customer_id}", - path_params={ - "customer_id": customer_id, - }, - query_params=serialize_query_params( - { - "start_date": start_date, - "end_date": end_date, - "metric_id": metric_id, - }, - ), - error_types={ - "default": _models.RestErrorResponse, - }, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - max_retries=max_retries, - ) - ) - return decode_response(response, UsageResponse) + @functools.cached_property + def customer(self) -> AsyncUsageCustomer: + """The customer API.""" + return AsyncUsageCustomer(self._cfg, self._httpx_client) async def retrieve_subscription( self, @@ -89,7 +59,10 @@ async def retrieve_subscription( """Get subscription usage Retrieve aggregated usage data for a subscription's usage-based components. - If start_date/end_date are omitted, defaults to the current billing period.""" + If start_date/end_date are omitted, defaults to the current billing period, and the response + then carries the running total (beta): what each metric's usage adds so far to the next invoice, + and what they add up to, before discounts and credits. That reading is shared for up to 30 + seconds (see `computed_at`). Counts towards the usage cost reads' rate limit.""" response = await self._request( ApiRequest( method="get", @@ -159,14 +132,17 @@ class AsyncUsageWithRawResponse: """The methods of :class:`AsyncUsage`, returning an :class:`APIResponse`.""" def __init__(self, resource: AsyncUsage) -> None: - self.retrieve_customer = async_to_raw_response_wrapper( - resource.retrieve_customer - ) + self._resource = resource self.retrieve_subscription = async_to_raw_response_wrapper( resource.retrieve_subscription ) self.retrieve_summary = async_to_raw_response_wrapper(resource.retrieve_summary) + @property + def customer(self) -> AsyncUsageCustomerWithRawResponse: + """The customer API.""" + return AsyncUsageCustomerWithRawResponse(self._resource.customer) + class Usage(ApiBaseSync): """Usage API.""" @@ -176,47 +152,10 @@ def with_raw_response(self) -> UsageWithRawResponse: """These methods, returning an :class:`APIResponse` with the status and headers.""" return UsageWithRawResponse(self) - def retrieve_customer( - self, - customer_id: str, - *, - start_date: date, - end_date: date, - metric_id: BillableMetricId | None = None, - extra_headers: t.Mapping[str, str] | None = None, - extra_query: t.Mapping[str, object] | None = None, - extra_body: t.Mapping[str, object] | None = None, - timeout: Timeout | Unset = UNSET, - max_retries: int | None = None, - ) -> UsageResponse: - """Get customer usage - - Retrieve aggregated usage data for a customer over a specified period.""" - response = self._request( - ApiRequest( - method="get", - path="/api/v1/usage/customer/{customer_id}", - path_params={ - "customer_id": customer_id, - }, - query_params=serialize_query_params( - { - "start_date": start_date, - "end_date": end_date, - "metric_id": metric_id, - }, - ), - error_types={ - "default": _models.RestErrorResponse, - }, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - max_retries=max_retries, - ) - ) - return decode_response(response, UsageResponse) + @functools.cached_property + def customer(self) -> UsageCustomer: + """The customer API.""" + return UsageCustomer(self._cfg, self._httpx_client) def retrieve_subscription( self, @@ -234,7 +173,10 @@ def retrieve_subscription( """Get subscription usage Retrieve aggregated usage data for a subscription's usage-based components. - If start_date/end_date are omitted, defaults to the current billing period.""" + If start_date/end_date are omitted, defaults to the current billing period, and the response + then carries the running total (beta): what each metric's usage adds so far to the next invoice, + and what they add up to, before discounts and credits. That reading is shared for up to 30 + seconds (see `computed_at`). Counts towards the usage cost reads' rate limit.""" response = self._request( ApiRequest( method="get", @@ -304,8 +246,13 @@ class UsageWithRawResponse: """The methods of :class:`Usage`, returning an :class:`APIResponse`.""" def __init__(self, resource: Usage) -> None: - self.retrieve_customer = to_raw_response_wrapper(resource.retrieve_customer) + self._resource = resource self.retrieve_subscription = to_raw_response_wrapper( resource.retrieve_subscription ) self.retrieve_summary = to_raw_response_wrapper(resource.retrieve_summary) + + @property + def customer(self) -> UsageCustomerWithRawResponse: + """The customer API.""" + return UsageCustomerWithRawResponse(self._resource.customer) diff --git a/meteroid/api/usage_costs.py b/meteroid/api/usage_costs.py new file mode 100644 index 0000000..961d4f2 --- /dev/null +++ b/meteroid/api/usage_costs.py @@ -0,0 +1,95 @@ +# this file is @generated +"""Usage costs API.""" + +from __future__ import annotations + +import functools + +from .common import ( + ApiBaseAsync, + ApiBaseSync, +) +from .usage_costs_customer import ( + AsyncUsageCostsCustomer, + AsyncUsageCostsCustomerWithRawResponse, + UsageCostsCustomer, + UsageCostsCustomerWithRawResponse, +) +from .usage_costs_subscription import ( + AsyncUsageCostsSubscription, + AsyncUsageCostsSubscriptionWithRawResponse, + UsageCostsSubscription, + UsageCostsSubscriptionWithRawResponse, +) + + +class AsyncUsageCosts(ApiBaseAsync): + """Usage costs API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncUsageCostsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncUsageCostsWithRawResponse(self) + + @functools.cached_property + def customer(self) -> AsyncUsageCostsCustomer: + """The customer API.""" + return AsyncUsageCostsCustomer(self._cfg, self._httpx_client) + + @functools.cached_property + def subscription(self) -> AsyncUsageCostsSubscription: + """The subscription API.""" + return AsyncUsageCostsSubscription(self._cfg, self._httpx_client) + + +class AsyncUsageCostsWithRawResponse: + """The methods of :class:`AsyncUsageCosts`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncUsageCosts) -> None: + self._resource = resource + + @property + def customer(self) -> AsyncUsageCostsCustomerWithRawResponse: + """The customer API.""" + return AsyncUsageCostsCustomerWithRawResponse(self._resource.customer) + + @property + def subscription(self) -> AsyncUsageCostsSubscriptionWithRawResponse: + """The subscription API.""" + return AsyncUsageCostsSubscriptionWithRawResponse(self._resource.subscription) + + +class UsageCosts(ApiBaseSync): + """Usage costs API.""" + + @property + def with_raw_response(self) -> UsageCostsWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return UsageCostsWithRawResponse(self) + + @functools.cached_property + def customer(self) -> UsageCostsCustomer: + """The customer API.""" + return UsageCostsCustomer(self._cfg, self._httpx_client) + + @functools.cached_property + def subscription(self) -> UsageCostsSubscription: + """The subscription API.""" + return UsageCostsSubscription(self._cfg, self._httpx_client) + + +class UsageCostsWithRawResponse: + """The methods of :class:`UsageCosts`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: UsageCosts) -> None: + self._resource = resource + + @property + def customer(self) -> UsageCostsCustomerWithRawResponse: + """The customer API.""" + return UsageCostsCustomerWithRawResponse(self._resource.customer) + + @property + def subscription(self) -> UsageCostsSubscriptionWithRawResponse: + """The subscription API.""" + return UsageCostsSubscriptionWithRawResponse(self._resource.subscription) diff --git a/meteroid/api/usage_costs_customer.py b/meteroid/api/usage_costs_customer.py new file mode 100644 index 0000000..272b443 --- /dev/null +++ b/meteroid/api/usage_costs_customer.py @@ -0,0 +1,288 @@ +# this file is @generated +"""Usage costs customer API.""" + +from __future__ import annotations + +import typing as t +from datetime import datetime + +from .. import models as _models +from ..models import ( + ListCustomerUsageCostsGranularity, + UsageCostListResponse, + UsageEventListResponse, +) +from ..serialization import UNSET, Unset +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + serialize_query_params, +) + + +class AsyncUsageCostsCustomer(ApiBaseAsync): + """Usage costs customer API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncUsageCostsCustomerWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncUsageCostsCustomerWithRawResponse(self) + + async def list_costs( + self, + customer_id: str, + *, + metric: str, + granularity: ListCustomerUsageCostsGranularity, + from_: datetime, + to: datetime, + group_by: str | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageCostListResponse: + """List a customer's usage costs over time + + A customer's usage of one billable metric and what it costs, per hour, day or month, optionally + split by an event property, summed across the subscriptions that bill it. Buckets are cut at UTC + boundaries and only cover the customer's billing periods. Per-unit and matrix pricing give exact + costs. For pricing that depends on the whole period's usage (tiered, volume, package, capacity), + a bucket's cost is what it adds to the period's price so far and `exact` is false. The range is + at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param group_by: Event property to split the usage by. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}/costs", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "group_by": group_by, + "granularity": granularity, + "from": from_, + "to": to, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageCostListResponse) + + async def list_events( + self, + customer_id: str, + *, + metric: str, + from_: datetime, + to: datetime, + cursor: str | None = None, + limit: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageEventListResponse: + """List a customer's usage events with their cost + + A customer's events for one billable metric, oldest first, each with the units it adds and what + they cost at the price in force. The cost is exact for per-unit and matrix pricing and absent + for pricing that depends on the whole period's usage (tiered, volume, package, capacity) and for + aggregations that cannot be split per event. The range is at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`. + :param cursor: The `next_cursor` of the previous page. + :param limit: Page size, 100 by default and 500 at most.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}/events", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "from": from_, + "to": to, + "cursor": cursor, + "limit": limit, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageEventListResponse) + + +class AsyncUsageCostsCustomerWithRawResponse: + """The methods of :class:`AsyncUsageCostsCustomer`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncUsageCostsCustomer) -> None: + self.list_costs = async_to_raw_response_wrapper(resource.list_costs) + self.list_events = async_to_raw_response_wrapper(resource.list_events) + + +class UsageCostsCustomer(ApiBaseSync): + """Usage costs customer API.""" + + @property + def with_raw_response(self) -> UsageCostsCustomerWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return UsageCostsCustomerWithRawResponse(self) + + def list_costs( + self, + customer_id: str, + *, + metric: str, + granularity: ListCustomerUsageCostsGranularity, + from_: datetime, + to: datetime, + group_by: str | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageCostListResponse: + """List a customer's usage costs over time + + A customer's usage of one billable metric and what it costs, per hour, day or month, optionally + split by an event property, summed across the subscriptions that bill it. Buckets are cut at UTC + boundaries and only cover the customer's billing periods. Per-unit and matrix pricing give exact + costs. For pricing that depends on the whole period's usage (tiered, volume, package, capacity), + a bucket's cost is what it adds to the period's price so far and `exact` is false. The range is + at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param group_by: Event property to split the usage by. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}/costs", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "group_by": group_by, + "granularity": granularity, + "from": from_, + "to": to, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageCostListResponse) + + def list_events( + self, + customer_id: str, + *, + metric: str, + from_: datetime, + to: datetime, + cursor: str | None = None, + limit: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageEventListResponse: + """List a customer's usage events with their cost + + A customer's events for one billable metric, oldest first, each with the units it adds and what + they cost at the price in force. The cost is exact for per-unit and matrix pricing and absent + for pricing that depends on the whole period's usage (tiered, volume, package, capacity) and for + aggregations that cannot be split per event. The range is at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`. + :param cursor: The `next_cursor` of the previous page. + :param limit: Page size, 100 by default and 500 at most.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}/events", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "from": from_, + "to": to, + "cursor": cursor, + "limit": limit, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageEventListResponse) + + +class UsageCostsCustomerWithRawResponse: + """The methods of :class:`UsageCostsCustomer`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: UsageCostsCustomer) -> None: + self.list_costs = to_raw_response_wrapper(resource.list_costs) + self.list_events = to_raw_response_wrapper(resource.list_events) diff --git a/meteroid/api/usage_costs_subscription.py b/meteroid/api/usage_costs_subscription.py new file mode 100644 index 0000000..1f3bae2 --- /dev/null +++ b/meteroid/api/usage_costs_subscription.py @@ -0,0 +1,280 @@ +# this file is @generated +"""Usage costs subscription API.""" + +from __future__ import annotations + +import typing as t +from datetime import datetime + +from .. import models as _models +from ..models import ( + ListSubscriptionUsageCostsGranularity, + UsageCostListResponse, + UsageEventListResponse, +) +from ..serialization import UNSET, Unset +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + serialize_query_params, +) + + +class AsyncUsageCostsSubscription(ApiBaseAsync): + """Usage costs subscription API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncUsageCostsSubscriptionWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncUsageCostsSubscriptionWithRawResponse(self) + + async def list_costs( + self, + subscription_id: str, + *, + metric: str, + granularity: ListSubscriptionUsageCostsGranularity, + from_: datetime, + to: datetime, + group_by: str | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageCostListResponse: + """List a subscription's usage costs over time + + The subscription's usage of one billable metric and what it costs, as for a customer but + counting only the events the subscription bills (see its usage filter) and only its own billing + periods. The range is at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param group_by: Event property to split the usage by. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/usage/subscription/{subscription_id}/costs", + path_params={ + "subscription_id": subscription_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "group_by": group_by, + "granularity": granularity, + "from": from_, + "to": to, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageCostListResponse) + + async def list_events( + self, + subscription_id: str, + *, + metric: str, + from_: datetime, + to: datetime, + cursor: str | None = None, + limit: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageEventListResponse: + """List a subscription's usage events with their cost + + The events of one billable metric that the subscription bills, oldest first: with a usage + filter, only the customer's events matching it; without, those no other subscription's filter + claims. Costs are as for the customer's events. The range is at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`. + :param cursor: The `next_cursor` of the previous page. + :param limit: Page size, 100 by default and 500 at most.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/usage/subscription/{subscription_id}/events", + path_params={ + "subscription_id": subscription_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "from": from_, + "to": to, + "cursor": cursor, + "limit": limit, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageEventListResponse) + + +class AsyncUsageCostsSubscriptionWithRawResponse: + """The methods of :class:`AsyncUsageCostsSubscription`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncUsageCostsSubscription) -> None: + self.list_costs = async_to_raw_response_wrapper(resource.list_costs) + self.list_events = async_to_raw_response_wrapper(resource.list_events) + + +class UsageCostsSubscription(ApiBaseSync): + """Usage costs subscription API.""" + + @property + def with_raw_response(self) -> UsageCostsSubscriptionWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return UsageCostsSubscriptionWithRawResponse(self) + + def list_costs( + self, + subscription_id: str, + *, + metric: str, + granularity: ListSubscriptionUsageCostsGranularity, + from_: datetime, + to: datetime, + group_by: str | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageCostListResponse: + """List a subscription's usage costs over time + + The subscription's usage of one billable metric and what it costs, as for a customer but + counting only the events the subscription bills (see its usage filter) and only its own billing + periods. The range is at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param group_by: Event property to split the usage by. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/usage/subscription/{subscription_id}/costs", + path_params={ + "subscription_id": subscription_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "group_by": group_by, + "granularity": granularity, + "from": from_, + "to": to, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageCostListResponse) + + def list_events( + self, + subscription_id: str, + *, + metric: str, + from_: datetime, + to: datetime, + cursor: str | None = None, + limit: int | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageEventListResponse: + """List a subscription's usage events with their cost + + The events of one billable metric that the subscription bills, oldest first: with a usage + filter, only the customer's events matching it; without, those no other subscription's filter + claims. Costs are as for the customer's events. The range is at most 93 days. + + Beta: the request and response shapes of this endpoint may change before general availability. + + :param metric: Billable metric, by ID or code. + :param from_: Start of the range, inclusive. + :param to: End of the range, exclusive. At most 93 days after `from`. + :param cursor: The `next_cursor` of the previous page. + :param limit: Page size, 100 by default and 500 at most.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/usage/subscription/{subscription_id}/events", + path_params={ + "subscription_id": subscription_id, + }, + query_params=serialize_query_params( + { + "metric": metric, + "from": from_, + "to": to, + "cursor": cursor, + "limit": limit, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageEventListResponse) + + +class UsageCostsSubscriptionWithRawResponse: + """The methods of :class:`UsageCostsSubscription`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: UsageCostsSubscription) -> None: + self.list_costs = to_raw_response_wrapper(resource.list_costs) + self.list_events = to_raw_response_wrapper(resource.list_events) diff --git a/meteroid/api/usage_customer.py b/meteroid/api/usage_customer.py new file mode 100644 index 0000000..a027a17 --- /dev/null +++ b/meteroid/api/usage_customer.py @@ -0,0 +1,238 @@ +# this file is @generated +"""Usage customer API.""" + +from __future__ import annotations + +import typing as t +from datetime import date + +from .. import models as _models +from ..models import ( + BillableMetricId, + UnattributedUsageResponse, + UsageResponse, +) +from ..serialization import UNSET, Unset +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + serialize_query_params, +) + + +class AsyncUsageCustomer(ApiBaseAsync): + """Usage customer API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncUsageCustomerWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncUsageCustomerWithRawResponse(self) + + async def retrieve( + self, + customer_id: str, + *, + start_date: date, + end_date: date, + metric_id: BillableMetricId | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageResponse: + """Get customer usage + + Retrieve aggregated usage data for a customer over a specified period.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "start_date": start_date, + "end_date": end_date, + "metric_id": metric_id, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageResponse) + + async def retrieve_unattributed( + self, + customer_id: str, + *, + start_date: date | None = None, + end_date: date | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UnattributedUsageResponse: + """Get unattributed usage + + Retrieve a customer's usage that no subscription bills. When subscriptions of the customer + pricing a metric set a usage filter, events matching none of the filters are billed by a + subscription without one; when every such subscription has a filter, those events are kept + and reported here instead of being billed. + + :param start_date: First day of the range. Defaults to the first day of the current month (UTC). + :param end_date: Day after the range (exclusive). Defaults to the first day of the next month.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}/unattributed", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "start_date": start_date, + "end_date": end_date, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UnattributedUsageResponse) + + +class AsyncUsageCustomerWithRawResponse: + """The methods of :class:`AsyncUsageCustomer`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncUsageCustomer) -> None: + self.retrieve = async_to_raw_response_wrapper(resource.retrieve) + self.retrieve_unattributed = async_to_raw_response_wrapper( + resource.retrieve_unattributed + ) + + +class UsageCustomer(ApiBaseSync): + """Usage customer API.""" + + @property + def with_raw_response(self) -> UsageCustomerWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return UsageCustomerWithRawResponse(self) + + def retrieve( + self, + customer_id: str, + *, + start_date: date, + end_date: date, + metric_id: BillableMetricId | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UsageResponse: + """Get customer usage + + Retrieve aggregated usage data for a customer over a specified period.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "start_date": start_date, + "end_date": end_date, + "metric_id": metric_id, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UsageResponse) + + def retrieve_unattributed( + self, + customer_id: str, + *, + start_date: date | None = None, + end_date: date | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> UnattributedUsageResponse: + """Get unattributed usage + + Retrieve a customer's usage that no subscription bills. When subscriptions of the customer + pricing a metric set a usage filter, events matching none of the filters are billed by a + subscription without one; when every such subscription has a filter, those events are kept + and reported here instead of being billed. + + :param start_date: First day of the range. Defaults to the first day of the current month (UTC). + :param end_date: Day after the range (exclusive). Defaults to the first day of the next month.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/usage/customer/{customer_id}/unattributed", + path_params={ + "customer_id": customer_id, + }, + query_params=serialize_query_params( + { + "start_date": start_date, + "end_date": end_date, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, UnattributedUsageResponse) + + +class UsageCustomerWithRawResponse: + """The methods of :class:`UsageCustomer`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: UsageCustomer) -> None: + self.retrieve = to_raw_response_wrapper(resource.retrieve) + self.retrieve_unattributed = to_raw_response_wrapper( + resource.retrieve_unattributed + ) diff --git a/meteroid/api/webhook_endpoints_endpoints.py b/meteroid/api/webhook_endpoints_endpoints.py index 1a27016..f0b5f59 100644 --- a/meteroid/api/webhook_endpoints_endpoints.py +++ b/meteroid/api/webhook_endpoints_endpoints.py @@ -92,7 +92,7 @@ async def create( The signing secret is returned once, in this response only. - :param event_types: Event types to subscribe to. Omit or leave empty to receive every event type. + :param event_types: Event types to subscribe to. Omit or leave empty to receive every event type except Beta ones, which are sent only to endpoints that list them. :param headers: Custom headers sent with every delivery. :param rate_limit_per_sec: Deliveries started per second, at most (1 to 1000). :param url: HTTPS destination. Private and loopback addresses are rejected unless the instance is configured to allow them.""" @@ -206,7 +206,7 @@ async def update( :param description: Omit to leave unchanged; send `null` or an empty string to clear. :param disabled: Re-enabling an endpoint also resets its consecutive failure count. - :param event_types: Replaces the subscription list. An empty array subscribes to every event type. + :param event_types: Replaces the subscription list. An empty array subscribes to every event type except Beta ones, which are sent only to endpoints that list them. :param headers: Replaces the custom header list. An empty array removes every header. :param rate_limit_per_sec: Omit to leave unchanged; send `null` to remove the rate limit.""" response = await self._request( @@ -430,7 +430,7 @@ def create( The signing secret is returned once, in this response only. - :param event_types: Event types to subscribe to. Omit or leave empty to receive every event type. + :param event_types: Event types to subscribe to. Omit or leave empty to receive every event type except Beta ones, which are sent only to endpoints that list them. :param headers: Custom headers sent with every delivery. :param rate_limit_per_sec: Deliveries started per second, at most (1 to 1000). :param url: HTTPS destination. Private and loopback addresses are rejected unless the instance is configured to allow them.""" @@ -544,7 +544,7 @@ def update( :param description: Omit to leave unchanged; send `null` or an empty string to clear. :param disabled: Re-enabling an endpoint also resets its consecutive failure count. - :param event_types: Replaces the subscription list. An empty array subscribes to every event type. + :param event_types: Replaces the subscription list. An empty array subscribes to every event type except Beta ones, which are sent only to endpoints that list them. :param headers: Replaces the custom header list. An empty array removes every header. :param rate_limit_per_sec: Omit to leave unchanged; send `null` to remove the rate limit.""" response = self._request( diff --git a/meteroid/models/__init__.py b/meteroid/models/__init__.py index 9a66570..bc0713a 100644 --- a/meteroid/models/__init__.py +++ b/meteroid/models/__init__.py @@ -12,6 +12,8 @@ from ..serialization import UNSET, UnknownVariant, Unset if t.TYPE_CHECKING: + from .actor import Actor + from .actor_type import ActorType, ActorTypeLiteral from .add_on import AddOn from .add_on_event import AddOnEvent from .add_on_event_data import AddOnEventData @@ -22,6 +24,16 @@ from .applied_coupon import AppliedCoupon from .applied_coupon_detailed import AppliedCouponDetailed from .applied_coupon_id import AppliedCouponId + from .auto_top_up import AutoTopUp + from .auto_top_up_cap_reached_event import AutoTopUpCapReachedEvent + from .auto_top_up_cap_reached_event_data import AutoTopUpCapReachedEventData + from .auto_top_up_consent_changed_event import AutoTopUpConsentChangedEvent + from .auto_top_up_consent_changed_event_data import AutoTopUpConsentChangedEventData + from .auto_top_up_event import AutoTopUpEvent + from .auto_top_up_event_data import AutoTopUpEventData + from .auto_top_up_method import AutoTopUpMethod, AutoTopUpMethodLiteral + from .auto_top_up_status import AutoTopUpStatus, AutoTopUpStatusLiteral + from .auto_top_up_write_request import AutoTopUpWriteRequest from .available_parameters import AvailableParameters from .bank_account_id import BankAccountId from .bank_transfer_payment_method_config import BankTransferPaymentMethodConfig @@ -49,6 +61,11 @@ from .boolean_entitlement_value import BooleanEntitlementValue from .boolean_feature_type import BooleanFeatureType from .boolean_resolved_entitlement_value import BooleanResolvedEntitlementValue + from .budget import Budget + from .budget_author import BudgetAuthor, BudgetAuthorLiteral + from .budget_behavior import BudgetBehavior, BudgetBehaviorLiteral + from .budget_list_response import BudgetListResponse + from .budget_period import BudgetPeriod from .calendar_reset_period import CalendarResetPeriod from .calendar_unit import CalendarUnit, CalendarUnitLiteral from .cancel_checkout_session_response import CancelCheckoutSessionResponse @@ -83,6 +100,7 @@ from .connection_type import ConnectionType, ConnectionTypeLiteral from .country_code import CountryCode from .coupon import Coupon + from .coupon_credit_scope import CouponCreditScope, CouponCreditScopeLiteral from .coupon_discount import CouponDiscount from .coupon_event import CouponEvent from .coupon_event_data import CouponEventData @@ -91,10 +109,13 @@ from .coupon_line_item import CouponLineItem from .coupon_list_response import CouponListResponse from .create_add_on_request import CreateAddOnRequest + from .create_budget_request import CreateBudgetRequest from .create_checkout_session_request import CreateCheckoutSessionRequest from .create_checkout_session_response import CreateCheckoutSessionResponse from .create_connected_account_request import CreateConnectedAccountRequest + from .create_connected_account_response import CreateConnectedAccountResponse from .create_coupon_request import CreateCouponRequest + from .create_credit_grant_request import CreateCreditGrantRequest from .create_entitlements_request import CreateEntitlementsRequest from .create_feature_request import CreateFeatureRequest from .create_metric_request import CreateMetricRequest @@ -104,8 +125,34 @@ from .create_product_request import CreateProductRequest from .create_subscription_add_on import CreateSubscriptionAddOn from .create_subscription_components import CreateSubscriptionComponents + from .create_top_up_request import CreateTopUpRequest from .create_webhook_endpoint_request import CreateWebhookEndpointRequest from .created_webhook_endpoint import CreatedWebhookEndpoint + from .credit_applies_to import CreditAppliesTo, CreditAppliesToLiteral + from .credit_cap import CreditCap + from .credit_caps import CreditCaps + from .credit_cost import CreditCost + from .credit_cost_request import CreditCostRequest + from .credit_discount import CreditDiscount + from .credit_exhausted_rule import CreditExhaustedRule + from .credit_exhausted_rule_invoice_variant import CreditExhaustedRuleInvoiceVariant + from .credit_exhausted_rule_pause_variant import CreditExhaustedRulePauseVariant + from .credit_expiry_unit import CreditExpiryUnit, CreditExpiryUnitLiteral + from .credit_fee import CreditFee + from .credit_fee_structure import CreditFeeStructure + from .credit_grant import CreditGrant + from .credit_grant_expired_event import CreditGrantExpiredEvent + from .credit_grant_expired_event_data import CreditGrantExpiredEventData + from .credit_grant_id import CreditGrantId + from .credit_grant_kind import CreditGrantKind, CreditGrantKindLiteral + from .credit_grant_lapse_reason import ( + CreditGrantLapseReason, + CreditGrantLapseReasonLiteral, + ) + from .credit_grant_list_response import CreditGrantListResponse + from .credit_grant_source import CreditGrantSource, CreditGrantSourceLiteral + from .credit_grant_status import CreditGrantStatus, CreditGrantStatusLiteral + from .credit_grant_type import CreditGrantType, CreditGrantTypeLiteral from .credit_note import CreditNote from .credit_note_custom_properties_request import CreditNoteCustomPropertiesRequest from .credit_note_event import CreditNoteEvent @@ -113,7 +160,18 @@ from .credit_note_id import CreditNoteId from .credit_note_list_response import CreditNoteListResponse from .credit_note_status import CreditNoteStatus, CreditNoteStatusLiteral + from .credit_pack_expiry import CreditPackExpiry + from .credit_pack_rollover import CreditPackRollover + from .credit_pack_unit import CreditPackUnit + from .credit_plan_fee import CreditPlanFee from .credit_type import CreditType, CreditTypeLiteral + from .credit_unit import CreditUnit + from .credit_unit_create_request import CreditUnitCreateRequest + from .credit_unit_list_response import CreditUnitListResponse + from .credit_unit_price import CreditUnitPrice + from .credit_unit_price_list_response import CreditUnitPriceListResponse + from .credit_unit_price_set_request import CreditUnitPriceSetRequest + from .credit_unit_update_request import CreditUnitUpdateRequest from .currency import Currency, CurrencyLiteral from .custom_property_definition import CustomPropertyDefinition from .custom_property_definition_create_request import ( @@ -154,8 +212,24 @@ from .effective_entitlement_list_response import EffectiveEntitlementListResponse from .effective_entitlement_value import EffectiveEntitlementValue from .entitlement import Entitlement + from .entitlement_check import EntitlementCheck + from .entitlement_check_feature import EntitlementCheckFeature + from .entitlement_check_limit import EntitlementCheckLimit + from .entitlement_check_limit_behavior import ( + EntitlementCheckLimitBehavior, + EntitlementCheckLimitBehaviorLiteral, + ) + from .entitlement_check_period import EntitlementCheckPeriod + from .entitlement_check_reason import ( + EntitlementCheckReason, + EntitlementCheckReasonLiteral, + ) from .entitlement_id import EntitlementId from .entitlement_list_response import EntitlementListResponse + from .entitlement_overage_behavior import ( + EntitlementOverageBehavior, + EntitlementOverageBehaviorLiteral, + ) from .entitlement_product_ref import EntitlementProductRef from .entitlement_spec_request import EntitlementSpecRequest from .entitlement_value import EntitlementValue @@ -181,6 +255,7 @@ from .feature_status import FeatureStatus, FeatureStatusLiteral from .feature_type import FeatureType from .fee import Fee + from .fee_credit_unit import FeeCreditUnit from .fixed_discount import FixedDiscount from .fixed_window_reset_period import FixedWindowResetPeriod from .get_checkout_session_response import GetCheckoutSessionResponse @@ -196,6 +271,7 @@ from .invoice_event import InvoiceEvent from .invoice_event_data import InvoiceEventData from .invoice_id import InvoiceId + from .invoice_line_credit_unit import InvoiceLineCreditUnit from .invoice_line_item import InvoiceLineItem from .invoice_list_response import InvoiceListResponse from .invoice_payment_status import ( @@ -206,8 +282,20 @@ from .invoice_type import InvoiceType, InvoiceTypeLiteral from .invoicing_entity_id import InvoicingEntityId from .json_config_value import JsonConfigValue + from .ledger_entry import LedgerEntry + from .ledger_entry_id import LedgerEntryId + from .ledger_entry_type import LedgerEntryType, LedgerEntryTypeLiteral + from .ledger_list_response import LedgerListResponse from .linked_segmentation_matrix import LinkedSegmentationMatrix from .list_checkout_sessions_response import ListCheckoutSessionsResponse + from .list_customer_usage_costs_granularity import ( + ListCustomerUsageCostsGranularity, + ListCustomerUsageCostsGranularityLiteral, + ) + from .list_subscription_usage_costs_granularity import ( + ListSubscriptionUsageCostsGranularity, + ListSubscriptionUsageCostsGranularityLiteral, + ) from .matrix_dimension import MatrixDimension from .matrix_plan_pricing import MatrixPlanPricing from .matrix_pricing import MatrixPricing @@ -257,7 +345,9 @@ from .package_plan_pricing import PackagePlanPricing from .package_pricing import PackagePricing from .pagination_response import PaginationResponse + from .patch_budget_request import PatchBudgetRequest from .patch_plan_request import PatchPlanRequest + from .patch_wallet_request import PatchWalletRequest from .payment_event import PaymentEvent from .payment_method_info import PaymentMethodInfo from .payment_method_type_enum import ( @@ -275,6 +365,7 @@ from .plan_add_on_input import PlanAddOnInput from .plan_event import PlanEvent from .plan_event_data import PlanEventData + from .plan_fee_credit_unit import PlanFeeCreditUnit from .plan_id import PlanId from .plan_list_response import PlanListResponse from .plan_status_enum import PlanStatusEnum, PlanStatusEnumLiteral @@ -352,6 +443,7 @@ from .subscription_component import SubscriptionComponent from .subscription_coupon import SubscriptionCoupon from .subscription_create_request import SubscriptionCreateRequest + from .subscription_credit import SubscriptionCredit from .subscription_details import SubscriptionDetails from .subscription_event import SubscriptionEvent from .subscription_event_data import SubscriptionEventData @@ -383,8 +475,13 @@ from .token_introspection_response import TokenIntrospectionResponse from .token_request import TokenRequest from .token_response import TokenResponse + from .top_up_collection import TopUpCollection, TopUpCollectionLiteral + from .top_up_response import TopUpResponse + from .top_up_status import TopUpStatus, TopUpStatusLiteral from .transaction import Transaction from .trial_config import TrialConfig + from .unattributed_metric_usage import UnattributedMetricUsage + from .unattributed_usage_response import UnattributedUsageResponse from .unit_conversion import UnitConversion from .unit_conversion_rounding_enum import ( UnitConversionRoundingEnum, @@ -397,8 +494,17 @@ from .update_metric_request import UpdateMetricRequest from .update_product_request import UpdateProductRequest from .update_webhook_endpoint_request import UpdateWebhookEndpointRequest + from .usage_at_zero import UsageAtZero, UsageAtZeroLiteral + from .usage_cost import UsageCost + from .usage_cost_list_response import UsageCostListResponse + from .usage_cost_row import UsageCostRow + from .usage_cost_unit import UsageCostUnit, UsageCostUnitLiteral + from .usage_event import UsageEvent + from .usage_event_list_response import UsageEventListResponse from .usage_fee import UsageFee from .usage_fee_structure import UsageFeeStructure + from .usage_filter import UsageFilter + from .usage_limit_id import UsageLimitId from .usage_model_enum import UsageModelEnum, UsageModelEnumLiteral from .usage_plan_fee import UsagePlanFee from .usage_pricing import UsagePricing @@ -406,6 +512,23 @@ from .usage_response import UsageResponse from .volume_plan_pricing import VolumePlanPricing from .volume_pricing import VolumePricing + from .wallet import Wallet + from .wallet_balance_low_event import WalletBalanceLowEvent + from .wallet_balance_low_event_data import WalletBalanceLowEventData + from .wallet_check import WalletCheck + from .wallet_credit_recovered_event import WalletCreditRecoveredEvent + from .wallet_credit_recovered_event_data import WalletCreditRecoveredEventData + from .wallet_credit_unrecovered_event import WalletCreditUnrecoveredEvent + from .wallet_credit_unrecovered_event_data import WalletCreditUnrecoveredEventData + from .wallet_debited_event import WalletDebitedEvent + from .wallet_debited_event_data import WalletDebitedEventData + from .wallet_depleted_event import WalletDepletedEvent + from .wallet_depleted_event_data import WalletDepletedEventData + from .wallet_funded_event import WalletFundedEvent + from .wallet_funded_event_data import WalletFundedEventData + from .wallet_list_response import WalletListResponse + from .wallet_recovered_event import WalletRecoveredEvent + from .wallet_recovered_event_data import WalletRecoveredEventData from .webhook_delivery import WebhookDelivery from .webhook_delivery_id import WebhookDeliveryId from .webhook_delivery_list_response import WebhookDeliveryListResponse @@ -425,6 +548,9 @@ from .webhook_header_input import WebhookHeaderInput _MODULES: dict[str, str] = { + "Actor": "actor", + "ActorType": "actor_type", + "ActorTypeLiteral": "actor_type", "AddOn": "add_on", "AddOnEvent": "add_on_event", "AddOnEventData": "add_on_event_data", @@ -435,6 +561,18 @@ "AppliedCoupon": "applied_coupon", "AppliedCouponDetailed": "applied_coupon_detailed", "AppliedCouponId": "applied_coupon_id", + "AutoTopUp": "auto_top_up", + "AutoTopUpCapReachedEvent": "auto_top_up_cap_reached_event", + "AutoTopUpCapReachedEventData": "auto_top_up_cap_reached_event_data", + "AutoTopUpConsentChangedEvent": "auto_top_up_consent_changed_event", + "AutoTopUpConsentChangedEventData": "auto_top_up_consent_changed_event_data", + "AutoTopUpEvent": "auto_top_up_event", + "AutoTopUpEventData": "auto_top_up_event_data", + "AutoTopUpMethod": "auto_top_up_method", + "AutoTopUpMethodLiteral": "auto_top_up_method", + "AutoTopUpStatus": "auto_top_up_status", + "AutoTopUpStatusLiteral": "auto_top_up_status", + "AutoTopUpWriteRequest": "auto_top_up_write_request", "AvailableParameters": "available_parameters", "BankAccountId": "bank_account_id", "BankTransferPaymentMethodConfig": "bank_transfer_payment_method_config", @@ -465,6 +603,13 @@ "BooleanEntitlementValue": "boolean_entitlement_value", "BooleanFeatureType": "boolean_feature_type", "BooleanResolvedEntitlementValue": "boolean_resolved_entitlement_value", + "Budget": "budget", + "BudgetAuthor": "budget_author", + "BudgetAuthorLiteral": "budget_author", + "BudgetBehavior": "budget_behavior", + "BudgetBehaviorLiteral": "budget_behavior", + "BudgetListResponse": "budget_list_response", + "BudgetPeriod": "budget_period", "CalendarResetPeriod": "calendar_reset_period", "CalendarUnit": "calendar_unit", "CalendarUnitLiteral": "calendar_unit", @@ -502,6 +647,8 @@ "ConnectionTypeLiteral": "connection_type", "CountryCode": "country_code", "Coupon": "coupon", + "CouponCreditScope": "coupon_credit_scope", + "CouponCreditScopeLiteral": "coupon_credit_scope", "CouponDiscount": "coupon_discount", "CouponEvent": "coupon_event", "CouponEventData": "coupon_event_data", @@ -511,10 +658,13 @@ "CouponLineItem": "coupon_line_item", "CouponListResponse": "coupon_list_response", "CreateAddOnRequest": "create_add_on_request", + "CreateBudgetRequest": "create_budget_request", "CreateCheckoutSessionRequest": "create_checkout_session_request", "CreateCheckoutSessionResponse": "create_checkout_session_response", "CreateConnectedAccountRequest": "create_connected_account_request", + "CreateConnectedAccountResponse": "create_connected_account_response", "CreateCouponRequest": "create_coupon_request", + "CreateCreditGrantRequest": "create_credit_grant_request", "CreateEntitlementsRequest": "create_entitlements_request", "CreateFeatureRequest": "create_feature_request", "CreateMetricRequest": "create_metric_request", @@ -524,8 +674,38 @@ "CreateProductRequest": "create_product_request", "CreateSubscriptionAddOn": "create_subscription_add_on", "CreateSubscriptionComponents": "create_subscription_components", + "CreateTopUpRequest": "create_top_up_request", "CreateWebhookEndpointRequest": "create_webhook_endpoint_request", "CreatedWebhookEndpoint": "created_webhook_endpoint", + "CreditAppliesTo": "credit_applies_to", + "CreditAppliesToLiteral": "credit_applies_to", + "CreditCap": "credit_cap", + "CreditCaps": "credit_caps", + "CreditCost": "credit_cost", + "CreditCostRequest": "credit_cost_request", + "CreditDiscount": "credit_discount", + "CreditExhaustedRule": "credit_exhausted_rule", + "CreditExhaustedRuleInvoiceVariant": "credit_exhausted_rule_invoice_variant", + "CreditExhaustedRulePauseVariant": "credit_exhausted_rule_pause_variant", + "CreditExpiryUnit": "credit_expiry_unit", + "CreditExpiryUnitLiteral": "credit_expiry_unit", + "CreditFee": "credit_fee", + "CreditFeeStructure": "credit_fee_structure", + "CreditGrant": "credit_grant", + "CreditGrantExpiredEvent": "credit_grant_expired_event", + "CreditGrantExpiredEventData": "credit_grant_expired_event_data", + "CreditGrantId": "credit_grant_id", + "CreditGrantKind": "credit_grant_kind", + "CreditGrantKindLiteral": "credit_grant_kind", + "CreditGrantLapseReason": "credit_grant_lapse_reason", + "CreditGrantLapseReasonLiteral": "credit_grant_lapse_reason", + "CreditGrantListResponse": "credit_grant_list_response", + "CreditGrantSource": "credit_grant_source", + "CreditGrantSourceLiteral": "credit_grant_source", + "CreditGrantStatus": "credit_grant_status", + "CreditGrantStatusLiteral": "credit_grant_status", + "CreditGrantType": "credit_grant_type", + "CreditGrantTypeLiteral": "credit_grant_type", "CreditNote": "credit_note", "CreditNoteCustomPropertiesRequest": "credit_note_custom_properties_request", "CreditNoteEvent": "credit_note_event", @@ -534,8 +714,19 @@ "CreditNoteListResponse": "credit_note_list_response", "CreditNoteStatus": "credit_note_status", "CreditNoteStatusLiteral": "credit_note_status", + "CreditPackExpiry": "credit_pack_expiry", + "CreditPackRollover": "credit_pack_rollover", + "CreditPackUnit": "credit_pack_unit", + "CreditPlanFee": "credit_plan_fee", "CreditType": "credit_type", "CreditTypeLiteral": "credit_type", + "CreditUnit": "credit_unit", + "CreditUnitCreateRequest": "credit_unit_create_request", + "CreditUnitListResponse": "credit_unit_list_response", + "CreditUnitPrice": "credit_unit_price", + "CreditUnitPriceListResponse": "credit_unit_price_list_response", + "CreditUnitPriceSetRequest": "credit_unit_price_set_request", + "CreditUnitUpdateRequest": "credit_unit_update_request", "Currency": "currency", "CurrencyLiteral": "currency", "CustomPropertyDefinition": "custom_property_definition", @@ -574,8 +765,18 @@ "EffectiveEntitlementListResponse": "effective_entitlement_list_response", "EffectiveEntitlementValue": "effective_entitlement_value", "Entitlement": "entitlement", + "EntitlementCheck": "entitlement_check", + "EntitlementCheckFeature": "entitlement_check_feature", + "EntitlementCheckLimit": "entitlement_check_limit", + "EntitlementCheckLimitBehavior": "entitlement_check_limit_behavior", + "EntitlementCheckLimitBehaviorLiteral": "entitlement_check_limit_behavior", + "EntitlementCheckPeriod": "entitlement_check_period", + "EntitlementCheckReason": "entitlement_check_reason", + "EntitlementCheckReasonLiteral": "entitlement_check_reason", "EntitlementId": "entitlement_id", "EntitlementListResponse": "entitlement_list_response", + "EntitlementOverageBehavior": "entitlement_overage_behavior", + "EntitlementOverageBehaviorLiteral": "entitlement_overage_behavior", "EntitlementProductRef": "entitlement_product_ref", "EntitlementSpecRequest": "entitlement_spec_request", "EntitlementValue": "entitlement_value", @@ -602,6 +803,7 @@ "FeatureStatusLiteral": "feature_status", "FeatureType": "feature_type", "Fee": "fee", + "FeeCreditUnit": "fee_credit_unit", "FixedDiscount": "fixed_discount", "FixedWindowResetPeriod": "fixed_window_reset_period", "GetCheckoutSessionResponse": "get_checkout_session_response", @@ -617,6 +819,7 @@ "InvoiceEvent": "invoice_event", "InvoiceEventData": "invoice_event_data", "InvoiceId": "invoice_id", + "InvoiceLineCreditUnit": "invoice_line_credit_unit", "InvoiceLineItem": "invoice_line_item", "InvoiceListResponse": "invoice_list_response", "InvoicePaymentStatus": "invoice_payment_status", @@ -627,8 +830,17 @@ "InvoiceTypeLiteral": "invoice_type", "InvoicingEntityId": "invoicing_entity_id", "JsonConfigValue": "json_config_value", + "LedgerEntry": "ledger_entry", + "LedgerEntryId": "ledger_entry_id", + "LedgerEntryType": "ledger_entry_type", + "LedgerEntryTypeLiteral": "ledger_entry_type", + "LedgerListResponse": "ledger_list_response", "LinkedSegmentationMatrix": "linked_segmentation_matrix", "ListCheckoutSessionsResponse": "list_checkout_sessions_response", + "ListCustomerUsageCostsGranularity": "list_customer_usage_costs_granularity", + "ListCustomerUsageCostsGranularityLiteral": "list_customer_usage_costs_granularity", + "ListSubscriptionUsageCostsGranularity": "list_subscription_usage_costs_granularity", + "ListSubscriptionUsageCostsGranularityLiteral": "list_subscription_usage_costs_granularity", "MatrixDimension": "matrix_dimension", "MatrixPlanPricing": "matrix_plan_pricing", "MatrixPricing": "matrix_pricing", @@ -678,7 +890,9 @@ "PackagePlanPricing": "package_plan_pricing", "PackagePricing": "package_pricing", "PaginationResponse": "pagination_response", + "PatchBudgetRequest": "patch_budget_request", "PatchPlanRequest": "patch_plan_request", + "PatchWalletRequest": "patch_wallet_request", "PaymentEvent": "payment_event", "PaymentMethodInfo": "payment_method_info", "PaymentMethodTypeEnum": "payment_method_type_enum", @@ -696,6 +910,7 @@ "PlanAddOnInput": "plan_add_on_input", "PlanEvent": "plan_event", "PlanEventData": "plan_event_data", + "PlanFeeCreditUnit": "plan_fee_credit_unit", "PlanId": "plan_id", "PlanListResponse": "plan_list_response", "PlanStatusEnum": "plan_status_enum", @@ -772,6 +987,7 @@ "SubscriptionComponent": "subscription_component", "SubscriptionCoupon": "subscription_coupon", "SubscriptionCreateRequest": "subscription_create_request", + "SubscriptionCredit": "subscription_credit", "SubscriptionDetails": "subscription_details", "SubscriptionEvent": "subscription_event", "SubscriptionEventData": "subscription_event_data", @@ -798,8 +1014,15 @@ "TokenIntrospectionResponse": "token_introspection_response", "TokenRequest": "token_request", "TokenResponse": "token_response", + "TopUpCollection": "top_up_collection", + "TopUpCollectionLiteral": "top_up_collection", + "TopUpResponse": "top_up_response", + "TopUpStatus": "top_up_status", + "TopUpStatusLiteral": "top_up_status", "Transaction": "transaction", "TrialConfig": "trial_config", + "UnattributedMetricUsage": "unattributed_metric_usage", + "UnattributedUsageResponse": "unattributed_usage_response", "UnitConversion": "unit_conversion", "UnitConversionRoundingEnum": "unit_conversion_rounding_enum", "UnitConversionRoundingEnumLiteral": "unit_conversion_rounding_enum", @@ -810,8 +1033,19 @@ "UpdateMetricRequest": "update_metric_request", "UpdateProductRequest": "update_product_request", "UpdateWebhookEndpointRequest": "update_webhook_endpoint_request", + "UsageAtZero": "usage_at_zero", + "UsageAtZeroLiteral": "usage_at_zero", + "UsageCost": "usage_cost", + "UsageCostListResponse": "usage_cost_list_response", + "UsageCostRow": "usage_cost_row", + "UsageCostUnit": "usage_cost_unit", + "UsageCostUnitLiteral": "usage_cost_unit", + "UsageEvent": "usage_event", + "UsageEventListResponse": "usage_event_list_response", "UsageFee": "usage_fee", "UsageFeeStructure": "usage_fee_structure", + "UsageFilter": "usage_filter", + "UsageLimitId": "usage_limit_id", "UsageModelEnum": "usage_model_enum", "UsageModelEnumLiteral": "usage_model_enum", "UsagePlanFee": "usage_plan_fee", @@ -820,6 +1054,23 @@ "UsageResponse": "usage_response", "VolumePlanPricing": "volume_plan_pricing", "VolumePricing": "volume_pricing", + "Wallet": "wallet", + "WalletBalanceLowEvent": "wallet_balance_low_event", + "WalletBalanceLowEventData": "wallet_balance_low_event_data", + "WalletCheck": "wallet_check", + "WalletCreditRecoveredEvent": "wallet_credit_recovered_event", + "WalletCreditRecoveredEventData": "wallet_credit_recovered_event_data", + "WalletCreditUnrecoveredEvent": "wallet_credit_unrecovered_event", + "WalletCreditUnrecoveredEventData": "wallet_credit_unrecovered_event_data", + "WalletDebitedEvent": "wallet_debited_event", + "WalletDebitedEventData": "wallet_debited_event_data", + "WalletDepletedEvent": "wallet_depleted_event", + "WalletDepletedEventData": "wallet_depleted_event_data", + "WalletFundedEvent": "wallet_funded_event", + "WalletFundedEventData": "wallet_funded_event_data", + "WalletListResponse": "wallet_list_response", + "WalletRecoveredEvent": "wallet_recovered_event", + "WalletRecoveredEventData": "wallet_recovered_event_data", "WebhookDelivery": "webhook_delivery", "WebhookDeliveryId": "webhook_delivery_id", "WebhookDeliveryListResponse": "webhook_delivery_list_response", @@ -853,6 +1104,9 @@ def __dir__() -> list[str]: "UNSET", "Unset", "UnknownVariant", + "Actor", + "ActorType", + "ActorTypeLiteral", "AddOn", "AddOnEvent", "AddOnEventData", @@ -863,6 +1117,18 @@ def __dir__() -> list[str]: "AppliedCoupon", "AppliedCouponDetailed", "AppliedCouponId", + "AutoTopUp", + "AutoTopUpCapReachedEvent", + "AutoTopUpCapReachedEventData", + "AutoTopUpConsentChangedEvent", + "AutoTopUpConsentChangedEventData", + "AutoTopUpEvent", + "AutoTopUpEventData", + "AutoTopUpMethod", + "AutoTopUpMethodLiteral", + "AutoTopUpStatus", + "AutoTopUpStatusLiteral", + "AutoTopUpWriteRequest", "AvailableParameters", "BankAccountId", "BankTransferPaymentMethodConfig", @@ -893,6 +1159,13 @@ def __dir__() -> list[str]: "BooleanEntitlementValue", "BooleanFeatureType", "BooleanResolvedEntitlementValue", + "Budget", + "BudgetAuthor", + "BudgetAuthorLiteral", + "BudgetBehavior", + "BudgetBehaviorLiteral", + "BudgetListResponse", + "BudgetPeriod", "CalendarResetPeriod", "CalendarUnit", "CalendarUnitLiteral", @@ -930,6 +1203,8 @@ def __dir__() -> list[str]: "ConnectionTypeLiteral", "CountryCode", "Coupon", + "CouponCreditScope", + "CouponCreditScopeLiteral", "CouponDiscount", "CouponEvent", "CouponEventData", @@ -939,10 +1214,13 @@ def __dir__() -> list[str]: "CouponLineItem", "CouponListResponse", "CreateAddOnRequest", + "CreateBudgetRequest", "CreateCheckoutSessionRequest", "CreateCheckoutSessionResponse", "CreateConnectedAccountRequest", + "CreateConnectedAccountResponse", "CreateCouponRequest", + "CreateCreditGrantRequest", "CreateEntitlementsRequest", "CreateFeatureRequest", "CreateMetricRequest", @@ -952,8 +1230,38 @@ def __dir__() -> list[str]: "CreateProductRequest", "CreateSubscriptionAddOn", "CreateSubscriptionComponents", + "CreateTopUpRequest", "CreateWebhookEndpointRequest", "CreatedWebhookEndpoint", + "CreditAppliesTo", + "CreditAppliesToLiteral", + "CreditCap", + "CreditCaps", + "CreditCost", + "CreditCostRequest", + "CreditDiscount", + "CreditExhaustedRule", + "CreditExhaustedRuleInvoiceVariant", + "CreditExhaustedRulePauseVariant", + "CreditExpiryUnit", + "CreditExpiryUnitLiteral", + "CreditFee", + "CreditFeeStructure", + "CreditGrant", + "CreditGrantExpiredEvent", + "CreditGrantExpiredEventData", + "CreditGrantId", + "CreditGrantKind", + "CreditGrantKindLiteral", + "CreditGrantLapseReason", + "CreditGrantLapseReasonLiteral", + "CreditGrantListResponse", + "CreditGrantSource", + "CreditGrantSourceLiteral", + "CreditGrantStatus", + "CreditGrantStatusLiteral", + "CreditGrantType", + "CreditGrantTypeLiteral", "CreditNote", "CreditNoteCustomPropertiesRequest", "CreditNoteEvent", @@ -962,8 +1270,19 @@ def __dir__() -> list[str]: "CreditNoteListResponse", "CreditNoteStatus", "CreditNoteStatusLiteral", + "CreditPackExpiry", + "CreditPackRollover", + "CreditPackUnit", + "CreditPlanFee", "CreditType", "CreditTypeLiteral", + "CreditUnit", + "CreditUnitCreateRequest", + "CreditUnitListResponse", + "CreditUnitPrice", + "CreditUnitPriceListResponse", + "CreditUnitPriceSetRequest", + "CreditUnitUpdateRequest", "Currency", "CurrencyLiteral", "CustomPropertyDefinition", @@ -1002,8 +1321,18 @@ def __dir__() -> list[str]: "EffectiveEntitlementListResponse", "EffectiveEntitlementValue", "Entitlement", + "EntitlementCheck", + "EntitlementCheckFeature", + "EntitlementCheckLimit", + "EntitlementCheckLimitBehavior", + "EntitlementCheckLimitBehaviorLiteral", + "EntitlementCheckPeriod", + "EntitlementCheckReason", + "EntitlementCheckReasonLiteral", "EntitlementId", "EntitlementListResponse", + "EntitlementOverageBehavior", + "EntitlementOverageBehaviorLiteral", "EntitlementProductRef", "EntitlementSpecRequest", "EntitlementValue", @@ -1030,6 +1359,7 @@ def __dir__() -> list[str]: "FeatureStatusLiteral", "FeatureType", "Fee", + "FeeCreditUnit", "FixedDiscount", "FixedWindowResetPeriod", "GetCheckoutSessionResponse", @@ -1045,6 +1375,7 @@ def __dir__() -> list[str]: "InvoiceEvent", "InvoiceEventData", "InvoiceId", + "InvoiceLineCreditUnit", "InvoiceLineItem", "InvoiceListResponse", "InvoicePaymentStatus", @@ -1055,8 +1386,17 @@ def __dir__() -> list[str]: "InvoiceTypeLiteral", "InvoicingEntityId", "JsonConfigValue", + "LedgerEntry", + "LedgerEntryId", + "LedgerEntryType", + "LedgerEntryTypeLiteral", + "LedgerListResponse", "LinkedSegmentationMatrix", "ListCheckoutSessionsResponse", + "ListCustomerUsageCostsGranularity", + "ListCustomerUsageCostsGranularityLiteral", + "ListSubscriptionUsageCostsGranularity", + "ListSubscriptionUsageCostsGranularityLiteral", "MatrixDimension", "MatrixPlanPricing", "MatrixPricing", @@ -1106,7 +1446,9 @@ def __dir__() -> list[str]: "PackagePlanPricing", "PackagePricing", "PaginationResponse", + "PatchBudgetRequest", "PatchPlanRequest", + "PatchWalletRequest", "PaymentEvent", "PaymentMethodInfo", "PaymentMethodTypeEnum", @@ -1124,6 +1466,7 @@ def __dir__() -> list[str]: "PlanAddOnInput", "PlanEvent", "PlanEventData", + "PlanFeeCreditUnit", "PlanId", "PlanListResponse", "PlanStatusEnum", @@ -1200,6 +1543,7 @@ def __dir__() -> list[str]: "SubscriptionComponent", "SubscriptionCoupon", "SubscriptionCreateRequest", + "SubscriptionCredit", "SubscriptionDetails", "SubscriptionEvent", "SubscriptionEventData", @@ -1226,8 +1570,15 @@ def __dir__() -> list[str]: "TokenIntrospectionResponse", "TokenRequest", "TokenResponse", + "TopUpCollection", + "TopUpCollectionLiteral", + "TopUpResponse", + "TopUpStatus", + "TopUpStatusLiteral", "Transaction", "TrialConfig", + "UnattributedMetricUsage", + "UnattributedUsageResponse", "UnitConversion", "UnitConversionRoundingEnum", "UnitConversionRoundingEnumLiteral", @@ -1238,8 +1589,19 @@ def __dir__() -> list[str]: "UpdateMetricRequest", "UpdateProductRequest", "UpdateWebhookEndpointRequest", + "UsageAtZero", + "UsageAtZeroLiteral", + "UsageCost", + "UsageCostListResponse", + "UsageCostRow", + "UsageCostUnit", + "UsageCostUnitLiteral", + "UsageEvent", + "UsageEventListResponse", "UsageFee", "UsageFeeStructure", + "UsageFilter", + "UsageLimitId", "UsageModelEnum", "UsageModelEnumLiteral", "UsagePlanFee", @@ -1248,6 +1610,23 @@ def __dir__() -> list[str]: "UsageResponse", "VolumePlanPricing", "VolumePricing", + "Wallet", + "WalletBalanceLowEvent", + "WalletBalanceLowEventData", + "WalletCheck", + "WalletCreditRecoveredEvent", + "WalletCreditRecoveredEventData", + "WalletCreditUnrecoveredEvent", + "WalletCreditUnrecoveredEventData", + "WalletDebitedEvent", + "WalletDebitedEventData", + "WalletDepletedEvent", + "WalletDepletedEventData", + "WalletFundedEvent", + "WalletFundedEventData", + "WalletListResponse", + "WalletRecoveredEvent", + "WalletRecoveredEventData", "WebhookDelivery", "WebhookDeliveryId", "WebhookDeliveryListResponse", diff --git a/meteroid/models/actor.py b/meteroid/models/actor.py new file mode 100644 index 0000000..4773fd4 --- /dev/null +++ b/meteroid/models/actor.py @@ -0,0 +1,22 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .actor_type import ActorType + + +@dataclasses.dataclass(kw_only=True) +class Actor(BaseModel): + """Who made a change.""" + + type: ActorType + """USER, CUSTOMER, API_KEY, OAUTH_APP or SYSTEM.""" + + id: str | None = None + """The user's id (`usr_…`) for USER, the customer's (`cus_…`) for CUSTOMER, the API key's + or app's for API_KEY and OAUTH_APP; absent for SYSTEM.""" diff --git a/meteroid/models/actor_type.py b/meteroid/models/actor_type.py new file mode 100644 index 0000000..617fa9c --- /dev/null +++ b/meteroid/models/actor_type.py @@ -0,0 +1,20 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class ActorType(StrEnum): + """The values of `ActorType`; others are kept as received.""" + + USER = "USER" + CUSTOMER = "CUSTOMER" + SYSTEM = "SYSTEM" + API_KEY = "API_KEY" + OAUTH_APP = "OAUTH_APP" + + +ActorTypeLiteral: t.TypeAlias = t.Literal[ + "USER", "CUSTOMER", "SYSTEM", "API_KEY", "OAUTH_APP" +] +"""The values of :class:`ActorType`, which arguments take as plain strings too.""" diff --git a/meteroid/models/all_components_scope.py b/meteroid/models/all_components_scope.py index 24c16bd..381bfb9 100644 --- a/meteroid/models/all_components_scope.py +++ b/meteroid/models/all_components_scope.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -9,3 +10,5 @@ @dataclasses.dataclass(kw_only=True) class AllComponentsScope(BaseModel): """Every revenue line counts toward the floor.""" + + type: t.Literal["all_components"] = "all_components" diff --git a/meteroid/models/auto_top_up.py b/meteroid/models/auto_top_up.py new file mode 100644 index 0000000..2f2a101 --- /dev/null +++ b/meteroid/models/auto_top_up.py @@ -0,0 +1,64 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .auto_top_up_method import AutoTopUpMethod + from .auto_top_up_status import AutoTopUpStatus + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUp(BaseModel): + """Automatic top-up of the credit that applies to ALL charges: it charges the customer's payment + method when the balance falls below `threshold`.""" + + method: AutoTopUpMethod + """FIXED buys `amount` each time; TO_TARGET buys back up to `target`.""" + + status: AutoTopUpStatus + """ACTIVE, DISABLED (terms kept) or SUSPENDED after repeated failed charges.""" + + threshold: int + """Tops up once the balance is at or below this, in minor units of the wallet.""" + + amount: int | None = None + """FIXED only: credit bought each time, in minor units of the wallet.""" + + customer_consent_attested_at: datetime | None = None + """When an operator attested that the customer agreed to automatic charges on exactly these + terms. Absent when the customer confirmed them in their portal, or the terms changed since.""" + + daily_cap: int | None = None + """Most charged in a rolling day, tax included. For a credit unit wallet caps are money, in + minor units of the customer's currency.""" + + last_error: str | None = None + """Why the last charge failed; may carry payment provider detail.""" + + last_succeeded_at: datetime | None = None + """The last charge that succeeded.""" + + maximum: int | None = None + """Largest single charge, in the same money as `minimum`.""" + + minimum: int | None = None + """Smallest single charge. For a credit unit wallet it is money (the price before tax, in + minor units of the customer's currency), like the caps; the threshold, amount and target + are credits.""" + + monthly_cap: int | None = None + """Most charged in a calendar month (UTC), tax included.""" + + payment_method_id: str | None = None + """Absent charges the customer's default payment method.""" + + suspended_at: datetime | None = None + """Set while SUSPENDED: since when.""" + + target: int | None = None + """TO_TARGET only: the balance a top-up brings the wallet back to, in minor units.""" diff --git a/meteroid/models/auto_top_up_cap_reached_event.py b/meteroid/models/auto_top_up_cap_reached_event.py new file mode 100644 index 0000000..e24ce1f --- /dev/null +++ b/meteroid/models/auto_top_up_cap_reached_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .auto_top_up_cap_reached_event_data import AutoTopUpCapReachedEventData + from .event_id import EventId + from .event_type import EventType + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUpCapReachedEvent(BaseModel): + """The `AutoTopUpCapReachedEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("auto_top_up_cap_reached_event_data",) + + auto_top_up_cap_reached_event_data: AutoTopUpCapReachedEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/auto_top_up_cap_reached_event_data.py b/meteroid/models/auto_top_up_cap_reached_event_data.py new file mode 100644 index 0000000..7c70e0e --- /dev/null +++ b/meteroid/models/auto_top_up_cap_reached_event_data.py @@ -0,0 +1,38 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .customer_id import CustomerId + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUpCapReachedEventData(BaseModel): + """A spending cap stopped an automatic top-up.""" + + attempted_amount: int + """What the suppressed top-up would have charged, tax included once it was quoted (before + tax when the cap stopped it before then). Caps are always money: for a credit unit wallet, + `currency` is the customer's currency.""" + + cap: int + """The cap, tax included, in minor units of `currency`.""" + + currency: str + """The currency the cap and the charge are in (ISO 4217).""" + + customer_id: CustomerId + + period: str + """`DAILY` (rolling day) or `MONTHLY` (calendar month, UTC).""" + + period_end: datetime + """End of the capped period.""" + + period_start: datetime + """Start of the capped period.""" diff --git a/meteroid/models/auto_top_up_consent_changed_event.py b/meteroid/models/auto_top_up_consent_changed_event.py new file mode 100644 index 0000000..d08254f --- /dev/null +++ b/meteroid/models/auto_top_up_consent_changed_event.py @@ -0,0 +1,30 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .auto_top_up_consent_changed_event_data import AutoTopUpConsentChangedEventData + from .event_id import EventId + from .event_type import EventType + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUpConsentChangedEvent(BaseModel): + """The `AutoTopUpConsentChangedEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ( + "auto_top_up_consent_changed_event_data", + ) + + auto_top_up_consent_changed_event_data: AutoTopUpConsentChangedEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/auto_top_up_consent_changed_event_data.py b/meteroid/models/auto_top_up_consent_changed_event_data.py new file mode 100644 index 0000000..05434f6 --- /dev/null +++ b/meteroid/models/auto_top_up_consent_changed_event_data.py @@ -0,0 +1,43 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .actor import Actor + from .customer_id import CustomerId + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUpConsentChangedEventData(BaseModel): + """The authorization of a wallet's automatic top-up was given, changed or withdrawn.""" + + actor: Actor + """Who gave, changed or withdrew the authorization.""" + + changed_at: datetime + """When the authorization changed.""" + + currency: str + """The wallet's currency (ISO 4217) or credit unit code.""" + + customer_id: CustomerId + + enabled: bool + """Whether automatic top-up is authorized after this change.""" + + configuration_revision: str | None = None + """Identifies the terms authorized now, without disclosing payment details; absent when + withdrawn.""" + + consent_attested_at: datetime | None = None + """When that attestation was made.""" + + consent_attested_by: Actor | None = None + + previous_configuration_revision: str | None = None + """Identifies the terms authorized before the change; absent when none were.""" diff --git a/meteroid/models/auto_top_up_event.py b/meteroid/models/auto_top_up_event.py new file mode 100644 index 0000000..d842353 --- /dev/null +++ b/meteroid/models/auto_top_up_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .auto_top_up_event_data import AutoTopUpEventData + from .event_id import EventId + from .event_type import EventType + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUpEvent(BaseModel): + """The `AutoTopUpEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("auto_top_up_event_data",) + + auto_top_up_event_data: AutoTopUpEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/auto_top_up_event_data.py b/meteroid/models/auto_top_up_event_data.py new file mode 100644 index 0000000..0a9a087 --- /dev/null +++ b/meteroid/models/auto_top_up_event_data.py @@ -0,0 +1,36 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .customer_id import CustomerId + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUpEventData(BaseModel): + """An automatic top-up charge failed, or the automatic top-up was suspended.""" + + amount: int + """The charge in minor units of `currency`: the credit it would have added, or for a credit + unit wallet the price of those credits in the customer's currency. When the credits could + not be priced (no selling price), the credits themselves, with `currency` the unit code. On + a suspension after a top-up payment was refunded or disputed: that payment's top-up.""" + + consecutive_failures: int + """Failed charges in a row, this one included.""" + + currency: str + """The currency charged (ISO 4217), or the credit unit code when the credits had no price.""" + + customer_id: CustomerId + + error: str + """May carry payment provider detail.""" + + suspended_at: datetime | None = None + """Set on `wallet.auto_top_up_suspended`: when it was suspended.""" diff --git a/meteroid/models/auto_top_up_method.py b/meteroid/models/auto_top_up_method.py new file mode 100644 index 0000000..b24765b --- /dev/null +++ b/meteroid/models/auto_top_up_method.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class AutoTopUpMethod(StrEnum): + """The values of `AutoTopUpMethod`; others are kept as received.""" + + FIXED = "FIXED" + TO_TARGET = "TO_TARGET" + + +AutoTopUpMethodLiteral: t.TypeAlias = t.Literal["FIXED", "TO_TARGET"] +"""The values of :class:`AutoTopUpMethod`, which arguments take as plain strings too.""" diff --git a/meteroid/models/auto_top_up_status.py b/meteroid/models/auto_top_up_status.py new file mode 100644 index 0000000..1332bbf --- /dev/null +++ b/meteroid/models/auto_top_up_status.py @@ -0,0 +1,16 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class AutoTopUpStatus(StrEnum): + """The values of `AutoTopUpStatus`; others are kept as received.""" + + ACTIVE = "ACTIVE" + DISABLED = "DISABLED" + SUSPENDED = "SUSPENDED" + + +AutoTopUpStatusLiteral: t.TypeAlias = t.Literal["ACTIVE", "DISABLED", "SUSPENDED"] +"""The values of :class:`AutoTopUpStatus`, which arguments take as plain strings too.""" diff --git a/meteroid/models/auto_top_up_write_request.py b/meteroid/models/auto_top_up_write_request.py new file mode 100644 index 0000000..1b85bc6 --- /dev/null +++ b/meteroid/models/auto_top_up_write_request.py @@ -0,0 +1,60 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .auto_top_up_method import AutoTopUpMethod + + +@dataclasses.dataclass(kw_only=True) +class AutoTopUpWriteRequest(BaseModel): + """An automatic top-up's terms, replaced as a whole. FIXED needs `amount` and no `target`; + TO_TARGET needs `target` above `threshold` and no `amount`; amounts are positive.""" + + method: AutoTopUpMethod + """FIXED buys `amount` each time; TO_TARGET buys back up to `target`.""" + + threshold: int + """Tops up once the balance is at or below this, in minor units of the wallet.""" + + amount: int | None | Unset = UNSET + """FIXED only: credit bought each time, in minor units of the wallet.""" + + at_current_price: bool | None = None + """Credit unit wallets: authorize today's price per credit for these terms, which lifts a + stop caused by a price increase. When false (the default) the terms keep the price they + were authorized at, and a rule stopped by a price increase stays stopped. + A price at or below the authorized one never stops the rule: top-ups charge it.""" + + customer_consent_attested: bool | None = None + """Attests that the customer agreed to automatic charges on these terms; recorded with the + API key and the time. Required (true) when the terms turn automatic top-up on, including + one the customer turned off, or change what it charges. Not needed to resend the stored + terms unchanged, nor to change terms the customer confirmed in their portal: those stop + until the customer confirms the new ones.""" + + daily_cap: int | None | Unset = UNSET + """Most charged in a rolling day, tax included. For a credit unit wallet caps are money, in + minor units of the customer's currency.""" + + maximum: int | None | Unset = UNSET + """Largest single charge, in the same money as `minimum`.""" + + minimum: int | None | Unset = UNSET + """Smallest single charge. For a credit unit wallet it is money (the price before tax, in + minor units of the customer's currency), like the caps; the threshold, amount and target + are credits.""" + + monthly_cap: int | None | Unset = UNSET + """Most charged in a calendar month (UTC), tax included.""" + + payment_method_id: str | None | Unset = UNSET + """Defaults to the customer's default payment method: the customer's agreement, and your + attestation of it, then cover whichever method is their default at each top-up.""" + + target: int | None | Unset = UNSET + """TO_TARGET only: the balance a top-up brings the wallet back to; must exceed `threshold`.""" diff --git a/meteroid/models/bank_transfer_payment_method_config.py b/meteroid/models/bank_transfer_payment_method_config.py index a87c9ed..60364dd 100644 --- a/meteroid/models/bank_transfer_payment_method_config.py +++ b/meteroid/models/bank_transfer_payment_method_config.py @@ -14,4 +14,6 @@ class BankTransferPaymentMethodConfig(BaseModel): """The `BankTransferPaymentMethodConfig` object.""" + type: t.Literal["bank_transfer"] = "bank_transfer" + account_id: BankAccountId | None | Unset = UNSET diff --git a/meteroid/models/billing_cycle_reset_period.py b/meteroid/models/billing_cycle_reset_period.py index 480af67..a082930 100644 --- a/meteroid/models/billing_cycle_reset_period.py +++ b/meteroid/models/billing_cycle_reset_period.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -9,3 +10,5 @@ @dataclasses.dataclass(kw_only=True) class BillingCycleResetPeriod(BaseModel): """Resets each time your subscription renews — anchored to your billing cycle.""" + + type: t.Literal["BILLING_CYCLE"] = "BILLING_CYCLE" diff --git a/meteroid/models/boolean_config_value.py b/meteroid/models/boolean_config_value.py index 081c90a..798afb2 100644 --- a/meteroid/models/boolean_config_value.py +++ b/meteroid/models/boolean_config_value.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -10,4 +11,6 @@ class BooleanConfigValue(BaseModel): """A boolean config value.""" + kind: t.Literal["BOOLEAN"] = "BOOLEAN" + value: bool diff --git a/meteroid/models/boolean_effective_entitlement_value.py b/meteroid/models/boolean_effective_entitlement_value.py index 6cfddc0..7e45b25 100644 --- a/meteroid/models/boolean_effective_entitlement_value.py +++ b/meteroid/models/boolean_effective_entitlement_value.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -11,3 +12,5 @@ class BooleanEffectiveEntitlementValue(BaseModel): """The `BooleanEffectiveEntitlementValue` object.""" enabled: bool + + type: t.Literal["BOOLEAN"] = "BOOLEAN" diff --git a/meteroid/models/boolean_entitlement_value.py b/meteroid/models/boolean_entitlement_value.py index 2775d9a..62c1d82 100644 --- a/meteroid/models/boolean_entitlement_value.py +++ b/meteroid/models/boolean_entitlement_value.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -11,3 +12,5 @@ class BooleanEntitlementValue(BaseModel): """The `BooleanEntitlementValue` object.""" enabled: bool + + type: t.Literal["BOOLEAN"] = "BOOLEAN" diff --git a/meteroid/models/boolean_feature_type.py b/meteroid/models/boolean_feature_type.py index 957e735..892b5ca 100644 --- a/meteroid/models/boolean_feature_type.py +++ b/meteroid/models/boolean_feature_type.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -9,3 +10,5 @@ @dataclasses.dataclass(kw_only=True) class BooleanFeatureType(BaseModel): """The `BooleanFeatureType` object.""" + + type: t.Literal["BOOLEAN"] = "BOOLEAN" diff --git a/meteroid/models/boolean_resolved_entitlement_value.py b/meteroid/models/boolean_resolved_entitlement_value.py index bb11674..38ce613 100644 --- a/meteroid/models/boolean_resolved_entitlement_value.py +++ b/meteroid/models/boolean_resolved_entitlement_value.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -11,3 +12,5 @@ class BooleanResolvedEntitlementValue(BaseModel): """The `BooleanResolvedEntitlementValue` object.""" enabled: bool + + type: t.Literal["BOOLEAN"] = "BOOLEAN" diff --git a/meteroid/models/budget.py b/meteroid/models/budget.py new file mode 100644 index 0000000..9943c02 --- /dev/null +++ b/meteroid/models/budget.py @@ -0,0 +1,65 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .budget_author import BudgetAuthor + from .budget_behavior import BudgetBehavior + from .budget_period import BudgetPeriod + from .currency import Currency + + +@dataclasses.dataclass(kw_only=True) +class Budget(BaseModel): + """A spending limit for one currency, set by you or by the customer, with alerts at percentages + of it. It counts the customer's whole spend, or one subscription's. A HARD budget also refuses + access once reached. A budget never charges the customer.""" + + amount: int + """In minor units of the currency or credit unit.""" + + behavior: BudgetBehavior + + created_at: datetime + + created_by: BudgetAuthor + + exact: bool + """Always false: the estimate prices usage that is not yet invoiced.""" + + id: str + + thresholds: list[int] + """Percentages of the amount at which an alert is sent.""" + + updated_at: datetime + + version: int + """Send it back as `expected_version` when patching.""" + + archived_at: datetime | None = None + + as_of: datetime | None = None + """When `estimated_spend` was last measured.""" + + credit_unit: str | None = None + """The credit unit (its code) whose usage the budget counts; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: Currency | None = None + + estimated_spend: int | None = None + """Spend in the current billing period, including usage not invoiced yet, in minor units. + Absent until first measured.""" + + period: BudgetPeriod | None = None + + subscription_id: str | None = None + """The subscription whose spend the budget counts; null when it counts the whole customer's.""" diff --git a/meteroid/models/budget_author.py b/meteroid/models/budget_author.py new file mode 100644 index 0000000..35717d8 --- /dev/null +++ b/meteroid/models/budget_author.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class BudgetAuthor(StrEnum): + """Who set the budget: you (TENANT) or the customer, from the portal (CUSTOMER).""" + + TENANT = "TENANT" + CUSTOMER = "CUSTOMER" + + +BudgetAuthorLiteral: t.TypeAlias = t.Literal["TENANT", "CUSTOMER"] +"""The values of :class:`BudgetAuthor`, which arguments take as plain strings too.""" diff --git a/meteroid/models/budget_behavior.py b/meteroid/models/budget_behavior.py new file mode 100644 index 0000000..88b5c12 --- /dev/null +++ b/meteroid/models/budget_behavior.py @@ -0,0 +1,18 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class BudgetBehavior(StrEnum): + """What reaching the amount does. A SOFT budget only sends alerts; a HARD budget also makes the + entitlement check answer BUDGET_EXCEEDED for the features whose usage counts in its spend. A + HARD budget on one subscription refuses the checks naming that subscription, and the checks + naming none once every subscription charging the feature's metric is refused by its own.""" + + SOFT = "SOFT" + HARD = "HARD" + + +BudgetBehaviorLiteral: t.TypeAlias = t.Literal["SOFT", "HARD"] +"""The values of :class:`BudgetBehavior`, which arguments take as plain strings too.""" diff --git a/meteroid/models/budget_list_response.py b/meteroid/models/budget_list_response.py new file mode 100644 index 0000000..02f6822 --- /dev/null +++ b/meteroid/models/budget_list_response.py @@ -0,0 +1,20 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .budget import Budget + from .pagination_response import PaginationResponse + + +@dataclasses.dataclass(kw_only=True) +class BudgetListResponse(BaseModel): + """The `BudgetListResponse` object.""" + + data: list[Budget] + + pagination_meta: PaginationResponse diff --git a/meteroid/models/budget_period.py b/meteroid/models/budget_period.py new file mode 100644 index 0000000..37b2eb3 --- /dev/null +++ b/meteroid/models/budget_period.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +from datetime import date + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class BudgetPeriod(BaseModel): + """The `BudgetPeriod` object.""" + + start: date + + end: date | None = None + """Absent when the period never ends.""" diff --git a/meteroid/models/calendar_reset_period.py b/meteroid/models/calendar_reset_period.py index 72c8545..2f03bba 100644 --- a/meteroid/models/calendar_reset_period.py +++ b/meteroid/models/calendar_reset_period.py @@ -16,4 +16,6 @@ class CalendarResetPeriod(BaseModel): interval: int + type: t.Literal["CALENDAR"] = "CALENDAR" + unit: CalendarUnit diff --git a/meteroid/models/capacity_fee.py b/meteroid/models/capacity_fee.py index 396ee35..258d50c 100644 --- a/meteroid/models/capacity_fee.py +++ b/meteroid/models/capacity_fee.py @@ -9,6 +9,7 @@ if t.TYPE_CHECKING: from .billable_metric_id import BillableMetricId + from .fee_credit_unit import FeeCreditUnit @dataclasses.dataclass(kw_only=True) @@ -22,3 +23,7 @@ class CapacityFee(BaseModel): overage_rate: Decimal rate: Decimal + + type: t.Literal["CAPACITY"] = "CAPACITY" + + credit_unit: FeeCreditUnit | None = None diff --git a/meteroid/models/capacity_fee_structure.py b/meteroid/models/capacity_fee_structure.py index 024f3ca..ed58ecb 100644 --- a/meteroid/models/capacity_fee_structure.py +++ b/meteroid/models/capacity_fee_structure.py @@ -15,3 +15,5 @@ class CapacityFeeStructure(BaseModel): """The `CapacityFeeStructure` object.""" metric_id: BillableMetricId + + type: t.Literal["CAPACITY"] = "CAPACITY" diff --git a/meteroid/models/capacity_plan_fee.py b/meteroid/models/capacity_plan_fee.py index e17a2b4..470982d 100644 --- a/meteroid/models/capacity_plan_fee.py +++ b/meteroid/models/capacity_plan_fee.py @@ -4,12 +4,13 @@ import dataclasses import typing as t -from ..serialization import BaseModel +from ..serialization import UNSET, BaseModel, Unset if t.TYPE_CHECKING: from .billable_metric_id import BillableMetricId from .billing_period_enum import BillingPeriodEnum from .capacity_threshold import CapacityThreshold + from .plan_fee_credit_unit import PlanFeeCreditUnit @dataclasses.dataclass(kw_only=True) @@ -21,3 +22,7 @@ class CapacityPlanFee(BaseModel): metric_id: BillableMetricId thresholds: list[CapacityThreshold] + + type: t.Literal["CAPACITY"] = "CAPACITY" + + credit_unit: PlanFeeCreditUnit | None | Unset = UNSET diff --git a/meteroid/models/capacity_pricing.py b/meteroid/models/capacity_pricing.py index 14b1e17..2fd7e6f 100644 --- a/meteroid/models/capacity_pricing.py +++ b/meteroid/models/capacity_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -16,3 +17,5 @@ class CapacityPricing(BaseModel): overage_rate: Decimal rate: Decimal + + type: t.Literal["CAPACITY"] = "CAPACITY" diff --git a/meteroid/models/components_scope.py b/meteroid/models/components_scope.py index ce88992..8df31ec 100644 --- a/meteroid/models/components_scope.py +++ b/meteroid/models/components_scope.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -11,3 +12,5 @@ class ComponentsScope(BaseModel): """Component names — matched against `ReplacePlanRequest::components[].name`.""" component_names: list[str] + + type: t.Literal["components"] = "components" diff --git a/meteroid/models/config_effective_entitlement_value.py b/meteroid/models/config_effective_entitlement_value.py index 120b3d3..c321544 100644 --- a/meteroid/models/config_effective_entitlement_value.py +++ b/meteroid/models/config_effective_entitlement_value.py @@ -14,4 +14,6 @@ class ConfigEffectiveEntitlementValue(BaseModel): """The `ConfigEffectiveEntitlementValue` object.""" + type: t.Literal["CONFIG"] = "CONFIG" + value: ConfigValue diff --git a/meteroid/models/config_entitlement_value.py b/meteroid/models/config_entitlement_value.py index f0c6672..d23bf72 100644 --- a/meteroid/models/config_entitlement_value.py +++ b/meteroid/models/config_entitlement_value.py @@ -14,4 +14,6 @@ class ConfigEntitlementValue(BaseModel): """The `ConfigEntitlementValue` object.""" + type: t.Literal["CONFIG"] = "CONFIG" + value: ConfigValue diff --git a/meteroid/models/config_feature_type.py b/meteroid/models/config_feature_type.py index c85b73a..287d33d 100644 --- a/meteroid/models/config_feature_type.py +++ b/meteroid/models/config_feature_type.py @@ -14,6 +14,8 @@ class ConfigFeatureType(BaseModel): """A static, typed configuration value. No metric — resolved synchronously.""" + type: t.Literal["CONFIG"] = "CONFIG" + value_type: ConfigValueType """The feature's value type, fixed at creation.""" diff --git a/meteroid/models/config_resolved_entitlement_value.py b/meteroid/models/config_resolved_entitlement_value.py index 902c7ee..df73c61 100644 --- a/meteroid/models/config_resolved_entitlement_value.py +++ b/meteroid/models/config_resolved_entitlement_value.py @@ -14,4 +14,6 @@ class ConfigResolvedEntitlementValue(BaseModel): """The `ConfigResolvedEntitlementValue` object.""" + type: t.Literal["CONFIG"] = "CONFIG" + value: ConfigValue diff --git a/meteroid/models/coupon.py b/meteroid/models/coupon.py index 7403e7b..0257632 100644 --- a/meteroid/models/coupon.py +++ b/meteroid/models/coupon.py @@ -18,6 +18,10 @@ class Coupon(BaseModel): """The `Coupon` object.""" code: str + """A promotional-credit (`CREDIT`) code is stored upper-case; other codes as written. A code + is redeemed by the coupon with exactly that code, else by the only live coupon equal to it + ignoring the case of ASCII letters; several such coupons and no exact match make it + ambiguous.""" created_at: datetime diff --git a/meteroid/models/coupon_credit_scope.py b/meteroid/models/coupon_credit_scope.py new file mode 100644 index 0000000..8db0ae5 --- /dev/null +++ b/meteroid/models/coupon_credit_scope.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CouponCreditScope(StrEnum): + """Which subscriptions a promotional credit coupon's credit pays.""" + + CUSTOMER = "CUSTOMER" + SUBSCRIPTION = "SUBSCRIPTION" + + +CouponCreditScopeLiteral: t.TypeAlias = t.Literal["CUSTOMER", "SUBSCRIPTION"] +"""The values of :class:`CouponCreditScope`, which arguments take as plain strings too.""" diff --git a/meteroid/models/coupon_discount.py b/meteroid/models/coupon_discount.py index f2ca3f1..ae11e9d 100644 --- a/meteroid/models/coupon_discount.py +++ b/meteroid/models/coupon_discount.py @@ -4,16 +4,18 @@ import typing as t from ..serialization import Discriminator, UnknownVariant +from .credit_discount import CreditDiscount from .fixed_discount import FixedDiscount from .percentage_discount import PercentageDiscount CouponDiscount: t.TypeAlias = t.Annotated[ - PercentageDiscount | FixedDiscount | UnknownVariant, + PercentageDiscount | FixedDiscount | CreditDiscount | UnknownVariant, Discriminator( "type", { "PERCENTAGE": PercentageDiscount, "FIXED": FixedDiscount, + "CREDIT": CreditDiscount, }, ), ] diff --git a/meteroid/models/create_budget_request.py b/meteroid/models/create_budget_request.py new file mode 100644 index 0000000..a6ea79e --- /dev/null +++ b/meteroid/models/create_budget_request.py @@ -0,0 +1,34 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .budget_behavior import BudgetBehavior + from .currency import Currency + + +@dataclasses.dataclass(kw_only=True) +class CreateBudgetRequest(BaseModel): + """The `CreateBudgetRequest` object.""" + + amount: int + """In minor units of the currency or credit unit.""" + + behavior: BudgetBehavior | None | Unset = UNSET + + credit_unit: str | None | Unset = UNSET + """A credit unit (its code): the budget counts the usage priced in that unit, in its minor + units.""" + + currency: Currency | None | Unset = UNSET + + subscription_id: str | None | Unset = UNSET + """An active subscription of the customer (`sub_…`): the budget then counts only that + subscription's spend, in its currency. Absent counts the whole customer's.""" + + thresholds: list[int] | None | Unset = UNSET + """Percentages of the amount (1 to 100, at most 10). Defaults to 50, 80 and 100.""" diff --git a/meteroid/models/create_checkout_session_request.py b/meteroid/models/create_checkout_session_request.py index 5a01b01..eb607e1 100644 --- a/meteroid/models/create_checkout_session_request.py +++ b/meteroid/models/create_checkout_session_request.py @@ -43,6 +43,10 @@ class CreateCheckoutSessionRequest(BaseModel): components: CreateSubscriptionComponents | None | Unset = UNSET coupon_code: str | None | Unset = UNSET + """Code of the coupon to apply, used only when `coupon_ids` is empty. It names the coupon with + exactly that code, else the only live coupon equal to it ignoring the case of ASCII + letters. An unknown or ambiguous code is refused with 400. If the code no longer names a + usable coupon when the customer pays, the payment is refused and nothing is charged.""" coupon_ids: list[CouponId] | None = None diff --git a/meteroid/models/create_connected_account_request.py b/meteroid/models/create_connected_account_request.py index 40d0df3..e4b8849 100644 --- a/meteroid/models/create_connected_account_request.py +++ b/meteroid/models/create_connected_account_request.py @@ -3,23 +3,26 @@ import dataclasses import typing as t -from uuid import UUID from ..serialization import UNSET, BaseModel, Unset if t.TYPE_CHECKING: - from .connection_type import ConnectionType - from .customer_id import CustomerId + from .country_code import CountryCode + from .onboarding_mode import OnboardingMode @dataclasses.dataclass(kw_only=True) class CreateConnectedAccountRequest(BaseModel): """The `CreateConnectedAccountRequest` object.""" - connected_organization_id: UUID + email: str - connection_type: ConnectionType | None | Unset = UNSET + organization_name: str + + country: CountryCode | None | Unset = UNSET metadata: t.Any = None - platform_customer_id: CustomerId | None | Unset = UNSET + mode: OnboardingMode | None | Unset = UNSET + + redirect_url: str | None | Unset = UNSET diff --git a/meteroid/models/create_connected_account_response.py b/meteroid/models/create_connected_account_response.py new file mode 100644 index 0000000..092f9d1 --- /dev/null +++ b/meteroid/models/create_connected_account_response.py @@ -0,0 +1,22 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .connected_account import ConnectedAccount + from .onboarding_link_response import OnboardingLinkResponse + + +@dataclasses.dataclass(kw_only=True) +class CreateConnectedAccountResponse(BaseModel): + """The created account (same fields as `ConnectedAccount`) plus its onboarding link.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("connected_account",) + + connected_account: ConnectedAccount + + onboarding_link: OnboardingLinkResponse diff --git a/meteroid/models/create_coupon_request.py b/meteroid/models/create_coupon_request.py index faaa310..4e33339 100644 --- a/meteroid/models/create_coupon_request.py +++ b/meteroid/models/create_coupon_request.py @@ -17,6 +17,8 @@ class CreateCouponRequest(BaseModel): """The `CreateCouponRequest` object.""" code: str + """1 to 64 ASCII letters, digits, hyphens or underscores. A promotional-credit (`CREDIT`) code + is stored upper-case. Codes are matched as described on `Coupon.code`.""" discount: CouponDiscount @@ -31,3 +33,5 @@ class CreateCouponRequest(BaseModel): redemption_limit: int | None | Unset = UNSET reusable: bool | None = None + """Whether one customer may redeem the coupon more than once. Must be false for a + promotional-credit (`CREDIT`) coupon.""" diff --git a/meteroid/models/create_credit_grant_request.py b/meteroid/models/create_credit_grant_request.py new file mode 100644 index 0000000..9a65ed4 --- /dev/null +++ b/meteroid/models/create_credit_grant_request.py @@ -0,0 +1,69 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .credit_applies_to import CreditAppliesTo + from .credit_cost_request import CreditCostRequest + from .credit_grant_type import CreditGrantType + from .currency import Currency + from .product_id import ProductId + + +@dataclasses.dataclass(kw_only=True) +class CreateCreditGrantRequest(BaseModel): + """Credit granted without charging or invoicing.""" + + amount: int + """Credit granted, in minor units of `currency`, or of `credit_unit` when set (e.g. 500000 + is 5,000.00 credits of a unit with precision 2). Positive.""" + + type: CreditGrantType + """PROMOTIONAL gives credit for free (a goodwill or marketing grant). PAID records credit + the customer paid for outside of Meteroid: nothing is charged or invoiced, and the grant + is shown as "Paid outside Meteroid". Usage paid with PAID currency credit is taxed on the + invoice that bills it, so do not charge tax on the external sale of currency credit, or + the customer is taxed twice. Credit in a credit unit is the exception: its usage is billed + in credits, untaxed, so tax its sale as you would any other. Use a top-up to sell credit + through Meteroid.""" + + applicable_product_ids: list[ProductId] | None | Unset = UNSET + """Restricts the credit to charges of these products; absent applies to every product. + Combined with `applies_to`. Not available for credit units.""" + + applies_to: CreditAppliesTo | None = None + """ALL pays any charge on an invoice; USAGE only usage charges.""" + + cost: CreditCostRequest | None | Unset = UNSET + + credit_unit: str | None | Unset = UNSET + """Grants in this credit unit (its code) instead of a currency; excludes `currency`. Credit in + a credit unit pays the usage priced in that unit, so `applies_to` must stay ALL.""" + + currency: Currency | None | Unset = UNSET + + expires_at: datetime | None | Unset = UNSET + """When unspent credit lapses; absent never expires.""" + + priority: int | None | Unset = UNSET + """Lower values are drawn first among grants of the same type that pay the same charges; + defaults to 100.""" + + reason: str | None | Unset = UNSET + """Shown on the credit ledger.""" + + reference: str | None | Unset = UNSET + """Your own reference, e.g. a contract or support ticket. Required for PAID credit that cost + more than zero when the customer's invoicing entity is in France: the number of the invoice + or receipt for the payment, as French law requires a supporting document for every + accounting entry. Refused with 400 when missing.""" + + subscription_id: str | None | Unset = UNSET + """The customer's subscription (`sub_…`) the credit belongs to: it pays only that + subscription's charges and lapses when the subscription ends. It must not have ended, + and its currency must be the grant's. Absent pays any of the customer's subscriptions.""" diff --git a/meteroid/models/create_metric_request.py b/meteroid/models/create_metric_request.py index efcac56..d0111de 100644 --- a/meteroid/models/create_metric_request.py +++ b/meteroid/models/create_metric_request.py @@ -40,4 +40,13 @@ class CreateMetricRequest(BaseModel): unit_conversion: UnitConversion | None | Unset = UNSET + unit_plural: str | None | Unset = UNSET + """What several units of the metric are called, e.g. `tokens`. Set together with + `unit_singular`. Trimmed; at most 32 characters.""" + + unit_singular: str | None | Unset = UNSET + """What one unit of the metric is called, e.g. `token`, used to label quantities and + prices ("$12.00 per 1M tokens"). Set both `unit_singular` and `unit_plural`, or neither. + Trimmed; at most 32 characters.""" + usage_group_key: str | None | Unset = UNSET diff --git a/meteroid/models/create_plan_request.py b/meteroid/models/create_plan_request.py index 5d41d27..56adfa3 100644 --- a/meteroid/models/create_plan_request.py +++ b/meteroid/models/create_plan_request.py @@ -8,6 +8,7 @@ if t.TYPE_CHECKING: from .billing_config import BillingConfig + from .credit_exhausted_rule import CreditExhaustedRule from .entitlement_spec_request import EntitlementSpecRequest from .plan_add_on_input import PlanAddOnInput from .plan_status_enum import PlanStatusEnum @@ -37,6 +38,8 @@ class CreatePlanRequest(BaseModel): billing: BillingConfig | None | Unset = UNSET + credit_exhausted_rule: CreditExhaustedRule | None | Unset = UNSET + description: str | None | Unset = UNSET entitlements: list[EntitlementSpecRequest] | None = None diff --git a/meteroid/models/create_top_up_request.py b/meteroid/models/create_top_up_request.py new file mode 100644 index 0000000..4e11b40 --- /dev/null +++ b/meteroid/models/create_top_up_request.py @@ -0,0 +1,47 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .currency import Currency + from .top_up_collection import TopUpCollection + + +@dataclasses.dataclass(kw_only=True) +class CreateTopUpRequest(BaseModel): + """Credit sold to the customer, charged or invoiced.""" + + amount: int + """Credit to buy, positive, applying to ALL charges. In minor units of `currency`, charged + one for one without tax: the usage it pays is taxed on the invoice that bills it. With + `credit_unit`, in minor units of that unit (e.g. 500000 is 5,000.00 credits of a unit with + precision 2), charged at the unit's price per credit in the customer's currency, plus tax + under the unit's product.""" + + collection: TopUpCollection | None = None + """CHARGE (default) charges a saved payment method now; SEND_INVOICE issues an invoice and + grants the credit once it is paid.""" + + credit_unit: str | None | Unset = UNSET + """Buy credits of this credit unit (its code) instead of money; excludes `currency`. They are + charged in the customer's currency at the customer's agreed price for the unit (the + wallet's `unit_price`), else its list price, plus tax, under the unit's product.""" + + currency: Currency | None | Unset = UNSET + + expires_at: datetime | None | Unset = UNSET + """Absolute expiry of the purchased credit; unset never expires.""" + + payment_method_id: str | None | Unset = UNSET + """CHARGE only; defaults to the customer's default payment method.""" + + unit_price: str | None | Unset = UNSET + """With `credit_unit`: the negotiated price of one credit for this purchase, in major units of + the customer's currency, as a decimal string (e.g. "0.0008"). Defaults to the wallet's + `unit_price` when set, else the list price; the unit must still have a list price in that + currency.""" diff --git a/meteroid/models/create_webhook_endpoint_request.py b/meteroid/models/create_webhook_endpoint_request.py index c437b81..7d56e7d 100644 --- a/meteroid/models/create_webhook_endpoint_request.py +++ b/meteroid/models/create_webhook_endpoint_request.py @@ -21,7 +21,8 @@ class CreateWebhookEndpointRequest(BaseModel): description: str | None | Unset = UNSET event_types: list[str] | None = None - """Event types to subscribe to. Omit or leave empty to receive every event type.""" + """Event types to subscribe to. Omit or leave empty to receive every event type except Beta + ones, which are sent only to endpoints that list them.""" headers: list[WebhookHeaderInput] | None = None """Custom headers sent with every delivery.""" diff --git a/meteroid/models/credit_applies_to.py b/meteroid/models/credit_applies_to.py new file mode 100644 index 0000000..cf366c0 --- /dev/null +++ b/meteroid/models/credit_applies_to.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CreditAppliesTo(StrEnum): + """Which charges credit pays: ALL pays any part of an invoice, USAGE only its usage charges.""" + + ALL = "ALL" + USAGE = "USAGE" + + +CreditAppliesToLiteral: t.TypeAlias = t.Literal["ALL", "USAGE"] +"""The values of :class:`CreditAppliesTo`, which arguments take as plain strings too.""" diff --git a/meteroid/models/credit_cap.py b/meteroid/models/credit_cap.py new file mode 100644 index 0000000..c78b221 --- /dev/null +++ b/meteroid/models/credit_cap.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class CreditCap(BaseModel): + """The `CreditCap` object.""" + + amount: int + """Usage beyond the credit invoiced per billing period, in minor units of `denomination`.""" + + denomination: str + """A currency or credit unit code.""" diff --git a/meteroid/models/credit_caps.py b/meteroid/models/credit_caps.py new file mode 100644 index 0000000..46ab5bb --- /dev/null +++ b/meteroid/models/credit_caps.py @@ -0,0 +1,20 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_cap import CreditCap + + +@dataclasses.dataclass(kw_only=True) +class CreditCaps(BaseModel): + """The `CreditCaps` object.""" + + caps: list[CreditCap] + """One per denomination; usage charged in a denomination without a cap pauses at zero.""" + + type: t.Literal["invoice_up_to"] = "invoice_up_to" diff --git a/meteroid/models/credit_cost.py b/meteroid/models/credit_cost.py new file mode 100644 index 0000000..bce4350 --- /dev/null +++ b/meteroid/models/credit_cost.py @@ -0,0 +1,20 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .currency import Currency + + +@dataclasses.dataclass(kw_only=True) +class CreditCost(BaseModel): + """Money in minor units of `currency`, net of tax.""" + + amount: int + """In minor units of `currency`.""" + + currency: Currency diff --git a/meteroid/models/credit_cost_request.py b/meteroid/models/credit_cost_request.py new file mode 100644 index 0000000..2982f7e --- /dev/null +++ b/meteroid/models/credit_cost_request.py @@ -0,0 +1,20 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .currency import Currency + + +@dataclasses.dataclass(kw_only=True) +class CreditCostRequest(BaseModel): + """Money in minor units of `currency`, net of tax.""" + + amount: int + """In minor units of `currency`; zero or positive.""" + + currency: Currency diff --git a/meteroid/models/credit_discount.py b/meteroid/models/credit_discount.py new file mode 100644 index 0000000..4b31dd9 --- /dev/null +++ b/meteroid/models/credit_discount.py @@ -0,0 +1,33 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .coupon_credit_scope import CouponCreditScope + + +@dataclasses.dataclass(kw_only=True) +class CreditDiscount(BaseModel): + """Promotional credit added to the customer's credit balance when the subscription the coupon is + redeemed on starts. It never discounts an invoice and applies before tax. A customer redeems + it at most once, so the coupon cannot be `reusable`. Its code is case-insensitive: it is stored + upper-case and redeemed whatever the case.""" + + amount: str + """Credit added per redemption, in major units (e.g. "25.00").""" + + currency: str + """ISO 4217 code; it must match the currency of the subscription the coupon is redeemed on.""" + + type: t.Literal["CREDIT"] = "CREDIT" + + expires_in_days: int | None | Unset = UNSET + """Days the credit stays spendable after redemption, 1 to 3660. Absent: it never expires.""" + + scope: CouponCreditScope | None = None + """CUSTOMER (default): the credit pays any of the customer's subscriptions. SUBSCRIPTION: the + credit pays only the subscription the code is redeemed on, and ends with it.""" diff --git a/meteroid/models/credit_exhausted_rule.py b/meteroid/models/credit_exhausted_rule.py new file mode 100644 index 0000000..dd7ac63 --- /dev/null +++ b/meteroid/models/credit_exhausted_rule.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import typing as t + +from ..serialization import Discriminator, UnknownVariant +from .credit_caps import CreditCaps +from .credit_exhausted_rule_invoice_variant import CreditExhaustedRuleInvoiceVariant +from .credit_exhausted_rule_pause_variant import CreditExhaustedRulePauseVariant + +CreditExhaustedRule: t.TypeAlias = t.Annotated[ + CreditExhaustedRuleInvoiceVariant + | CreditExhaustedRulePauseVariant + | CreditCaps + | UnknownVariant, + Discriminator( + "type", + { + "invoice": CreditExhaustedRuleInvoiceVariant, + "pause": CreditExhaustedRulePauseVariant, + "invoice_up_to": CreditCaps, + }, + ), +] +"""What usage does once the credit it draws on runs out, for every denomination (currency or +credit unit) the plan charges usage in. A customer's wallet can override it. + +Told apart by `type`; a variant this SDK version does not know is an `UnknownVariant`.""" diff --git a/meteroid/models/credit_exhausted_rule_invoice_variant.py b/meteroid/models/credit_exhausted_rule_invoice_variant.py new file mode 100644 index 0000000..bc3f1a1 --- /dev/null +++ b/meteroid/models/credit_exhausted_rule_invoice_variant.py @@ -0,0 +1,14 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class CreditExhaustedRuleInvoiceVariant(BaseModel): + """Usage beyond the credit is invoiced.""" + + type: t.Literal["invoice"] = "invoice" diff --git a/meteroid/models/credit_exhausted_rule_pause_variant.py b/meteroid/models/credit_exhausted_rule_pause_variant.py new file mode 100644 index 0000000..15417ec --- /dev/null +++ b/meteroid/models/credit_exhausted_rule_pause_variant.py @@ -0,0 +1,14 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class CreditExhaustedRulePauseVariant(BaseModel): + """Access checks refuse usage once the credit the subscription can spend is used up.""" + + type: t.Literal["pause"] = "pause" diff --git a/meteroid/models/credit_expiry_unit.py b/meteroid/models/credit_expiry_unit.py new file mode 100644 index 0000000..e72c075 --- /dev/null +++ b/meteroid/models/credit_expiry_unit.py @@ -0,0 +1,16 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CreditExpiryUnit(StrEnum): + """The values of `CreditExpiryUnit`; others are kept as received.""" + + DAY = "DAY" + MONTH = "MONTH" + BILLING_PERIOD = "BILLING_PERIOD" + + +CreditExpiryUnitLiteral: t.TypeAlias = t.Literal["DAY", "MONTH", "BILLING_PERIOD"] +"""The values of :class:`CreditExpiryUnit`, which arguments take as plain strings too.""" diff --git a/meteroid/models/credit_fee.py b/meteroid/models/credit_fee.py new file mode 100644 index 0000000..d7d1427 --- /dev/null +++ b/meteroid/models/credit_fee.py @@ -0,0 +1,43 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from decimal import Decimal + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_pack_expiry import CreditPackExpiry + from .credit_pack_rollover import CreditPackRollover + from .credit_pack_unit import CreditPackUnit + + +@dataclasses.dataclass(kw_only=True) +class CreditFee(BaseModel): + """A credit pack: charged like a fixed fee, it grants `credit_amount` × `quantity` of credit once + paid, in the subscription currency or in `credit_unit`. A zero `unit_price` makes it credit + included with the plan (promotional). A currency pack priced below its credit grants the price + paid as paid credit and the difference as promotional bonus credit, which applies before tax.""" + + credit_amount: Decimal + """Credit granted per pack, in major units of the subscription currency, or a number of + credits of `credit_unit` when set.""" + + quantity: int + """Packs billed and granted each time.""" + + type: t.Literal["CREDIT"] = "CREDIT" + + unit_price: Decimal + """Price of one pack, in major units of the subscription currency.""" + + credit_unit: CreditPackUnit | None = None + + expiry: CreditPackExpiry | None = None + + once: bool | None = None + """When true, billed and granted once, on the subscription's first invoice, instead of every + period.""" + + rollover: CreditPackRollover | None = None diff --git a/meteroid/models/credit_fee_structure.py b/meteroid/models/credit_fee_structure.py new file mode 100644 index 0000000..3693903 --- /dev/null +++ b/meteroid/models/credit_fee_structure.py @@ -0,0 +1,14 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class CreditFeeStructure(BaseModel): + """The `CreditFeeStructure` object.""" + + type: t.Literal["CREDIT"] = "CREDIT" diff --git a/meteroid/models/credit_grant.py b/meteroid/models/credit_grant.py new file mode 100644 index 0000000..9defc05 --- /dev/null +++ b/meteroid/models/credit_grant.py @@ -0,0 +1,115 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_applies_to import CreditAppliesTo + from .credit_cost import CreditCost + from .credit_grant_kind import CreditGrantKind + from .credit_grant_source import CreditGrantSource + from .credit_grant_status import CreditGrantStatus + from .credit_grant_type import CreditGrantType + from .currency import Currency + from .product_id import ProductId + + +@dataclasses.dataclass(kw_only=True) +class CreditGrant(BaseModel): + """A lot of credit given to or bought by a customer. An invoice applies promotional currency + credit before tax and paid credit after it. Within each, it draws the grants that belong to + the invoiced subscription first, then grants restricted to products (the fewest products + first), then usage-only grants, then the others; then by lowest `priority`, then the soonest + to lapse.""" + + amount: int + """Amount granted, in minor units of `currency` or `credit_unit`.""" + + applicable_product_ids: list[ProductId] + """The products whose charges the credit pays, combined with `applies_to`; empty when it + applies to every product.""" + + applies_to: CreditAppliesTo + """ALL pays any charge on an invoice; USAGE only usage charges.""" + + created_at: datetime + """When the grant was recorded.""" + + effective_at: datetime + """When the credit became spendable.""" + + id: str + """The grant's id (`cgr_…`).""" + + kind: CreditGrantKind + """The group the grant is shown in: INCLUDED, PREPAID, BONUS or ACCOUNT_BALANCE.""" + + kind_label: str + """The group's name as Meteroid shows it: "Included with ", "Purchased credit", "Free + credit" or "Account balance".""" + + priority: int + """Lower values are drawn first among grants of the same type that pay the same charges; 100 + by default.""" + + reason: str + """Shown on the credit ledger.""" + + remaining: int + """Not spent yet, in the same minor units.""" + + source: CreditGrantSource + """Where the credit came from.""" + + status: CreditGrantStatus + """PENDING until `effective_at`, then ACTIVE, DEPLETED once spent, or EXPIRED.""" + + type: CreditGrantType + """PAID or PROMOTIONAL: whether it applies after or before tax.""" + + bonus_grant_id: str | None = None + """Only on the response that created a PAID currency grant whose `cost` was below its + amount: the PROMOTIONAL "Bonus credit" grant created beside it for the difference, with + the same expiry, scope and priority.""" + + cost: CreditCost | None = None + + cost_basis: str | None = None + """`cost` per one major unit of credit, in major units of the cost currency (e.g. "0.008"), + rounded to 12 decimal places, for display only.""" + + credit_note_id: str | None = None + """The credit note that gave the credit back.""" + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: Currency | None = None + + ended_at: datetime | None = None + """When the subscription the grant belongs to ended, if it ended. Unspent credit lapses then + (status EXPIRED), even before `expires_at`.""" + + expired_amount: int | None = None + """Lost to expiry; present only on an expired grant.""" + + expires_at: datetime | None = None + """When unspent credit lapses; absent never expires.""" + + invoice_id: str | None = None + """The invoice that sold the credit, or whose void or negative total gave it back.""" + + reference: str | None = None + """Your own reference, e.g. a contract or support ticket.""" + + subscription_id: str | None = None + """The subscription the grant belongs to (`sub_…`): it pays only that subscription's + charges, and lapses when the subscription ends. Null when it pays any of the customer's + subscriptions.""" diff --git a/meteroid/models/credit_grant_expired_event.py b/meteroid/models/credit_grant_expired_event.py new file mode 100644 index 0000000..103f99b --- /dev/null +++ b/meteroid/models/credit_grant_expired_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_grant_expired_event_data import CreditGrantExpiredEventData + from .event_id import EventId + from .event_type import EventType + + +@dataclasses.dataclass(kw_only=True) +class CreditGrantExpiredEvent(BaseModel): + """The `CreditGrantExpiredEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("credit_grant_expired_event_data",) + + credit_grant_expired_event_data: CreditGrantExpiredEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/credit_grant_expired_event_data.py b/meteroid/models/credit_grant_expired_event_data.py new file mode 100644 index 0000000..985ff2d --- /dev/null +++ b/meteroid/models/credit_grant_expired_event_data.py @@ -0,0 +1,58 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_applies_to import CreditAppliesTo + from .credit_grant_id import CreditGrantId + from .credit_grant_lapse_reason import CreditGrantLapseReason + from .customer_id import CustomerId + from .ledger_entry_id import LedgerEntryId + + +@dataclasses.dataclass(kw_only=True) +class CreditGrantExpiredEventData(BaseModel): + """A credit grant expired, or the subscription it belongs to ended, with credit left, which + lapsed.""" + + applicable_product_ids: list[str] + """The products whose charges the grant pays; empty when it pays every product.""" + + applies_to: CreditAppliesTo + """What the grant pays: ALL any charge, USAGE only usage charges.""" + + credit_grant_id: CreditGrantId + """The grant that expired.""" + + customer_id: CustomerId + + expired_amount: int + """Unspent credit that lapsed, in minor units of `currency` or `credit_unit`.""" + + expired_at: datetime + """When the grant expired, or its subscription ended.""" + + ledger_entry_id: LedgerEntryId + """The credit ledger entry recording the expiry.""" + + reason: CreditGrantLapseReason + """SUBSCRIPTION_ENDED when the subscription the grant belongs to ended at or before the grant's + expiry, EXPIRED otherwise.""" + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" + + subscription_id: str | None = None + """The subscription the grant belongs to: it pays only that subscription's charges. Null when + it pays any of the customer's subscriptions.""" diff --git a/meteroid/models/credit_grant_id.py b/meteroid/models/credit_grant_id.py new file mode 100644 index 0000000..99214bb --- /dev/null +++ b/meteroid/models/credit_grant_id.py @@ -0,0 +1,4 @@ +# this file is @generated +import typing as t + +CreditGrantId: t.TypeAlias = str diff --git a/meteroid/models/credit_grant_kind.py b/meteroid/models/credit_grant_kind.py new file mode 100644 index 0000000..456d7b9 --- /dev/null +++ b/meteroid/models/credit_grant_kind.py @@ -0,0 +1,24 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CreditGrantKind(StrEnum): + """The group a grant is shown in, from where it came from and what it pays: + - INCLUDED: credit a subscription's plan includes, which renews with it; + - PREPAID: credit bought (purchases, top-ups, paid packs, credit paid for outside Meteroid); + - BONUS: credit given (the bonus part of discounted credit, promo codes, welcome credit); + - ACCOUNT_BALANCE: the customer's money that pays any invoice (credit notes, returned invoices, + the balance carried from before credit grants, credit added for any charge).""" + + INCLUDED = "INCLUDED" + PREPAID = "PREPAID" + BONUS = "BONUS" + ACCOUNT_BALANCE = "ACCOUNT_BALANCE" + + +CreditGrantKindLiteral: t.TypeAlias = t.Literal[ + "INCLUDED", "PREPAID", "BONUS", "ACCOUNT_BALANCE" +] +"""The values of :class:`CreditGrantKind`, which arguments take as plain strings too.""" diff --git a/meteroid/models/credit_grant_lapse_reason.py b/meteroid/models/credit_grant_lapse_reason.py new file mode 100644 index 0000000..164a534 --- /dev/null +++ b/meteroid/models/credit_grant_lapse_reason.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CreditGrantLapseReason(StrEnum): + """Why a credit grant's unspent credit lapsed.""" + + EXPIRED = "EXPIRED" + SUBSCRIPTION_ENDED = "SUBSCRIPTION_ENDED" + + +CreditGrantLapseReasonLiteral: t.TypeAlias = t.Literal["EXPIRED", "SUBSCRIPTION_ENDED"] +"""The values of :class:`CreditGrantLapseReason`, which arguments take as plain strings too.""" diff --git a/meteroid/models/credit_grant_list_response.py b/meteroid/models/credit_grant_list_response.py new file mode 100644 index 0000000..efbf238 --- /dev/null +++ b/meteroid/models/credit_grant_list_response.py @@ -0,0 +1,20 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_grant import CreditGrant + from .pagination_response import PaginationResponse + + +@dataclasses.dataclass(kw_only=True) +class CreditGrantListResponse(BaseModel): + """A page of credit grants.""" + + data: list[CreditGrant] + + pagination_meta: PaginationResponse diff --git a/meteroid/models/credit_grant_source.py b/meteroid/models/credit_grant_source.py new file mode 100644 index 0000000..c080299 --- /dev/null +++ b/meteroid/models/credit_grant_source.py @@ -0,0 +1,32 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CreditGrantSource(StrEnum): + """The values of `CreditGrantSource`; others are kept as received.""" + + MANUAL = "MANUAL" + PURCHASE = "PURCHASE" + PLAN = "PLAN" + CREDIT_NOTE = "CREDIT_NOTE" + ADJUSTMENT = "ADJUSTMENT" + MIGRATED = "MIGRATED" + ROLLOVER = "ROLLOVER" + COUPON = "COUPON" + OVERPAYMENT = "OVERPAYMENT" + + +CreditGrantSourceLiteral: t.TypeAlias = t.Literal[ + "MANUAL", + "PURCHASE", + "PLAN", + "CREDIT_NOTE", + "ADJUSTMENT", + "MIGRATED", + "ROLLOVER", + "COUPON", + "OVERPAYMENT", +] +"""The values of :class:`CreditGrantSource`, which arguments take as plain strings too.""" diff --git a/meteroid/models/credit_grant_status.py b/meteroid/models/credit_grant_status.py new file mode 100644 index 0000000..ea6d010 --- /dev/null +++ b/meteroid/models/credit_grant_status.py @@ -0,0 +1,19 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CreditGrantStatus(StrEnum): + """The values of `CreditGrantStatus`; others are kept as received.""" + + PENDING = "PENDING" + ACTIVE = "ACTIVE" + DEPLETED = "DEPLETED" + EXPIRED = "EXPIRED" + + +CreditGrantStatusLiteral: t.TypeAlias = t.Literal[ + "PENDING", "ACTIVE", "DEPLETED", "EXPIRED" +] +"""The values of :class:`CreditGrantStatus`, which arguments take as plain strings too.""" diff --git a/meteroid/models/credit_grant_type.py b/meteroid/models/credit_grant_type.py new file mode 100644 index 0000000..5b1446b --- /dev/null +++ b/meteroid/models/credit_grant_type.py @@ -0,0 +1,18 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class CreditGrantType(StrEnum): + """PAID credit was bought, through Meteroid or outside it, and applies after tax like a payment. + PROMOTIONAL credit was given (included with a plan, a coupon, bonus credit, a goodwill grant): + it applies before tax, like a discount, so it is spent before paid credit. Credit-unit grants + are drawn together, whatever their type.""" + + PAID = "PAID" + PROMOTIONAL = "PROMOTIONAL" + + +CreditGrantTypeLiteral: t.TypeAlias = t.Literal["PAID", "PROMOTIONAL"] +"""The values of :class:`CreditGrantType`, which arguments take as plain strings too.""" diff --git a/meteroid/models/credit_pack_expiry.py b/meteroid/models/credit_pack_expiry.py new file mode 100644 index 0000000..d8c4f5b --- /dev/null +++ b/meteroid/models/credit_pack_expiry.py @@ -0,0 +1,21 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_expiry_unit import CreditExpiryUnit + + +@dataclasses.dataclass(kw_only=True) +class CreditPackExpiry(BaseModel): + """How long funded credit stays spendable. BILLING_PERIOD takes value 1.""" + + unit: CreditExpiryUnit + """DAY, MONTH or BILLING_PERIOD.""" + + value: int + """How many days or months; 1 for BILLING_PERIOD.""" diff --git a/meteroid/models/credit_pack_rollover.py b/meteroid/models/credit_pack_rollover.py new file mode 100644 index 0000000..f082f66 --- /dev/null +++ b/meteroid/models/credit_pack_rollover.py @@ -0,0 +1,23 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from decimal import Decimal + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .credit_pack_expiry import CreditPackExpiry + + +@dataclasses.dataclass(kw_only=True) +class CreditPackRollover(BaseModel): + """Unspent included credit carried once into a new grant when it expires, spent before the next + period's credit.""" + + expiry: CreditPackExpiry + """Term of the rolled credit from the original expiry: DAY or MONTH.""" + + max_amount: Decimal | None | Unset = UNSET + """Cap per pack, in the pack's credit; absent carries all that is left.""" diff --git a/meteroid/models/credit_pack_unit.py b/meteroid/models/credit_pack_unit.py new file mode 100644 index 0000000..91d7a1f --- /dev/null +++ b/meteroid/models/credit_pack_unit.py @@ -0,0 +1,21 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class CreditPackUnit(BaseModel): + """The credit unit a credit pack grants.""" + + code: str + """The credit unit's code, e.g. `CREDITS`.""" + + credit_unit_id: str + """The credit unit's id (`cun_…`).""" + + precision: int + """Decimal places of the unit: credit balances and ledger amounts in it are integers of + 10^-precision credit.""" diff --git a/meteroid/models/credit_plan_fee.py b/meteroid/models/credit_plan_fee.py new file mode 100644 index 0000000..cd5176a --- /dev/null +++ b/meteroid/models/credit_plan_fee.py @@ -0,0 +1,46 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from decimal import Decimal + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .billing_period_enum import BillingPeriodEnum + from .credit_pack_expiry import CreditPackExpiry + from .credit_pack_rollover import CreditPackRollover + + +@dataclasses.dataclass(kw_only=True) +class CreditPlanFee(BaseModel): + """Credit pack: charged `unit_price` × `quantity` in advance each billing period, it grants + `credit_amount` × `quantity` of credit, in the plan currency or in `credit_unit_id`, once paid. + A zero `unit_price` makes it credit included with the plan (promotional). A currency pack + priced below its credit (e.g. 500.00 buys 550.00) grants the price paid as paid credit and the + difference as promotional bonus credit, which applies before tax.""" + + cadence: BillingPeriodEnum + + credit_amount: Decimal + + quantity: int + + type: t.Literal["CREDIT"] = "CREDIT" + + unit_price: Decimal + + credit_unit_id: str | None | Unset = UNSET + """The credit unit `credit_amount` is granted in; absent grants the plan currency. The pack is + paid in the plan currency, and taxed when sold if it grants a credit unit.""" + + expiry: CreditPackExpiry | None | Unset = UNSET + + once: bool | None = None + """When true, the pack is billed and its credit granted once, on the subscription's first + invoice, instead of every period (e.g. a welcome credit). A plan change grants it on the + next invoice only if the subscription never received a one-time credit of the same + product; credit packs billed every period don't count. Cannot be combined with `rollover`.""" + + rollover: CreditPackRollover | None | Unset = UNSET diff --git a/meteroid/models/credit_unit.py b/meteroid/models/credit_unit.py new file mode 100644 index 0000000..8c77016 --- /dev/null +++ b/meteroid/models/credit_unit.py @@ -0,0 +1,44 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_unit_price import CreditUnitPrice + + +@dataclasses.dataclass(kw_only=True) +class CreditUnit(BaseModel): + """A tenant-defined denomination usage can be priced or credit granted in, instead of money. + `code` and `precision` are immutable once created.""" + + archived: bool + """Archived units take no new prices, packs or grants; existing ones keep working.""" + + code: str + """Upper-case code naming the unit in wallets, grants and webhooks, e.g. `CREDITS`.""" + + id: str + """The unit's id (`cun_…`).""" + + name: str + """Singular display name, e.g. "Credit".""" + + plural_name: str + """Plural display name, e.g. "Credits".""" + + precision: int + """Minor-unit exponent: amounts of this unit are integers of 10^-precision credit.""" + + prices: list[CreditUnitPrice] + """The active selling prices, one per currency.""" + + product_id: str | None = None + """The product sales of this unit are booked under: their tax category and accounting code. + The unit can be sold (top-ups) only once it is set.""" + + symbol: str | None = None + """Short display symbol, e.g. "cr".""" diff --git a/meteroid/models/credit_unit_create_request.py b/meteroid/models/credit_unit_create_request.py new file mode 100644 index 0000000..519244e --- /dev/null +++ b/meteroid/models/credit_unit_create_request.py @@ -0,0 +1,27 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import UNSET, BaseModel, Unset + + +@dataclasses.dataclass(kw_only=True) +class CreditUnitCreateRequest(BaseModel): + """A new credit unit.""" + + code: str + """Immutable once created: 4 to 16 characters, A-Z/0-9/_, starting with a letter. Never an + ISO currency code (those are always 3 letters).""" + + name: str + """Singular display name, 1 to 64 characters, e.g. "Credit".""" + + plural_name: str + """Plural display name, 1 to 64 characters, e.g. "Credits".""" + + precision: int + """Immutable once created: 0 to 6.""" + + symbol: str | None | Unset = UNSET + """Short display symbol, at most 8 characters.""" diff --git a/meteroid/models/credit_unit_list_response.py b/meteroid/models/credit_unit_list_response.py new file mode 100644 index 0000000..ba9f04a --- /dev/null +++ b/meteroid/models/credit_unit_list_response.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_unit import CreditUnit + + +@dataclasses.dataclass(kw_only=True) +class CreditUnitListResponse(BaseModel): + """The tenant's credit units.""" + + data: list[CreditUnit] diff --git a/meteroid/models/credit_unit_price.py b/meteroid/models/credit_unit_price.py new file mode 100644 index 0000000..47fa231 --- /dev/null +++ b/meteroid/models/credit_unit_price.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +from datetime import datetime + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class CreditUnitPrice(BaseModel): + """What one credit sells for in a currency. A price is never edited: setting a new one for the + same currency archives it.""" + + created_at: datetime + """When the price was set.""" + + currency: str + """ISO 4217 code of the currency the unit sells in.""" + + id: str + """The price's id (`cup_…`).""" + + unit_price: str + """Major units of `currency` per credit, as a decimal string (e.g. "0.001").""" + + archived_at: datetime | None = None + """When a newer price, or its removal, replaced it; absent while active.""" diff --git a/meteroid/models/credit_unit_price_list_response.py b/meteroid/models/credit_unit_price_list_response.py new file mode 100644 index 0000000..aed3266 --- /dev/null +++ b/meteroid/models/credit_unit_price_list_response.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_unit_price import CreditUnitPrice + + +@dataclasses.dataclass(kw_only=True) +class CreditUnitPriceListResponse(BaseModel): + """A credit unit's selling prices, newest first.""" + + data: list[CreditUnitPrice] diff --git a/meteroid/models/credit_unit_price_set_request.py b/meteroid/models/credit_unit_price_set_request.py new file mode 100644 index 0000000..47a1cb6 --- /dev/null +++ b/meteroid/models/credit_unit_price_set_request.py @@ -0,0 +1,15 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class CreditUnitPriceSetRequest(BaseModel): + """The new price of one credit in a currency.""" + + unit_price: str + """Major units of the currency per credit, as a decimal string: positive, at most 12 decimal + places.""" diff --git a/meteroid/models/credit_unit_update_request.py b/meteroid/models/credit_unit_update_request.py new file mode 100644 index 0000000..f032a31 --- /dev/null +++ b/meteroid/models/credit_unit_update_request.py @@ -0,0 +1,29 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import UNSET, BaseModel, Unset + + +@dataclasses.dataclass(kw_only=True) +class CreditUnitUpdateRequest(BaseModel): + """Partial update: an absent field is left unchanged. `code` and `precision` have no field here, + they are immutable. `symbol` set to `null` clears it.""" + + archived: bool | None | Unset = UNSET + """Blocks the unit from new prices, packs and grants; existing ones keep working.""" + + name: str | None | Unset = UNSET + """Singular display name, 1 to 64 characters.""" + + plural_name: str | None | Unset = UNSET + """Plural display name, 1 to 64 characters.""" + + product_id: str | None | Unset = UNSET + """The product sales of the unit are booked under; `null` clears it, which stops the unit + from being sold.""" + + symbol: str | None | Unset = UNSET + """Validated by the store (trimmed, at most 8 characters) rather than here: `validator` has + no built-in support for a double `Option`.""" diff --git a/meteroid/models/decline_kind.py b/meteroid/models/decline_kind.py index db65bc9..f2f2ffc 100644 --- a/meteroid/models/decline_kind.py +++ b/meteroid/models/decline_kind.py @@ -15,6 +15,7 @@ class DeclineKind(StrEnum): FRAUD = "FRAUD" PROCESSING_ERROR = "PROCESSING_ERROR" OTHER = "OTHER" + DO_NOT_RETRY = "DO_NOT_RETRY" DeclineKindLiteral: t.TypeAlias = t.Literal[ @@ -26,5 +27,6 @@ class DeclineKind(StrEnum): "FRAUD", "PROCESSING_ERROR", "OTHER", + "DO_NOT_RETRY", ] """The values of :class:`DeclineKind`, which arguments take as plain strings too.""" diff --git a/meteroid/models/double_segmentation_matrix.py b/meteroid/models/double_segmentation_matrix.py index 8ad5ed9..5e6f84b 100644 --- a/meteroid/models/double_segmentation_matrix.py +++ b/meteroid/models/double_segmentation_matrix.py @@ -17,3 +17,5 @@ class DoubleSegmentationMatrix(BaseModel): dimension1: MetricDimension dimension2: MetricDimension + + type: t.Literal["DOUBLE"] = "DOUBLE" diff --git a/meteroid/models/entitlement_check.py b/meteroid/models/entitlement_check.py new file mode 100644 index 0000000..1fb2f6b --- /dev/null +++ b/meteroid/models/entitlement_check.py @@ -0,0 +1,69 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime +from decimal import Decimal + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .config_value import ConfigValue + from .entitlement_check_feature import EntitlementCheckFeature + from .entitlement_check_limit import EntitlementCheckLimit + from .entitlement_check_period import EntitlementCheckPeriod + from .entitlement_check_reason import EntitlementCheckReason + + +@dataclasses.dataclass(kw_only=True) +class EntitlementCheck(BaseModel): + """Whether a customer may consume more of a feature now.""" + + as_of: datetime + """When the usage behind the answer was measured.""" + + exact: bool + """Always false: the answer reflects usage up to `as_of`.""" + + feature: EntitlementCheckFeature + + has_access: bool + + in_overage: bool + """The entitlement allows overage (`ALLOW`) and the requested units go beyond its limit: + access is granted, and the overage is billed.""" + + lag_ms: int + """Age of that usage, in milliseconds.""" + + limits: list[EntitlementCheckLimit] + """Deprecated: always empty. Customer-specific limits are entitlement overrides, reported in + `used`, `limit` and `remaining`.""" + + near_limit: bool + """True when a hard limit is within 5% of being reached, reached, or refuses the request. + While true, check before every request with its actual `requested`, and do not cache an + answer that grants access.""" + + reason: EntitlementCheckReason + + limit: Decimal | None = None + + period: EntitlementCheckPeriod | None = None + + remaining: Decimal | None = None + """Units left before the limit, grace excluded: 0 while `has_access` is true when usage is + within the grace allowance.""" + + retry_after_ms: int | None = None + """When to check again, in milliseconds. After a refusal by a limit whose period ends within + 24 hours: the time until it ends (a smaller `requested` may still be allowed sooner); + absent after a refusal by a sliding window, whose usage leaves it gradually. While + `near_limit` is true and access is granted: 1000.""" + + used: Decimal | None = None + """`used`, `limit`, `remaining` and `period` report the entitlement's limit. Absent when the + entitlement has no limit.""" + + value: ConfigValue | None = None diff --git a/meteroid/models/entitlement_check_feature.py b/meteroid/models/entitlement_check_feature.py new file mode 100644 index 0000000..db21cb9 --- /dev/null +++ b/meteroid/models/entitlement_check_feature.py @@ -0,0 +1,19 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .feature_id import FeatureId + + +@dataclasses.dataclass(kw_only=True) +class EntitlementCheckFeature(BaseModel): + """The `EntitlementCheckFeature` object.""" + + code: str + + id: FeatureId diff --git a/meteroid/models/entitlement_check_limit.py b/meteroid/models/entitlement_check_limit.py new file mode 100644 index 0000000..8f8f07e --- /dev/null +++ b/meteroid/models/entitlement_check_limit.py @@ -0,0 +1,32 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from decimal import Decimal + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .entitlement_check_limit_behavior import EntitlementCheckLimitBehavior + from .usage_limit_id import UsageLimitId + + +@dataclasses.dataclass(kw_only=True) +class EntitlementCheckLimit(BaseModel): + """Deprecated: customer usage limits were replaced by entitlement overrides; never returned.""" + + behavior: EntitlementCheckLimitBehavior + + has_access: bool + + limit: Decimal + + remaining: Decimal + + usage_limit_id: UsageLimitId + + used: Decimal + + group: dict[str, str] | None = None + """The group the limit was evaluated for, when the limit is grouped.""" diff --git a/meteroid/models/entitlement_check_limit_behavior.py b/meteroid/models/entitlement_check_limit_behavior.py new file mode 100644 index 0000000..162074f --- /dev/null +++ b/meteroid/models/entitlement_check_limit_behavior.py @@ -0,0 +1,16 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class EntitlementCheckLimitBehavior(StrEnum): + """The values of `EntitlementCheckLimitBehavior`; others are kept as received.""" + + TRACK = "TRACK" + SOFT = "SOFT" + HARD = "HARD" + + +EntitlementCheckLimitBehaviorLiteral: t.TypeAlias = t.Literal["TRACK", "SOFT", "HARD"] +"""The values of :class:`EntitlementCheckLimitBehavior`, which arguments take as plain strings too.""" diff --git a/meteroid/models/entitlement_check_period.py b/meteroid/models/entitlement_check_period.py new file mode 100644 index 0000000..447ef76 --- /dev/null +++ b/meteroid/models/entitlement_check_period.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +from datetime import datetime + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class EntitlementCheckPeriod(BaseModel): + """The `EntitlementCheckPeriod` object.""" + + start: datetime + + end: datetime | None = None + """Absent when the period never ends.""" diff --git a/meteroid/models/entitlement_check_reason.py b/meteroid/models/entitlement_check_reason.py new file mode 100644 index 0000000..3d3f578 --- /dev/null +++ b/meteroid/models/entitlement_check_reason.py @@ -0,0 +1,28 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class EntitlementCheckReason(StrEnum): + """The values of `EntitlementCheckReason`; others are kept as received.""" + + WITHIN_LIMIT = "WITHIN_LIMIT" + LIMIT_REACHED = "LIMIT_REACHED" + NO_ENTITLEMENT = "NO_ENTITLEMENT" + DISABLED = "DISABLED" + BUDGET_EXCEEDED = "BUDGET_EXCEEDED" + INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE" + TOO_MANY_GROUPS = "TOO_MANY_GROUPS" + + +EntitlementCheckReasonLiteral: t.TypeAlias = t.Literal[ + "WITHIN_LIMIT", + "LIMIT_REACHED", + "NO_ENTITLEMENT", + "DISABLED", + "BUDGET_EXCEEDED", + "INSUFFICIENT_BALANCE", + "TOO_MANY_GROUPS", +] +"""The values of :class:`EntitlementCheckReason`, which arguments take as plain strings too.""" diff --git a/meteroid/models/entitlement_overage_behavior.py b/meteroid/models/entitlement_overage_behavior.py new file mode 100644 index 0000000..d7bf1be --- /dev/null +++ b/meteroid/models/entitlement_overage_behavior.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class EntitlementOverageBehavior(StrEnum): + """What happens once usage reaches the limit.""" + + BLOCK = "BLOCK" + ALLOW = "ALLOW" + + +EntitlementOverageBehaviorLiteral: t.TypeAlias = t.Literal["BLOCK", "ALLOW"] +"""The values of :class:`EntitlementOverageBehavior`, which arguments take as plain strings too.""" diff --git a/meteroid/models/event_type.py b/meteroid/models/event_type.py index 79d9025..4bb41cd 100644 --- a/meteroid/models/event_type.py +++ b/meteroid/models/event_type.py @@ -44,7 +44,20 @@ class EventType(StrEnum): REFUND_SETTLED = "refund.settled" REFUND_FAILED = "refund.failed" PAYMENT_REVERSED = "payment.reversed" + PAYMENT_REINSTATED = "payment.reinstated" PAYMENT_FAILED = "payment.failed" + WALLET_FUNDED = "wallet.funded" + WALLET_DEBITED = "wallet.debited" + WALLET_BALANCE_LOW = "wallet.balance_low" + WALLET_CREDIT_UNRECOVERED = "wallet.credit_unrecovered" + WALLET_CREDIT_RECOVERED = "wallet.credit_recovered" + WALLET_AUTO_TOP_UP_FAILED = "wallet.auto_top_up_failed" + WALLET_AUTO_TOP_UP_SUSPENDED = "wallet.auto_top_up_suspended" + WALLET_AUTO_TOP_UP_CONSENT_CHANGED = "wallet.auto_top_up_consent_changed" + WALLET_DEPLETED = "wallet.depleted" + WALLET_RECOVERED = "wallet.recovered" + WALLET_AUTO_TOP_UP_CAP_REACHED = "wallet.auto_top_up_cap_reached" + CREDIT_GRANT_EXPIRED = "credit_grant.expired" EventTypeLiteral: t.TypeAlias = t.Literal[ @@ -85,6 +98,19 @@ class EventType(StrEnum): "refund.settled", "refund.failed", "payment.reversed", + "payment.reinstated", "payment.failed", + "wallet.funded", + "wallet.debited", + "wallet.balance_low", + "wallet.credit_unrecovered", + "wallet.credit_recovered", + "wallet.auto_top_up_failed", + "wallet.auto_top_up_suspended", + "wallet.auto_top_up_consent_changed", + "wallet.depleted", + "wallet.recovered", + "wallet.auto_top_up_cap_reached", + "credit_grant.expired", ] """The values of :class:`EventType`, which arguments take as plain strings too.""" diff --git a/meteroid/models/existing_price_ref.py b/meteroid/models/existing_price_ref.py index b2ebf0e..4b05e73 100644 --- a/meteroid/models/existing_price_ref.py +++ b/meteroid/models/existing_price_ref.py @@ -15,3 +15,5 @@ class ExistingPriceRef(BaseModel): """The `ExistingPriceRef` object.""" id: PriceId + + type: t.Literal["EXISTING"] = "EXISTING" diff --git a/meteroid/models/existing_product_ref.py b/meteroid/models/existing_product_ref.py index 3eee4fa..4bd2a6a 100644 --- a/meteroid/models/existing_product_ref.py +++ b/meteroid/models/existing_product_ref.py @@ -15,3 +15,5 @@ class ExistingProductRef(BaseModel): """The `ExistingProductRef` object.""" id: ProductId + + type: t.Literal["EXISTING"] = "EXISTING" diff --git a/meteroid/models/external_payment_method_config.py b/meteroid/models/external_payment_method_config.py index e446092..7b2b296 100644 --- a/meteroid/models/external_payment_method_config.py +++ b/meteroid/models/external_payment_method_config.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -9,3 +10,5 @@ @dataclasses.dataclass(kw_only=True) class ExternalPaymentMethodConfig(BaseModel): """The `ExternalPaymentMethodConfig` object.""" + + type: t.Literal["external"] = "external" diff --git a/meteroid/models/extra_recurring_fee_structure.py b/meteroid/models/extra_recurring_fee_structure.py index e68bd46..d73b9ce 100644 --- a/meteroid/models/extra_recurring_fee_structure.py +++ b/meteroid/models/extra_recurring_fee_structure.py @@ -15,3 +15,5 @@ class ExtraRecurringFeeStructure(BaseModel): """The `ExtraRecurringFeeStructure` object.""" billing_type: ExtraRecurringBillingTypeEnum + + type: t.Literal["EXTRA_RECURRING"] = "EXTRA_RECURRING" diff --git a/meteroid/models/extra_recurring_plan_fee.py b/meteroid/models/extra_recurring_plan_fee.py index a79c82c..2d7de2f 100644 --- a/meteroid/models/extra_recurring_plan_fee.py +++ b/meteroid/models/extra_recurring_plan_fee.py @@ -22,4 +22,6 @@ class ExtraRecurringPlanFee(BaseModel): quantity: int + type: t.Literal["EXTRA_RECURRING"] = "EXTRA_RECURRING" + unit_price: Decimal diff --git a/meteroid/models/extra_recurring_pricing.py b/meteroid/models/extra_recurring_pricing.py index 54307f3..6275880 100644 --- a/meteroid/models/extra_recurring_pricing.py +++ b/meteroid/models/extra_recurring_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -13,4 +14,6 @@ class ExtraRecurringPricing(BaseModel): quantity: int + type: t.Literal["EXTRA_RECURRING"] = "EXTRA_RECURRING" + unit_price: Decimal diff --git a/meteroid/models/fee.py b/meteroid/models/fee.py index 0e9549f..178e209 100644 --- a/meteroid/models/fee.py +++ b/meteroid/models/fee.py @@ -5,6 +5,7 @@ from ..serialization import Discriminator, UnknownVariant from .capacity_plan_fee import CapacityPlanFee +from .credit_plan_fee import CreditPlanFee from .extra_recurring_plan_fee import ExtraRecurringPlanFee from .one_time_plan_fee import OneTimePlanFee from .rate_plan_fee import RatePlanFee @@ -18,6 +19,7 @@ | UsagePlanFee | ExtraRecurringPlanFee | OneTimePlanFee + | CreditPlanFee | UnknownVariant, Discriminator( "type", @@ -28,6 +30,7 @@ "USAGE": UsagePlanFee, "EXTRA_RECURRING": ExtraRecurringPlanFee, "ONE_TIME": OneTimePlanFee, + "CREDIT": CreditPlanFee, }, ), ] diff --git a/meteroid/models/fee_credit_unit.py b/meteroid/models/fee_credit_unit.py new file mode 100644 index 0000000..f17db99 --- /dev/null +++ b/meteroid/models/fee_credit_unit.py @@ -0,0 +1,27 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +from decimal import Decimal + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class FeeCreditUnit(BaseModel): + """The credit unit a usage fee's amounts are expressed in, with its value in the subscription + currency.""" + + code: str + """The credit unit's code, e.g. `CREDITS`.""" + + credit_unit_id: str + """The credit unit's id (`cun_…`).""" + + precision: int + """Decimal places of the unit: credit balances and ledger amounts in it are integers of + 10^-precision credit.""" + + rate: Decimal + """Subscription-currency major units per credit: the money billed, before tax, for each + credit the customer's balance in the unit does not cover.""" diff --git a/meteroid/models/fixed_discount.py b/meteroid/models/fixed_discount.py index 54f5cba..a8ed74e 100644 --- a/meteroid/models/fixed_discount.py +++ b/meteroid/models/fixed_discount.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -13,3 +14,5 @@ class FixedDiscount(BaseModel): amount: str currency: str + + type: t.Literal["FIXED"] = "FIXED" diff --git a/meteroid/models/fixed_window_reset_period.py b/meteroid/models/fixed_window_reset_period.py index 0c4981d..8e93f46 100644 --- a/meteroid/models/fixed_window_reset_period.py +++ b/meteroid/models/fixed_window_reset_period.py @@ -16,4 +16,6 @@ class FixedWindowResetPeriod(BaseModel): interval: int + type: t.Literal["FIXED_WINDOW"] = "FIXED_WINDOW" + unit: CalendarUnit diff --git a/meteroid/models/invoice.py b/meteroid/models/invoice.py index 3af00f4..240af47 100644 --- a/meteroid/models/invoice.py +++ b/meteroid/models/invoice.py @@ -30,6 +30,8 @@ class Invoice(BaseModel): amount_due: int applied_credits: int + """Paid credit applied after tax, in minor units of the invoice currency: it settles part of + `total` like a payment, and `amount_due` is net of it.""" coupons: list[CouponLineItem] @@ -58,6 +60,10 @@ class Invoice(BaseModel): payment_status: InvoicePaymentStatus + settled_by_credit_notes: bool + """True when the invoice is `PAID` without any payment: finalized credit notes cancelled its + whole total. Nothing was collected, so it books no cash.""" + status: InvoiceStatus subtotal: int @@ -97,6 +103,16 @@ class Invoice(BaseModel): parent_invoice_id: InvoiceId | None = None + payment_disputed_at: datetime | None = None + """When a dispute or chargeback first took money still lost on this invoice's payments. The + invoice stays paid; absent when nothing is disputed.""" + + promotional_credits: int | None = None + """Promotional credit applied before tax (credit included with the plan, coupon credit, bonus + credit), in minor units of the invoice currency. Like a discount, it reduces the taxable + amount: `tax_amount` and `total` already exclude it, `subtotal` does not. Unlike + `applied_credits`, which settles part of the `total` after tax.""" + purchase_order: str | None = None reference: str | None = None diff --git a/meteroid/models/invoice_event_data.py b/meteroid/models/invoice_event_data.py index a47846d..3745943 100644 --- a/meteroid/models/invoice_event_data.py +++ b/meteroid/models/invoice_event_data.py @@ -28,6 +28,10 @@ class InvoiceEventData(BaseModel): invoice_id: InvoiceId + settled_by_credit_notes: bool + """On `invoice.paid`: true when finalized credit notes, not a payment, settled the invoice's + whole total. Nothing was collected, so it books no cash. False on every other event.""" + status: InvoiceStatus tax_amount: int diff --git a/meteroid/models/invoice_line_credit_unit.py b/meteroid/models/invoice_line_credit_unit.py new file mode 100644 index 0000000..3ecc2ee --- /dev/null +++ b/meteroid/models/invoice_line_credit_unit.py @@ -0,0 +1,31 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +from decimal import Decimal + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class InvoiceLineCreditUnit(BaseModel): + """How a usage line priced in a credit unit was paid: the credits it consumed, those the + customer's balance in the unit covered, and the rate the rest was billed at.""" + + code: str + """The credit unit's code, e.g. `CREDITS`.""" + + consumed: int + """Credits the usage priced.""" + + covered: int + """Of which paid by the customer's credit wallet.""" + + precision: int + """Credit amounts are integers in 10^-precision credit.""" + + rate: Decimal + """Invoice-currency major units per credit.""" + + unit_price: Decimal | None = None + """Credits per usage unit.""" diff --git a/meteroid/models/invoice_line_item.py b/meteroid/models/invoice_line_item.py index 4760a8d..820dc6a 100644 --- a/meteroid/models/invoice_line_item.py +++ b/meteroid/models/invoice_line_item.py @@ -9,6 +9,7 @@ from ..serialization import BaseModel if t.TYPE_CHECKING: + from .invoice_line_credit_unit import InvoiceLineCreditUnit from .sub_line_item import SubLineItem @@ -28,6 +29,8 @@ class InvoiceLineItem(BaseModel): tax_rate: Decimal + credit_unit: InvoiceLineCreditUnit | None = None + description: str | None = None quantity: Decimal | None = None diff --git a/meteroid/models/json_config_value.py b/meteroid/models/json_config_value.py index 855e136..8591626 100644 --- a/meteroid/models/json_config_value.py +++ b/meteroid/models/json_config_value.py @@ -11,4 +11,6 @@ class JsonConfigValue(BaseModel): """A structured (JSON) config value — the "metadata" case, several fields in one entitlement.""" + kind: t.Literal["JSON"] = "JSON" + value: t.Any diff --git a/meteroid/models/ledger_entry.py b/meteroid/models/ledger_entry.py new file mode 100644 index 0000000..9dde302 --- /dev/null +++ b/meteroid/models/ledger_entry.py @@ -0,0 +1,74 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_applies_to import CreditAppliesTo + from .credit_cost import CreditCost + from .currency import Currency + from .ledger_entry_type import LedgerEntryType + + +@dataclasses.dataclass(kw_only=True) +class LedgerEntry(BaseModel): + """One movement of a customer's credit.""" + + amount: int + """Signed change to the credit on account, in minor units.""" + + applies_to: CreditAppliesTo + """What the moved credit's grant pays: ALL any charge, USAGE only usage charges.""" + + balance_after: int + """The credit on account after this entry, whatever it applies to.""" + + credit_grant_id: str + """The grant the credit moved in or out of.""" + + effective_at: datetime + """When the movement took effect; the ledger is ordered by it.""" + + id: str + """The entry's id (`wle_…`).""" + + reason: str + """Why the credit moved, as shown on the ledger.""" + + recorded_at: datetime + """When the movement was recorded.""" + + sequence: int + """Position within the currency's ledger in the order the movements took effect, from 1. A + movement recorded later with an earlier date shifts the positions after it: identify an + entry by its `id`.""" + + type: LedgerEntryType + """What moved the credit.""" + + cost: CreditCost | None = None + + credit_note_id: str | None = None + """The credit note that gave the credit back or took it back.""" + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: Currency | None = None + + invoice_id: str | None = None + """The invoice the credit was applied to, bought on, or given back by.""" + + reference: str | None = None + """Your own reference, e.g. a contract or support ticket.""" + + subscription_id: str | None = None + """The subscription the moved credit's grant belongs to; null when it pays any of the + customer's subscriptions.""" diff --git a/meteroid/models/ledger_entry_id.py b/meteroid/models/ledger_entry_id.py new file mode 100644 index 0000000..27552ec --- /dev/null +++ b/meteroid/models/ledger_entry_id.py @@ -0,0 +1,4 @@ +# this file is @generated +import typing as t + +LedgerEntryId: t.TypeAlias = str diff --git a/meteroid/models/ledger_entry_type.py b/meteroid/models/ledger_entry_type.py new file mode 100644 index 0000000..d641145 --- /dev/null +++ b/meteroid/models/ledger_entry_type.py @@ -0,0 +1,28 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class LedgerEntryType(StrEnum): + """The values of `LedgerEntryType`; others are kept as received.""" + + CREDIT = "CREDIT" + INVOICE_APPLICATION = "INVOICE_APPLICATION" + INVOICE_RETURN = "INVOICE_RETURN" + CREDIT_REVERSAL = "CREDIT_REVERSAL" + CREDIT_REINSTATEMENT = "CREDIT_REINSTATEMENT" + CREDIT_EXPIRY = "CREDIT_EXPIRY" + CREDIT_ROLLOVER = "CREDIT_ROLLOVER" + + +LedgerEntryTypeLiteral: t.TypeAlias = t.Literal[ + "CREDIT", + "INVOICE_APPLICATION", + "INVOICE_RETURN", + "CREDIT_REVERSAL", + "CREDIT_REINSTATEMENT", + "CREDIT_EXPIRY", + "CREDIT_ROLLOVER", +] +"""The values of :class:`LedgerEntryType`, which arguments take as plain strings too.""" diff --git a/meteroid/models/ledger_list_response.py b/meteroid/models/ledger_list_response.py new file mode 100644 index 0000000..acb161d --- /dev/null +++ b/meteroid/models/ledger_list_response.py @@ -0,0 +1,20 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .ledger_entry import LedgerEntry + from .pagination_response import PaginationResponse + + +@dataclasses.dataclass(kw_only=True) +class LedgerListResponse(BaseModel): + """A page of ledger entries, newest first.""" + + data: list[LedgerEntry] + + pagination_meta: PaginationResponse diff --git a/meteroid/models/linked_segmentation_matrix.py b/meteroid/models/linked_segmentation_matrix.py index b23ee88..c736db0 100644 --- a/meteroid/models/linked_segmentation_matrix.py +++ b/meteroid/models/linked_segmentation_matrix.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -14,4 +15,6 @@ class LinkedSegmentationMatrix(BaseModel): dimension2_key: str + type: t.Literal["LINKED"] = "LINKED" + values: dict[str, list[str]] diff --git a/meteroid/models/list_customer_usage_costs_granularity.py b/meteroid/models/list_customer_usage_costs_granularity.py new file mode 100644 index 0000000..509c790 --- /dev/null +++ b/meteroid/models/list_customer_usage_costs_granularity.py @@ -0,0 +1,18 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class ListCustomerUsageCostsGranularity(StrEnum): + """The values of `ListCustomerUsageCostsGranularity`; others are kept as received.""" + + HOUR = "HOUR" + DAY = "DAY" + MONTH = "MONTH" + + +ListCustomerUsageCostsGranularityLiteral: t.TypeAlias = t.Literal[ + "HOUR", "DAY", "MONTH" +] +"""The values of :class:`ListCustomerUsageCostsGranularity`, which arguments take as plain strings too.""" diff --git a/meteroid/models/list_subscription_usage_costs_granularity.py b/meteroid/models/list_subscription_usage_costs_granularity.py new file mode 100644 index 0000000..fe7044f --- /dev/null +++ b/meteroid/models/list_subscription_usage_costs_granularity.py @@ -0,0 +1,18 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class ListSubscriptionUsageCostsGranularity(StrEnum): + """The values of `ListSubscriptionUsageCostsGranularity`; others are kept as received.""" + + HOUR = "HOUR" + DAY = "DAY" + MONTH = "MONTH" + + +ListSubscriptionUsageCostsGranularityLiteral: t.TypeAlias = t.Literal[ + "HOUR", "DAY", "MONTH" +] +"""The values of :class:`ListSubscriptionUsageCostsGranularity`, which arguments take as plain strings too.""" diff --git a/meteroid/models/matrix_plan_pricing.py b/meteroid/models/matrix_plan_pricing.py index fd722eb..bda9ef8 100644 --- a/meteroid/models/matrix_plan_pricing.py +++ b/meteroid/models/matrix_plan_pricing.py @@ -15,3 +15,5 @@ class MatrixPlanPricing(BaseModel): """The `MatrixPlanPricing` object.""" rates: list[MatrixRow] + + type: t.Literal["MATRIX"] = "MATRIX" diff --git a/meteroid/models/matrix_pricing.py b/meteroid/models/matrix_pricing.py index b550b77..e498471 100644 --- a/meteroid/models/matrix_pricing.py +++ b/meteroid/models/matrix_pricing.py @@ -15,3 +15,5 @@ class MatrixPricing(BaseModel): """The `MatrixPricing` object.""" rates: list[MatrixRow] + + type: t.Literal["MATRIX"] = "MATRIX" diff --git a/meteroid/models/metered_effective_entitlement_value.py b/meteroid/models/metered_effective_entitlement_value.py index 2c4e364..d11ad2c 100644 --- a/meteroid/models/metered_effective_entitlement_value.py +++ b/meteroid/models/metered_effective_entitlement_value.py @@ -17,4 +17,6 @@ class MeteredEffectiveEntitlementValue(BaseModel): spec: MeteredEntitlementSpec + type: t.Literal["METERED"] = "METERED" + usage: MeteredEntitlementUsage diff --git a/meteroid/models/metered_entitlement_spec.py b/meteroid/models/metered_entitlement_spec.py index 2f9c118..d439c1a 100644 --- a/meteroid/models/metered_entitlement_spec.py +++ b/meteroid/models/metered_entitlement_spec.py @@ -9,6 +9,7 @@ if t.TYPE_CHECKING: from .billable_metric_id import BillableMetricId + from .entitlement_overage_behavior import EntitlementOverageBehavior from .reset_period import ResetPeriod @@ -17,9 +18,25 @@ class MeteredEntitlementSpec(BaseModel): """The `MeteredEntitlementSpec` object.""" enabled: bool + """False when switched off, or once a `BLOCK` limit (plus its grace) is reached. An `ALLOW` + limit never turns it off.""" metric_id: BillableMetricId reset_period: ResetPeriod + grace_pct: int | None = None + """`BLOCK` only: usage allowed beyond the limit before it is refused, in percent of it.""" + + group_by: str | None = None + """Event property the limit applies to separately; usage is then the largest value's.""" + limit: Decimal | None = None + + overage_behavior: EntitlementOverageBehavior | None = None + + thresholds: list[int] | None = None + """Percents of the limit at which an alert is sent, ascending.""" + + warning_threshold_pct: int | None = None + """Deprecated: the lowest of `thresholds`.""" diff --git a/meteroid/models/metered_entitlement_usage.py b/meteroid/models/metered_entitlement_usage.py index 53a123c..f11215c 100644 --- a/meteroid/models/metered_entitlement_usage.py +++ b/meteroid/models/metered_entitlement_usage.py @@ -14,6 +14,10 @@ class MeteredEntitlementUsage(BaseModel): consumed: Decimal | None = None + limit_reached: bool | None = None + """Switched on, but refused: the `BLOCK` limit (plus its grace) is reached, so `enabled` is + false until the window resets. Never set for a limit grouped by a property.""" + remaining: Decimal | None = None reset_at: datetime | None = None diff --git a/meteroid/models/metered_entitlement_value.py b/meteroid/models/metered_entitlement_value.py index 612be0d..4bcdb97 100644 --- a/meteroid/models/metered_entitlement_value.py +++ b/meteroid/models/metered_entitlement_value.py @@ -8,6 +8,7 @@ from ..serialization import UNSET, BaseModel, Unset if t.TYPE_CHECKING: + from .entitlement_overage_behavior import EntitlementOverageBehavior from .reset_period import ResetPeriod @@ -15,10 +16,30 @@ class MeteredEntitlementValue(BaseModel): """The `MeteredEntitlementValue` object.""" + type: t.Literal["METERED"] = "METERED" + enabled: bool | None = None """Per-entitlement kill switch. `false` means disabled.""" + grace_pct: int | None | Unset = UNSET + """`BLOCK` only: usage allowed beyond the limit before it is refused, in percent of the + limit (1 to 1000).""" + + group_by: str | None | Unset = UNSET + """Event property the limit applies to separately, one limit per value (e.g. `user_id`).""" + limit: Decimal | None | Unset = UNSET """Cap on usage. Null means unlimited.""" + overage_behavior: EntitlementOverageBehavior | None | Unset = UNSET + reset_period: ResetPeriod | None = None + + thresholds: list[int] | None | Unset = UNSET + """Percents of the limit (1 to 100, at most 10) at which an alert is sent, once per period: + `entitlement.usage_threshold_reached`, or `entitlement.limit_reached` at 100. Requires a + limit and a window that is not sliding.""" + + warning_threshold_pct: int | None | Unset = UNSET + """Deprecated: use `thresholds`. A single alert threshold, read only when `thresholds` is + omitted; returned as the lowest of `thresholds`.""" diff --git a/meteroid/models/metered_feature_type.py b/meteroid/models/metered_feature_type.py index 0c54ef6..b4305c5 100644 --- a/meteroid/models/metered_feature_type.py +++ b/meteroid/models/metered_feature_type.py @@ -15,3 +15,5 @@ class MeteredFeatureType(BaseModel): """The `MeteredFeatureType` object.""" metric_id: BillableMetricId + + type: t.Literal["METERED"] = "METERED" diff --git a/meteroid/models/metered_resolved_entitlement_value.py b/meteroid/models/metered_resolved_entitlement_value.py index fb123c7..fb0ad4a 100644 --- a/meteroid/models/metered_resolved_entitlement_value.py +++ b/meteroid/models/metered_resolved_entitlement_value.py @@ -9,6 +9,7 @@ if t.TYPE_CHECKING: from .billable_metric_id import BillableMetricId + from .entitlement_overage_behavior import EntitlementOverageBehavior from .reset_period import ResetPeriod @@ -22,4 +23,20 @@ class MeteredResolvedEntitlementValue(BaseModel): reset_period: ResetPeriod + type: t.Literal["METERED"] = "METERED" + + grace_pct: int | None = None + """`BLOCK` only: usage allowed beyond the limit before it is refused, in percent of it.""" + + group_by: str | None = None + """Event property the limit applies to separately; usage is then the largest value's.""" + limit: Decimal | None = None + + overage_behavior: EntitlementOverageBehavior | None = None + + thresholds: list[int] | None = None + """Percents of the limit at which an alert is sent, ascending.""" + + warning_threshold_pct: int | None = None + """Deprecated: the lowest of `thresholds`.""" diff --git a/meteroid/models/metric.py b/meteroid/models/metric.py index 6e83196..708f3f1 100644 --- a/meteroid/models/metric.py +++ b/meteroid/models/metric.py @@ -47,4 +47,10 @@ class Metric(BaseModel): unit_conversion: UnitConversion | None = None + unit_plural: str | None = None + """What several units of the metric are called, e.g. `tokens`.""" + + unit_singular: str | None = None + """What one unit of the metric is called, e.g. `token`. Set together with `unit_plural`.""" + usage_group_key: str | None = None diff --git a/meteroid/models/metric_event_data.py b/meteroid/models/metric_event_data.py index bfc664d..9cbf177 100644 --- a/meteroid/models/metric_event_data.py +++ b/meteroid/models/metric_event_data.py @@ -44,4 +44,10 @@ class MetricEventData(BaseModel): unit_conversion_rounding: UnitConversionRoundingEnum | None = None + unit_plural: str | None = None + """What several units of the metric are called, e.g. `tokens`.""" + + unit_singular: str | None = None + """What one unit of the metric is called, e.g. `token`.""" + usage_group_key: str | None = None diff --git a/meteroid/models/metric_summary.py b/meteroid/models/metric_summary.py index c365d6b..87babc1 100644 --- a/meteroid/models/metric_summary.py +++ b/meteroid/models/metric_summary.py @@ -31,3 +31,9 @@ class MetricSummary(BaseModel): archived_at: datetime | None = None description: str | None = None + + unit_plural: str | None = None + """What several units of the metric are called, e.g. `tokens`.""" + + unit_singular: str | None = None + """What one unit of the metric is called, e.g. `token`.""" diff --git a/meteroid/models/metric_usage.py b/meteroid/models/metric_usage.py index 0a6cf29..4f818e1 100644 --- a/meteroid/models/metric_usage.py +++ b/meteroid/models/metric_usage.py @@ -10,6 +10,7 @@ if t.TYPE_CHECKING: from .billable_metric_id import BillableMetricId from .grouped_usage import GroupedUsage + from .usage_cost import UsageCost @dataclasses.dataclass(kw_only=True) @@ -25,3 +26,7 @@ class MetricUsage(BaseModel): metric_name: str total_value: Decimal + + costs: list[UsageCost] | None = None + """Beta: what the metric's usage adds so far to the subscription's next invoice, per currency + or credit unit; empty while it bills nothing, as during a free trial or once cancelled.""" diff --git a/meteroid/models/never_reset_period.py b/meteroid/models/never_reset_period.py index 2672155..3e20d59 100644 --- a/meteroid/models/never_reset_period.py +++ b/meteroid/models/never_reset_period.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -9,3 +10,5 @@ @dataclasses.dataclass(kw_only=True) class NeverResetPeriod(BaseModel): """Never resets — counts all usage since the subscription was activated.""" + + type: t.Literal["NEVER"] = "NEVER" diff --git a/meteroid/models/new_product_ref.py b/meteroid/models/new_product_ref.py index e1da865..aeafcdc 100644 --- a/meteroid/models/new_product_ref.py +++ b/meteroid/models/new_product_ref.py @@ -20,3 +20,5 @@ class NewProductRef(BaseModel): fee_type: ProductFeeTypeEnum name: str + + type: t.Literal["NEW"] = "NEW" diff --git a/meteroid/models/number_config_value.py b/meteroid/models/number_config_value.py index 2b93bfa..1717f3d 100644 --- a/meteroid/models/number_config_value.py +++ b/meteroid/models/number_config_value.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -11,4 +12,6 @@ class NumberConfigValue(BaseModel): """A number config value (decimal, encoded as a string).""" + kind: t.Literal["NUMBER"] = "NUMBER" + value: Decimal diff --git a/meteroid/models/one_time_fee.py b/meteroid/models/one_time_fee.py index 577d875..748de00 100644 --- a/meteroid/models/one_time_fee.py +++ b/meteroid/models/one_time_fee.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -14,3 +15,5 @@ class OneTimeFee(BaseModel): quantity: int rate: Decimal + + type: t.Literal["ONE_TIME"] = "ONE_TIME" diff --git a/meteroid/models/one_time_fee_structure.py b/meteroid/models/one_time_fee_structure.py index ad88228..ebbcade 100644 --- a/meteroid/models/one_time_fee_structure.py +++ b/meteroid/models/one_time_fee_structure.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -9,3 +10,5 @@ @dataclasses.dataclass(kw_only=True) class OneTimeFeeStructure(BaseModel): """The `OneTimeFeeStructure` object.""" + + type: t.Literal["ONE_TIME"] = "ONE_TIME" diff --git a/meteroid/models/one_time_plan_fee.py b/meteroid/models/one_time_plan_fee.py index e8630b0..a64a75b 100644 --- a/meteroid/models/one_time_plan_fee.py +++ b/meteroid/models/one_time_plan_fee.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -13,4 +14,6 @@ class OneTimePlanFee(BaseModel): quantity: int + type: t.Literal["ONE_TIME"] = "ONE_TIME" + unit_price: Decimal diff --git a/meteroid/models/one_time_pricing.py b/meteroid/models/one_time_pricing.py index 094e744..146f507 100644 --- a/meteroid/models/one_time_pricing.py +++ b/meteroid/models/one_time_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -13,4 +14,6 @@ class OneTimePricing(BaseModel): quantity: int + type: t.Literal["ONE_TIME"] = "ONE_TIME" + unit_price: Decimal diff --git a/meteroid/models/online_payment_method_config.py b/meteroid/models/online_payment_method_config.py index f5342fc..7238470 100644 --- a/meteroid/models/online_payment_method_config.py +++ b/meteroid/models/online_payment_method_config.py @@ -14,4 +14,6 @@ class OnlinePaymentMethodConfig(BaseModel): """The `OnlinePaymentMethodConfig` object.""" + type: t.Literal["online"] = "online" + config: OnlineMethodsConfig | None | Unset = UNSET diff --git a/meteroid/models/package_plan_pricing.py b/meteroid/models/package_plan_pricing.py index ba58b50..b5685c3 100644 --- a/meteroid/models/package_plan_pricing.py +++ b/meteroid/models/package_plan_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -14,3 +15,5 @@ class PackagePlanPricing(BaseModel): block_size: int rate: Decimal + + type: t.Literal["PACKAGE"] = "PACKAGE" diff --git a/meteroid/models/package_pricing.py b/meteroid/models/package_pricing.py index cef64c8..9555f04 100644 --- a/meteroid/models/package_pricing.py +++ b/meteroid/models/package_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -14,3 +15,5 @@ class PackagePricing(BaseModel): block_size: int rate: Decimal + + type: t.Literal["PACKAGE"] = "PACKAGE" diff --git a/meteroid/models/patch_budget_request.py b/meteroid/models/patch_budget_request.py new file mode 100644 index 0000000..fbe39da --- /dev/null +++ b/meteroid/models/patch_budget_request.py @@ -0,0 +1,24 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .budget_behavior import BudgetBehavior + + +@dataclasses.dataclass(kw_only=True) +class PatchBudgetRequest(BaseModel): + """The currency or credit unit, and the subscription, are fixed at creation.""" + + expected_version: int + """The `version` you read; a newer stored version is refused with 409.""" + + amount: int | None | Unset = UNSET + + behavior: BudgetBehavior | None | Unset = UNSET + + thresholds: list[int] | None | Unset = UNSET diff --git a/meteroid/models/patch_wallet_request.py b/meteroid/models/patch_wallet_request.py new file mode 100644 index 0000000..0665818 --- /dev/null +++ b/meteroid/models/patch_wallet_request.py @@ -0,0 +1,32 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import UNSET, BaseModel, Unset + +if t.TYPE_CHECKING: + from .auto_top_up_write_request import AutoTopUpWriteRequest + from .usage_at_zero import UsageAtZero + + +@dataclasses.dataclass(kw_only=True) +class PatchWalletRequest(BaseModel): + """Partial update of a wallet's configuration: omit a field to leave it unchanged.""" + + auto_top_up: AutoTopUpWriteRequest | None | Unset = UNSET + + low_balance_thresholds: list[int] | None | Unset = UNSET + """Ascending, distinct, at most 5, non-negative. Omit to leave unchanged; an empty list + clears every threshold.""" + + unit_price: str | None | Unset = UNSET + """Credit unit wallets: the price this customer agreed for one credit, in major units of the + customer's currency, as a decimal string (positive, at most 12 decimal places). Purchases + and automatic top-ups default to it instead of the list price. Omit to leave it unchanged; + `null` returns the customer to the list price. A change applies to purchases quoted after + it. An automatic top-up keeps running at the new price when it is at or below the price + its terms were authorized at, and stops until they are confirmed again when it is above.""" + + usage_at_zero: UsageAtZero | None | Unset = UNSET diff --git a/meteroid/models/per_unit_plan_pricing.py b/meteroid/models/per_unit_plan_pricing.py index 19afa05..4eeae1a 100644 --- a/meteroid/models/per_unit_plan_pricing.py +++ b/meteroid/models/per_unit_plan_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -12,3 +13,5 @@ class PerUnitPlanPricing(BaseModel): """The `PerUnitPlanPricing` object.""" rate: Decimal + + type: t.Literal["PER_UNIT"] = "PER_UNIT" diff --git a/meteroid/models/per_unit_pricing.py b/meteroid/models/per_unit_pricing.py index 836a162..bf03c3f 100644 --- a/meteroid/models/per_unit_pricing.py +++ b/meteroid/models/per_unit_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -12,3 +13,5 @@ class PerUnitPricing(BaseModel): """The `PerUnitPricing` object.""" rate: Decimal + + type: t.Literal["PER_UNIT"] = "PER_UNIT" diff --git a/meteroid/models/percentage_discount.py b/meteroid/models/percentage_discount.py index 15251bc..04b4180 100644 --- a/meteroid/models/percentage_discount.py +++ b/meteroid/models/percentage_discount.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -11,3 +12,5 @@ class PercentageDiscount(BaseModel): """The `PercentageDiscount` object.""" percentage: str + + type: t.Literal["PERCENTAGE"] = "PERCENTAGE" diff --git a/meteroid/models/plan.py b/meteroid/models/plan.py index 9cf2d25..cd59852 100644 --- a/meteroid/models/plan.py +++ b/meteroid/models/plan.py @@ -9,6 +9,7 @@ if t.TYPE_CHECKING: from .available_parameters import AvailableParameters + from .credit_exhausted_rule import CreditExhaustedRule from .currency import Currency from .entitlement import Entitlement from .minimum_commitment import MinimumCommitment @@ -57,6 +58,8 @@ class Plan(BaseModel): billing_cycles: int | None = None + credit_exhausted_rule: CreditExhaustedRule | None = None + description: str | None = None entitlements: list[Entitlement] | None = None diff --git a/meteroid/models/plan_fee_credit_unit.py b/meteroid/models/plan_fee_credit_unit.py new file mode 100644 index 0000000..a400e6f --- /dev/null +++ b/meteroid/models/plan_fee_credit_unit.py @@ -0,0 +1,19 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +from decimal import Decimal + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class PlanFeeCreditUnit(BaseModel): + """The credit unit a plan's usage price is expressed in, with its value in the plan currency.""" + + credit_unit_id: str + """An active credit unit of the tenant (`GET /api/v1/credit-units`).""" + + rate: Decimal + """Plan-currency major units per credit (e.g. "0.01"), positive: the money billed for each + credit the customer's balance in the unit does not cover, before tax.""" diff --git a/meteroid/models/price_input.py b/meteroid/models/price_input.py index 46d8f53..53766ca 100644 --- a/meteroid/models/price_input.py +++ b/meteroid/models/price_input.py @@ -20,3 +20,5 @@ class PriceInput(BaseModel): currency: str pricing: Pricing + + type: t.Literal["NEW"] = "NEW" diff --git a/meteroid/models/product_fee_structure.py b/meteroid/models/product_fee_structure.py index 7a450d8..949ec61 100644 --- a/meteroid/models/product_fee_structure.py +++ b/meteroid/models/product_fee_structure.py @@ -5,6 +5,7 @@ from ..serialization import Discriminator, UnknownVariant from .capacity_fee_structure import CapacityFeeStructure +from .credit_fee_structure import CreditFeeStructure from .extra_recurring_fee_structure import ExtraRecurringFeeStructure from .one_time_fee_structure import OneTimeFeeStructure from .rate_fee_structure import RateFeeStructure @@ -18,6 +19,7 @@ | UsageFeeStructure | ExtraRecurringFeeStructure | OneTimeFeeStructure + | CreditFeeStructure | UnknownVariant, Discriminator( "type", @@ -28,6 +30,7 @@ "USAGE": UsageFeeStructure, "EXTRA_RECURRING": ExtraRecurringFeeStructure, "ONE_TIME": OneTimeFeeStructure, + "CREDIT": CreditFeeStructure, }, ), ] diff --git a/meteroid/models/product_fee_type_enum.py b/meteroid/models/product_fee_type_enum.py index ac12ca4..b88d566 100644 --- a/meteroid/models/product_fee_type_enum.py +++ b/meteroid/models/product_fee_type_enum.py @@ -13,9 +13,10 @@ class ProductFeeTypeEnum(StrEnum): USAGE = "USAGE" EXTRA_RECURRING = "EXTRA_RECURRING" ONE_TIME = "ONE_TIME" + CREDIT = "CREDIT" ProductFeeTypeEnumLiteral: t.TypeAlias = t.Literal[ - "RATE", "SLOT", "CAPACITY", "USAGE", "EXTRA_RECURRING", "ONE_TIME" + "RATE", "SLOT", "CAPACITY", "USAGE", "EXTRA_RECURRING", "ONE_TIME", "CREDIT" ] """The values of :class:`ProductFeeTypeEnum`, which arguments take as plain strings too.""" diff --git a/meteroid/models/products_scope.py b/meteroid/models/products_scope.py index c730820..1ef6cdf 100644 --- a/meteroid/models/products_scope.py +++ b/meteroid/models/products_scope.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -12,3 +13,5 @@ class ProductsScope(BaseModel): components, overrides and ad-hoc extras, so a subscription's billed set is matched uniformly.""" product_ids: list[str] + + type: t.Literal["products"] = "products" diff --git a/meteroid/models/rate_fee.py b/meteroid/models/rate_fee.py index 1aad2a4..987b761 100644 --- a/meteroid/models/rate_fee.py +++ b/meteroid/models/rate_fee.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -12,3 +13,5 @@ class RateFee(BaseModel): """The `RateFee` object.""" rate: Decimal + + type: t.Literal["RATE"] = "RATE" diff --git a/meteroid/models/rate_fee_structure.py b/meteroid/models/rate_fee_structure.py index 7635d51..4804867 100644 --- a/meteroid/models/rate_fee_structure.py +++ b/meteroid/models/rate_fee_structure.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -9,3 +10,5 @@ @dataclasses.dataclass(kw_only=True) class RateFeeStructure(BaseModel): """The `RateFeeStructure` object.""" + + type: t.Literal["RATE"] = "RATE" diff --git a/meteroid/models/rate_plan_fee.py b/meteroid/models/rate_plan_fee.py index 48ae02a..89ca9a1 100644 --- a/meteroid/models/rate_plan_fee.py +++ b/meteroid/models/rate_plan_fee.py @@ -15,3 +15,5 @@ class RatePlanFee(BaseModel): """Recurring rate fee (e.g., monthly subscription)""" rates: list[TermRate] + + type: t.Literal["RATE"] = "RATE" diff --git a/meteroid/models/rate_pricing.py b/meteroid/models/rate_pricing.py index 27606d6..059eeed 100644 --- a/meteroid/models/rate_pricing.py +++ b/meteroid/models/rate_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -12,3 +13,5 @@ class RatePricing(BaseModel): """The `RatePricing` object.""" rate: Decimal + + type: t.Literal["RATE"] = "RATE" diff --git a/meteroid/models/recurring_fee.py b/meteroid/models/recurring_fee.py index c460739..5ee6c5c 100644 --- a/meteroid/models/recurring_fee.py +++ b/meteroid/models/recurring_fee.py @@ -20,3 +20,5 @@ class RecurringFee(BaseModel): quantity: int rate: Decimal + + type: t.Literal["RECURRING"] = "RECURRING" diff --git a/meteroid/models/refund_event_data.py b/meteroid/models/refund_event_data.py index 851f1ed..7909117 100644 --- a/meteroid/models/refund_event_data.py +++ b/meteroid/models/refund_event_data.py @@ -38,6 +38,13 @@ class RefundEventData(BaseModel): transaction_id: PaymentTransactionId + amount_disputed: int | None = None + """The part of `amount_reversed` lost to disputes or chargebacks: the invoice stays paid.""" + + amount_reversed_excess: int | None = None + """What the provider withdrew above what the payment still held (refunded before); never + applied to the invoice or credit. Contest it with the provider.""" + credit_note_id: CreditNoteId | None = None decline_kind: DeclineKind | None = None diff --git a/meteroid/models/replace_plan_request.py b/meteroid/models/replace_plan_request.py index 0ef8020..5d7d795 100644 --- a/meteroid/models/replace_plan_request.py +++ b/meteroid/models/replace_plan_request.py @@ -8,6 +8,7 @@ if t.TYPE_CHECKING: from .billing_config import BillingConfig + from .credit_exhausted_rule import CreditExhaustedRule from .entitlement_spec_request import EntitlementSpecRequest from .minimum_commitment_input import MinimumCommitmentInput from .plan_add_on_input import PlanAddOnInput @@ -30,6 +31,8 @@ class ReplacePlanRequest(BaseModel): billing: BillingConfig | None | Unset = UNSET + credit_exhausted_rule: CreditExhaustedRule | None | Unset = UNSET + description: str | None | Unset = UNSET entitlements: list[EntitlementSpecRequest] | None = None diff --git a/meteroid/models/sliding_window_reset_period.py b/meteroid/models/sliding_window_reset_period.py index 071bef4..3002a61 100644 --- a/meteroid/models/sliding_window_reset_period.py +++ b/meteroid/models/sliding_window_reset_period.py @@ -12,8 +12,12 @@ @dataclasses.dataclass(kw_only=True) class SlidingWindowResetPeriod(BaseModel): - """Always ends at now — e.g. 30 days means the last 30 days, old usage drops off automatically.""" + """Always ends at now — e.g. 30 days means the last 30 days, old usage drops off automatically. + A sliding window has no reset moment (`reset_at` is absent) and sends no threshold or + limit-reached webhooks; it is enforced by the entitlement check.""" interval: int + type: t.Literal["SLIDING_WINDOW"] = "SLIDING_WINDOW" + unit: CalendarUnit diff --git a/meteroid/models/slot_fee.py b/meteroid/models/slot_fee.py index 8ff53b0..436ab80 100644 --- a/meteroid/models/slot_fee.py +++ b/meteroid/models/slot_fee.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import BaseModel @@ -13,6 +14,8 @@ class SlotFee(BaseModel): initial_slots: int + type: t.Literal["SLOT"] = "SLOT" + unit: str unit_rate: Decimal diff --git a/meteroid/models/slot_fee_structure.py b/meteroid/models/slot_fee_structure.py index 41c123e..c8ffc26 100644 --- a/meteroid/models/slot_fee_structure.py +++ b/meteroid/models/slot_fee_structure.py @@ -19,4 +19,6 @@ class SlotFeeStructure(BaseModel): slot_unit_name: str + type: t.Literal["SLOT"] = "SLOT" + upgrade_policy: SlotUpgradePolicyEnum diff --git a/meteroid/models/slot_plan_fee.py b/meteroid/models/slot_plan_fee.py index 9f00197..52eb580 100644 --- a/meteroid/models/slot_plan_fee.py +++ b/meteroid/models/slot_plan_fee.py @@ -18,6 +18,8 @@ class SlotPlanFee(BaseModel): slot_unit_name: str + type: t.Literal["SLOT"] = "SLOT" + minimum_count: int | None | Unset = UNSET quota: int | None | Unset = UNSET diff --git a/meteroid/models/slot_pricing.py b/meteroid/models/slot_pricing.py index 8aaf4e9..97e210a 100644 --- a/meteroid/models/slot_pricing.py +++ b/meteroid/models/slot_pricing.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from decimal import Decimal from ..serialization import UNSET, BaseModel, Unset @@ -11,6 +12,8 @@ class SlotPricing(BaseModel): """The `SlotPricing` object.""" + type: t.Literal["SLOT"] = "SLOT" + unit_rate: Decimal max_slots: int | None | Unset = UNSET diff --git a/meteroid/models/subscription.py b/meteroid/models/subscription.py index 5bdd130..2fb5323 100644 --- a/meteroid/models/subscription.py +++ b/meteroid/models/subscription.py @@ -16,6 +16,7 @@ from .plan_version_id import PlanVersionId from .subscription_id import SubscriptionId from .subscription_status_enum import SubscriptionStatusEnum + from .usage_filter import UsageFilter @dataclasses.dataclass(kw_only=True) @@ -97,3 +98,9 @@ class Subscription(BaseModel): trial_duration: int | None = None """Trial duration in days""" + + usage_filter: UsageFilter | None = None + + usage_filter_effective_from: datetime | None = None + """When the current usage filter, or its removal, started to apply: the start of the usage + neither this subscription nor the customer's others had invoiced when it was set.""" diff --git a/meteroid/models/subscription_add_on_parameterization.py b/meteroid/models/subscription_add_on_parameterization.py index c5e8782..1a29ee6 100644 --- a/meteroid/models/subscription_add_on_parameterization.py +++ b/meteroid/models/subscription_add_on_parameterization.py @@ -14,6 +14,8 @@ class SubscriptionAddOnParameterization(BaseModel): """The `SubscriptionAddOnParameterization` object.""" + type: t.Literal["PARAMETERIZATION"] = "PARAMETERIZATION" + billing_period: BillingPeriodEnum | None | Unset = UNSET committed_capacity: int | None | Unset = UNSET diff --git a/meteroid/models/subscription_add_on_price_override.py b/meteroid/models/subscription_add_on_price_override.py index cf6f191..9320635 100644 --- a/meteroid/models/subscription_add_on_price_override.py +++ b/meteroid/models/subscription_add_on_price_override.py @@ -16,4 +16,6 @@ class SubscriptionAddOnPriceOverride(BaseModel): price_entry: PriceEntry + type: t.Literal["PRICE_OVERRIDE"] = "PRICE_OVERRIDE" + name: str | None | Unset = UNSET diff --git a/meteroid/models/subscription_create_request.py b/meteroid/models/subscription_create_request.py index e4616fa..9866cee 100644 --- a/meteroid/models/subscription_create_request.py +++ b/meteroid/models/subscription_create_request.py @@ -15,6 +15,7 @@ from .subscription_activation_condition_enum import ( SubscriptionActivationConditionEnum, ) + from .usage_filter import UsageFilter @dataclasses.dataclass(kw_only=True) @@ -42,6 +43,10 @@ class SubscriptionCreateRequest(BaseModel): charge_automatically: bool | None = None coupon_codes: list[str] | None = None + """Codes of the coupons to apply. A code matches whatever the case of its ASCII letters; when + several coupons differ from it only in that case, the one with exactly that code applies. A + code that names no live coupon, or several differing only in case, refuses the request with + 400 listing the codes (unknown codes used to be ignored silently).""" custom_properties: t.Any = None """User-defined custom property values, keyed by definition `key`. Validated against the @@ -66,4 +71,8 @@ class SubscriptionCreateRequest(BaseModel): trial_days: int | None = None + usage_filter: UsageFilter | None = None + """Bill only the customer's events whose property has this value. Omit to bill the events + no usage filter of the customer's other subscriptions matches.""" + version: int | None = None diff --git a/meteroid/models/subscription_credit.py b/meteroid/models/subscription_credit.py new file mode 100644 index 0000000..d2c84c8 --- /dev/null +++ b/meteroid/models/subscription_credit.py @@ -0,0 +1,16 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class SubscriptionCredit(BaseModel): + """Credit on account that only one subscription can use.""" + + credit_on_account: int + """In minor units of the wallet.""" + + subscription_id: str diff --git a/meteroid/models/subscription_details.py b/meteroid/models/subscription_details.py index 6876402..da7e45a 100644 --- a/meteroid/models/subscription_details.py +++ b/meteroid/models/subscription_details.py @@ -21,6 +21,7 @@ from .subscription_component import SubscriptionComponent from .subscription_id import SubscriptionId from .subscription_status_enum import SubscriptionStatusEnum + from .usage_filter import UsageFilter @dataclasses.dataclass(kw_only=True) @@ -110,3 +111,9 @@ class SubscriptionDetails(BaseModel): trial_duration: int | None = None """Trial duration in days""" + + usage_filter: UsageFilter | None = None + + usage_filter_effective_from: datetime | None = None + """When the current usage filter, or its removal, started to apply: the start of the usage + neither this subscription nor the customer's others had invoiced when it was set.""" diff --git a/meteroid/models/subscription_fee.py b/meteroid/models/subscription_fee.py index f9cac43..67c7d43 100644 --- a/meteroid/models/subscription_fee.py +++ b/meteroid/models/subscription_fee.py @@ -5,6 +5,7 @@ from ..serialization import Discriminator, UnknownVariant from .capacity_fee import CapacityFee +from .credit_fee import CreditFee from .one_time_fee import OneTimeFee from .rate_fee import RateFee from .recurring_fee import RecurringFee @@ -18,6 +19,7 @@ | CapacityFee | SlotFee | UsageFee + | CreditFee | UnknownVariant, Discriminator( "type", @@ -28,6 +30,7 @@ "CAPACITY": CapacityFee, "SLOT": SlotFee, "USAGE": UsageFee, + "CREDIT": CreditFee, }, ), ] diff --git a/meteroid/models/subscription_update_request.py b/meteroid/models/subscription_update_request.py index bc1b6a4..b620b5b 100644 --- a/meteroid/models/subscription_update_request.py +++ b/meteroid/models/subscription_update_request.py @@ -8,6 +8,7 @@ if t.TYPE_CHECKING: from .payment_methods_config import PaymentMethodsConfig + from .usage_filter import UsageFilter @dataclasses.dataclass(kw_only=True) @@ -34,3 +35,5 @@ class SubscriptionUpdateRequest(BaseModel): purchase_order: str | None | Unset = UNSET """Purchase order number""" + + usage_filter: UsageFilter | None | Unset = UNSET diff --git a/meteroid/models/text_config_value.py b/meteroid/models/text_config_value.py index 2b28cf8..c663043 100644 --- a/meteroid/models/text_config_value.py +++ b/meteroid/models/text_config_value.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses +import typing as t from ..serialization import BaseModel @@ -10,4 +11,6 @@ class TextConfigValue(BaseModel): """A text config value.""" + kind: t.Literal["TEXT"] = "TEXT" + value: str diff --git a/meteroid/models/tiered_plan_pricing.py b/meteroid/models/tiered_plan_pricing.py index 359efef..892d9a6 100644 --- a/meteroid/models/tiered_plan_pricing.py +++ b/meteroid/models/tiered_plan_pricing.py @@ -16,4 +16,6 @@ class TieredPlanPricing(BaseModel): tiers: list[TierRow] + type: t.Literal["TIERED"] = "TIERED" + block_size: int | None | Unset = UNSET diff --git a/meteroid/models/tiered_pricing.py b/meteroid/models/tiered_pricing.py index 38e3031..66e610f 100644 --- a/meteroid/models/tiered_pricing.py +++ b/meteroid/models/tiered_pricing.py @@ -16,4 +16,6 @@ class TieredPricing(BaseModel): tiers: list[TierRow] + type: t.Literal["TIERED"] = "TIERED" + block_size: int | None | Unset = UNSET diff --git a/meteroid/models/top_up_collection.py b/meteroid/models/top_up_collection.py new file mode 100644 index 0000000..6aeab16 --- /dev/null +++ b/meteroid/models/top_up_collection.py @@ -0,0 +1,16 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class TopUpCollection(StrEnum): + """How a top-up is paid: CHARGE a saved payment method now, or SEND_INVOICE and grant the credit + once the invoice is paid.""" + + CHARGE = "CHARGE" + SEND_INVOICE = "SEND_INVOICE" + + +TopUpCollectionLiteral: t.TypeAlias = t.Literal["CHARGE", "SEND_INVOICE"] +"""The values of :class:`TopUpCollection`, which arguments take as plain strings too.""" diff --git a/meteroid/models/top_up_response.py b/meteroid/models/top_up_response.py new file mode 100644 index 0000000..a0beeb7 --- /dev/null +++ b/meteroid/models/top_up_response.py @@ -0,0 +1,41 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .currency import Currency + from .top_up_status import TopUpStatus + + +@dataclasses.dataclass(kw_only=True) +class TopUpResponse(BaseModel): + """Where a top-up stands. Its credit is granted once it is paid; `wallet.funded` reports it.""" + + amount: int + """Credit bought, in minor units of `currency`, or of `credit_unit` when set.""" + + currency: Currency + """The money the top-up is charged in.""" + + status: TopUpStatus + """SUCCEEDED once paid; otherwise what the payment is waiting for.""" + + charged_amount: int | None = None + """Charged to the payment method, tax included, in minor units of `currency`; absent until a + charge is made.""" + + checkout_url: str | None = None + """Hosted page where the customer completes the payment; present while it awaits them.""" + + credit_unit: str | None = None + """Set when credits of this credit unit were bought: `amount` is in its minor units.""" + + invoice_id: str | None = None + """The invoice documenting the purchase.""" + + payment_transaction_id: str | None = None + """The charge made on the payment method; absent until a charge is made.""" diff --git a/meteroid/models/top_up_status.py b/meteroid/models/top_up_status.py new file mode 100644 index 0000000..b627ae7 --- /dev/null +++ b/meteroid/models/top_up_status.py @@ -0,0 +1,19 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class TopUpStatus(StrEnum): + """The values of `TopUpStatus`; others are kept as received.""" + + SUCCEEDED = "SUCCEEDED" + PENDING = "PENDING" + REQUIRES_CUSTOMER_ACTION = "REQUIRES_CUSTOMER_ACTION" + AWAITING_PAYMENT = "AWAITING_PAYMENT" + + +TopUpStatusLiteral: t.TypeAlias = t.Literal[ + "SUCCEEDED", "PENDING", "REQUIRES_CUSTOMER_ACTION", "AWAITING_PAYMENT" +] +"""The values of :class:`TopUpStatus`, which arguments take as plain strings too.""" diff --git a/meteroid/models/transaction.py b/meteroid/models/transaction.py index ebad176..10673cc 100644 --- a/meteroid/models/transaction.py +++ b/meteroid/models/transaction.py @@ -40,6 +40,14 @@ class Transaction(BaseModel): status: PaymentStatusEnum + amount_disputed: int | None = None + """On a PAYMENT: the part of `amount_reversed` lost to disputes or chargebacks. The invoice + stays paid; only the rest of `amount_reversed` (a failed collection) reopens it.""" + + amount_reversed_excess: int | None = None + """On a PAYMENT: what the provider withdrew above what the payment still held (refunded + before). Never applied to the invoice or credit: contest it with the provider.""" + credit_note_id: CreditNoteId | None = None error: str | None = None diff --git a/meteroid/models/unattributed_metric_usage.py b/meteroid/models/unattributed_metric_usage.py new file mode 100644 index 0000000..74a6fa3 --- /dev/null +++ b/meteroid/models/unattributed_metric_usage.py @@ -0,0 +1,34 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime +from decimal import Decimal + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .billable_metric_id import BillableMetricId + + +@dataclasses.dataclass(kw_only=True) +class UnattributedMetricUsage(BaseModel): + """A metric's usage over `[from, until)` that no subscription bills: it matches none of the + usage filters of the subscriptions pricing the metric, and none of them bills without one.""" + + _JSON_KEYS: t.ClassVar[t.Mapping[str, str]] = { + "from_": "from", + } + + from_: datetime + + metric_code: str + + metric_id: BillableMetricId + + metric_name: str + + total_value: Decimal + + until: datetime diff --git a/meteroid/models/unattributed_usage_response.py b/meteroid/models/unattributed_usage_response.py new file mode 100644 index 0000000..856e32a --- /dev/null +++ b/meteroid/models/unattributed_usage_response.py @@ -0,0 +1,22 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import date + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .unattributed_metric_usage import UnattributedMetricUsage + + +@dataclasses.dataclass(kw_only=True) +class UnattributedUsageResponse(BaseModel): + """The `UnattributedUsageResponse` object.""" + + period_end: date + + period_start: date + + usage: list[UnattributedMetricUsage] diff --git a/meteroid/models/unit_conversion.py b/meteroid/models/unit_conversion.py index e066917..882fb1b 100644 --- a/meteroid/models/unit_conversion.py +++ b/meteroid/models/unit_conversion.py @@ -12,8 +12,12 @@ @dataclasses.dataclass(kw_only=True) class UnitConversion(BaseModel): - """The `UnitConversion` object.""" + """Converts the aggregated usage into the unit prices are set in, e.g. tokens priced per 1M. + The usage is divided by `factor`, then rounded. Rounding applies to each priced group's + total for the billing period: each group of a segmented or grouped metric, and each matrix + cell, is rounded on its own, never each event.""" factor: int + """Raw units per priced unit, e.g. `1000000` to price per 1M.""" rounding: UnitConversionRoundingEnum diff --git a/meteroid/models/update_metric_request.py b/meteroid/models/update_metric_request.py index 9ada95f..f6dcaf6 100644 --- a/meteroid/models/update_metric_request.py +++ b/meteroid/models/update_metric_request.py @@ -26,3 +26,11 @@ class UpdateMetricRequest(BaseModel): segmentation_matrix: MetricSegmentationMatrix | None | Unset = UNSET unit_conversion: UnitConversion | None | Unset = UNSET + + unit_plural: str | None | Unset = UNSET + """What several units of the metric are called, e.g. `tokens`. See `unit_singular`.""" + + unit_singular: str | None | Unset = UNSET + """What one unit of the metric is called, e.g. `token`. Send `unit_singular` and + `unit_plural` together; omit both to leave the label unchanged, or send both as empty + strings to remove it. Trimmed; at most 32 characters.""" diff --git a/meteroid/models/update_webhook_endpoint_request.py b/meteroid/models/update_webhook_endpoint_request.py index 5224aff..392a45f 100644 --- a/meteroid/models/update_webhook_endpoint_request.py +++ b/meteroid/models/update_webhook_endpoint_request.py @@ -21,7 +21,8 @@ class UpdateWebhookEndpointRequest(BaseModel): """Re-enabling an endpoint also resets its consecutive failure count.""" event_types: list[str] | None | Unset = UNSET - """Replaces the subscription list. An empty array subscribes to every event type.""" + """Replaces the subscription list. An empty array subscribes to every event type except Beta + ones, which are sent only to endpoints that list them.""" headers: list[WebhookHeaderInput] | None | Unset = UNSET """Replaces the custom header list. An empty array removes every header.""" diff --git a/meteroid/models/usage_at_zero.py b/meteroid/models/usage_at_zero.py new file mode 100644 index 0000000..1f0c768 --- /dev/null +++ b/meteroid/models/usage_at_zero.py @@ -0,0 +1,19 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class UsageAtZero(StrEnum): + """What a customer's usage does once the credit it can use runs out: what the subscription's plan + says (`FOLLOW_PLAN`), or always or never refused for this customer.""" + + FOLLOW_PLAN = "FOLLOW_PLAN" + ALWAYS_PAUSE = "ALWAYS_PAUSE" + NEVER_PAUSE = "NEVER_PAUSE" + + +UsageAtZeroLiteral: t.TypeAlias = t.Literal[ + "FOLLOW_PLAN", "ALWAYS_PAUSE", "NEVER_PAUSE" +] +"""The values of :class:`UsageAtZero`, which arguments take as plain strings too.""" diff --git a/meteroid/models/usage_cost.py b/meteroid/models/usage_cost.py new file mode 100644 index 0000000..878fd56 --- /dev/null +++ b/meteroid/models/usage_cost.py @@ -0,0 +1,24 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from decimal import Decimal + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .usage_cost_unit import UsageCostUnit + + +@dataclasses.dataclass(kw_only=True) +class UsageCost(BaseModel): + """Usage priced as its invoice line prices it, before discounts and credits: tax-inclusive when + the subscription's prices are.""" + + amount: Decimal + + currency: str + """A currency code, or a credit unit code when `unit` is `CREDIT_UNIT`.""" + + unit: UsageCostUnit diff --git a/meteroid/models/usage_cost_list_response.py b/meteroid/models/usage_cost_list_response.py new file mode 100644 index 0000000..6be2cbc --- /dev/null +++ b/meteroid/models/usage_cost_list_response.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .usage_cost_row import UsageCostRow + + +@dataclasses.dataclass(kw_only=True) +class UsageCostListResponse(BaseModel): + """The `UsageCostListResponse` object.""" + + data: list[UsageCostRow] diff --git a/meteroid/models/usage_cost_row.py b/meteroid/models/usage_cost_row.py new file mode 100644 index 0000000..1bfda57 --- /dev/null +++ b/meteroid/models/usage_cost_row.py @@ -0,0 +1,32 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime +from decimal import Decimal + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .usage_cost import UsageCost + + +@dataclasses.dataclass(kw_only=True) +class UsageCostRow(BaseModel): + """The `UsageCostRow` object.""" + + bucket_start: datetime + + exact: bool + """False when the price depends on the whole period's usage (tiered, volume, package, + capacity): the cost is then what the bucket adds to the period's price so far. A period's + rows still add up to its invoiced amount.""" + + quantity: Decimal + + cost: UsageCost | None = None + + group: str | None = None + """The value of `group_by` for the row. Absent when the request is not grouped, or the usage + has no value for the property.""" diff --git a/meteroid/models/usage_cost_unit.py b/meteroid/models/usage_cost_unit.py new file mode 100644 index 0000000..2c73ab0 --- /dev/null +++ b/meteroid/models/usage_cost_unit.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class UsageCostUnit(StrEnum): + """What a cost is counted in.""" + + CURRENCY = "CURRENCY" + CREDIT_UNIT = "CREDIT_UNIT" + + +UsageCostUnitLiteral: t.TypeAlias = t.Literal["CURRENCY", "CREDIT_UNIT"] +"""The values of :class:`UsageCostUnit`, which arguments take as plain strings too.""" diff --git a/meteroid/models/usage_event.py b/meteroid/models/usage_event.py new file mode 100644 index 0000000..8b9ca2e --- /dev/null +++ b/meteroid/models/usage_event.py @@ -0,0 +1,29 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime +from decimal import Decimal + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .usage_cost import UsageCost + + +@dataclasses.dataclass(kw_only=True) +class UsageEvent(BaseModel): + """The `UsageEvent` object.""" + + id: str + + properties: dict[str, str] + + timestamp: datetime + + cost: UsageCost | None = None + + quantity: Decimal | None = None + """Billed units the event adds. Absent when the metric's aggregation cannot be split per + event, or when the event does not count towards the metric.""" diff --git a/meteroid/models/usage_event_list_response.py b/meteroid/models/usage_event_list_response.py new file mode 100644 index 0000000..fc25c91 --- /dev/null +++ b/meteroid/models/usage_event_list_response.py @@ -0,0 +1,20 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .usage_event import UsageEvent + + +@dataclasses.dataclass(kw_only=True) +class UsageEventListResponse(BaseModel): + """The `UsageEventListResponse` object.""" + + data: list[UsageEvent] + + next_cursor: str | None = None + """Absent on the last page.""" diff --git a/meteroid/models/usage_fee.py b/meteroid/models/usage_fee.py index 632755c..41024da 100644 --- a/meteroid/models/usage_fee.py +++ b/meteroid/models/usage_fee.py @@ -8,6 +8,7 @@ if t.TYPE_CHECKING: from .billable_metric_id import BillableMetricId + from .fee_credit_unit import FeeCreditUnit from .usage_pricing_model import UsagePricingModel @@ -18,3 +19,7 @@ class UsageFee(BaseModel): metric_id: BillableMetricId model: UsagePricingModel + + type: t.Literal["USAGE"] = "USAGE" + + credit_unit: FeeCreditUnit | None = None diff --git a/meteroid/models/usage_fee_structure.py b/meteroid/models/usage_fee_structure.py index 32c1afc..8cfa650 100644 --- a/meteroid/models/usage_fee_structure.py +++ b/meteroid/models/usage_fee_structure.py @@ -18,3 +18,5 @@ class UsageFeeStructure(BaseModel): metric_id: BillableMetricId model: UsageModelEnum + + type: t.Literal["USAGE"] = "USAGE" diff --git a/meteroid/models/usage_filter.py b/meteroid/models/usage_filter.py new file mode 100644 index 0000000..f4cbd47 --- /dev/null +++ b/meteroid/models/usage_filter.py @@ -0,0 +1,23 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class UsageFilter(BaseModel): + """Restricts the customer's usage events a subscription bills to those whose `property` equals + `value` (for example `api_key` = `key-A`). Among the customer's subscriptions pricing the same + metric, one with a usage filter bills the events matching it (an event two filters match goes + to the older subscription); one without bills the events matching no other subscription's + filter. Two subscriptions without a usage filter both bill every such event. Events matching + no filter while every subscription pricing their metric has one are billed by none and + reported as unattributed usage.""" + + property: str + """Event property name, e.g. `api_key`.""" + + value: str + """Value the property must have.""" diff --git a/meteroid/models/usage_limit_id.py b/meteroid/models/usage_limit_id.py new file mode 100644 index 0000000..d756c8a --- /dev/null +++ b/meteroid/models/usage_limit_id.py @@ -0,0 +1,4 @@ +# this file is @generated +import typing as t + +UsageLimitId: t.TypeAlias = str diff --git a/meteroid/models/usage_plan_fee.py b/meteroid/models/usage_plan_fee.py index 7a62fa6..0596765 100644 --- a/meteroid/models/usage_plan_fee.py +++ b/meteroid/models/usage_plan_fee.py @@ -4,11 +4,12 @@ import dataclasses import typing as t -from ..serialization import BaseModel +from ..serialization import UNSET, BaseModel, Unset if t.TYPE_CHECKING: from .billable_metric_id import BillableMetricId from .billing_period_enum import BillingPeriodEnum + from .plan_fee_credit_unit import PlanFeeCreditUnit from .plan_usage_pricing_model import PlanUsagePricingModel @@ -21,3 +22,7 @@ class UsagePlanFee(BaseModel): metric_id: BillableMetricId pricing: PlanUsagePricingModel + + type: t.Literal["USAGE"] = "USAGE" + + credit_unit: PlanFeeCreditUnit | None | Unset = UNSET diff --git a/meteroid/models/usage_pricing.py b/meteroid/models/usage_pricing.py index b52261a..5fb04d6 100644 --- a/meteroid/models/usage_pricing.py +++ b/meteroid/models/usage_pricing.py @@ -15,3 +15,5 @@ class UsagePricing(BaseModel): """The `UsagePricing` object.""" model: UsagePricingModel + + type: t.Literal["USAGE"] = "USAGE" diff --git a/meteroid/models/usage_response.py b/meteroid/models/usage_response.py index 319ab70..ad7be9a 100644 --- a/meteroid/models/usage_response.py +++ b/meteroid/models/usage_response.py @@ -3,12 +3,13 @@ import dataclasses import typing as t -from datetime import date +from datetime import date, datetime from ..serialization import BaseModel if t.TYPE_CHECKING: from .metric_usage import MetricUsage + from .usage_cost import UsageCost @dataclasses.dataclass(kw_only=True) @@ -20,3 +21,11 @@ class UsageResponse(BaseModel): period_start: date usage: list[MetricUsage] + + computed_at: datetime | None = None + """Beta: when `totals` and the metrics' `costs` were computed. These are present only when a + subscription's current billing period is read (no `start_date` nor `end_date`) and its + usage could be priced.""" + + totals: list[UsageCost] | None = None + """Beta: the sum of the metrics' `costs`, per currency or credit unit.""" diff --git a/meteroid/models/volume_plan_pricing.py b/meteroid/models/volume_plan_pricing.py index b272ced..8b0ced1 100644 --- a/meteroid/models/volume_plan_pricing.py +++ b/meteroid/models/volume_plan_pricing.py @@ -16,4 +16,6 @@ class VolumePlanPricing(BaseModel): tiers: list[TierRow] + type: t.Literal["VOLUME"] = "VOLUME" + block_size: int | None | Unset = UNSET diff --git a/meteroid/models/volume_pricing.py b/meteroid/models/volume_pricing.py index 0c0fd2f..8d035d4 100644 --- a/meteroid/models/volume_pricing.py +++ b/meteroid/models/volume_pricing.py @@ -16,4 +16,6 @@ class VolumePricing(BaseModel): tiers: list[TierRow] + type: t.Literal["VOLUME"] = "VOLUME" + block_size: int | None | Unset = UNSET diff --git a/meteroid/models/wallet.py b/meteroid/models/wallet.py new file mode 100644 index 0000000..2d3c0b0 --- /dev/null +++ b/meteroid/models/wallet.py @@ -0,0 +1,94 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .auto_top_up import AutoTopUp + from .currency import Currency + from .subscription_credit import SubscriptionCredit + from .usage_at_zero import UsageAtZero + + +@dataclasses.dataclass(kw_only=True) +class Wallet(BaseModel): + """A customer's credit in one currency or credit unit. Amounts are minor units of it. Read with + `subscription_id`, every amount counts only the credit that subscription can use.""" + + credit_on_account: int + """The ledger total: credit granted and effective, not yet applied to an invoice or expired, + whatever it applies to. Usage not invoiced yet is not deducted (see `balance`).""" + + credit_on_account_usage_only: int + """The part of `credit_on_account` that pays only usage charges.""" + + low_balance_thresholds: list[int] + """Ascending, distinct, at most 5. `wallet.balance_low` fires once when `balance` falls below + one; a drop below several at once fires once, for the lowest. All credit counts, including + credit that belongs to a subscription or is limited to usage or products, which + `wallet.depleted` leaves out: a wallet can be depleted without an alert firing.""" + + pauses_at_zero: bool + """Usage stops as soon as the credit is used up, for at least one active subscription: by + `usage_at_zero`, or by the plan of a subscription charging usage in this wallet.""" + + subscription_credit: list[SubscriptionCredit] + """The part of `credit_on_account` that belongs to a subscription, which only that + subscription's charges can use, per subscription. The rest pays any subscription.""" + + upcoming_credit: int + """Credit granted that takes effect later.""" + + usage_at_zero: UsageAtZero + """What this customer's usage does once the credit a subscription can use for it is used up + (its own credit, credit that belongs to no subscription, and credit limited to the product: + the `balance` of `GET /wallets/check`). `FOLLOW_PLAN` applies the "when credit runs out" + rule of each subscription's plan.""" + + as_of: datetime | None = None + """When `balance` and `unbilled_usage` were computed. They follow usage within a few seconds + while the balance is near zero or a threshold, and within a minute otherwise.""" + + auto_top_up: AutoTopUp | None = None + + balance: int | None = None + """What the customer has left: `credit_on_account` minus the part of `unbilled_usage` that + credit will pay. Never negative: usage beyond the credit stays in `unbilled_usage`, to be + invoiced. Low-balance alerts use it; automatic top-ups watch only its part that pays any + charge (credit that belongs to no subscription and is not limited to usage or products). + Absent, with `unbilled_usage` and `as_of`, until the customer's usage has been priced once. + With `subscription_id`: the credit that subscription can spend once every subscription's + unbilled usage is paid, minus its own unbilled usage no credit pays (negative then), as + `GET /wallets/check` reads it. Credit restricted to products it is not charged for is + left out. Credit that belongs to no subscription counts in the balance of every + subscription that can use it (see `shared_balance`): never add subscriptions' balances up.""" + + credit_unit: str | None = None + """The credit unit (its code) the wallet holds; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: Currency | None = None + + shared_balance: int | None = None + """With `subscription_id` only: the part of `balance` that belongs to no subscription, which + the customer's other subscriptions can spend too.""" + + unbilled_usage: int | None = None + """Usage recorded and priced but not invoiced yet, tax included, in minor units: what the + next invoices will charge for it, paid from credit as far as the credit goes. With + `subscription_id`, that subscription's usage only; other subscriptions' usage paid from + shared credit still lowers `balance`.""" + + unit_price: str | None = None + """Credit unit wallets: the price this customer agreed for one credit, in major units of + `unit_price_currency`, as a decimal string (e.g. "0.0008"). Purchases and automatic + top-ups in that currency charge it instead of the list price, unless a purchase sets its + own `unit_price`. Absent: the list price applies.""" + + unit_price_currency: Currency | None = None diff --git a/meteroid/models/wallet_balance_low_event.py b/meteroid/models/wallet_balance_low_event.py new file mode 100644 index 0000000..ed45ce2 --- /dev/null +++ b/meteroid/models/wallet_balance_low_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .event_id import EventId + from .event_type import EventType + from .wallet_balance_low_event_data import WalletBalanceLowEventData + + +@dataclasses.dataclass(kw_only=True) +class WalletBalanceLowEvent(BaseModel): + """The `WalletBalanceLowEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("wallet_balance_low_event_data",) + + wallet_balance_low_event_data: WalletBalanceLowEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/wallet_balance_low_event_data.py b/meteroid/models/wallet_balance_low_event_data.py new file mode 100644 index 0000000..f0bf025 --- /dev/null +++ b/meteroid/models/wallet_balance_low_event_data.py @@ -0,0 +1,41 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .customer_id import CustomerId + + +@dataclasses.dataclass(kw_only=True) +class WalletBalanceLowEventData(BaseModel): + """A wallet's balance fell below one of its low-balance thresholds.""" + + balance: int + """The balance when it fell below `threshold`, in minor units: the wallet's `balance`, all + its credit on account net of the unbilled usage it pays, including credit that belongs to + a subscription or is limited to usage or products. Sent once per crossing, for the lowest + threshold crossed; it re-arms once the balance is back at the threshold. Because that + credit counts here and not in `wallet.depleted`, a wallet can be depleted without this + event firing.""" + + customer_id: CustomerId + + threshold: int + """The low-balance threshold crossed, in the same minor units.""" + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" + + shared_balance: int | None = None + """The part of `balance` that pays any charge of any subscription: what automatic top-ups + refill and `wallet.depleted` watches. Absent on events recorded before it was reported.""" diff --git a/meteroid/models/wallet_check.py b/meteroid/models/wallet_check.py new file mode 100644 index 0000000..47c1357 --- /dev/null +++ b/meteroid/models/wallet_check.py @@ -0,0 +1,64 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .currency import Currency + + +@dataclasses.dataclass(kw_only=True) +class WalletCheck(BaseModel): + """Whether a customer may keep consuming what their prepaid credit pays for.""" + + as_of: datetime + """When `balance` was computed.""" + + balance: int + """The balance: credit on account net of the usage not invoiced yet that it will pay, in + minor units of `currency` or `credit_unit`; never negative for the customer as a whole, + as `balance` of `GET /wallets`. With `product_id`, only the credit that can pay for that + product, net of its usage no credit covers. With `subscription_id`, only the credit that + subscription can spend (credit restricted to products it is not charged for left out), + net of its usage no credit covers; credit that belongs to no subscription counts in every + subscription's balance (see `shared_balance`).""" + + exact: bool + """Always false: `balance` may lag usage by a few seconds (see `as_of`), and the answer is + cached for one second.""" + + exhausted: bool + """True once `balance` is at or below zero, or, with `subscription_id`, once no credit is + left for some of its usage. Independent of `has_access`: a wallet that allows overage + still reports this, so the caller can warn without refusing.""" + + has_access: bool + """False only once `balance` is at or below zero and `blocks_when_exhausted` is true. + Customers without a wallet in `currency` do not prepay, and always have access. With + `subscription_id`: false when `blocks_when_exhausted` is true and no credit that + subscription can spend is left for some of its usage (of `product_id` when given), as the + access gate refuses it; a subscription charged no usage in the wallet keeps access.""" + + lag_ms: int + """Age of `balance`, in milliseconds.""" + + pauses_at_zero: bool + """Whether usage is refused as soon as the credit is used up: the wallet's `usage_at_zero`, + else the plan of the subscription checked (of any active subscription without one). A plan + that invoices usage up to a cap refuses it past the cap only, as `has_access` reports.""" + + credit_unit: str | None = None + """The credit unit (its code) checked; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: `balance` is in integers of 10^-precision credit.""" + + currency: Currency | None = None + + shared_balance: int | None = None + """With `subscription_id` only: the part of `balance` that belongs to no subscription, which + the customer's other subscriptions can spend too.""" diff --git a/meteroid/models/wallet_credit_recovered_event.py b/meteroid/models/wallet_credit_recovered_event.py new file mode 100644 index 0000000..f27c5eb --- /dev/null +++ b/meteroid/models/wallet_credit_recovered_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .event_id import EventId + from .event_type import EventType + from .wallet_credit_recovered_event_data import WalletCreditRecoveredEventData + + +@dataclasses.dataclass(kw_only=True) +class WalletCreditRecoveredEvent(BaseModel): + """The `WalletCreditRecoveredEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("wallet_credit_recovered_event_data",) + + wallet_credit_recovered_event_data: WalletCreditRecoveredEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/wallet_credit_recovered_event_data.py b/meteroid/models/wallet_credit_recovered_event_data.py new file mode 100644 index 0000000..283ae8a --- /dev/null +++ b/meteroid/models/wallet_credit_recovered_event_data.py @@ -0,0 +1,47 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_note_id import CreditNoteId + from .customer_id import CustomerId + from .invoice_id import InvoiceId + from .payment_transaction_id import PaymentTransactionId + + +@dataclasses.dataclass(kw_only=True) +class WalletCreditRecoveredEventData(BaseModel): + """Spent credit reported by `wallet.credit_unrecovered` is backed again.""" + + cause: str + """Cause of the loss this recovers: `PAYMENT_DISPUTED`, `PAYMENT_REVERSED`, + `PAYMENT_REFUNDED` or `CREDIT_NOTE_REFUNDED`.""" + + credit_recovered: int + """Spent credit an earlier `wallet.credit_unrecovered` reported that is backed again, in + minor units: the money came back, or the credit was returned and taken back.""" + + credit_unrecovered_total: int + """The loss of this money flow (an invoice's payments, a credit note, or a payment) once + this event applies.""" + + customer_id: CustomerId + + credit_note_id: CreditNoteId | None = None + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" + + invoice_id: InvoiceId | None = None + + payment_id: PaymentTransactionId | None = None diff --git a/meteroid/models/wallet_credit_unrecovered_event.py b/meteroid/models/wallet_credit_unrecovered_event.py new file mode 100644 index 0000000..353cc87 --- /dev/null +++ b/meteroid/models/wallet_credit_unrecovered_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .event_id import EventId + from .event_type import EventType + from .wallet_credit_unrecovered_event_data import WalletCreditUnrecoveredEventData + + +@dataclasses.dataclass(kw_only=True) +class WalletCreditUnrecoveredEvent(BaseModel): + """The `WalletCreditUnrecoveredEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("wallet_credit_unrecovered_event_data",) + + wallet_credit_unrecovered_event_data: WalletCreditUnrecoveredEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/wallet_credit_unrecovered_event_data.py b/meteroid/models/wallet_credit_unrecovered_event_data.py new file mode 100644 index 0000000..76a3673 --- /dev/null +++ b/meteroid/models/wallet_credit_unrecovered_event_data.py @@ -0,0 +1,53 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_note_id import CreditNoteId + from .customer_id import CustomerId + from .invoice_id import InvoiceId + from .payment_transaction_id import PaymentTransactionId + + +@dataclasses.dataclass(kw_only=True) +class WalletCreditUnrecoveredEventData(BaseModel): + """Purchased credit was already spent when its money came back.""" + + cause: str + """`PAYMENT_DISPUTED` (lost to a dispute or chargeback), `PAYMENT_REVERSED` (a failed + collection clawed back), `PAYMENT_REFUNDED` (a payment refunded without a credit note, at + the provider or as an overpayment, after the credit it backed was spent) or + `CREDIT_NOTE_REFUNDED`.""" + + credit_taken_back: int + """Unspent credit taken back from the wallet at the same time, in minor units.""" + + credit_unrecovered: int + """Spent credit this event adds to the loss, in minor units. A failed collection reopens its + invoice, which collects it again; a dispute's loss stays unless the dispute is won. + `wallet.credit_recovered` reports what comes back.""" + + credit_unrecovered_total: int + """The loss of this money flow (an invoice's payments, a credit note, or a payment) once + this event applies.""" + + customer_id: CustomerId + + credit_note_id: CreditNoteId | None = None + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" + + invoice_id: InvoiceId | None = None + + payment_id: PaymentTransactionId | None = None diff --git a/meteroid/models/wallet_debited_event.py b/meteroid/models/wallet_debited_event.py new file mode 100644 index 0000000..d884027 --- /dev/null +++ b/meteroid/models/wallet_debited_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .event_id import EventId + from .event_type import EventType + from .wallet_debited_event_data import WalletDebitedEventData + + +@dataclasses.dataclass(kw_only=True) +class WalletDebitedEvent(BaseModel): + """The `WalletDebitedEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("wallet_debited_event_data",) + + wallet_debited_event_data: WalletDebitedEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/wallet_debited_event_data.py b/meteroid/models/wallet_debited_event_data.py new file mode 100644 index 0000000..886c317 --- /dev/null +++ b/meteroid/models/wallet_debited_event_data.py @@ -0,0 +1,60 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .actor import Actor + from .credit_applies_to import CreditAppliesTo + from .credit_grant_id import CreditGrantId + from .credit_note_id import CreditNoteId + from .customer_id import CustomerId + from .invoice_id import InvoiceId + from .ledger_entry_id import LedgerEntryId + + +@dataclasses.dataclass(kw_only=True) +class WalletDebitedEventData(BaseModel): + """Credit left a customer's wallet.""" + + amount: int + """In minor units of `currency` or `credit_unit`.""" + + applicable_product_ids: list[str] + """The products whose charges the grant pays; empty when it pays every product.""" + + applies_to: CreditAppliesTo + """What the grant pays: ALL any charge, USAGE only usage charges.""" + + credit_grant_id: CreditGrantId + """The grant the credit left.""" + + customer_id: CustomerId + + ledger_entry_id: LedgerEntryId + """The credit ledger entry recording the movement.""" + + reason: str + """Why the credit moved, as shown on the ledger.""" + + actor: Actor | None = None + + credit_note_id: CreditNoteId | None = None + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" + + invoice_id: InvoiceId | None = None + + subscription_id: str | None = None + """The subscription the grant belongs to: it pays only that subscription's charges. Null when + it pays any of the customer's subscriptions.""" diff --git a/meteroid/models/wallet_depleted_event.py b/meteroid/models/wallet_depleted_event.py new file mode 100644 index 0000000..89a01cd --- /dev/null +++ b/meteroid/models/wallet_depleted_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .event_id import EventId + from .event_type import EventType + from .wallet_depleted_event_data import WalletDepletedEventData + + +@dataclasses.dataclass(kw_only=True) +class WalletDepletedEvent(BaseModel): + """The `WalletDepletedEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("wallet_depleted_event_data",) + + wallet_depleted_event_data: WalletDepletedEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/wallet_depleted_event_data.py b/meteroid/models/wallet_depleted_event_data.py new file mode 100644 index 0000000..3de922b --- /dev/null +++ b/meteroid/models/wallet_depleted_event_data.py @@ -0,0 +1,27 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .customer_id import CustomerId + + +@dataclasses.dataclass(kw_only=True) +class WalletDepletedEventData(BaseModel): + """A wallet's balance (credit that pays any charge of any of the customer's subscriptions, net of + unbilled usage) reached zero; credit included with a subscription may remain.""" + + customer_id: CustomerId + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" diff --git a/meteroid/models/wallet_funded_event.py b/meteroid/models/wallet_funded_event.py new file mode 100644 index 0000000..29cc231 --- /dev/null +++ b/meteroid/models/wallet_funded_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .event_id import EventId + from .event_type import EventType + from .wallet_funded_event_data import WalletFundedEventData + + +@dataclasses.dataclass(kw_only=True) +class WalletFundedEvent(BaseModel): + """The `WalletFundedEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("wallet_funded_event_data",) + + wallet_funded_event_data: WalletFundedEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/wallet_funded_event_data.py b/meteroid/models/wallet_funded_event_data.py new file mode 100644 index 0000000..0793d55 --- /dev/null +++ b/meteroid/models/wallet_funded_event_data.py @@ -0,0 +1,62 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .credit_applies_to import CreditAppliesTo + from .credit_grant_id import CreditGrantId + from .credit_grant_source import CreditGrantSource + from .credit_grant_type import CreditGrantType + from .credit_note_id import CreditNoteId + from .customer_id import CustomerId + from .invoice_id import InvoiceId + from .ledger_entry_id import LedgerEntryId + + +@dataclasses.dataclass(kw_only=True) +class WalletFundedEventData(BaseModel): + """Credit entered a customer's wallet.""" + + amount: int + """In minor units of `currency` or `credit_unit`.""" + + applicable_product_ids: list[str] + """The products whose charges the grant pays; empty when it pays every product.""" + + applies_to: CreditAppliesTo + """What the grant pays: ALL any charge, USAGE only usage charges.""" + + credit_grant_id: CreditGrantId + """The grant the credit entered.""" + + customer_id: CustomerId + + grant_type: CreditGrantType + """PAID or PROMOTIONAL: whether the credit applies after or before tax.""" + + ledger_entry_id: LedgerEntryId + """The credit ledger entry recording the movement.""" + + source: CreditGrantSource + """Where the credit came from.""" + + credit_note_id: CreditNoteId | None = None + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" + + invoice_id: InvoiceId | None = None + + subscription_id: str | None = None + """The subscription the grant belongs to: it pays only that subscription's charges. Null when + it pays any of the customer's subscriptions.""" diff --git a/meteroid/models/wallet_list_response.py b/meteroid/models/wallet_list_response.py new file mode 100644 index 0000000..f9e047c --- /dev/null +++ b/meteroid/models/wallet_list_response.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .wallet import Wallet + + +@dataclasses.dataclass(kw_only=True) +class WalletListResponse(BaseModel): + """A customer's wallets, the customer's own currency first.""" + + data: list[Wallet] diff --git a/meteroid/models/wallet_recovered_event.py b/meteroid/models/wallet_recovered_event.py new file mode 100644 index 0000000..445e30f --- /dev/null +++ b/meteroid/models/wallet_recovered_event.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .event_id import EventId + from .event_type import EventType + from .wallet_recovered_event_data import WalletRecoveredEventData + + +@dataclasses.dataclass(kw_only=True) +class WalletRecoveredEvent(BaseModel): + """The `WalletRecoveredEvent` object.""" + + _FLATTENED: t.ClassVar[tuple[str, ...]] = ("wallet_recovered_event_data",) + + wallet_recovered_event_data: WalletRecoveredEventData + + id: EventId + + timestamp: datetime + + type: EventType diff --git a/meteroid/models/wallet_recovered_event_data.py b/meteroid/models/wallet_recovered_event_data.py new file mode 100644 index 0000000..0a52721 --- /dev/null +++ b/meteroid/models/wallet_recovered_event_data.py @@ -0,0 +1,32 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .customer_id import CustomerId + + +@dataclasses.dataclass(kw_only=True) +class WalletRecoveredEventData(BaseModel): + """A wallet's balance (credit that pays any charge of any of the customer's subscriptions, net of + unbilled usage) came back above zero.""" + + balance: int + """The balance (credit on account that pays any charge of any of the customer's subscriptions, + net of unbilled usage) now that it is back above zero, in + minor units.""" + + customer_id: CustomerId + + credit_unit: str | None = None + """The credit unit (its code) the amounts are in; absent for a currency.""" + + credit_unit_precision: int | None = None + """Decimal places of `credit_unit`: amounts are integers of 10^-precision credit.""" + + currency: str | None = None + """Absent when the amounts are in a credit unit.""" diff --git a/meteroid/models/webhook_endpoint.py b/meteroid/models/webhook_endpoint.py index d7ead8c..e89223a 100644 --- a/meteroid/models/webhook_endpoint.py +++ b/meteroid/models/webhook_endpoint.py @@ -25,7 +25,8 @@ class WebhookEndpoint(BaseModel): disabled: bool event_types: list[str] - """Subscribed event types. Empty means every event type.""" + """Subscribed event types. Empty means every event type except Beta ones, which are sent + only to endpoints that list them.""" headers: list[WebhookHeader] """Custom headers sent with every delivery.""" diff --git a/tests/test_budgets.py b/tests/test_budgets.py new file mode 100644 index 0000000..1303028 --- /dev/null +++ b/tests/test_budgets.py @@ -0,0 +1,16 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from perseid_mock import call, mock + + +class BudgetsTest(unittest.TestCase): + def test_list(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"data":[{"amount":-9007199254740993,"behavior":"SOFT","created_at":"2023-12-31T23:59:59.999-05:30","created_by":"TENANT","exact":false,"id":"sample","thresholds":[-123456789],"updated_at":"2024-03-15T10:30:45.123+02:00","version":123456789}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + ) + call(client.budgets.list, "id_or_alias") + self.assertEqual(requests, ["GET /api/v1/customers/id_or_alias/budgets"]) diff --git a/tests/test_checks.py b/tests/test_checks.py new file mode 100644 index 0000000..055da15 --- /dev/null +++ b/tests/test_checks.py @@ -0,0 +1,19 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from perseid_mock import call, mock + + +class ChecksTest(unittest.TestCase): + def test_check_entitlement(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"as_of":"2023-12-31T23:59:59.999-05:30","exact":true,"feature":{"code":"sample","id":"feature_id_19"},"has_access":true,"in_overage":false,"lag_ms":9007199254740993,"limits":[{"behavior":"HARD","has_access":false,"limit":"-0.000123","remaining":"12345.6789","usage_limit_id":"usage_limit_id_23","used":"12345.6789"}],"near_limit":false,"reason":"INSUFFICIENT_BALANCE"}', + ) + call(client.checks.check_entitlement, "id_or_alias", "feature_id_or_code") + self.assertEqual( + requests, + ["GET /api/v1/customers/id_or_alias/entitlements/feature_id_or_code/check"], + ) diff --git a/tests/test_connect.py b/tests/test_connect.py index 508c9ff..244faf5 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -21,13 +21,13 @@ def test_create_connected_account(self) -> None: client, requests = mock( 200, "application/json", - '{"connection_type":"express","created_at":"2024-03-15T10:30:45.123+02:00","id":"connected_account_id_47","onboarding_mode":"express","platform_organization_id":"organization_id_83","status":"active"}', + '{"connection_type":"standard","created_at":"2024-03-15T10:30:45.123+02:00","id":"connected_account_id_67","onboarding_mode":"full","platform_organization_id":"organization_id_23","status":"active","onboarding_link":{"expires_at":"2023-12-31T23:59:59.999-05:30","url":"sample"}}', ) call( client.connect.create_connected_account, body=decode( CreateConnectedAccountRequest, - '{"connected_organization_id":"00000000-0000-0000-0000-000000000000"}', + '{"email":"alice@example.com","organization_name":"sample"}', ), ) self.assertEqual(requests, ["POST /api/v1/connected-accounts"]) diff --git a/tests/test_credit_notes.py b/tests/test_credit_notes.py index b394622..a46aa65 100644 --- a/tests/test_credit_notes.py +++ b/tests/test_credit_notes.py @@ -12,7 +12,7 @@ def test_list(self) -> None: client, requests = mock( 200, "application/json", - '{"data":[{"created_at":"2023-12-31T23:59:59.999-05:30","credit_note_number":"sample","credit_type":"DEBT_CANCELLATION","credited_amount_cents":9007199254740993,"currency":"TMT","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_id":"customer_id_78","id":"credit_note_id_47","invoice_id":"invoice_id_67","invoice_number":"sample","line_items":[{"amount_total":-9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"2024-02-29","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"12345.6789"}],"refunded_amount_cents":9007199254740993,"status":"DRAFT","subtotal":-9007199254740993,"tax_amount":9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":-9007199254740993,"tax_rate":"-0.000123","taxable_amount":-9007199254740993}],"total":-9007199254740993}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + '{"data":[{"created_at":"2023-12-31T23:59:59.999-05:30","credit_note_number":"sample","credit_type":"DEBT_CANCELLATION","credited_amount_cents":9007199254740993,"currency":"TMT","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_id":"customer_id_78","id":"credit_note_id_47","invoice_id":"invoice_id_67","invoice_number":"sample","line_items":[{"amount_total":-9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"-0.000123","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"-0.000123"}],"refunded_amount_cents":9007199254740993,"status":"DRAFT","subtotal":-9007199254740993,"tax_amount":9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":-9007199254740993,"tax_rate":"-0.000123","taxable_amount":-9007199254740993}],"total":-9007199254740993}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', ) call(client.credit_notes.list) self.assertEqual(requests, ["GET /api/v1/credit-notes"]) @@ -21,7 +21,7 @@ def test_retrieve(self) -> None: client, requests = mock( 200, "application/json", - '{"created_at":"2023-12-31T23:59:59.999-05:30","credit_note_number":"sample","credit_type":"REFUND","credited_amount_cents":9007199254740993,"currency":"MMK","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_id":"customer_id_13","id":"credit_note_id_99","invoice_id":"invoice_id_90","invoice_number":"sample","line_items":[{"amount_total":-9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":9007199254740993,"unit_price":"-0.000123"}],"tax_rate":"12345.6789"}],"refunded_amount_cents":-9007199254740993,"status":"DRAFT","subtotal":9007199254740993,"tax_amount":9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"12345.6789","taxable_amount":9007199254740993}],"total":-9007199254740993}', + '{"created_at":"2023-12-31T23:59:59.999-05:30","credit_note_number":"sample","credit_type":"REFUND","credited_amount_cents":9007199254740993,"currency":"MMK","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_id":"customer_id_13","id":"credit_note_id_99","invoice_id":"invoice_id_90","invoice_number":"sample","line_items":[{"amount_total":-9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"-0.000123","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"12345.6789"}],"refunded_amount_cents":-9007199254740993,"status":"DRAFT","subtotal":9007199254740993,"tax_amount":9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"12345.6789","taxable_amount":9007199254740993}],"total":-9007199254740993}', ) call(client.credit_notes.retrieve, "credit_note_id") self.assertEqual(requests, ["GET /api/v1/credit-notes/credit_note_id"]) @@ -30,7 +30,7 @@ def test_update_custom_properties(self) -> None: client, requests = mock( 200, "application/json", - '{"created_at":"2023-12-31T23:59:59.999-05:30","credit_note_number":"sample","credit_type":"REFUND","credited_amount_cents":9007199254740993,"currency":"MMK","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_id":"customer_id_13","id":"credit_note_id_99","invoice_id":"invoice_id_90","invoice_number":"sample","line_items":[{"amount_total":-9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":9007199254740993,"unit_price":"-0.000123"}],"tax_rate":"12345.6789"}],"refunded_amount_cents":-9007199254740993,"status":"DRAFT","subtotal":9007199254740993,"tax_amount":9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"12345.6789","taxable_amount":9007199254740993}],"total":-9007199254740993}', + '{"created_at":"2023-12-31T23:59:59.999-05:30","credit_note_number":"sample","credit_type":"REFUND","credited_amount_cents":9007199254740993,"currency":"MMK","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_id":"customer_id_13","id":"credit_note_id_99","invoice_id":"invoice_id_90","invoice_number":"sample","line_items":[{"amount_total":-9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"-0.000123","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"12345.6789"}],"refunded_amount_cents":-9007199254740993,"status":"DRAFT","subtotal":9007199254740993,"tax_amount":9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"12345.6789","taxable_amount":9007199254740993}],"total":-9007199254740993}', ) call( client.credit_notes.update_custom_properties, diff --git a/tests/test_credit_units.py b/tests/test_credit_units.py new file mode 100644 index 0000000..84e772b --- /dev/null +++ b/tests/test_credit_units.py @@ -0,0 +1,53 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from meteroid.models import CreditUnitCreateRequest, CreditUnitUpdateRequest + +from perseid_mock import call, decode, mock + + +class CreditUnitsTest(unittest.TestCase): + def test_list(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"data":[{"archived":false,"code":"sample","id":"sample","name":"sample","plural_name":"sample","precision":123456789,"prices":[{"created_at":"2023-12-31T23:59:59.999-05:30","currency":"sample","id":"sample","unit_price":"sample"}]}]}', + ) + call(client.credit_units.list) + self.assertEqual(requests, ["GET /api/v1/credit-units"]) + + def test_create(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"archived":false,"code":"sample","id":"sample","name":"sample","plural_name":"sample","precision":2147483647,"prices":[{"created_at":"2024-03-15T10:30:45.123+02:00","currency":"sample","id":"sample","unit_price":"sample"}]}', + ) + call( + client.credit_units.create, + body=decode( + CreditUnitCreateRequest, + '{"code":"sample","name":"sample","plural_name":"sample","precision":123456789}', + ), + ) + self.assertEqual(requests, ["POST /api/v1/credit-units"]) + + def test_retrieve(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"archived":false,"code":"sample","id":"sample","name":"sample","plural_name":"sample","precision":2147483647,"prices":[{"created_at":"2024-03-15T10:30:45.123+02:00","currency":"sample","id":"sample","unit_price":"sample"}]}', + ) + call(client.credit_units.retrieve, "id") + self.assertEqual(requests, ["GET /api/v1/credit-units/id"]) + + def test_update(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"archived":false,"code":"sample","id":"sample","name":"sample","plural_name":"sample","precision":2147483647,"prices":[{"created_at":"2024-03-15T10:30:45.123+02:00","currency":"sample","id":"sample","unit_price":"sample"}]}', + ) + call( + client.credit_units.update, "id", body=decode(CreditUnitUpdateRequest, "{}") + ) + self.assertEqual(requests, ["PATCH /api/v1/credit-units/id"]) diff --git a/tests/test_credit_units_prices.py b/tests/test_credit_units_prices.py new file mode 100644 index 0000000..04c228e --- /dev/null +++ b/tests/test_credit_units_prices.py @@ -0,0 +1,41 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from meteroid.models import CreditUnitPriceSetRequest + +from perseid_mock import call, decode, mock + + +class CreditUnitsPricesTest(unittest.TestCase): + def test_list(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"data":[{"created_at":"2023-12-31T23:59:59.999-05:30","currency":"sample","id":"sample","unit_price":"sample"}]}', + ) + call(client.credit_units.prices.list, "id") + self.assertEqual(requests, ["GET /api/v1/credit-units/id/prices"]) + + def test_update(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"created_at":"2024-03-15T10:30:45.123+02:00","currency":"sample","id":"sample","unit_price":"sample"}', + ) + call( + client.credit_units.prices.update, + "id", + "currency", + body=decode(CreditUnitPriceSetRequest, '{"unit_price":"sample"}'), + ) + self.assertEqual(requests, ["PUT /api/v1/credit-units/id/prices/currency"]) + + def test_archive(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"created_at":"2024-03-15T10:30:45.123+02:00","currency":"sample","id":"sample","unit_price":"sample"}', + ) + call(client.credit_units.prices.archive, "id", "currency") + self.assertEqual(requests, ["DELETE /api/v1/credit-units/id/prices/currency"]) diff --git a/tests/test_credits.py b/tests/test_credits.py new file mode 100644 index 0000000..1b94e27 --- /dev/null +++ b/tests/test_credits.py @@ -0,0 +1,16 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from perseid_mock import call, mock + + +class CreditsTest(unittest.TestCase): + def test_list_customer_credit_ledger(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"data":[{"amount":-9007199254740993,"applies_to":"USAGE","balance_after":9007199254740993,"credit_grant_id":"sample","effective_at":"2023-12-31T23:59:59.999-05:30","id":"sample","reason":"sample","recorded_at":"2023-12-31T23:59:59.999-05:30","sequence":9007199254740993,"type":"CREDIT"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + ) + call(client.credits.list_customer_credit_ledger, "id_or_alias") + self.assertEqual(requests, ["GET /api/v1/customers/id_or_alias/credit-ledger"]) diff --git a/tests/test_credits_grants.py b/tests/test_credits_grants.py new file mode 100644 index 0000000..276a119 --- /dev/null +++ b/tests/test_credits_grants.py @@ -0,0 +1,16 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from perseid_mock import call, mock + + +class CreditsGrantsTest(unittest.TestCase): + def test_list(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"data":[{"amount":-9007199254740993,"applicable_product_ids":["product_id_39"],"applies_to":"ALL","created_at":"2024-03-15T10:30:45.123+02:00","effective_at":"2023-12-31T23:59:59.999-05:30","id":"sample","kind":"ACCOUNT_BALANCE","kind_label":"sample","priority":123456789,"reason":"sample","remaining":-9007199254740993,"source":"OVERPAYMENT","status":"ACTIVE","type":"PAID"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + ) + call(client.credits.grants.list, "id_or_alias") + self.assertEqual(requests, ["GET /api/v1/customers/id_or_alias/credit-grants"]) diff --git a/tests/test_credits_wallets.py b/tests/test_credits_wallets.py new file mode 100644 index 0000000..0fbae7b --- /dev/null +++ b/tests/test_credits_wallets.py @@ -0,0 +1,25 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from perseid_mock import call, mock + + +class CreditsWalletsTest(unittest.TestCase): + def test_list(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"data":[{"credit_on_account":9007199254740993,"credit_on_account_usage_only":-9007199254740993,"low_balance_thresholds":[-9007199254740993],"pauses_at_zero":false,"subscription_credit":[{"credit_on_account":-9007199254740993,"subscription_id":"sample"}],"upcoming_credit":9007199254740993,"usage_at_zero":"FOLLOW_PLAN"}]}', + ) + call(client.credits.wallets.list, "id_or_alias") + self.assertEqual(requests, ["GET /api/v1/customers/id_or_alias/wallets"]) + + def test_check(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"as_of":"2023-12-31T23:59:59.999-05:30","balance":9007199254740993,"exact":true,"exhausted":false,"has_access":true,"lag_ms":-9007199254740993,"pauses_at_zero":true}', + ) + call(client.credits.wallets.check, "id_or_alias") + self.assertEqual(requests, ["GET /api/v1/customers/id_or_alias/wallets/check"]) diff --git a/tests/test_invoices.py b/tests/test_invoices.py index bc9a9c6..7b26b93 100644 --- a/tests/test_invoices.py +++ b/tests/test_invoices.py @@ -12,7 +12,7 @@ def test_list(self) -> None: client, requests = mock( 200, "application/json", - '{"data":[{"amount_due":-9007199254740993,"applied_credits":-9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"CNY","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_39","name":"sample","snapshot_at":"2023-12-31T23:59:59.999-05:30"},"customer_id":"customer_id_67","id":"invoice_id_67","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"RECURRING","line_items":[{"amount_total":9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"2024-02-29","sub_line_items":[{"id":"sample","name":"sample","quantity":"-0.000123","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"12345.6789"}],"net_terms":-2147483648,"payment_status":"PARTIALLY_PAID","status":"FINALIZED","subtotal":-9007199254740993,"subtotal_recurring":9007199254740993,"tax_amount":9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":-9007199254740993,"tax_rate":"12345.6789","taxable_amount":9007199254740993}],"tax_inclusive":false,"total":-9007199254740993,"transactions":[{"amount":9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":-9007199254740993,"currency":"sample","id":"payment_transaction_id_94","payment_type":"PAYMENT","status":"READY"}]}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + '{"data":[{"amount_due":-9007199254740993,"applied_credits":-9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"CNY","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_39","name":"sample","snapshot_at":"2023-12-31T23:59:59.999-05:30"},"customer_id":"customer_id_67","id":"invoice_id_67","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"RECURRING","line_items":[{"amount_total":9007199254740993,"end_date":"2024-02-29","name":"sample","start_date":"2024-02-29","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"-0.000123"}],"net_terms":-2147483648,"payment_status":"ERRORED","settled_by_credit_notes":false,"status":"FINALIZED","subtotal":9007199254740993,"subtotal_recurring":-9007199254740993,"tax_amount":-9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"12345.6789","taxable_amount":9007199254740993}],"tax_inclusive":true,"total":-9007199254740993,"transactions":[{"amount":9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":9007199254740993,"currency":"sample","id":"payment_transaction_id_30","payment_type":"REFUND","status":"READY"}]}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', ) call(client.invoices.list) self.assertEqual(requests, ["GET /api/v1/invoices"]) @@ -21,7 +21,7 @@ def test_retrieve(self) -> None: client, requests = mock( 200, "application/json", - '{"amount_due":-9007199254740993,"applied_credits":9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"NAD","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_62","name":"sample","snapshot_at":"2024-03-15T10:30:45.123+02:00"},"customer_id":"customer_id_90","id":"invoice_id_31","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"ONE_OFF","line_items":[{"amount_total":9007199254740993,"end_date":"1999-12-31","name":"sample","start_date":"2024-02-29","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":-9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"-0.000123"}],"net_terms":-2147483648,"payment_status":"UNPAID","status":"DRAFT","subtotal":9007199254740993,"subtotal_recurring":9007199254740993,"tax_amount":-9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"-0.000123","taxable_amount":9007199254740993}],"tax_inclusive":true,"total":-9007199254740993,"transactions":[{"amount":-9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":-9007199254740993,"currency":"sample","id":"payment_transaction_id_7","payment_type":"PAYMENT","status":"CANCELLED"}]}', + '{"amount_due":-9007199254740993,"applied_credits":9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"NAD","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_62","name":"sample","snapshot_at":"2024-03-15T10:30:45.123+02:00"},"customer_id":"customer_id_90","id":"invoice_id_31","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"ONE_OFF","line_items":[{"amount_total":9007199254740993,"end_date":"1999-12-31","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"12345.6789"}],"net_terms":-2147483648,"payment_status":"PARTIALLY_PAID","settled_by_credit_notes":true,"status":"VOID","subtotal":-9007199254740993,"subtotal_recurring":9007199254740993,"tax_amount":-9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"-0.000123","taxable_amount":-9007199254740993}],"tax_inclusive":false,"total":-9007199254740993,"transactions":[{"amount":-9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":9007199254740993,"currency":"sample","id":"payment_transaction_id_88","payment_type":"REFUND","status":"PENDING"}]}', ) call(client.invoices.retrieve, "invoice_id") self.assertEqual(requests, ["GET /api/v1/invoices/invoice_id"]) @@ -30,7 +30,7 @@ def test_update_custom_properties(self) -> None: client, requests = mock( 200, "application/json", - '{"amount_due":-9007199254740993,"applied_credits":9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"NAD","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_62","name":"sample","snapshot_at":"2024-03-15T10:30:45.123+02:00"},"customer_id":"customer_id_90","id":"invoice_id_31","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"ONE_OFF","line_items":[{"amount_total":9007199254740993,"end_date":"1999-12-31","name":"sample","start_date":"2024-02-29","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":-9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"-0.000123"}],"net_terms":-2147483648,"payment_status":"UNPAID","status":"DRAFT","subtotal":9007199254740993,"subtotal_recurring":9007199254740993,"tax_amount":-9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"-0.000123","taxable_amount":9007199254740993}],"tax_inclusive":true,"total":-9007199254740993,"transactions":[{"amount":-9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":-9007199254740993,"currency":"sample","id":"payment_transaction_id_7","payment_type":"PAYMENT","status":"CANCELLED"}]}', + '{"amount_due":-9007199254740993,"applied_credits":9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"NAD","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_62","name":"sample","snapshot_at":"2024-03-15T10:30:45.123+02:00"},"customer_id":"customer_id_90","id":"invoice_id_31","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"ONE_OFF","line_items":[{"amount_total":9007199254740993,"end_date":"1999-12-31","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"12345.6789"}],"net_terms":-2147483648,"payment_status":"PARTIALLY_PAID","settled_by_credit_notes":true,"status":"VOID","subtotal":-9007199254740993,"subtotal_recurring":9007199254740993,"tax_amount":-9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"-0.000123","taxable_amount":-9007199254740993}],"tax_inclusive":false,"total":-9007199254740993,"transactions":[{"amount":-9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":9007199254740993,"currency":"sample","id":"payment_transaction_id_88","payment_type":"REFUND","status":"PENDING"}]}', ) call( client.invoices.update_custom_properties, @@ -53,7 +53,7 @@ def test_refresh(self) -> None: client, requests = mock( 200, "application/json", - '{"amount_due":-9007199254740993,"applied_credits":9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"NAD","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_62","name":"sample","snapshot_at":"2024-03-15T10:30:45.123+02:00"},"customer_id":"customer_id_90","id":"invoice_id_31","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"ONE_OFF","line_items":[{"amount_total":9007199254740993,"end_date":"1999-12-31","name":"sample","start_date":"2024-02-29","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":-9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"-0.000123"}],"net_terms":-2147483648,"payment_status":"UNPAID","status":"DRAFT","subtotal":9007199254740993,"subtotal_recurring":9007199254740993,"tax_amount":-9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"-0.000123","taxable_amount":9007199254740993}],"tax_inclusive":true,"total":-9007199254740993,"transactions":[{"amount":-9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":-9007199254740993,"currency":"sample","id":"payment_transaction_id_7","payment_type":"PAYMENT","status":"CANCELLED"}]}', + '{"amount_due":-9007199254740993,"applied_credits":9007199254740993,"coupons":[{"coupon_id":"sample","name":"sample","total":-9007199254740993}],"created_at":"2024-03-15T10:30:45.123+02:00","currency":"NAD","custom_properties":{"key":"value","count":3,"ratio":0.5,"flags":[true,false],"nested":{"ok":true}},"customer_details":{"id":"customer_id_62","name":"sample","snapshot_at":"2024-03-15T10:30:45.123+02:00"},"customer_id":"customer_id_90","id":"invoice_id_31","invoice_date":"1999-12-31","invoice_number":"sample","invoice_type":"ONE_OFF","line_items":[{"amount_total":9007199254740993,"end_date":"1999-12-31","name":"sample","start_date":"1999-12-31","sub_line_items":[{"id":"sample","name":"sample","quantity":"12345.6789","total":9007199254740993,"unit_price":"12345.6789"}],"tax_rate":"12345.6789"}],"net_terms":-2147483648,"payment_status":"PARTIALLY_PAID","settled_by_credit_notes":true,"status":"VOID","subtotal":-9007199254740993,"subtotal_recurring":9007199254740993,"tax_amount":-9007199254740993,"tax_breakdown":[{"name":"sample","tax_amount":9007199254740993,"tax_rate":"-0.000123","taxable_amount":-9007199254740993}],"tax_inclusive":false,"total":-9007199254740993,"transactions":[{"amount":-9007199254740993,"amount_refunded":9007199254740993,"amount_reversed":9007199254740993,"currency":"sample","id":"payment_transaction_id_88","payment_type":"REFUND","status":"PENDING"}]}', ) call(client.invoices.refresh, "invoice_id") self.assertEqual(requests, ["POST /api/v1/invoices/invoice_id/refresh"]) diff --git a/tests/test_plans.py b/tests/test_plans.py index e7bc300..b873302 100644 --- a/tests/test_plans.py +++ b/tests/test_plans.py @@ -46,7 +46,7 @@ def test_list(self) -> None: client, requests = mock( 200, "application/json", - '{"data":[{"available_parameters":{},"created_at":"2024-03-15T10:30:45.123+02:00","currency":"WST","id":"plan_id_78","name":"sample","net_terms":-2147483648,"plan_type":"FREE","price_components":[{"id":"price_component_id_82","name":"sample"}],"product_family":{"id":"product_family_id_59","name":"sample"},"status":"INACTIVE","tax_inclusive":true,"version":-2147483648,"version_id":"plan_version_id_92"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + '{"data":[{"available_parameters":{},"created_at":"2024-03-15T10:30:45.123+02:00","currency":"TMT","id":"plan_id_25","name":"sample","net_terms":-2147483648,"plan_type":"CUSTOM","price_components":[{"id":"price_component_id_8","name":"sample"}],"product_family":{"id":"product_family_id_2","name":"sample"},"status":"DRAFT","tax_inclusive":false,"version":123456789,"version_id":"plan_version_id_3"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', ) call(client.plans.list) self.assertEqual(requests, ["GET /api/v1/plans"]) @@ -55,13 +55,13 @@ def test_create(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"COP","id":"plan_id_13","name":"sample","net_terms":2147483647,"plan_type":"FREE","price_components":[{"id":"price_component_id_38","name":"sample"}],"product_family":{"id":"product_family_id_66","name":"sample"},"status":"ARCHIVED","tax_inclusive":false,"version":123456789,"version_id":"plan_version_id_84"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', ) call( client.plans.create, body=decode( CreatePlanRequest, - '{"components":[{"fee":{"type":"RATE","rates":[{"price":"-0.000123","term":"ANNUAL"}]},"name":"sample"}],"currency":"sample","name":"sample","plan_type":"CUSTOM","product_family_id":"product_family_id_99","status":"ACTIVE"}', + '{"components":[{"fee":{"type":"RATE","rates":[{"price":"-0.000123","term":"ANNUAL"}]},"name":"sample"}],"currency":"sample","name":"sample","plan_type":"CUSTOM","product_family_id":"product_family_id_90","status":"INACTIVE"}', ), ) self.assertEqual(requests, ["POST /api/v1/plans"]) @@ -70,7 +70,7 @@ def test_retrieve(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"COP","id":"plan_id_13","name":"sample","net_terms":2147483647,"plan_type":"FREE","price_components":[{"id":"price_component_id_38","name":"sample"}],"product_family":{"id":"product_family_id_66","name":"sample"},"status":"ARCHIVED","tax_inclusive":false,"version":123456789,"version_id":"plan_version_id_84"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', ) call(client.plans.retrieve, "plan_id") self.assertEqual(requests, ["GET /api/v1/plans/plan_id"]) @@ -79,7 +79,7 @@ def test_replace(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"COP","id":"plan_id_13","name":"sample","net_terms":2147483647,"plan_type":"FREE","price_components":[{"id":"price_component_id_38","name":"sample"}],"product_family":{"id":"product_family_id_66","name":"sample"},"status":"ARCHIVED","tax_inclusive":false,"version":123456789,"version_id":"plan_version_id_84"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', ) call( client.plans.replace, @@ -95,7 +95,7 @@ def test_update(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"COP","id":"plan_id_13","name":"sample","net_terms":2147483647,"plan_type":"FREE","price_components":[{"id":"price_component_id_38","name":"sample"}],"product_family":{"id":"product_family_id_66","name":"sample"},"status":"ARCHIVED","tax_inclusive":false,"version":123456789,"version_id":"plan_version_id_84"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', ) call(client.plans.update, "plan_id", body=decode(PatchPlanRequest, "{}")) self.assertEqual(requests, ["PATCH /api/v1/plans/plan_id"]) @@ -109,7 +109,7 @@ def test_publish(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"COP","id":"plan_id_13","name":"sample","net_terms":2147483647,"plan_type":"FREE","price_components":[{"id":"price_component_id_38","name":"sample"}],"product_family":{"id":"product_family_id_66","name":"sample"},"status":"ARCHIVED","tax_inclusive":false,"version":123456789,"version_id":"plan_version_id_84"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', ) call(client.plans.publish, "plan_id") self.assertEqual(requests, ["POST /api/v1/plans/plan_id/publish"]) diff --git a/tests/test_products.py b/tests/test_products.py index 0a587bb..a61ada3 100644 --- a/tests/test_products.py +++ b/tests/test_products.py @@ -12,7 +12,7 @@ def test_list(self) -> None: client, requests = mock( 200, "application/json", - '{"data":[{"catalog":false,"created_at":"2024-03-15T10:30:45.123+02:00","fee_structure":{"type":"RATE"},"fee_type":"CAPACITY","id":"product_id_78","name":"sample","product_family_id":"product_family_id_47"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + '{"data":[{"catalog":false,"created_at":"2024-03-15T10:30:45.123+02:00","fee_structure":{"type":"RATE"},"fee_type":"USAGE","id":"product_id_78","name":"sample","product_family_id":"product_family_id_47"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', ) call(client.products.list) self.assertEqual(requests, ["GET /api/v1/products"]) @@ -21,7 +21,7 @@ def test_create(self) -> None: client, requests = mock( 200, "application/json", - '{"catalog":true,"created_at":"2023-12-31T23:59:59.999-05:30","fee_structure":{"type":"RATE"},"fee_type":"RATE","id":"product_id_13","name":"sample","product_family_id":"product_family_id_99"}', + '{"catalog":true,"created_at":"2023-12-31T23:59:59.999-05:30","fee_structure":{"type":"RATE"},"fee_type":"CAPACITY","id":"product_id_13","name":"sample","product_family_id":"product_family_id_99"}', ) call( client.products.create, @@ -36,7 +36,7 @@ def test_retrieve(self) -> None: client, requests = mock( 200, "application/json", - '{"catalog":true,"created_at":"2023-12-31T23:59:59.999-05:30","fee_structure":{"type":"RATE"},"fee_type":"RATE","id":"product_id_13","name":"sample","product_family_id":"product_family_id_99"}', + '{"catalog":true,"created_at":"2023-12-31T23:59:59.999-05:30","fee_structure":{"type":"RATE"},"fee_type":"CAPACITY","id":"product_id_13","name":"sample","product_family_id":"product_family_id_99"}', ) call(client.products.retrieve, "product_id") self.assertEqual(requests, ["GET /api/v1/products/product_id"]) @@ -45,7 +45,7 @@ def test_update(self) -> None: client, requests = mock( 200, "application/json", - '{"catalog":true,"created_at":"2023-12-31T23:59:59.999-05:30","fee_structure":{"type":"RATE"},"fee_type":"RATE","id":"product_id_13","name":"sample","product_family_id":"product_family_id_99"}', + '{"catalog":true,"created_at":"2023-12-31T23:59:59.999-05:30","fee_structure":{"type":"RATE"},"fee_type":"CAPACITY","id":"product_id_13","name":"sample","product_family_id":"product_family_id_99"}', ) call( client.products.update, diff --git a/tests/test_usage.py b/tests/test_usage.py index a6d9524..96bcb14 100644 --- a/tests/test_usage.py +++ b/tests/test_usage.py @@ -10,7 +10,7 @@ def test_retrieve_subscription(self) -> None: client, requests = mock( 200, "application/json", - '{"period_end":"1999-12-31","period_start":"2024-02-29","usage":[{"grouped_usage":[{"dimensions":{"alpha":"sample"},"value":"12345.6789"}],"metric_code":"sample","metric_id":"billable_metric_id_44","metric_name":"sample","total_value":"12345.6789"}]}', + '{"period_end":"2024-02-29","period_start":"1999-12-31","usage":[{"grouped_usage":[{"dimensions":{"alpha":"sample"},"value":"-0.000123"}],"metric_code":"sample","metric_id":"billable_metric_id_52","metric_name":"sample","total_value":"12345.6789"}]}', ) call(client.usage.retrieve_subscription, "subscription_id") self.assertEqual(requests, ["GET /api/v1/usage/subscription/subscription_id"]) diff --git a/tests/test_usage_customer.py b/tests/test_usage_customer.py new file mode 100644 index 0000000..b28d46e --- /dev/null +++ b/tests/test_usage_customer.py @@ -0,0 +1,18 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from perseid_mock import call, mock + + +class UsageCustomerTest(unittest.TestCase): + def test_retrieve_unattributed(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"period_end":"1999-12-31","period_start":"2024-02-29","usage":[{"from":"2024-03-15T10:30:45.123+02:00","metric_code":"sample","metric_id":"billable_metric_id_44","metric_name":"sample","total_value":"12345.6789","until":"2023-12-31T23:59:59.999-05:30"}]}', + ) + call(client.usage.customer.retrieve_unattributed, "customer_id") + self.assertEqual( + requests, ["GET /api/v1/usage/customer/customer_id/unattributed"] + )