> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sakawave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# eSIM use cases

> Four flows — a travel checkout, topping up instead of reselling, warning before the data runs out, and reconciling a month.

## 1. A travel checkout

The whole service in one flow, with the price confirmation where it belongs.

```ts theme={null}
// 1. The customer picks a destination and a plan from your cached catalog.
const plan = await get(`/v1/esims/catalog/packages/${form.planSlug}`);

// 2. Ask what it costs from the account they are paying with. Skip only if
//    that account is USD, where price and payable are the same thing.
const quote = await post('/v1/esims/quote', {
  packageSlug: plan.slug,
  quantity: 1,
  debitAccountId: form.accountId,
});

// 3. Show the figure that will actually leave the account — not the USD price.
const confirmed = await confirm({
  plan: plan.name,
  data: plan.data.display,
  validFor: `${plan.validityDays} days`,
  youPay: `${quote.payable.display} ${quote.payable.currency}`,
  rate: quote.rate ? `1 USD = ${quote.rate} ${quote.payable.currency}` : null,
});
if (!confirmed) return;

// 4. Quotes lapse in about two minutes. If they hesitated, ask again — and
//    show the new figure rather than charging it silently.
let live = quote;
if (Date.parse(quote.expiresAt) - Date.now() < 15_000) {
  live = await post('/v1/esims/quote', { /* same body */ });
  if (!(await confirmPriceChanged(live))) return;
}

// 5. Buy. This debits immediately and provisions afterwards.
const order = await post('/v1/esims', {
  packageSlug: plan.slug,
  quantity: 1,
  debitAccountId: form.accountId,
  fxQuoteId: live.fxQuoteId ?? undefined,   // omitted for a USD account
  externalReference: `esim_${orderId}`,     // your key; reuse on retry
});

// 6. Almost always `provisioning`. Show a waiting state, not a spinner —
//    and let them leave the page.
await showPreparing(order);
```

Fulfilment arrives as `esim.order.completed`, or you poll the order. Either way the last step is the same: fetch `/installation` and put the QR in front of them.

## 2. Top up instead of selling a second eSIM

A customer whose data ran out mid-trip does not want to reinstall anything.

```ts theme={null}
const esim = await get(`/v1/esims/${esimId}`);

// Only plans covering the same destination can extend it; a plan for
// somewhere else would sell coverage the SIM cannot use.
const plans = await get(
  `/v1/esims/catalog/regions/${esim.package.region.code}/packages`,
);
const topUps = plans.items.filter((p) => p.supportsTopUp);

const quote = await post(`/v1/esims/${esimId}/top-ups/quote`, {
  packageSlug: chosen.slug,
  debitAccountId: account.id,
});

await post(`/v1/esims/${esimId}/top-ups`, {
  packageSlug: chosen.slug,
  debitAccountId: account.id,
  fxQuoteId: quote.fxQuoteId ?? undefined,
  externalReference: `esim_topup_${topUpId}`,
});
```

The allowance grows on the profile they already have. Nothing is reinstalled, and the ICCID does not change.

<Note>
  A depleted eSIM can still be topped up. Do not treat `depleted` as the end of its life — it is the moment to offer more data.
</Note>

## 3. Warn before the data runs out

Two events fire on their own; you do not need to poll for either.

```ts theme={null}
switch (event.event) {
  case 'esim.usage.low':
    // Under ~100 MB left. Fires once, not on every sync.
    await notify(customerFor(event.data.id), {
      title: 'Your data is running low',
      body: `${format(event.data.data.remainingBytes)} left on your ${event.data.package.name} eSIM.`,
      action: { label: 'Add more data', esimId: event.data.id },
    });
    break;

  case 'esim.expiry.near':
    // Under a day of validity left. Also once.
    await notify(customerFor(event.data.id), {
      title: 'Your eSIM expires tomorrow',
      body: 'Top it up to keep the same eSIM working.',
    });
    break;

  case 'esim.profile.depleted':
    // Allowance gone. The eSIM still exists and can be topped up.
    await offerTopUp(event.data.id);
    break;
}
```

Each of `esim.usage.low` and `esim.expiry.near` fires **once per eSIM**, so you can send them straight through without keeping your own "have I already warned them" flag.

## 4. Reconcile a month

Orders carry both figures, which is what makes them reconcilable against two different things.

```ts theme={null}
const orders = await paginate('/v1/esims/orders', { pageSize: 100 });

for (const order of orders) {
  // `paid` reconciles against the customer's statement and your ledger:
  // it is what actually left the account, in the account's currency.
  ledger.match(order.externalReference, order.paid.amount, order.paid.currency);

  // `price` reconciles against the catalog: what the plan listed for, in USD,
  // regardless of who paid in what.
  revenue.record(order.package.code, order.price.amount);

  // A refund is money already returned. It is not an exception to chase.
  if (Number(order.refunded.amount) > 0) {
    ledger.matchCredit(order.id, order.refunded.amount, order.refunded.currency);
  }
}
```

<Warning>
  Do not reconcile `price` against a bank statement. It is USD; the statement is in whatever the account holds. `paid` is the only figure that will agree with a bank.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.