WreathBrowse
Reference

Billing and subscriptions#

Wreath's billing surface deliberately accepts provider price identifiers, customers and payment identifiers, but never a PAN, CVC, track value or payment-method token. Checkout always redirects to the provider's hosted page. That architecture can reduce a deployment's PCI DSS questionnaire to SAQ A when every other eligibility condition is met; it is not a claim that an application is compliant. Confirm the applicable validation method with the acquirer or payment brand and retain the provider's own compliance evidence.

Stripe Checkout#

Create Stripe's outbound client through the application so lifespan management and infrastructure inference can see it. Product and Price objects remain owned by Stripe; the catalog is a startup-validated map from stable application SKUs to those Price IDs.

from wreath import Wreath
from wreath.billing import DeploymentMerchant, HostedRedirect, PostgresBillingLedger
from wreath.billing.providers.stripe import StripeBilling
from wreath.config import Secret
from wreath.subscriptions import Plan, PlanCatalog

app = Wreath()
app.postgres("main", dsn="postgresql://wreath@postgres/app")
stripe_http = app.http_client("stripe", base_url="https://api.stripe.com")
stripe = StripeBilling(
    client=stripe_http,
    api_key=Secret("rk_live_..."),
    api_version="2026-08-26.dahlia",
    allowed_return_origins=("https://app.example",),
)
billing = app.billing(
    "commerce",
    backend=stripe,
    catalog=PlanCatalog(
        Plan("pro", "price_live_pro", entitlements=frozenset({"api", "export"})),
        Plan("credits", "price_live_credits", mode="payment"),
    ),
    merchant=DeploymentMerchant(),
    capture=HostedRedirect(),
    customer_for=provider_customer_for_subject,
    payment_for=provider_payment_for_subject,
    ledger=PostgresBillingLedger(),
    database="main",
)

The ledger contributes billing_commands, billing_payments, billing_subscriptions, billing_invoices and billing_reconciliation to Wreath's schema lifecycle. One-time payment state advances monotonically; subscription and invoice facts are merged in either arrival order. In a multi-database app, database makes that ownership explicit. Without a durable ledger, billing.compliance_posture() reports the missing production control.

The application chooses the SKU, quantity and HTTPS return locations. Wreath resolves the provider price and uses the opaque reference as Stripe's idempotency key and Checkout client_reference_id.

session = await billing.checkout(
    subject="organization:acme",
    plan="pro",
    quantity=3,
    success_url="https://app.example/billing/success",
    cancel_url="https://app.example/billing/cancel",
    reference="01JCOMMERCECHECKOUT",
)

The browser return is presentation only. Grant access from a verified webhook projection, not from arrival at success_url.

Synchronous Stripe calls use Stripe's idempotency record as the outbound source of truth: after a process dies following provider success, repeat the same opaque reference and identical request to recover the same Stripe result. The local billing_commands lifecycle is for queue-backed dispatchers; such a dispatcher registers and fences the command before I/O and marks an expired post-send lease unknown for explicit reconciliation. Wreath does not hold a PostgreSQL transaction open across Stripe network I/O.

Managed Payments#

Managed Payments makes Stripe the merchant of record for eligible digital goods. It requires Stripe API version 2025-03-31.basil or later, an activated account, accepted terms and an eligible tax code on every product. Wreath adds managed_payments[enabled]=true, refuses a Connect combination, and requires the declaration to use ProviderMerchant.

from wreath.billing import ProviderMerchant

stripe = StripeBilling(
    client=stripe_http,
    api_key=Secret("rk_live_..."),
    api_version="2026-08-26.dahlia",
    allowed_return_origins=("https://app.example",),
    managed_payments=True,
)
billing = app.billing(
    "commerce",
    backend=stripe,
    catalog=PlanCatalog(Plan("pro", "price_managed_pro")),
    merchant=ProviderMerchant(),
    capture=HostedRedirect(),
)

Stripe still leaves product eligibility, product support and some jurisdictional tax cases with the deployment. billing.compliance_posture() therefore reports unresolved work rather than returning a compliance boolean.

Connect#

Connect account selection is a topology decision, separate from tenancy and merchant-of-record responsibility. ConnectedMerchants resolves the account from the billing subject; request callers cannot override it.

from wreath.billing import ConnectedMerchant, ConnectedMerchants
from wreath.billing.providers.stripe import DirectCharges, StripeConnect

stripe = StripeBilling(
    client=stripe_http,
    api_key=Secret("rk_live_..."),
    api_version="2026-08-26.dahlia",
    allowed_return_origins=("https://app.example",),
    connect=StripeConnect(
        DirectCharges(
            application_fee_percent="12.5",
            refund_application_fee=True,
        )
    ),
)
billing = app.billing(
    "marketplace",
    backend=stripe,
    catalog=PlanCatalog(Plan("pro", "price_connected_pro")),
    merchant=ConnectedMerchant(),
    topology=ConnectedMerchants(
        account_for=merchant_account_for_subject,
        price_for=provider_price_for_subject_plan_account,
        sku_for_price=sku_for_subject_provider_price_account,
    ),
    capture=HostedRedirect(),
)

Direct charges use the connected account's Price and the Stripe-Account header. Because Stripe Prices belong to an account, direct charges require paired forward and inverse Price resolvers. Wreath compares the inverse with the active subject and account before accepting a webhook projection. Destination charges use the platform Price and put the destination in the PaymentIntent or subscription. Their refund liability is fixed at declaration:

from wreath.billing import DeploymentMerchant
from wreath.billing.providers.stripe import DestinationCharges, DestinationRefunds

connect = StripeConnect(
    DestinationCharges(
        application_fee_percent="8",
        refunds=DestinationRefunds(
            reverse_transfer=True,
            refund_application_fee=True,
        ),
    )
)

Use ConnectedMerchant instead when on_behalf_of=True; otherwise destination charges use DeploymentMerchant. Separate charges and transfers are intentionally not accepted: they need a separate one-to-many settlement ledger rather than a Checkout option. For destination charges, Wreath writes the original destination account into namespaced Checkout, PaymentIntent or Subscription metadata. Authoritative projections recover that immutable route, so a later refund cannot silently follow a tenant's changed account mapping.

Portal, refunds and webhook truth#

billing.portal() creates a host-validated Stripe Customer Portal session. billing.refund() preserves Stripe's pending, action-required, succeeded, failed and canceled outcomes. Both operations use idempotency keys. Direct Connect operations carry the stored account scope; destination-refund transfer and application-fee recovery come from the declaration above. The application-owned customer_for resolver may return None for a first purchase; an existing Customer is sent to Checkout so a later portal session sees the same subscription. The payment_for resolver returns ProviderPayment, including currency and the original Connect account, so a partial refund cannot silently change currency or route through the subject's current account.

Use Wreath's existing Stripe signature verifier and durable webhook inbox. The projection policy separately pins the event destination's API version, live/test mode and account scope. Projection policies require 2025-03-31.basil or later because the invoice parser accepts only Stripe's current typed parent shape. Zero-total Checkout sessions project as succeeded Checkout facts keyed by their Session ID; they do not invent a refundable PaymentIntent.

from wreath.billing.providers.stripe import StripeWebhookPolicy
from wreath.webhooks import StripeWebhookVerifier

source = app.webhooks("billing").source(
    "stripe-account",
    path="/webhooks/stripe",
    verifier=StripeWebhookVerifier("whsec_..."),
    inbox=inbox,
    session_factory=session_factory,
)
projection_policy = StripeWebhookPolicy(
    event_version="2026-08-26.dahlia",
    livemode=True,
    scope="account",
)
billing.stripe_webhooks(
    source,
    webhook=projection_policy,
    checkout_subject_for=subject_for_checkout_reference,
    subscription_subject_for=subject_for_stripe_customer,
)

Webhook handlers run inside the inbox transaction, so applying the projection and marking the event processed commit together. The ledger makes invoice-first and subscription-first delivery equivalent across workers and restarts, deduplicates invoice IDs, retains the furthest paid-through instant and refuses a payment or subscription that changes subject, account, currency or amount. A subscription status event never advances paid-through on its own.

For direct charges, use a distinct Connect webhook endpoint and secret with scope="connected_accounts". The verified event must carry its top-level Stripe account, and the customer/account pair must resolve through the application's central mapping before any tenant scope is entered. Construct its projection with plan_for_price=billing.plan_for_provider_price; connected-account projections refuse to start without that inverse resolver.

SubscriptionEntitlements reads the local projection. Every non-trial state selected by an AccessPolicy grants only through paid_through; trials grant only before trial_ends_at. Past-due access is not selected by the default policy. Cedar resolves the plan and entitlements from one snapshot, so an account change cannot combine facts from two reads.

Read side and entitlements#

billing.queries() builds the subject-scoped PostgreSQL read side. Every lookup takes the application's internal billing subject; callers cannot supply a Stripe customer or Connect account as authority. Invoice history is keyset-paginated and bounded to 100 rows per page.

queries = billing.queries(session_factory)

subscription = await queries.subscription("organization:acme")
payment = await queries.payment("organization:acme", "pi_...")
page = await queries.invoices("organization:acme", limit=20)

The entitlement adapter performs one subscription query and gives Cedar one atomic plan and entitlement snapshot. The identity-to-subject mapping remains application-owned.

from wreath.authorization import CedarAuthorizer

entitlements = billing.entitlements(
    queries,
    subject_for=lambda identity: f"user:{identity.id}",
)
authorizer = CedarAuthorizer(engine=policies, entitlements=entitlements)

Reconciliation, counters and alerts#

billing.reconciliation() registers a durable Stripe reconciliation task on a Wreath JobRunner. The provider page is fetched without holding a database transaction. Projection writes and compare-and-swap cursor advancement then commit together. Pages are bounded to 100 objects, and every object must match the requested Connect account before the ledger sees it.

jobs = app.jobs("billing", database="main")
reconciliation = billing.reconciliation(
    jobs=jobs,
    session_factory=session_factory,
    retrieve_page=retrieve_stripe_reconciliation_page,
    merchant_accounts=(None,),
    cron="*/15 * * * *",
)

The billing control plane is automatically visible to wreath.metrics.collect(app) through billing.operations. Its cached counters perform no database or network I/O. Add its non-critical alert to a health router when webhook lag, reconciliation age, unknown command outcomes or dead outcomes need an operator rather than instance eviction.

billing.preflight() remains non-empty until the Stripe webhook binding and durable reconciliation have both been declared. It performs no I/O, so deployment tooling can inspect the built application before startup.

alert = billing.operations.alert(
    webhook_lag=300,
    reconciliation_age=1800,
)

Support and agent safety#

The support facade derives the billing subject from the linked chat identity and tenant. Reads are off until SupportAccess supplies both an application permission and Cedar authorization. Money movement is a second, separately disabled capability.

from wreath.billing import BillingSupport, SupportAccess

support = billing.support(
    reader=queries,
    subject_for=subject_for_support_identity,
    access=SupportAccess(authorize=authorize_support_read),
)
payment = await support.payment(chat_context, "pi_...")

A refund cannot be executed through the agent request path. Enabling refunds requires an explicit SupportMoneyMovement, a linked principal with billing.refund, a Cedar permit at proposal time and again at execution time, fresh authentication, a single-use human approval, and a completed audit write before Stripe is called. The exact subject, payment, amount, currency and reference are sealed into the approval record; button input carries only its opaque approval ID. Use the PostgreSQL store in any multi-process or multi-instance deployment so a different worker can safely receive the button callback.

from wreath.agents import ChatApprovalFlow, PostgresApprovalStore
from wreath.billing import SupportMoneyMovement

approval_store = PostgresApprovalStore(session_factory, schema="wreath")
chat_approval_flow = ChatApprovalFlow(chat, approval_store)
money = SupportMoneyMovement(
    approvals=chat_approval_flow,
    authorize=authorize_refund,
    audit=write_durable_billing_audit,
)
support = billing.support(
    reader=queries,
    subject_for=subject_for_support_identity,
    access=SupportAccess(authorize=authorize_support_read),
    money=money,
)

proposal = await support.propose_refund(
    chat_context,
    payment="pi_...",
    reference="support-case-1842",
    amount=Money("USD", 1000),
)

The call above only renders Approve and Deny controls. Stripe is reached only after the bound human clicks Approve. A delegated principal or any context carrying an agent request is refused, even if it has the permission and Cedar would otherwise permit it.

wreath.payments#

CheckoutItem#

CheckoutItem(price: 'str', quantity: 'int' = 1)

CheckoutItem(price: str, quantity: int = 1) -> None

CheckoutRequest#

CheckoutRequest(subject: 'str', items: 'tuple[CheckoutItem, ...]', mode: "Literal['payment', 'subscription']", success_url: 'str', cancel_url: 'str', reference: 'str', customer: 'str | None' = None, merchant_account: 'str | None' = None)

CheckoutRequest(subject: str, items: tuple[CheckoutItem, ...], mode: "Literal['payment', 'subscription']", success_url: str, cancel_url: str, reference: str, customer: str | None = None, merchant_account: str | None = None) -> None

CheckoutSession#

CheckoutSession(provider: 'str', id: 'str', url: 'str', expires_at: 'datetime | None' = None)

CheckoutSession(provider: str, id: str, url: str, expires_at: datetime | None = None) -> None

Money#

Money(currency: 'str', minor: 'int')

Money(currency: str, minor: int) -> None

PaymentSnapshot#

PaymentSnapshot(provider: 'str', id: 'str', subject: 'str', reference: 'str', amount: 'Money', state: 'PaymentState', customer: 'str | None' = None, merchant_account: 'str | None' = None)

PaymentSnapshot(provider: str, id: str, subject: str, reference: str, amount: Money, state: PaymentState, customer: str | None = None, merchant_account: str | None = None) -> None

PaymentState#

PortalRequest#

PortalRequest(subject: 'str', customer: 'str', return_url: 'str', reference: 'str', merchant_account: 'str | None' = None)

PortalRequest(subject: str, customer: str, return_url: str, reference: str, merchant_account: str | None = None) -> None

PortalSession#

PortalSession(provider: 'str', id: 'str', url: 'str')

PortalSession(provider: str, id: str, url: str) -> None

ProviderPayment#

ProviderPayment(provider_id: 'str', currency: 'str', merchant_account: 'str | None' = None)

ProviderPayment(provider_id: str, currency: str, merchant_account: str | None = None) -> None

Refund#

Refund(provider: 'str', id: 'str', state: 'RefundState', amount: 'Money')

Refund(provider: str, id: str, state: RefundState, amount: Money) -> None

RefundRequest#

RefundRequest(payment: 'str', reference: 'str', amount: 'Money | None' = None, merchant_account: 'str | None' = None)

RefundRequest(payment: str, reference: str, amount: Money | None = None, merchant_account: str | None = None) -> None

RefundState#

wreath.subscriptions#

AccessPolicy#

AccessPolicy(granted: 'frozenset[SubscriptionState]' = frozenset({<SubscriptionState.ACTIVE: 'active'>, <SubscriptionState.TRIALING: 'trialing'>}))

AccessPolicy(granted: frozenset[SubscriptionState] = frozenset({<SubscriptionState.ACTIVE: active>, <SubscriptionState.TRIALING: trialing>})) -> None

Plan#

Plan(sku: 'str', provider_price: 'str', entitlements: 'frozenset[str]' = frozenset(), mode: "Literal['payment', 'subscription']" = 'subscription')

Plan(sku: str, provider_price: str, entitlements: frozenset[str] = frozenset(), mode: "Literal['payment', 'subscription']" = 'subscription') -> None

PlanCatalog#

PlanCatalog(*plans: Plan) -> None

entitlement_names (property)#

entitlement_names: frozenset[str]

for_provider_price#

for_provider_price(provider_price: str) -> Plan

SubscriptionAccess#

SubscriptionAccess(plan: 'str | None', entitlements: 'frozenset[str]')

SubscriptionAccess(plan: str | None, entitlements: frozenset[str]) -> None

SubscriptionEntitlements#

SubscriptionEntitlements(catalog: PlanCatalog, *, subscription_for: Callable[[Any], SubscriptionSnapshot | None] | None = None, subscription_for_request: Callable[[Any], SubscriptionSnapshot | None] | None = None, access: AccessPolicy = AccessPolicy(granted=frozenset({<SubscriptionState.ACTIVE: active>, <SubscriptionState.TRIALING: trialing>})), now: Callable[[], datetime] = <function _utc_now>) -> None

entitlements#

entitlements(identity: Any) -> frozenset[str]

for_request#

for_request(request: Any) -> frozenset[str]

names#

names() -> frozenset[str]

plan_for#

plan_for(identity: Any) -> str | None

plan_for_request#

plan_for_request(request: Any) -> str | None

resolve#

resolve(identity: Any) -> SubscriptionAccess

resolve_request#

resolve_request(request: Any) -> SubscriptionAccess

SubscriptionLedger#

SubscriptionLedger() -> None

apply#

apply(value: SubscriptionSnapshot | SubscriptionPayment) -> SubscriptionSnapshot | None

get#

get(provider: str, subscription: str, subject: str, *, merchant_account: str | None = None) -> SubscriptionSnapshot | None

SubscriptionPayment#

SubscriptionPayment(provider: 'str', invoice: 'str', subscription: 'str', subject: 'str', paid_through: 'datetime', merchant_account: 'str | None' = None)

SubscriptionPayment(provider: str, invoice: str, subscription: str, subject: str, paid_through: datetime, merchant_account: str | None = None) -> None

SubscriptionSnapshot#

SubscriptionSnapshot(provider: 'str', id: 'str', subject: 'str', plan: 'str', state: 'SubscriptionState', provider_state: 'str', paid_through: 'datetime | None' = None, trial_ends_at: 'datetime | None' = None, merchant_account: 'str | None' = None)

SubscriptionSnapshot(provider: str, id: str, subject: str, plan: str, state: SubscriptionState, provider_state: str, paid_through: datetime | None = None, trial_ends_at: datetime | None = None, merchant_account: str | None = None) -> None

SubscriptionState#

wreath.billing#

Billing#

Billing(name: str, *, backend: Any, catalog: PlanCatalog, merchant: DeploymentMerchant | ConnectedMerchant | ProviderMerchant, capture: HostedRedirect, topology: SharedMerchant | ConnectedMerchants = SharedMerchant(), customer_for: Any = None, payment_for: Any = None, ledger: Any = None) -> None

checkout#

async checkout(*, subject: str, plan: str, success_url: str, cancel_url: str, reference: str, quantity: int = 1) -> CheckoutSession

compliance_posture#

compliance_posture() -> CompliancePosture

counter_sources (property)#

counter_sources: tuple[BillingOperations, ...]

entitlements#

entitlements(queries: Any, *, subject_for: Any, access: AccessPolicy = AccessPolicy(granted=frozenset({<SubscriptionState.ACTIVE: active>, <SubscriptionState.TRIALING: trialing>})), now: Any = None) -> Any

plan_for_provider_price#

plan_for_provider_price(*, subject: str, provider_price: str, merchant_account: str | None = None) -> Plan

portal#

async portal(*, subject: str, return_url: str, reference: str) -> PortalSession

preflight#

preflight() -> tuple[str, ...]

queries#

queries(session_factory: Any) -> Any

reconciliation#

reconciliation(*, jobs: Any, session_factory: Any, retrieve_page: Any, merchant_accounts: tuple[str | None, ...] = (None,), cron: str | None = '*/15 * * * *', limit: int = 100) -> Any

refund#

async refund(*, subject: str, payment: str, reference: str, amount: Money | None = None) -> Refund

schema_owners (property)#

schema_owners: tuple[Any, ...]

stripe_webhooks#

stripe_webhooks(source: Any, *, webhook: Any, checkout_subject_for: Any, subscription_subject_for: Any) -> Any

support#

support(*, reader: Any, subject_for: Any, access: Any = None, money: Any = None, max_pending: int = 1024) -> Any

BillingCapabilities#

BillingCapabilities(hosted_checkout: 'bool' = False, hosted_portal: 'bool' = False, subscriptions: 'bool' = False, connect: 'bool' = False, refunds: 'bool' = False, account_scoped_prices: 'bool' = False, merchant: "Literal['deployment', 'connected', 'provider']" = 'deployment')

BillingCapabilities(hosted_checkout: bool = False, hosted_portal: bool = False, subscriptions: bool = False, connect: bool = False, refunds: bool = False, account_scoped_prices: bool = False, merchant: "Literal['deployment', 'connected', 'provider']" = 'deployment') -> None

merchant_of_record (property)#

merchant_of_record: bool

BillingConfigurationError#

CompliancePosture#

CompliancePosture(capture: 'str', cardholder_data: 'str', candidate_saq: 'str', merchant_of_record: 'str', unresolved: 'tuple[str, ...]')

CompliancePosture(capture: str, cardholder_data: str, candidate_saq: str, merchant_of_record: str, unresolved: tuple[str, ...]) -> None

ConnectedMerchant#

ConnectedMerchant()

ConnectedMerchant() -> None

ConnectedMerchants#

ConnectedMerchants(account_for: 'Any', price_for: 'Any' = None, sku_for_price: 'Any' = None)

ConnectedMerchants(account_for: Any, price_for: Any = None, sku_for_price: Any = None) -> None

DeploymentMerchant#

DeploymentMerchant()

DeploymentMerchant() -> None

HostedRedirect#

HostedRedirect()

HostedRedirect() -> None

ProviderMerchant#

ProviderMerchant()

ProviderMerchant() -> None

SharedMerchant#

SharedMerchant()

SharedMerchant() -> None

wreath.billing.ledger#

BillingCommand#

BillingCommand(identity: 'BillingCommandIdentity', state: 'BillingCommandState', fencing_token: 'int', provider_reference: 'str | None' = None, failure_code: 'str | None' = None)

BillingCommand(identity: BillingCommandIdentity, state: BillingCommandState, fencing_token: int, provider_reference: str | None = None, failure_code: str | None = None) -> None

BillingCommandIdentity#

BillingCommandIdentity(provider: 'str', operation: 'str', idempotency_key: 'str', digest: 'str', subject: 'str', merchant_account: 'str | None' = None)

BillingCommandIdentity(provider: str, operation: str, idempotency_key: str, digest: str, subject: str, merchant_account: str | None = None) -> None

BillingCommandState#

PostgresBillingLedger#

PostgresBillingLedger(*, schema: str = 'wreath', component_name: str = 'billing-ledger') -> None

advance#

async advance(session: Any, *, provider: str, merchant_account: str | None, expected: str | None, cursor: str | None) -> bool

apply_checkout#

async apply_checkout(session: Any, payment: PaymentSnapshot) -> None

apply_payment#

async apply_payment(session: Any, payment: SubscriptionPayment, *, merchant_account: str | None = None) -> None

apply_subscription#

async apply_subscription(session: Any, snapshot: SubscriptionSnapshot, *, merchant_account: str | None = None) -> None

claim_command#

async claim_command(session: Any, *, lease_owner: str, lease_seconds: float) -> BillingCommand | None

component#

component() -> Component

load#

async load(session: Any, *, provider: str, merchant_account: str | None) -> str | None

mark_confirmed#

async mark_confirmed(session: Any, command: BillingCommand, *, provider_reference: str) -> BillingCommand

mark_failed#

async mark_failed(session: Any, command: BillingCommand, *, failure_code: str) -> BillingCommand

mark_sending#

async mark_sending(session: Any, command: BillingCommand) -> BillingCommand

mark_unknown#

async mark_unknown(session: Any, command: BillingCommand, *, failure_code: str) -> BillingCommand

register_command#

async register_command(session: Any, identity: BillingCommandIdentity) -> BillingCommand

schema_sql#

schema_sql() -> str

settle_expired_sending#

async settle_expired_sending(session: Any, *, limit: int = 1000) -> int

wreath.billing.operations#

BillingOperations#

BillingOperations(name: str, *, clock: Any = <built-in function monotonic>) -> None

alert#

alert(*, webhook_lag: float, reconciliation_age: float, timeout: float | None = 1.0) -> HealthCheck

counters#

counters() -> Counters

outcome_dead#

outcome_dead(count: int = 1) -> None

outcome_resolved#

outcome_resolved(*, unknown: int = 0, dead: int = 0) -> None

outcome_unknown#

outcome_unknown(count: int = 1) -> None

reconciliation_completed#

reconciliation_completed() -> None

reconciliation_failed#

reconciliation_failed() -> None

webhook_applied#

webhook_applied() -> None

webhook_failed#

webhook_failed() -> None

BillingOperationsUnhealthy#

wreath.billing.queries#

InvoiceCursor#

InvoiceCursor(paid_through: 'datetime', invoice: 'str')

InvoiceCursor(paid_through: datetime, invoice: str) -> None

InvoicePage#

InvoicePage(items: 'tuple[SubscriptionPayment, ...]', next_cursor: 'InvoiceCursor | None')

InvoicePage(items: tuple[SubscriptionPayment, ...], next_cursor: InvoiceCursor | None) -> None

PostgresBillingQueries#

PostgresBillingQueries(session_factory: Callable[[], Any], *, provider: str, merchant_account_for: Callable[[str], str | None] | None = None, schema: str = 'wreath') -> None

command#

async command(subject: str, operation: str, idempotency_key: str) -> BillingCommand | None

invoice#

async invoice(subject: str, invoice: str) -> SubscriptionPayment | None

invoices#

async invoices(subject: str, *, subscription: str | None = None, cursor: InvoiceCursor | None = None, limit: int = 20) -> InvoicePage

payment#

async payment(subject: str, payment: str) -> PaymentSnapshot | None

subscription#

async subscription(subject: str) -> SubscriptionSnapshot | None

PostgresSubscriptionEntitlements#

PostgresSubscriptionEntitlements(queries: PostgresBillingQueries, catalog: PlanCatalog, *, subject_for: Callable[[Any], str], access: AccessPolicy = AccessPolicy(granted=frozenset({<SubscriptionState.ACTIVE: active>, <SubscriptionState.TRIALING: trialing>})), now: Callable[[], datetime] | None = None) -> None

entitlements#

async entitlements(identity: Any) -> frozenset[str]

names#

names() -> frozenset[str]

plan_for#

async plan_for(identity: Any) -> str | None

resolve#

async resolve(identity: Any) -> SubscriptionAccess

resolve_request#

async resolve_request(request: Any) -> SubscriptionAccess

wreath.billing.reconciliation#

ReconciliationPage#

ReconciliationPage(resources: 'tuple[ReconciliationResource, ...]', cursor: 'str | None', has_more: 'bool')

ReconciliationPage(resources: tuple[ReconciliationResource, ...], cursor: str | None, has_more: bool) -> None

ReconciliationSnapshot#

ReconciliationSnapshot(name: 'str', merchant_account: 'str | None', cursor: 'str | None', pages_completed: 'int', resources_applied: 'int', failures: 'int', running: 'int')

ReconciliationSnapshot(name: str, merchant_account: str | None, cursor: str | None, pages_completed: int, resources_applied: int, failures: int, running: int) -> None

StripeReconciliation#

StripeReconciliation(name: str, *, jobs: Any, session_factory: Any, state: Any, ledger: Any, retrieve_page: Any, merchant_accounts: tuple[str | None, ...] = (None,), cron: str | None = '*/15 * * * *', limit: int = 100, operations: BillingOperations | None = None) -> None

request#

async request(merchant_account: str | None = None) -> int | None

run_once#

async run_once(merchant_account: str | None = None) -> ReconciliationSnapshot

snapshot#

snapshot() -> ReconciliationSnapshot

wreath.billing.stripe_webhooks#

bind_stripe_webhooks#

bind_stripe_webhooks(source: WebhookSource, *, billing: Billing, webhook: StripeWebhookPolicy, checkout_subject_for: Any, subscription_subject_for: Any, operations: BillingOperations | None = None) -> WebhookSource

stripe_webhooks#

stripe_webhooks(source: WebhookSource, *, billing: Billing, webhook: StripeWebhookPolicy, checkout_subject_for: Any, subscription_subject_for: Any, operations: BillingOperations | None = None) -> WebhookSource

wreath.billing.support#

BillingAuditEvent#

BillingAuditEvent(action: 'str', actor: 'str', subject: 'str', resource: 'str', approval_id: 'str')

BillingAuditEvent(action: str, actor: str, subject: str, resource: str, approval_id: str) -> None

BillingSupport#

BillingSupport(*, billing: Any, reader: Any, subject_for: Callable[[Identity, str], str], access: SupportAccess | None = None, money: SupportMoneyMovement | None = None, max_pending: int = 1024) -> None

command (property)#

command: Callable[..., Awaitable[BillingCommand | None]]

invoices (property)#

invoices: Callable[..., Awaitable[InvoicePage]]

payment (property)#

payment: Callable[..., Awaitable[PaymentSnapshot | None]]

propose_refund (property)#

propose_refund: Callable[..., Awaitable[ChatReply]]

schema_owners (property)#

schema_owners: tuple[Any, ...]

subscription (property)#

subscription: Callable[..., Awaitable[SubscriptionSnapshot | None]]

MoneyMovementDisabled#

SupportAccess#

SupportAccess(authorize: 'Callable[[ChatContext, PolicyRequirement], Awaitable[AuthorizationDecision]]', permission: 'str' = 'billing.read', action: 'str' = 'billing.read')

SupportAccess(authorize: Callable[[ChatContext, PolicyRequirement], Awaitable[AuthorizationDecision]], permission: str = 'billing.read', action: str = 'billing.read') -> None

SupportAccessDisabled#

SupportMoneyMovement#

SupportMoneyMovement(approvals: 'ChatApprovalFlow', authorize: 'Callable[[ChatContext, PolicyRequirement], Awaitable[AuthorizationDecision]]', audit: 'Callable[[BillingAuditEvent], Awaitable[None]]', ttl: 'float' = 300.0, permission: 'str' = 'billing.refund', action: 'str' = 'billing.refund')

SupportMoneyMovement(approvals: ChatApprovalFlow, authorize: Callable[[ChatContext, PolicyRequirement], Awaitable[AuthorizationDecision]], audit: Callable[[BillingAuditEvent], Awaitable[None]], ttl: float = 300.0, permission: str = 'billing.refund', action: str = 'billing.refund') -> None

wreath.billing.providers.stripe#

DestinationCharges#

DestinationCharges(refunds: 'DestinationRefunds', application_fee_percent: 'str | None' = None, application_fee_amount: 'int | None' = None, on_behalf_of: 'bool' = False)

DestinationCharges(refunds: DestinationRefunds, application_fee_percent: str | None = None, application_fee_amount: int | None = None, on_behalf_of: bool = False) -> None

DestinationRefunds#

DestinationRefunds(reverse_transfer: 'bool', refund_application_fee: 'bool')

DestinationRefunds(reverse_transfer: bool, refund_application_fee: bool) -> None

DirectCharges#

DirectCharges(application_fee_percent: 'str | None' = None, application_fee_amount: 'int | None' = None, refund_application_fee: 'bool' = False)

DirectCharges(application_fee_percent: str | None = None, application_fee_amount: int | None = None, refund_application_fee: bool = False) -> None

SeparateChargesAndTransfers#

SeparateChargesAndTransfers()

SeparateChargesAndTransfers() -> None

StripeBilling#

StripeBilling(*, client: Any, api_key: Secret[str], api_version: str, allowed_return_origins: tuple[str, ...], managed_payments: bool = False, connect: StripeConnect | None = None) -> None

compliance_unresolved#

compliance_unresolved() -> tuple[str, ...]

create_checkout#

async create_checkout(request: CheckoutRequest, *, idempotency_key: str) -> CheckoutSession

create_portal#

async create_portal(request: PortalRequest, *, idempotency_key: str) -> PortalSession

create_refund#

async create_refund(request: RefundRequest, *, idempotency_key: str) -> Refund

retrieve_checkout#

async retrieve_checkout(session_id: str, merchant_account: str | None) -> dict[str, Any]

retrieve_subscription#

async retrieve_subscription(subscription_id: str, merchant_account: str | None) -> dict[str, Any]

StripeConnect#

StripeConnect(charges: 'DirectCharges | DestinationCharges')

StripeConnect(charges: DirectCharges | DestinationCharges) -> None

StripeError#

StripeInvoiceProjection#

StripeInvoiceProjection(*, webhook: StripeWebhookPolicy, subject_for: Any) -> None

project#

project(envelope: WebhookEnvelope) -> SubscriptionPayment | None

StripeSubscriptionProjection#

StripeSubscriptionProjection(catalog: PlanCatalog, *, webhook: StripeWebhookPolicy, subject_for: Any, retrieve_subscription: Any, plan_for_price: Any = None) -> None

project#

async project(envelope: WebhookEnvelope) -> SubscriptionSnapshot | None

StripeWebhookPolicy#

StripeWebhookPolicy(event_version: 'str', livemode: 'bool', scope: "Literal['account', 'connected_accounts']")

StripeWebhookPolicy(event_version: str, livemode: bool, scope: "Literal['account', 'connected_accounts']") -> None

validate#

validate(envelope: WebhookEnvelope, payload: dict[str, Any]) -> str | None

wreath.billing.providers.stripe_checkout#

StripeCheckoutProjection#

StripeCheckoutProjection(*, webhook: StripeWebhookPolicy, retrieve_checkout: Callable[[str, str | None], Awaitable[dict[str, Any]]], subject_for: Callable[[str, str | None, str | None], str | None]) -> None

project#

async project(envelope: WebhookEnvelope) -> PaymentSnapshot | None
esc
↑↓ navigate↵ openesc close