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

# Custom checkout

> Build your own payment screens on quotes and funding instructions.

When the hosted page and the component do not fit, build the screens yourself. Useroutr still
does the hard part: it prices the route, creates the deposit account, detects the payment and
settles it. You decide how each step looks.

## The flow

<Steps>
  <Step title="Create the funding intent">
    ```ts theme={null}
    const intent = await useroutr.fundingIntents.create(
      { amount: "25", asset: "asset_usdc_stellar", reference: orderId },
      { idempotencyKey: orderId },
    );
    ```
  </Step>

  <Step title="Let your user pick an asset">
    Offer the assets Useroutr can take. `network` and `symbol` are for display; the `id` is what
    you send back.

    ```ts theme={null}
    const { data: assets } = await useroutr.assets.list();
    ```

    If your user holds the asset you asked for, skip to step 4: no quote is needed.
  </Step>

  <Step title="Quote and lock the route">
    A quote says how much of your user's asset covers your requirement. Selecting it locks the
    price and switches the intent's instructions to that asset.

    ```ts theme={null}
    const quote = await useroutr.quotes.create(intent.id, {
      source: { network: "stellar", asset: "asset_xlm_stellar" },
    });

    // quote.source.amount is the most your user sends; quote.fees lists every fee
    await useroutr.quotes.select(quote.id);
    ```

    See [Quotes and routes](/concepts/quotes-and-routes) for exact-input quotes, slippage and
    routes across networks.
  </Step>

  <Step title="Show the funding instructions">
    Read the intent again and show exactly what `funding_instructions` says.

    ```ts theme={null}
    const { funding_instructions: instructions } = await useroutr.fundingIntents.retrieve(intent.id);

    // instructions.deposit.address  where to send, created for this intent alone
    // instructions.amount           what is still owed, in instructions.asset
    // instructions.expires_at       when the intent stops taking payment
    ```

    No memo is needed: the deposit address alone identifies the intent.
  </Step>

  <Step title="Follow the payment">
    Listen for [webhooks](/guides/webhooks) on your server, or poll the intent. Show progress
    from its `status`: `PARTIALLY_FUNDED` means send the rest, `PAYMENT_CONFIRMED` onward means
    received, `CREDITED` means done.
  </Step>
</Steps>

## Building it in the browser

Your browser code can read an intent's state without an API key through the public checkout
endpoints, addressed by the funding token. `@useroutr/checkout-core` wraps them together with
the checkout's state machine, so you get the same states the embedded checkout uses and render
them your way.

```ts theme={null}
import { CheckoutController, HttpCheckoutApi } from "@useroutr/checkout-core";

const controller = new CheckoutController({
  api: new HttpCheckoutApi({ baseUrl: "https://api.sandbox.useroutr.com/v1/public", token }),
});

controller.subscribe(() => {
  const state = controller.getState();
  render(state.status, state.intent); // your own rendering
});

await controller.start(); // loads the intent and keeps polling it
```

The controller also takes your user's choices: `selectMethod`, `selectNetwork` and
`selectAsset`, plus `back` and `retry`. Call `stop` when the screen goes away.

Creating the intent, quoting for amounts you decide and reading your ledger stay on your server,
with your API key.


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