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

# SDK v3 Reference

> Use the Pagou browser SDK v3 API to mount hosted card fields, submit card payments, and handle 3D Secure.

Use this page when you need the exact browser SDK contract for Payment Element v3.

## Load the SDK

```html theme={null}
<script src="https://js.pagou.ai/payments/v3.js"></script>
```

The script exposes `window.Pagou`.

```js theme={null}
Pagou.setEnvironment("sandbox");
```

Supported environments:

| Environment  | Use                                                |
| ------------ | -------------------------------------------------- |
| `production` | Default. Uses production collection endpoints.     |
| `sandbox`    | Use for test public keys and sandbox transactions. |
| `local`      | Local development only.                            |

## Initialize Elements

```js theme={null}
const elements = Pagou.elements({
  publicKey: "pk_test_your_public_key",
  locale: "en",
  origin: window.location.origin,
});
```

Options:

| Option      | Required          | Description                                                                          |
| ----------- | ----------------- | ------------------------------------------------------------------------------------ |
| `publicKey` | Yes before submit | Company public key. Use `pk_test_*` in sandbox and `pk_live_*` in production.        |
| `locale`    | No                | Locale forwarded to the hosted card field. Defaults to `pt-BR`.                      |
| `origin`    | No                | Checkout origin stored on the Element session. Defaults to `window.location.origin`. |

You can update an existing instance before submit:

```js theme={null}
elements.update({
  publicKey: "pk_live_your_public_key",
  locale: "en",
});
```

## Create and mount the card field

```html theme={null}
<div id="card-element"></div>
```

```js theme={null}
const card = elements.create("card", {
  theme: "default",
  locale: "en",
});

card.mount("#card-element");
```

`elements.create("card")` creates one hosted card field. If a card field already exists on the same `elements` instance, the SDK unmounts it before creating the new one.

Card options:

| Option           | Description                                                                   |
| ---------------- | ----------------------------------------------------------------------------- |
| `theme`          | `default`, `night`, or `flat`.                                                |
| `locale`         | Overrides the Elements locale for this card field.                            |
| `style`          | Style object forwarded to the hosted field.                                   |
| `mountTimeoutMs` | Mount timeout in milliseconds. Defaults to `8000`.                            |
| `telemetry`      | Sends best-effort mount failure telemetry when supported. Defaults to `true`. |

## Card events

```js theme={null}
card.on("ready", () => {
  messageEl.textContent = "";
});

card.on("change", (event) => {
  submitButton.disabled = !event.valid;
  brandEl.textContent = event.brand ?? "";
  errorEl.textContent = Object.values(event.errors ?? {})[0] ?? "";
});

card.on("error", (event) => {
  errorEl.textContent = event.message;
});
```

Supported events:

| Event    | Payload                    | Use                                                   |
| -------- | -------------------------- | ----------------------------------------------------- |
| `ready`  | `{}`                       | Hosted iframe loaded and completed the SDK handshake. |
| `change` | `{ valid, brand, errors }` | Enable submit only when `valid` is `true`.            |
| `error`  | `{ code, message }`        | Show initialization, mount, or card field errors.     |

Remove handlers during component teardown when the same card instance can stay alive:

```js theme={null}
card.off("change", handleCardChange);
```

## Submit a payment

`elements.submit(...)` is the main SDK action. It creates an Element session, tokenizes the hosted card field, calls your `createTransaction` callback, completes any card authentication the payment requires, and resolves the final result.

```js theme={null}
const result = await elements.submit({
  createTransaction: async (tokenData) => {
    const response = await fetch("/api/pay", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        external_ref: "order_2001",
        amount: 2490,
        currency: "BRL",
        method: "credit_card",
        token: tokenData.token,
        installments: 1,
      }),
    });

    const payload = await response.json();
    return payload.data ?? payload;
  },
});
```

`createTransaction` receives:

```json theme={null}
{
  "token": "pgct_token_from_browser",
  "brand": "visa",
  "last4": "4242",
  "exp_month": "12",
  "exp_year": "2029"
}
```

Only send `token` to your backend as the payment credential. Treat `brand`, `last4`, `exp_month`, and `exp_year` as display or bookkeeping metadata.

## Submit result

```json theme={null}
{
  "status": "pending",
  "transaction": {
    "id": "018f1f2e-7b43-7c9a-8d3e-1a2b3c4d5e70",
    "status": "pending"
  }
}
```

Possible `status` values include:

| Status             | Meaning                                                                                                                                                                          |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Transaction status | When nothing further is required, the SDK returns the transaction status from your backend response.                                                                             |
| `completed`        | Fallback when the backend response has no `status`.                                                                                                                              |
| `processing`       | The payment is still resolving. Wait for webhook or reconciliation.                                                                                                              |
| `requires_action`  | Further action is required and automatic handling is disabled for the Element session.                                                                                           |
| `requires_reentry` | Returned by `resume()` when a pre-charge 3DS payment needs the card re-entered after a reload. Re-mount a `CardElement` and call `resume()` again with the same `transactionId`. |
| `succeeded`        | The payment was approved.                                                                                                                                                        |
| `failed`           | The payment failed, or required card authentication could not be completed.                                                                                                      |
| `refused`          | Card authentication was refused.                                                                                                                                                 |
| `canceled`         | Buyer closed the authentication window.                                                                                                                                          |
| `timed_out`        | Authentication did not finish within the SDK timeout.                                                                                                                            |
| `error`            | Tokenization, session creation, callback, or SDK flow failed.                                                                                                                    |

Do not fulfill an order from the browser status alone. Use webhook delivery or server-side reconciliation as the final source of truth.

## 3D Secure

`elements.submit(...)` completes card authentication for you when the payment requires it, using the
`buyer` and `products` your server already sends when it creates the transaction. You collect nothing
extra in the browser. See [3D Secure](/frontend/payment-element/three-d-secure).

Return your transaction payload from the backend unchanged. If it asks for further action, the SDK
detects it and continues the flow.

If you already created the transaction server-side and only need the SDK to finish an action it
reported, pass that action straight through:

```js theme={null}
const result = await Pagou.handleNextAction(transaction.next_action);
```

Or pass the transaction to an Elements instance:

```js theme={null}
const result = await elements.submit({
  transaction,
  createTransaction: async () => transaction,
});
```

## Resume after a reload

If the page reloads or the buyer navigates away mid-payment, resume the same transaction instead of
starting a new checkout attempt. `resume()` recreates the Element session from your public key, so it
works even after a full page load.

```js theme={null}
const result = await elements.resume({
  transactionId: "018f1f2e-7b43-7c9a-8d3e-1a2b3c4d5e70",
});
```

| Option          | Required | Description                      |
| --------------- | -------- | -------------------------------- |
| `transactionId` | Yes      | Id of the transaction to resume. |

`resume()` returns the same result shape as `submit()`. It picks up an outstanding action when one is
still pending, returns the terminal status when the payment already finished, and returns
`processing` when the outcome has not arrived yet. Persist the transaction id before the buyer can
navigate away so you have it on the way back.

### Card re-entry (auto-heal)

A payment that was waiting on card authentication (pre-charge 3DS) was bound to the Element session
that captured the card; a reload starts a new session and the original challenge can no longer run.
`resume()` **self-heals** this by default — the element/SDK owns the whole recovery, identical for
hosted checkout and raw integrators. Your only responsibility: keep a `CardElement` mounted and call
`resume()`.

```js theme={null}
const card = elements.create("card");
card.mount("#card-element");

const result = await elements.resume({ transactionId }); // terminal result
```

On a stale-session pre-charge challenge, `resume()` asks the buyer to re-enter the card in the mounted
element, re-tokenizes it under the current session, supersedes the stale challenge, runs the fresh
one, and resolves to a terminal status. No new transaction is created.

| Option             | Required | Description                                                                                                                              |
| ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `manualReentry`    | No       | Opt out of auto-heal. `resume()` returns `status: "requires_reentry"` so you can run your own re-collect UX, then call `resume()` again. |
| `reentryTimeoutMs` | No       | Auto-heal only. How long to wait for the buyer to re-enter the card. Defaults to 3 minutes.                                              |

<Warning>
  Only the same card can supersede the payment — a different card is refused. With no card element
  mounted, or if the buyer does not re-enter within the timeout, `resume()` resolves to a clear
  terminal result telling the buyer to start a new payment attempt. It never loops or hangs.
</Warning>

## Token mode

Pass a `mode` to `submit` to declare the caller's intent and select the resulting token type:

| `mode`              | Token              | When to use                          |
| ------------------- | ------------------ | ------------------------------------ |
| `payment` (default) | single-use `pgct_` | One-off charge                       |
| `upsell`            | reusable `pgpm_`   | Initial purchase + one-click upsell  |
| `subscription`      | single-use `pgct_` | Card capture to start a subscription |

```js theme={null}
// Upsell flow — same card charged twice (initial + upsell)
const result = await elements.submit({
  mode: "upsell",
  createTransaction: async (tokenData) => createPaymentWithToken(tokenData.token),
});
```

```js theme={null}
// Subscription flow
const result = await elements.submit({
  mode: "subscription",
  createTransaction: async (tokenData) => createSubscriptionWithToken(tokenData.token),
});
```

## Cleanup

Unmount a card field when leaving the checkout screen:

```js theme={null}
card.unmount();
```

Destroy the Elements instance when the entire payment flow is gone:

```js theme={null}
elements.destroy();
```

## Production rules

* Use `pk_test_*` only with `Pagou.setEnvironment("sandbox")`.
* Use `pk_live_*` with the default production environment.
* Never send raw card data to your backend.
* Never log `pgct_*` or `pgpm_*` tokens, or card data.
* Disable duplicate submits while `elements.submit(...)` is running.
* Persist the transaction id so you can `resume(...)` after a reload.
* Return your transaction payload from the backend without removing `id`, `status`, or `next_action`.
* Treat browser status as provisional until webhook or reconciliation confirms the final payment state.
