# Flute Elements

Flute Elements is a drop-in JavaScript library that embeds PCI-compliant payment fields directly into your checkout page.
It lets you collect card details without sensitive data ever touching your servers, as the embedded elements are securely rendered and managed by Flute's infrastructure.

Flute Elements renders secure, isolated payment input fields, such as card number, expiry, and CVV, inside your page using iframes managed by the `flute.js` library.
This means:

- **PCI-DSS compliance**. Card data is captured and transmitted through Flute's secure infrastructure, not your servers.
- **Custom user experience**. You control the flow after submission, either by handling the result inline with a callback or redirecting the customer to a custom result page.
- **Full styling control**. Customize fonts, colors, borders, and layout to match your branding.


## Integration Overview

```mermaid
sequenceDiagram
    autonumber
    participant C as Customer
    participant FE as Merchant Frontend
    participant BE as Merchant Backend
    participant API as Flute API

    BE->>API: POST /pay-int-api/payment-sessions
    API-->>BE: { "id": "SESSION_ID" }
    BE-->>FE: SESSION_ID

    FE->>FE: Initialize Flute Elements (session ID)
    FE-->>C: Renders secure card fields

    C->>FE: Fills in card details and submits
    FE->>API: flute.submit()
    API-->>FE: submission_callback()

    FE->>BE: Fetch session result
    BE->>API: GET /pay-int-api/payment-sessions/{id}
    API-->>BE: Session object
    BE-->>FE: Session result
    FE-->>C: Show result
```

## Building with Flute Elements

The integration with Flute Elements follows three steps:

- **Step 1: Loading the flute.js Library** into your checkout page.
- **Step 2: Building the Payment Form** using a payment session identifier from the backend.
- **Step 3: Submitting the Form** and retrieving the result from the backend.


### Step 1: Loading the flute.js Library

Add the script tag to your checkout page:

```html
<head>
  <script src="https://public.flute.com/lib/v1.0/flute.mjs" type="module"></script>
</head>
```

### Step 2: Building the Payment Form

To build the payment form, create a payment session from the backend and initialize the Flute Elements form on the frontend using the payment session identifier.

#### Creating a Payment Session

The backend must create a payment session before initializing the Flute Elements form.
To create the payment session, call the payment session endpoint from the backend.

**Sandbox Environment:**

```HTML
POST https://sandbox.api.flute.com/pay-int-api/payment-sessions
```

**Production Environment:**

```HTML
POST https://api.flute.com/pay-int-api/payment-sessions
```

A merchant API token is needed when creating payment sessions.
For more information, see [Generating an API Token](/api-reference/authorization) in the API reference guide.

**Request Body Parameters:**

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| amount | number(float) | Yes | Base charge amount. For example, `129.50` for $129.50. |
| mode | string | No | Controls what the payment session is allowed to do. Accepted values: `"Payment"` (default), `"PaymentAndSave"`, and `"SaveMethod"` (requires `amount: 0`). See [Operating Modes](#operating-modes) for details. |
| referenceId | string | No | Merchant-supplied identifier (max 100 characters) for duplicate payment detection. Payments with the same `referenceId` within the duplicate-detection window are rejected. |
| skipAddressVerification | boolean | No | When `false` (the default), AVS checks are enabled and the form must include the `address` element. When `true`, AVS checks are skipped. See [Address Verification Service (AVS)](#address-verification-service-avs) for details. |
| tipAmount | number(float) | No | Tip amount charged on top of the base amount. For example, `5.00` for $5.00. Only accepted when tips are enabled for the merchant. |
| customerId | string (UUID) | No | Customer identifier to associate saved payment methods with. Only relevant for `SaveMethod` and `PaymentAndSave` payment sessions.• **If omitted**: a new customer is created automatically; retrieve the resulting `customerId` through `GET /pay-int-api/payment-sessions/{paymentSessionId}`.• **If provided**: the new payment method is saved under that existing customer.Cannot be combined with `customerHandling: "TokenOnly"`. |
| customerHandling | string | No | Controls whether a saved payment method is attached to a customer record. Only relevant for `SaveMethod` and `PaymentAndSave` payment sessions. Accepted values:• `"CreateCustomer"` (default): a customer is auto-created or reused.• `"TokenOnly"`: saves the payment method as a bare token with no customer record. Cannot be combined with `customerId`. See [Standalone tokenization (no customer record)](#standalone-tokenization-no-customer-record).This is recognized only on the v2 payment session endpoint. |


The following is a sample request for the sandbox environment.

```bash
curl -X POST 'https://sandbox.api.flute.com/pay-int-api/payment-sessions' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 129.50,
    "mode": "PaymentAndSave",
    "referenceId": "order-8675309",
    "skipAddressVerification": false,
    "tipAmount": 5.00,
    "customerId": "123e4567-e89b-12d3-a456-426614174000"
  }'
```

The following is a sample response from the previous call.

```json
{
  "id": "0cdddcb6-fa98-4c46-b096-a2723b56c750"
}
```

The payment session endpoint response contains the payment session identifier in the `id` field.
The frontend uses this value to initialize the form.

#### Adding the Form Wrapper

Add a wrapper element that will contain the Flute Elements embedded form.
You can also add a submit button or use your existing checkout button to trigger the form submission.

```html
<div id="flute-payment-form"></div>
<button id="flute-payment-submit-btn">Pay</button>
```

Flute Elements will render the payment fields inside the `#flute-payment-form` wrapper element.
The tag IDs (`#flute-payment-form` and `#flute-payment-submit-btn`) can be customized, but they must match the IDs used in the mounting and event listener steps below.

#### Mounting the Flute Elements Form

Pass the payment session identifier (the `id` field returned by the backend) to Flute Elements, create the customer name, payment, and address elements, and mount them together into the wrapper element.

```js
const flute = new window.Flute();

const form = flute.elements({
  sessionId: 'SESSION_ID', // Payment session identifier from the backend.
  appearance: {},          // Optional. See Customizing the Appearance below.
});

form.create('customer-name');
form.create('payment');
form.create('address'); // Required while AVS is enabled (the default): collects the ZIP code that AVS verifies.

form.mount('#flute-payment-form'); // Identifier of the wrapper element in your page.
```

The address (ZIP code) element is required while AVS is enabled, which is the default for every payment session (`skipAddressVerification: false`).
Without it, AVS has nothing to verify and the payment is declined.
To hide or pre-fill the customer-name and address fields when you collect this information elsewhere in your checkout, see [Address Verification Service (AVS)](#address-verification-service-avs).

### Step 3: Submitting the Form

Attach a click handler to your submit button.

```js
const submitButton = document.getElementById('flute-payment-submit-btn');

submitButton.addEventListener('click', () => {
  flute.submit({
    confirmParams: {
      submission_callback: () => {
        // Payment interaction complete.
        // Call the backend to `GET /pay-int-api/payment-sessions/{flute_session_id}`
        // to retrieve the transaction result.
      },
      error_callback: (message, code) => {
        alert(message);
        // If code is 3 or 4, generate a new payment session from the backend and refresh the form.
        if (code === 3 || code === 4) {
          const newSessionId = 'NEW_SESSION_ID_FROM_BACKEND';
          flute.updateSessionId(newSessionId);
        }
      },
    },
  });
});
```

#### Retrieving the Session Result

Always retrieve the payment result from the backend.
Never trust the frontend for payment session status or results, as it can be spoofed.

After the customer completes the form submission, the backend must call the payment session endpoint to get the actual status and details.
This will return one of the following payment session statuses: `Created`, `Completed`, `Cancelled`, or `Failed`.
The response will also contain more information about the payment session, such as transaction or customer details.

```bash
GET /pay-int-api/payment-sessions/{flute_session_id}
```

## Error Handling

When a payment submission fails, the `error_callback` receives a message and a numeric error code.
For error codes **1** and **2** the payment session has not been consumed and it can be retried.
For error codes **3** and **4**, the payment session is consumed and cannot be reused.
In this case, generate a new payment session from the backend and call `flute.updateSessionId(newSessionId)` to retry.

**Error Codes:**

| Code | Message | Meaning |
|  --- | --- | --- |
| 1 | Something went wrong, check payment details and try again. | The payment was not submitted due to a validation error such as an invalid BIN (card number) or a duplicate transaction detected within a short time frame. Session remains open for new payment attempts. |
| 2 | An integration problem has been detected. Contact the administrator. | Integration or server-side error. The payment session stays open for retry. We recommended reviewing the payload data and mounted elements before retrying. |
| 3 | Something went wrong, check the payment session identifier and try again. | Invalid or expired payment session identifier. Generate a new payment session from your backend and update the form. |
| 4 | The payment could not be completed. Check payment details and try again. | Transaction declined, such as insufficient funds, an incorrect CVV, or missing or failed AVS data (for example, no ZIP code was collected while AVS is enabled; see [Address Verification Service (AVS)](#address-verification-service-avs)). Generate a new payment session to retry. |


## Address Verification Service (AVS)

AVS (address verification service) is a system that verifies the cardholder's billing address with the card issuer.
It attempts to reduce fraud and improve transaction approval rates.

Because AVS is enabled by default (`skipAddressVerification: false`), the quickstart form in Step 2 already includes the `customer-name` and `address` elements alongside the `payment` element, with no extra setup needed to satisfy AVS.
This section describes how to adapt those elements when you collect the cardholder's name or billing address elsewhere in your checkout.
Each element supports a `hidden` option to submit values without rendering the fields (`hidden: true`) and a `values` option to pre-fill them.

### Creating the Elements

After initializing `form` with a payment session identifier, use `form.create()` to add the cardholder elements before mounting.

#### Customer Name Element

Collects the cardholder's first and last name.

```js
// Visible: fields are shown empty for the customer to fill in
form.create('customer-name');

// Visible and pre-filled: fields are shown pre-filled; the customer can edit them
form.create('customer-name', {
  values: { firstName: 'John', lastName: 'Doe' },
});

// Hidden: values are submitted without displaying the fields
form.create('customer-name', {
  hidden: true,
  values: { firstName: 'John', lastName: 'Doe' },
});
```

| Option | Type | Required | Description |
|  --- | --- | --- | --- |
| hidden | boolean | No | When `true`, the fields are not rendered in the UI. When `false` (or omitted), `values` act as optional defaults the customer can override. Defaults to `false`. |
| values.firstName | string | Conditional | Cardholder first name. Required when `hidden` is `true`. Both `firstName` and `lastName` must be provided together. |
| values.lastName | string | Conditional | Cardholder last name. Required when `hidden` is `true`. Both `firstName` and `lastName` must be provided together. |


#### Address Element

Collects the cardholder's billing address for AVS (address verification service) verification.

```js
// Visible: field is shown pre-filled; the customer can edit it
form.create('address', {
  values: { zipCode: '12345' },
});

// Hidden: value is submitted without displaying the field
form.create('address', {
  hidden: true,
  values: { zipCode: '12345' },
});
```

| Option | Type | Required | Description |
|  --- | --- | --- | --- |
| hidden | boolean | No | When `true`, the field is not rendered in the UI. When `false` (or omitted), `values` acts as the default input value, which the customer can override. Defaults to `false`. |
| values.zipCode | string | Conditional | Postal or ZIP code. Required when `hidden` is `true`. |


### Mounting All Elements

After creating the payment, customer-name, and address elements, mount them all into a single secure iframe.
You can create the elements in any order.

```js
const flute = new window.Flute();

const form = flute.elements({
  sessionId: 'SESSION_ID',
  appearance: {},
});

form.create('customer-name');
form.create('payment');
form.create('address');

form.mount('#flute-payment-form');
```

Elements are rendered in the order they are created.
If `address` is created before `payment`, the address fields will appear above the payment fields in the form.
Adjust the order of your `form.create()` calls to control the layout.

## Saving Payment Methods

Flute Elements can save a customer's card for future payments, either as a standalone operation or alongside a payment.
The backend controls the behavior through the `mode` field at payment session creation, and the frontend through the `saveMethod` option on the payment element.

When saving a card, Flute can either create and manage a customer record on your behalf (the default), or return a standalone payment method token with no customer record attached.
After the customer submits the form, retrieve the `vaultedPaymentMethodId` from the backend and store it to charge the same card again in future transactions without re-collecting the card details.

To manage saved cards and the customers they belong to, see the [Customers API Reference](/api-reference/customers), specifically the `cards` response object on `GET /v2/customers/{customerId}`.

### Operating Modes

The `mode` field (set when creating the payment session on the backend) and `saveMethod` (set on the frontend element) must be used together. The table below shows the valid combinations and their behavior.

| `mode` (backend) | `saveMethod` (frontend) | Behavior |
|  --- | --- | --- |
| `"Payment"` (default) | *(not applicable)* | Processes the payment only. Card is not stored. |
| `"SaveMethod"` | `"vaultOnly"` | Saves the card without charging. A disclosure notice is shown. If no `customerId` is provided, a new customer record is created automatically. |
| `"PaymentAndSave"` | `"askConsent"` | Processes the payment. A checkbox lets the customer opt in to saving their card. |
| `"PaymentAndSave"` | `"implicit"` | Processes the payment and always saves the card. A disclosure notice is shown. |


### Payment Session Setup

**Payment session (default)**

```bash
curl -X POST '.../pay-int-api/payment-sessions' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 129.50
  }'
```

**Payment with customer vaulting**

```bash
curl -X POST '.../pay-int-api/payment-sessions' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 129.50,
    "mode": "PaymentAndSave"
  }'
```

**Save customer and method only (no payment)**

```bash
curl -X POST '.../pay-int-api/payment-sessions' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 0,
    "mode": "SaveMethod",
    "customerId": "CUSTOMER_ID"
  }'
```

For save-only payment sessions, the `amount` must be `0`.
Optionally provide a `customerId` to attach the card to an existing customer record.
If omitted, a new customer record is created automatically.
The resulting `customerId` can be retrieved with `GET /pay-int-api/payment-sessions/{paymentSessionId}`.

**Standalone tokenization (no customer record)**

If you manage your own customer identities and don't want Flute to create or store a customer record, set `customerHandling` to `TokenOnly`. This is available for both `SaveMethod` and `PaymentAndSave` modes and cannot be combined with `customerId`.

Note
`customerHandling` is only recognized on the v2 payment session endpoint.
This field is not supported on v1.

**v2 Payment Session Endpoints**

| Environment | Endpoint |
|  --- | --- |
| Sandbox | `https://sandbox.api.flute.com/v2/payment-sessions` |
| Production | `https://api.flute.com/v2/payment-sessions` |


```bash
curl -X POST 'https://sandbox.api.flute.com/v2/payment-sessions' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 0,
    "mode": "SaveMethod",
    "customerHandling": "TokenOnly"
  }'
```

The resulting payment session always has `customerId: null`.
Retrieve the token through `vaultedPaymentMethodId` from `GET /pay-int-api/payment-sessions/{paymentSessionId}`.
If the cardholder name and/or address elements are submitted, they're used for AVS verification.
They are not stored on any customer record.

### Frontend Element Setup for Saving

Set `saveMethod` when calling `form.create('payment', options)`:

```js

// For Session mode "PaymentAndSave":

// Payment with optional vaulting: shows an opt-in checkbox
const paymentElement = form.create('payment', {
  saveMethod: 'askConsent',
});

// Payment with mandatory vaulting: shows a disclosure
const paymentElement = form.create('payment', {
  saveMethod: 'implicit',
});

// For Session mode "SaveMethod":

// Save-only: shows a disclosure, no payment is processed
const paymentElement = form.create('payment', {
  saveMethod: 'vaultOnly',
});
```

### Label Overrides

When using `askConsent`, `implicit`, or `vaultOnly` save methods, Flute Elements displays a checkbox or disclosure to inform the customer about saving their card.

The checkbox label (for `"askConsent"`) and the disclosure label (for `"implicit"` and `"vaultOnly"`) can be overridden with the `labels` option.
Values must not be empty strings.

| Option | Default Value | Shown for |
|  --- | --- | --- |
| labels.saveConsent | "Save this card for future payments." | `saveMethod: "askConsent"` |
| labels.disclosure | "This card will be saved for future payments." | `saveMethod: "implicit"` and `saveMethod: "vaultOnly"` |


**Example with custom checkbox label:**

```js
const paymentElement = form.create('payment', {
  saveMethod: 'askConsent',
  labels: {
    saveConsent: 'Remember my card for next time.',
  },
});
```

### Managing Customers and Saved Cards

Once a card is saved, the backend can manage customers and their payment methods through the Customer API.

**List your customers:**

```
GET /v1/customers
```

**Get a specific customer:**

```
GET /v1/customers/{customerId}
```

**List payment methods for a customer:**

```
GET /v1/customers/{customerId}/payment-methods
```

See the [Customers API Reference](/api-reference/customers) for full request or response schemas and pagination options.

## Customizing the Appearance

Flute Elements supports custom theming through the `appearance` option to make the form match your brand and design.

```js
const form = flute.elements({
  sessionId: sessionId,
  appearance: {
    elements: {
      formBackgroundColor: '#e1e9eb',
      fontFamily: 'Verdana, sans-serif',
      fontSizeBase: '16px',
      labelColor: '#107b92',
      labelAsteriskColor: '#0000ff',
      inputTextColor: '#107b92',
      inputBorder: 'none',
      inputBackgroundColor: '#c8d6d9',
      inputBorderRadius: '6px',
      inputFocusedBottomBorderColor: '#107b92',
      inputErrorBackgroundColor: '#ecc9c9',
      errorFontSize: '14px',
      errorColor: '#cd2424',
    },
  },
});
```

**Available Appearance Properties:**

| Property | Description |
|  --- | --- |
| formBackgroundColor | Background color of the payment form container. |
| fontFamily | Font family for all text elements. |
| fontSizeBase | Base font size for input fields |
| labelColor | Color of field labels. |
| labelAsteriskColor | Color of the required field asterisk. |
| inputTextColor | Color of text inside input fields. |
| inputBorder | Border style for input fields. |
| inputBackgroundColor | Background color of input fields. |
| inputBorderRadius | Border radius of input fields. |
| inputFocusedBottomBorderColor | Bottom border color when an input is focused. |
| inputErrorBackgroundColor | Background color of input fields in an error state. |
| errorFontSize | Font size for error messages. |
| errorColor | Color of error message text. |


## Migration Notes

If you have an existing Flute Elements integration, the following methods and parameters have been deprecated.

**`flute.confirmPayment()` is deprecated.** Use `flute.submit()` instead. The interface is identical: replace the method name and no other changes are needed.

**`return_url` is deprecated.** Use `submission_callback` instead and redirect manually when needed:

```js
submission_callback: () => {
  window.location.href = '/your-success-page';
},
```