Orders and invoices
Fetch an order's invoice link, lines, and totals after checking the caller owns it.
Read the order and invoice
import type { Vatly } from '@getvatly/fluent';
export async function invoice(vatly: Vatly, orderId: string) {
const order = vatly.order(orderId);
const model = await order.model();
return {
id: model.id,
status: model.status,
total: model.total,
lines: await order.lines(),
invoiceUrl: await order.invoiceUrl(),
};
}
The handle fetches once and caches the API resource. sync() refreshes it.
invoiceUrl() returns null if no link exists. These reads need no local order or
line repository. Use webhooks to persist payments and fulfill orders.
Link order lines to subscriptions
Each line's relatedResources.subscriptionId names the subscription it belongs
to. Signup, renewal and immediate-update lines all use it. Signup lines keep
productType: 'subscription_plan' with the plan in productId. Renewals use
'subscription'.
import type { Order } from '@getvatly/sdk';
export function subscriptionLinks(order: Order) {
const linked = new Set<string>();
const unresolved: string[] = [];
for (const line of order.lines) {
const id = line.relatedResources?.subscriptionId;
if (id) linked.add(id);
else if (line.productType === 'subscription_plan') unresolved.push(line.id);
}
return { subscriptionIds: [...linked], unresolvedLineIds: unresolved };
}
relatedResources is null on historical and unrelated lines, such as one-off
products, and older payloads may omit it. { subscriptionId: null } means a
signup's subscription is not available yet. It does not mean creation failed.
Fetch the order again later with vatly.order(id).sync(), because a stored
order.paid snapshot keeps the value it was signed with. An ID does not prove
the subscription is active or that access is owed. Check its status and owner first.
order.paid handling stores the whole signed order, including these links, in
your orders repository. Fluent does not query orders by subscription. Index the
stored line IDs yourself if you need that.
Money and reversals
Use toMinorUnits for bigint arithmetic; keep the currency with the amount.
import { toMinorUnits } from '@getvatly/sdk';
import type { Vatly } from '@getvatly/fluent';
export async function reversalSummary(vatly: Vatly, orderId: string) {
const order = vatly.order(orderId);
const model = await order.model();
return {
totalMinor: toMinorUnits(model.total),
reversedMinor: await order.reversedSubtotal(),
refundableMinor: await order.refundableSubtotal(),
fullyReversed: await order.isFullyReversed(),
partiallyReversed: await order.isPartiallyReversed(),
};
}
Money uses decimal strings, not floats. Convert bigints to strings before returning
JSON. Reversal predicates read the cached API resource, not local reversal lists.
isReversed() includes partial reversals. A paid order stays paid after a reversal.
order.refunds() and order.chargebacks() read your wired reversal repositories.
Without a driver they return an empty list. Use vatly.api.orders and
vatly.api.refunds to create refunds or query remote reversals. Check API state
before retrying a refund whose outcome is unknown.
For fields and refund rules, see Orders, Order Refunds, and Chargebacks in the API reference.