Manage owned subscriptions
Use the facade with a saved, authorized billable. It checks local ownership before subscription mutations. A raw Vatly customer ID is never enough to authorize one.
Read current local subscriptions
from vatly_django.billing import Billing
def example(billable):
return list(
Billing().resources(billable, "subscription").values("vatly_id", "snapshot", "ends_at")
)
| Stored field | Meaning |
|---|---|
vatly_id | Subscription identifier |
snapshot | Full Vatly resource using wire keys, including status |
ends_at | Derived expiry, not cancellation request time |
entity_kind, entity_id, scope | Host ownership and tenant/storefront/mode |
BillableMixin.subscribed() requires active/trial/on_grace_period status and no
expired local end. It is a convenience policy, not your complete entitlement rule.
Failed or stale synchronization must not be treated as proof of access.
Cancel or resume from a user action
These functions belong in authenticated, CSRF-protected POST handlers:
from vatly_django.billing import Billing
def example(billable):
Billing().subscription_action(billable, "subscription_example", "cancel", immediately=False)
from vatly_django.billing import Billing
def example(billable):
Billing().subscription_action(billable, "subscription_example", "resume")
| Action | Behavior |
|---|---|
cancel | Request cancellation; signed events update local state |
resume | Resume during grace and persist the API response |
sync | Refresh a subscription snapshot |
swap, swap_and_invoice, swap_without_charge | Plan-change policies; pass plan_id |
The facade's action helper returns None. Use Fluent/SDK RequestOptions directly when your mutation workflow needs an explicit stable idempotency key.
Reconcile a known checkout
from vatly_django.billing import Billing
def example(billable):
result = Billing().reconcile(billable, "checkout_example")
# Schedule a bounded later reconcile while either tuple is not empty.
return result.unresolved_line_ids, result.pending_subscription_ids
The facade checks the checkout's host record, performs bounded API reads and persists snapshots/links transactionally. This may hold the scope lock during reads; run it in a worker for busy deployments. It emits no created outbox event.
Signup lines reference their plan and name their subscription in
relatedResources.subscriptionId; renewal lines reference the subscription as
their product. Both link the subscription to the checkout's billable.
| Result field | Meaning | What to do |
|---|---|---|
subscriptions | Linked and stored subscriptions | Nothing |
pending_subscription_ids | Referenced, but not readable yet | Reconcile again later |
unresolved_line_ids | Signup lines whose subscriptionId is still null | Reconcile again later; it may never resolve |
legacy_plan_ids | Plan lines on historical orders without relatedResources | Resolve manually |
A signed order.paid snapshot is frozen when it is sent and can carry a null
subscriptionId, so the first order may still need an explicit reconcile. Schedule
a bounded number of later reconciles while unresolved_line_ids is not empty.
Never guess by customer/plan/quantity. Missing subscription IDs remain pending,
not entitled. Trial flows may lack an order.paid event.
Grace expiry uses Fluent's shared mapper. canceledAt is the request time.
Missing or ambiguous period deadlines raise and retain the prior committed state;
monitor and reconcile instead of inventing an expiry. Active snapshots clear stale
ends after resume. Fluent's guide
describes this policy and its limits.