Vatly
JavaScript Fluent

Orders and invoices

Fetch an order's invoice link, lines, and totals after checking the caller owns it.

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.

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.

Copyright © 2026