# Payments and ads

> Lia.pay, Lia.charges and Lia.onCharge for Lia's pay sheet, and Lia.showAd and its shortcuts for full screen ads. From the Lia developer docs: https://docs.lialabs.org/reference/sdk-payments-and-ads

## Payments

See [Payments and the pay sheet](/concepts/payments) for how they work, and [Sell an item](/guides/sell-an-item) for the whole flow.

### Lia.pay(productId, options)

Opens Lia's pay sheet for one of your products. Resolves once it's paid; rejects if it isn't.

```js
const { chargeId, productId } = await Lia.pay("extra-lives");

// With your own key (SDK 1.5): asking again with the same key charges once and answers with the same chargeId.
const order = "order-" + crypto.randomUUID();
const paid = await Lia.pay("extra-lives", { idempotencyKey: order });
```

| Parameter | Type | |
| --- | --- | --- |
| `productId` | string | One of your products' ids, up to 64 characters. |
| `options.idempotencyKey` | string | Optional, 1 to 64 of letters, digits, `_`, `-`, `.` and `:`. See [Idempotency keys](#idempotency-keys). |

Resolves with `{ chargeId, productId }`. Then send the `chargeId` to your server to deliver it.

| Error | When |
| --- | --- |
| `cancelled` | The person closed the sheet without paying. |
| `no_product` | No active product with that id. |
| `busy` | Another of Lia's screens is open over your app. |
| `bad_productId` | The id isn't text up to 64 characters. |
| `bad_idempotency_key` | The key isn't in the right shape. |
| `not_ready`, `not_in_lia` | Lia isn't ready, or this isn't Lia. |

If the page reloads while the sheet is open, the charge arrives as a `"charge"` event instead (see `Lia.onCharge`).

### Idempotency keys

From SDK 1.5, the calls that change something (`Lia.pay`, `Lia.inviteFriend`, `Lia.submitScore` and cloud storage's writes) take an optional `idempotencyKey`. The same key again, within 24 hours, doesn't do it twice: it answers as the first call did.

- **Lia already keeps each call once** when it retries it itself (a dropped connection). Your own key adds the same safety across your page's retries and reloads: keep it (in `Lia.deviceStorage`, say) until the call is answered.
- **Make one key per action** (`crypto.randomUUID()` is fine) and reuse it only to retry that action.
- **The same key with different arguments** is refused. Use a new key for a new action.
- For a payment, the pay sheet still opens for the person to confirm; confirming with a key that already paid answers with that first charge and charges nothing more.
- A Lia app older than SDK 1.5's release ignores the key, so the call still works, just without your key.

### Lia.charges()

This player's paid charges that your server hasn't delivered yet, oldest first, up to 50. Call it when your app opens, and deliver any you find.

```js
const pending = await Lia.charges(); // [{ chargeId, productId }]
for (const charge of pending) await deliverOnServer(charge.chargeId);
```

Resolves with an array of `{ chargeId, productId }`.

### Lia.onCharge(listener)

Hears a charge that was confirmed while no `Lia.pay()` was waiting for it, such as after a reload. The listener gets `{ chargeId, productId }`. Returns a function that stops listening. The same as `Lia.on("charge", listener)`.

## Ads

See [Ads](/concepts/ads) for the formats, pacing and checking on your server.

### Lia.showAd(options)

Plays a full screen ad over your app. Resolves once it's closed.

```js
const result = await Lia.showAd({ format: "rewarded" });
if (result.watched) giveReward(result.adId);
```

| Option | Type | |
| --- | --- | --- |
| `format` | string | `"rewarded"` (the default), `"rewardedInterstitial"` or `"interstitial"`. |
| `onState` | function | Optional. Hears each stage: `"loading"`, `"showing"`, `"closed"`. |

Resolves with:

| Field | Type | |
| --- | --- | --- |
| `watched` | boolean | A rewarded ad was watched to the end. |
| `shown` | boolean | An ad went on screen. |
| `adId` | string | Present when `watched` is true, and when an interstitial was shown. Check it on your server with [`POST /ads/verify`](/reference/server-api#post-ads-verify). |
| `reason` | string | When `watched` is false: `cancelled`, `skipped`, `no_ad`, `too_soon`, `failed` or a connection problem. |

Errors: `bad_format`, `busy`. Not having an ad to show isn't an error: it resolves with `reason: "no_ad"`.

### Lia.showRewardedAd(options)

The same as `Lia.showAd({ format: "rewarded", onState })`. A video the person chose to watch.

### Lia.showRewardedInterstitialAd(options)

The same as `Lia.showAd({ format: "rewardedInterstitial", onState })`. Lia first asks the person, with Watch and Skip; Skip resolves with `reason: "skipped"`.

### Lia.showInterstitialAd(options)

The same as `Lia.showAd({ format: "interstitial", onState })`. Paced by Lia: when it isn't its turn, it resolves straight away with `shown: false, reason: "too_soon"`.

```js
// Between rounds: ask every time, and let Lia decide.
await Lia.showInterstitialAd();
startNextRound();
```

### Lia.onAd(listener)

Hears every ad's stages, `{ state, format }`, with `state` one of `"loading"`, `"showing"` and `"closed"`. Returns a function that stops listening. The same as `Lia.on("ad", listener)`.
