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

# Your first sale

> From zero to a paid invoice: which path to pick, what to store, and what to do about the double click.

There are **three** ways to charge for a product, and they solve the same
problem with very different amounts of work on your side. Choose first,
implement after.

## Which path is yours

|                                   | What you build           | When to use it                                                                          |
| --------------------------------- | ------------------------ | --------------------------------------------------------------------------------------- |
| **Payment link**                  | nothing                  | A one-off sale, charging over WhatsApp, a first sale before an app exists               |
| **`checkout()`**                  | your own "thanks" page   | You have an app and want the buyer inside it, but do not want to build a payment screen |
| **`invoices.createForProduct()`** | the whole payment screen | You want full control of the visuals and the flow                                       |

All three end at the same invoice and the same webhook. The difference is **how
much of the experience is yours**.

<Info>
  **In doubt, start with the link.** It is the only one that asks nothing of your app. You switch later — the product
  and the catalog are the same.
</Info>

## The middle path, end to end

Assuming you already have a **published** product (see
[catalog](https://beinfi.com/en/catalogo)) and a key:

```ts theme={null}
// 1. creates customer + invoice, and returns the hosted URL
const { invoice, url } = await infi.checkout({
  slug: "your-tenant",
  productId,
  customer: {
    externalId: yourUserId,         // YOUR user's id
    email: "customer@company.com",
    taxId: "52998224725",           // pix and boleto require a tax id
  },
  successUrl: "https://your-app.com/thanks",
});

// 2. STORE THIS. It is the step nobody documents and everybody forgets.
await db.orders.insert({ userId: yourUserId, invoiceId: invoice.id, status: "open" });
```

<Warning>
  **You have to map `invoiceId` → your user.** We do not know who your user is — you pass an `externalId` and we return an
  `invoiceId`. When the payment confirms, the webhook carries the `invoiceId`,
  **not your user**. If you did not store the pair, you received money and do not
  know whose it is.

  It is the most important architectural decision on this page and it fits in one
  column of one table in your database.
</Warning>

## Charging, and showing the pix on your own screen

```ts theme={null}
const pay = await infi.pay.charge({ slug, invoiceId: invoice.id, method: "pix" });

pay.pixPayload;   // copy-and-paste EMV — render it as a QR code
pay.pixQrImage;   // base64 PNG, ready to use: use it when it comes
pay.pixExpiresAt; // when the QR dies
```

<Warning>
  **Today only pix has an artifact for your screen.** `boleto` and `card` return **only** `invoiceUrl` — the provider's hosted page.
  There is no digitable line or barcode field in the response, and
  `clientSecret`/`publishableKey` (card confirmed in the browser) only appear
  where card is enabled on the tenant.

  Since the payer should **not** go to the provider's site, today's path for
  boleto and card is the [payment link](https://beinfi.com/en/link-de-pagamento) or the `url`
  that `checkout()` returns — both are our checkout, carrying your merchant name.
  Check `cardEnabled` on the link's public read before offering card.
</Warning>

## Knowing that they paid

Do not trust what `charge` returns: it comes back `pending`. Payment is
asynchronous.

```ts theme={null}
// production: signed webhook
// sandbox: polling, because registering a webhook answers 503
const paid = await infi.pay.waitForPaid({ slug, invoiceId: invoice.id, timeoutMs: 15000 });
```

When it confirms, use the event's `invoiceId` to find the order you stored in
step 2. Details in [webhooks](https://beinfi.com/en/webhooks).

## The buyer clicked "Buy" twice

Every non-`GET` method **on the authenticated API** requires an
`Idempotency-Key` (the public `/pay/*` routes, which the buyer's browser calls,
do not). This is not bureaucracy: it is what keeps two clicks from becoming two
invoices.

```ts theme={null}
// the SAME key for the SAME purchase intent
const key = `order-${yourUserId}-${productId}-${new Date().toISOString().slice(0,10)}`;

const { invoiceId } = await infi.checkout({ slug, productId, customer, idempotencyKey: key });
await infi.pay.charge({ slug, invoiceId, method: "pix", idempotencyKey: `${key}-pix` });
```

From `@beinfi/sdk@0.10.2` on, both accept `idempotencyKey` (and since `0.10.4`
`checkout()` returns `invoiceId` already typed as `string`, with no `!` needed).
The resource methods (`products.create`, `invoices.create`, `coupons.create`, …)
already took the key as their last argument.

* **Same key, same body** → you get the original response back. One invoice.
* **Same key, different body** → `409 idempotency_key_reused`. That is
  protection: it means you reused the key for something else.
* **No key** → `400 idempotency_key_required`.

The SDK generates one when you do not pass it — which protects against a
*network retry*, not against a double click, because each new call gets a new
key. For the double click, the key has to come from something stable in your
intent, as above.

<Tip>
  **The simplest fix is not letting them click twice.** Disable the button on the first click and treat the `Idempotency-Key` as the
  safety net, not as the first line of defence.
</Tip>

## Before selling: the name the buyer sees

Your tenant is born with a placeholder name. If you do not change it, the
checkout and the payment link literally say **"New app"** — and nobody buys from
a store called New app.

```ts theme={null}
await infi.account.update({ name: "Orvalho Coffee" });
// optional, cited in the payment mandate:
await infi.account.update({ termsUrl: "https://orvalhocoffee.com/terms" });
```

It takes effect immediately, with no republishing and no new link — the same
link starts showing the new name. `infi.account.get()` reads it back. (From
`@beinfi/sdk@0.10.7`; before that it is `PATCH /account/tenant`.)

## You sold. Now deliver

If what you sell is a file or an access, do not build that by hand: attach the
deliverable to the product and Infi sends the buyer their personal link when the
payment confirms — and returns the same link to you, to show on your thanks
page. It is in [delivering the product](https://beinfi.com/en/entrega-do-produto).

The buyer's side — finding out they paid, delivery, and the two polls nobody
guesses — is in [the thank-you page](https://beinfi.com/en/pagina-de-obrigado).
