Load a locally owned subscription, authorize its entity, then use a handle for
plan changes or cancellation. Examples accept the configured vatly from
Configuration; its repositories must be wired.
from vatly import RequestOptions
from vatly_fluent import EntityRef, FluentError, Vatly
async def example(vatly: Vatly):
record = await vatly.wiring.subscriptions.find_by_vatly_id("subscription_example")
if record is None or record.entity != EntityRef("team", "42"):
raise FluentError("Subscription is not owned by this team")
handle = vatly.subscription(record)
await handle.swap(
"subscription_plan_pro",
options=RequestOptions(
idempotency_key="team-42:plan-change-1",
),
)
return handle.model().resourceThe host must authorize team 42 separately. A handle's model() is local; methods
such as sync() make API calls. Scheduled changes do not prematurely replace the
active plan.
| Method | Behavior |
|---|---|
swap |
Schedule a plan change by default |
swap_and_invoice |
Apply and invoice immediately |
swap_without_charge |
Apply now, disable proration charge |
cancel(immediately=False) |
Request cancellation; wait for signed events to update local state |
resume |
Resume through the API and save its returned snapshot |
sync |
Refresh full resource and local expiry |
update_billing |
Return hosted billing URL; requires both redirect URLs |
from vatly import RequestOptions
from vatly_fluent import LocalSubscription, Vatly
async def example(vatly: Vatly, local: LocalSubscription):
# local has already been authorized by the caller.
await vatly.subscription(local).cancel(
immediately=False, options=RequestOptions(idempotency_key="cancel:42")
)Only offer resume while Vatly permits it. scheduled_update(),
has_scheduled_update() and cancellation_reason() read remotely.
from vatly_fluent import LocalSubscription, Vatly
async def example(vatly: Vatly, local: LocalSubscription):
handle = vatly.subscription(local)
await handle.sync()
return handle.model().resource.status, handle.state()state() uses ends_at only. Check the resource status and your product's access
rules too. canceled_at is the request time, not grace expiry. The shared mapper
uses actual ended_at, otherwise usable future renewal/paid/trial information or
a trusted prior end. Missing or conflicting trial/cycle deadlines raise
FluentError, preserving committed state. Monitor and reconcile failures; stale
state is not proof of continuing access. An active snapshot clears stale expiry.
from vatly_fluent import Vatly
async def example(vatly: Vatly):
result = await vatly.reconcile_checkout("checkout_example", attempts=2)
return result.subscriptions, result.pending_subscription_ids, result.legacy_plan_idsAuthorize the checkout and wrap local writes in your driver's transaction. Reads
follow checkout → order → distinct subscription IDs from each line's
relatedResources.subscriptionId and from subscription renewal lines; repeated
404s remain pending. A signup line whose subscriptionId is still null is
reported in unresolved_line_ids: core has not resolved that subscription yet, so
reconcile again later; it may never resolve. Plan lines on historical orders
without relatedResources are reported as legacy_plan_ids. A plan/customer
match is never an ownership fallback. Trial flows can lack order.paid, so reconciliation
must be explicit. Unknown linkage must not grant access.