---
title: "Payment Element"
description: "The payment form: its methods, layouts, look and options."
url: https://docs.fluveo.com/payments/payment-element/
---

> Index of every page: https://docs.fluveo.com/llms.txt. Integrating Fluveo? Read https://docs.fluveo.com/agents/integrate.md first.

> Too long? This page with code in one language only: https://docs.fluveo.com/payments/payment-element.curl.md (cURL), https://docs.fluveo.com/payments/payment-element.node.md (Node.js), https://docs.fluveo.com/payments/payment-element.python.md (Python), https://docs.fluveo.com/payments/payment-element.php.md (PHP).

# Payment Element

The payment form: its methods, layouts, look and options.

The Payment Element is Fluveo's payment form. `fluveo.js` puts it on your own page, in a frame of Fluveo's, so the card number never reaches your page or your server. The form shows the payment methods of one PaymentIntent, checks what the buyer types and shows each error where it belongs.

Load it with a script tag, or with the [two npm packages](https://docs.fluveo.com/payments/fluveo-js.md#packages) when you use a bundler or React. [Custom checkout](https://docs.fluveo.com/payments/custom-checkout.md) builds a whole checkout around it.

![The payment form with a test card typed in: the Card tab, then the card number, the expiration date, the security code, the country and the ZIP code.](https://docs.fluveo.com/payments/img/payment-element/form.png "The form, with a test card typed in")

The page shows the form itself in place of this picture, live and in test mode, when the docs site has a test key for it: https://docs.fluveo.com/payments/payment-element/

> **For AI agents:**
>
> - The form's look is set only in code, with the `appearance` option. No Dashboard setting changes it, and the CSS of your page does not reach it.
> - Use only the option names and values that the fluveo.js reference lists under Form options.
> - A name the script does not take throws an error at the call, and its message says what to use. Fix the call as it says; never work around it.

## Compatible APIs

The form works with the [PaymentIntents API](https://docs.fluveo.com/api/payment_intents.md). Your server creates one PaymentIntent per order with its secret key and gives the page only the `client_secret`. [Accept a payment](https://docs.fluveo.com/payments/accept-a-payment.md) explains the PaymentIntent and its states, and [Custom checkout](https://docs.fluveo.com/payments/custom-checkout.md#build) has the server route to copy.

| Your code | The form |
| --- | --- |
| Creates the PaymentIntent on your server and passes its `client_secret` to `fluveo.elements()`. | Reads the PaymentIntent and shows its payment methods. |
| Mounts the form in a container of your page. | Draws its fields in a frame, checks each one and says what to fix. |
| Calls `fluveo.confirmPayment()` when the buyer presses your Pay button. | Sends the card to Fluveo and confirms the PaymentIntent, through 3D Secure when the card asks for it. |
| Marks the order paid on the `payment_intent.succeeded` webhook. | Nothing: the page's result is only for the buyer. |
| Or mounts the form from an amount and a currency, and creates the PaymentIntent when the buyer presses Pay. | Shows before a PaymentIntent exists, and confirms the one your code passes then. |

The last row is the second way to use the form: see [Mount the form before its PaymentIntent](https://docs.fluveo.com/payments/fluveo-js.md#mode).

## Payment methods

The form shows one payment method, the card. It asks for the card number, the expiration date, the security code, the country and, where cards have one, the postal code.

A PaymentIntent's `payment_method_types` says which methods it accepts. The form reads that list when it mounts and, with a test key, prints it to the browser console, as [Test mode](https://docs.fluveo.com/payments/fluveo-js.md#test-mode) describes. Every other method is taken outside the form:

| Method | Where it is taken |
| --- | --- |
| [Card](https://docs.fluveo.com/payments/cards.md) | In this form, and on the [hosted checkout](https://docs.fluveo.com/payments/checkout.md) page. |
| [Pix](https://docs.fluveo.com/payments/pix.md) | Your server creates it and your page shows its code. |
| [Boleto](https://docs.fluveo.com/payments/boleto.md) | Your server issues it and your page shows its line and its PDF. |
| [Apple Pay](https://docs.fluveo.com/payments/apple-pay.md) | Your page sends the buyer's approval to Fluveo as a card token. |

## Layout

The form lists its payment methods as tabs above the fields, or as an accordion whose open row holds the fields. Choose with the `layout` option of `elements.create("payment")`.

![The same payment form three times, side by side: with tabs, as an accordion with a radio button, and as an accordion without one.](https://docs.fluveo.com/payments/img/payment-element/layouts.png "The three layouts")

### Tabs

Each payment method is a box above the fields, and the open one is outlined. Tabs are the default, so [layout](https://docs.fluveo.com/payments/fluveo-js.md#form-options-layout) can be left out.

*checkout.js*

```javascript
const fluveo = Fluveo("pk_test_...");
const elements = fluveo.elements({ clientSecret });
const paymentElement = elements.create("payment", {
  layout: { type: "tabs" },
});
paymentElement.mount("#payment-element");
```

That is the whole script, once the page has its `clientSecret`. [Custom checkout](https://docs.fluveo.com/payments/custom-checkout.md#build) has the page around it.

![The payment form in the tabs layout, empty, as the buyer first sees it on a narrow page.](https://docs.fluveo.com/payments/img/payment-element/layout-tabs.png "Tabs")

### Accordion with radio buttons

The methods are rows, and the open row holds the fields. `radios: "always"` draws a radio button in each row, also when the form has one method. [layout](https://docs.fluveo.com/payments/fluveo-js.md#form-options-layout) has four parts: `type`, `defaultCollapsed`, `radios` and `spacedAccordionItems`.

*checkout.js*

```javascript
const fluveo = Fluveo("pk_test_...");
const elements = fluveo.elements({ clientSecret });
const paymentElement = elements.create("payment", {
  layout: { type: "accordion", radios: "always" },
});
paymentElement.mount("#payment-element");
```

![The payment form as an accordion: one row with a radio button and the word Card, open, with the card fields inside it.](https://docs.fluveo.com/payments/img/payment-element/layout-accordion-radios.png "Accordion with radio buttons")

### Accordion without radio buttons

`radios: "never"` leaves the radio button out of every row. An accordion with one method draws none either when [layout](https://docs.fluveo.com/payments/fluveo-js.md#form-options-layout) has no `radios`.

*checkout.js*

```javascript
const fluveo = Fluveo("pk_test_...");
const elements = fluveo.elements({ clientSecret });
const paymentElement = elements.create("payment", {
  layout: { type: "accordion", radios: "never" },
});
paymentElement.mount("#payment-element");
```

![The payment form as an accordion with no radio button: the row Card, open, with the card fields inside it.](https://docs.fluveo.com/payments/img/payment-element/layout-accordion.png "Accordion without radio buttons")

## Appearance

The form is a frame, so the CSS of your page does not reach it: its look is set in code, with the `appearance` option of `fluveo.elements()`. A theme is a whole look with a name. A variable changes one value of the theme, such as the main colour, the font or the corner radius, and every part of the form that uses that value follows it.

![The payment form in its three themes, side by side: fluveo is light, night is dark, and flat has filled fields with no border.](https://docs.fluveo.com/payments/img/fluveo-js-appearance/themes.png "The three themes")

*checkout.js*

```javascript
const fluveo = Fluveo("pk_test_...");
const elements = fluveo.elements({
  clientSecret,
  appearance: {
    theme: "flat",
    variables: { colorPrimary: "#0f766e" },
  },
});
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
```

![The payment form in another theme, with a store's own main colour on the Card tab.](https://docs.fluveo.com/payments/img/payment-element/appearance.png "A theme and one variable")

[Appearance](https://docs.fluveo.com/payments/fluveo-js-appearance.md) shows each theme and gives every variable's default in each theme.

## Options

Every option is optional: the form works with `clientSecret` alone. `appearance`, `locale` and `loader` go to `fluveo.elements()`. `layout`, `fields` and `defaultValues` go to `elements.create("payment")`.

*checkout.js*

```javascript
const fluveo = Fluveo("pk_test_...");
const elements = fluveo.elements({
  clientSecret,
  locale: "en",
  loader: "always",
});
const paymentElement = elements.create("payment", {
  layout: "accordion",
  defaultValues: { billingDetails: { address: { country: "CA" } } },
});
paymentElement.mount("#payment-element");
```

| Option | What it does |
| --- | --- |
| [appearance](https://docs.fluveo.com/payments/fluveo-js.md#form-options-appearance) | Sets the theme and the variables of the form's look. |
| [locale](https://docs.fluveo.com/payments/fluveo-js.md#form-options-locale) | Sets the language of the form. Left out, it follows the buyer's browser. |
| [loader](https://docs.fluveo.com/payments/fluveo-js.md#form-options-loader) | Shows or hides the placeholder while the form loads. |
| [layout](https://docs.fluveo.com/payments/fluveo-js.md#form-options-layout) | Lists the payment methods as tabs or as an accordion. |
| [fields](https://docs.fluveo.com/payments/fluveo-js.md#form-options-fields) | Hides the country or the postal code when your page already asked for it. |
| [defaultValues](https://docs.fluveo.com/payments/fluveo-js.md#form-options-defaultValues) | Sets the country and the postal code the form opens with. |

## Errors

The form shows the buyer's errors by itself, each one once:

- a field that is incomplete or not valid, or that the API refused, under that field;
- a declined card, right under the card number;
- a form that could not load, in place of the fields.

With `inputs: "condensed"` in [appearance](https://docs.fluveo.com/payments/fluveo-js.md#form-options-appearance), the fields are joined in one block, and the errors of the fields and of the card show under it: the fields' in their order, then the card's.

Its messages are in English and in Brazilian Portuguese, as `locale` picks. Where the form has no sentence of its own, it shows the API's message.

Your code reads the rest: `confirmPayment()` resolves with [the error object](https://docs.fluveo.com/payments/fluveo-js.md#error-object), and [Errors](https://docs.fluveo.com/errors.md) lists the API's codes. The form shows no message when the answer to a payment did not come back, so show that error's `message` on your page. A mistake in your code throws at the call; one the form learns only at the press, such as [currency_mismatch](https://docs.fluveo.com/payments/fluveo-js-errors.md#currency_mismatch), resolves as `{ error }` of the type `invalid_request_error`. The options throw [option_not_supported](https://docs.fluveo.com/payments/fluveo-js-errors.md#option_not_supported), [appearance_value_invalid](https://docs.fluveo.com/payments/fluveo-js-errors.md#appearance_value_invalid) and [billing_details_missing](https://docs.fluveo.com/payments/fluveo-js-errors.md#billing_details_missing).

Type a [test card](https://docs.fluveo.com/start/testing.md#typed-cards) into the form to see a decline or 3D Secure.

## With other elements

The Payment Element is the one element of `fluveo.js`: `elements.create()` takes `"payment"` and no other name. The rest of the checkout is your own page: the e-mail field, the address, the cart and the Pay button.

Pass what your page already knows about the buyer:

| Your page has | Pass it as |
| --- | --- |
| The buyer's name, e-mail or phone | [confirmParams](https://docs.fluveo.com/payments/fluveo-js.md#confirm-payment), in the `billing_details` of `payment_method_data`. |
| The country or the postal code, asked in your own address fields | The `address` of that `billing_details`, with that field set to `"never"` in [fields](https://docs.fluveo.com/payments/fluveo-js.md#form-options-fields) so the form does not ask again. |
| A country or a postal code to start from | [defaultValues](https://docs.fluveo.com/payments/fluveo-js.md#form-options-defaultValues). The buyer may still change it. |

## Move from the hosted checkout

[Hosted checkout](https://docs.fluveo.com/payments/checkout.md) sends the buyer to a page of Fluveo's. Move when the payment belongs inside your own page: your layout around the form, your cart beside it, and a declined card that leaves the buyer where they are. The hosted page also shows Pix and Boleto, so keep it for an order paid that way.

Your server keeps its secret key, its `Idempotency-Key` and its webhook endpoint. It creates a PaymentIntent in place of a Checkout Session, gives the page the `client_secret` and marks the order paid on `payment_intent.succeeded`, which a session's payment already sends. Your page gains the form and the calls that mount it and confirm the payment: [Custom checkout](https://docs.fluveo.com/payments/custom-checkout.md) has every file.
