> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://ixopay.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://ixopay.ferndocs.com/_mcp/server.

# Hosted fields — payment.js

⁮payment.js is a flexible way to accept payments that requires programming knowledge. With payment.js, you have full control over the look and feel of your payment form, and the customer remains on your website during the payment process, rather than being redirected.

```mermaid
%%{ init: { "sequence": {"mirrorActors": false} } }%%
sequenceDiagram
  actor C as Customer
  participant B as Merchant website
  participant M as Merchant backend
  participant G as IXOPAY platform
  participant PSP
  C-->>B: Checkout
  B->>B: Load payment.js
  B->>B: Define your payment fields
  B->>+G: Initialize payment.js
  G-->>-B: Load <iframe>
  B-->>C: Show payment form
  C-->>B: Enter payment details
  B->>G: Intercept form submit
  B->>B: Error handling
  B-->>M: Submit cc-token
  M->>G: Perform transaction
  activate G
  G-->>+PSP: Perform payment
  PSP->>-G: Payment complete
  G->>M: Payment complete
  deactivate G
  M-->>B: Show success page
```

> **Reference**
>
> For more details on  payment.js, check out the in-depth article on [payment.js](https://documentation.ixopay.com/docs/reference/integration/processing-options/payment.js "Payment.js reference article") in the reference.

## How to use payment.js

### Step 1: Include payment.js

The first step is to prepare the checkout page on your website to use payment.js. Start by opening the checkout page's source (where your customer enters their credit card number).

The following section guides you through the process of integrating payment.js step-by-step. To access the full sample code, see Appendix: Full code sample.

#### Load payment.js

To use payment.js, you have to load the provided JavaScript file. Include the script in `&lt;head&gt;` to optimize the browser's resource loading. Load the file from `https://gateway.ixopay.com/js/integrated/payment.1.3.min.js` and make sure the attribute `data-main="payment-js"` is included in the `&lt;script&gt;`-tag.

* HTML

```html
<html>
  <head>
    <title>Your checkout page</title>
    <script
      data-main="payment-js"
      src="https://gateway.ixopay.com/js/integrated/payment.1.3.min.js"
    ></script>
  </head>
  <!-- ... -->
</html>
```

#### Define your payment fields

Add payment.js to your credit card `&lt;form&gt;`. payment.js handles the *card number* and *CVV* fields and will replace them with secure iframes.

Build the form with the following elements:

* `&lt;input&gt;` elements for
  * Card holder *name* (or separate fields for *first name* and *last name*)
  * Expiration *year*
  * Expiration *month*
* `&lt;div&gt;` or `&lt;span&gt;` elements for
  * *Card number*
  * *CVV*

Make sure you have a way to easily access these fields in JavaScript, for example by assigning unique `id`-attributes.

Add an `onsubmit` handler to handle a user clicking submit. This intercept handler is responsible for tokenization of the credit card data.

* HTML

```html
...
<p>Please enter your credit card details:</p>
<form id="payment-form" method="POST" action="/checkout" onsubmit="interceptSubmit()">
  <!-- add additional fields for your internal processing if necessary -->
  <input type="hidden" name="cc-token" id="cc-token" />
  <div>
    <label for="cc-name">Card holder</label>
    <input type="text" id="cc-name" name="cc-name" autocomplete="cc-name" />
    <p id="cc-name-error"></p>
  </div>
  <div>
    <label for="cc-number">Card number</label>
    <div id="cc-number"></div>
    <p id="cc-number-error"></p>
  </div>
  <div>
    <label for="cc-csc">CVV</label>
    <div id="cc-csc"></div>
    <p id="cc-csc-error"></p>
  </div>
  <div>
    <label for="cc-exp-month">Expiration Month</label>
    <input type="number" id="cc-exp-month" name="cc-exp-month" autocomplete="cc-exp-month" />
    <p id="cc-exp-month-error"></p>
  </div>
  <div>
    <label for="cc-exp-year">Expiration Year</label>
    <input type="number" id="cc-exp-year" name="cc-exp-year" autocomplete="cc-exp-year" />
    <p id="cc-exp-year-error"></p>
  </div>
  <div>
    <input type="submit" value="Submit" />
  </div>
</form>
...
```

#### Initialize payment.js

After you have created your payment form, create a `PaymentJs` object and call [`init()`](https://documentation.ixopay.com/docs/reference/integration/payment.js/methods#init) to initialize payment.js. Add this code to the end of your page, so payment.js is already loaded and page rendering is not blocked.

The `init()` method requires these arguments:

* The `$INTEGRATION_KEY` from [Setting up your account — Create a connector](https://documentation.ixopay.com/docs/guides/getting-started/setup#step-3-create-a-connector).

To find the integration key for an existing connector.

1. Navigate to the edit view of your connector.
2. In the *Base Data* section on the left, find the entry called *Public Integration Key (e.g. for payment.js)*.
3. Click the button to reveal the key.

* The `id` of the *card number* element

* The `id` of the *CVV* element

* Optional: A callback function to customize payment.js, see Style your hosted fields and [Advanced form styling](https://documentation.ixopay.com/docs/reference/integration/payment.js/advanced-guides/form-styling).

* Javascript

```html
<html>
  <!-- ... -->
  <body>
    <!-- ... -->
    <script>
      const payment = new PaymentJs();
      // Activate payment.js with your $INTEGRATION_KEY and bind it to the payments fields.
      payment.init("$INTEGRATION_KEY", "cc-number", "cc-csc");

      // TODO intercept form submit and tokenize credit card
    </script>
  </body>
</html>
```

#### Intercept form submit

Finally, when the user clicks on submit, we need to intercept the event and call [`tokenize()`](https://documentation.ixopay.com/docs/reference/integration/payment.js/methods#tokenize). We extract the required data from the `&lt;form&gt;` and pass it to payment.js. Then we register two callbacks to handle the success and error cases.

If tokenization is successful, we store the received `token` in the `&lt;form&gt;` and submit it to our backend. In the case of an error, add [error handling](https://documentation.ixopay.com/docs/reference/integration/payment.js/error-codes) to the error callback and display it to the customer.

* Javascript

```html
<html>
  <!-- ... -->
  <body>
    <!-- ... -->
    <script>
      const payment = new PaymentJs();
      // Activate payment.js with your $INTEGRATION_KEY and bind it to the payments fields.
      // You can get the $INTEGRATION_KEY from the connector page in the IXOPAY admin interface.

      payment.init("$INTEGRATION_KEY", "cc-number", "cc-csc");

      function interceptSubmit() {
        // additional data, MUST include card_holder (or first_name & last_name),
        // month and year
        const data = {
          card_holder: document.getElementById("cc-name").value,
          month: document.getElementById("cc-exp-month").value,
          year: document.getElementById("cc-exp-year").value,
        };
        payment.tokenize(
          data,
          // success callback function
          (token, cardData) => {
            document.getElementById("cc-token").value = token; // store transaction token
            document.getElementById("payment-form").submit(); // submit the form
          },
          // error callback function
          (errors) => {
            // TODO: display error to customer
          },
        );
      }
    </script>
  </body>
</html>
```

#### Error handling

You need to inform the customer if any data they entered was invalid. To do that, add an implementation for the error hook of the [`tokenize()`](https://documentation.ixopay.com/docs/reference/integration/payment.js/methods#tokenize) method.

The errors are described in detail in [Error handling](https://documentation.ixopay.com/docs/reference/integration/payment.js/error-codes).

* Javascript

```html
<html>
  <!-- ... -->
  <body>
    <!-- ... -->
    <script>
      const payment = new PaymentJs();
      // Activate payment.js with your $INTEGRATION_KEY and bind it to the payments fields.
      payment.init("$INTEGRATION_KEY", "cc-number", "cc-csc");

      function interceptSubmit() {
        const errorElements = {
          number: document.getElementById("cc-number-error"),
          cvv: document.getElementById("cc-csc-error"),
          month: document.getElementById("cc-exp-month-error"),
          year: document.getElementById("cc-exp-year-error"),
          card_holder: document.getElementById("cc-name-error"),
          // we don't use the integration variant where we have separate fields for the
          // first and last name, just add any errors returned for them to the card_holder
          // error element.
          first_name: document.getElementById("cc-name-error"),
          last_name: document.getElementById("cc-name-error"),
        };
        // reset error text
        Object.values(errorElements).forEach((elem) => (elem.innerHTML = ""));
        // additional data, MUST include card_holder (or first_name & last_name),
        // month and year
        const data = {
          card_holder: document.getElementById("cc-name").value,
          month: document.getElementById("cc-exp-month").value,
          year: document.getElementById("cc-exp-year").value,
        };
        payment.tokenize(
          data,
          // success callback function
          (token, cardData) => {
            document.getElementById("cc-token").value = token; // store transaction token
            document.getElementById("payment-form").submit(); // submit the form
          },
          // error callback function
          (errors) => {
            for (const error of errors) {
              errorElements[error.attribute].innerHTML += `${error.message}<br />`;
            }
          },
        );
      }
    </script>
  </body>
</html>
```

### Step 2: Perform transaction

Now that we have successfully obtained the `cc-token` for the tokenized credit card, we can accept payment from the customer.

> **Transaction token**
>
> We refer to the transaction token received via the `&lt;form&gt;` submit `cc-token` as `$CC_TOKEN` in the following sample code.

To perform the transaction in your backend system, we need to create a transaction by sending a `POST` request to `gateway.ixopay.com`.

Here we choose a [Debit](https://documentation.ixopay.com/api/transaction/debit) transaction, which when successful, will immediately transfer funds. Depending on your business model a [Preauthorize](https://documentation.ixopay.com/api/transaction/preauthorize) transaction - followed by a [Capture](https://documentation.ixopay.com/api/transaction/capture) transaction - might be more appropriate.

> **Authentication**
>
> For details on how to authenticate API calls to the IXOPAY platform, see [Authentication](https://documentation.ixopay.com/api/transaction/transaction-api).

> **Tip**
>
> This is a minimal example. Include as much information as possible in the `customer` field. This reduces friction and provides the most benefit for risk checks, 3D-secure authentication and other validations.

* curl
* Python
* PHP
* Java

```sh
curl --request POST -sL \
  --url "https://gateway.ixopay.com/api/v3/transaction/${API_KEY}/debit" \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header "Authorization: Basic $(echo -n "$USERNAME:$PASSWORD" | base64)" \
  --data-raw "$(cat <<JSON
  {
    "merchantTransactionId": "your-unique-identifier",
    "transactionToken": "$CC_TOKEN",
    "description": "Purchase description shown on credit card statement.",
    "amount": "9.99",
    "currency": "EUR",
    "successUrl": "https://shop.example.org/checkout/success",
    "cancelUrl": "https://shop.example.org/checkout/cancelled",
    "errorUrl": "https://shop.example.org/checkout/error"
  }
JSON
")
```

```python
import requests
import json
import base64
import os

url = "https://gateway.ixopay.com/api/v3/transaction/{apiKey}/debit".format(
    apiKey=os.environ["API_KEY"]
)
auth = base64.b64encode("%s:%s" % (os.environ["USERNAME"], os.environ["PASSWORD"]))
cc_token = os.environ["CC_TOKEN"]

payload = json.dumps(
    {
        "merchantTransactionId": "your-unique-identifier",
        "transactionToken": cc_token,
        "description": "Purchase description shown on credit card statement.",
        "amount": "9.99",
        "currency": "EUR",
        "successUrl": "https://shop.example.org/checkout/success",
        "cancelUrl": "https://shop.example.org/checkout/cancelled",
        "errorUrl": "https://shop.example.org/checkout/error"
    }
)
headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "Authorization": "Basic {auth}".format(auth=auth),
}

response = requests.request("POST", url, headers=headers, data=payload)
```

```php
<?php

$curl = curl_init();

$auth = base64_encode("$USERNAME:$PASSWORD");
$transactionToken = $_REQUEST['cc-token'];

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://gateway.ixopay.com/api/v3/transaction/$API_KEY/debit",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => <<<EOD
  {
    "merchantTransactionId": "your-unique-identifier",
    "transactionToken": {$transactionToken},
    "description": "Purchase description shown on credit card statement.",
    "amount": "9.99",
    "currency": "EUR",
    "successUrl": "https://shop.example.org/checkout/success",
    "cancelUrl": "https://shop.example.org/checkout/cancelled",
    "errorUrl": "https://shop.example.org/checkout/error"
  }
  EOD,
  CURLOPT_HTTPHEADER => array(
    'Content-Type: application/json',
    'Accept: application/json',
    "Authorization: Basic $auth"
  ),
));

$response = curl_exec($curl);

curl_close($curl);
```

```java
String transactionToken = req.getParameter("cc-token");

OkHttpClient client = new OkHttpClient().newBuilder().build();
RequestBody body = RequestBody.create(
  MediaType.parse("application/json"),
  "{" +
    "\"merchantTransactionId\": \"your-unique-identifier\"," +
    "\"transactionToken\": \"" + transactionToken + "\"," +
    "\"description\": \"Purchase description shown on credit card statement.\"," +
    "\"amount\": \"9.99\"," +
    "\"currency\": \"EUR\"," +
    "\"successUrl\": \"https://shop.example.org/checkout/success\"," +
    "\"cancelUrl\": \"https://shop.example.org/checkout/cancelled\"," +
    "\"errorUrl\": \"https://shop.example.org/checkout/error\"" +
  "}"
);
String auth = Base64.getEncoder().encodeToString(
  "%s:%s".format(System.getenv("USERNAME"), System.getenv("PASSWORD")));
Request request = new Request.Builder()
  .url("https://gateway.ixopay.com/api/v3/transaction/%s/debit"
    .format(System.getenv("API_KEY")))
  .method("POST", body)
  .addHeader("Content-Type", "application/json")
  .addHeader("Accept", "application/json")
  .addHeader("Authorization", "Basic %s".format(auth))
  .build();
Response response = client.newCall(request).execute();
```

### Step 3: Payment complete

Your backend system receives the response to the transaction call. For a successful transaction, display your checkout complete page to the customer.

Additionally, if you specify a `callbackUrl` in the [Debit](https://documentation.ixopay.com/api/transaction/debit) transaction request, the IXOPAY platform will send a callback with the status of the payment. For more information on how to set up and use callbacks, please see the [callbacks](https://documentation.ixopay.com/docs/guides/getting-started/callbacks) page.

For details on how to handle unsuccessful transaction results, see [Handling errors](https://documentation.ixopay.com/docs/guides/production/handling-errors).

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "uuid": "d94c0d72f3a36e21f16e",
  "purchaseId": "20260922-d94c0d72f3a36e21f16e",
  "returnType": "FINISHED",
  "paymentMethod": "Creditcard"
}
```

> **3-D Secure**
>
> Sometimes [3D Secure](https://documentation.ixopay.com/docs/guides/features/3d-secure) authentication is required for credit card payments. For details on how to handle 3D Secure transaction results, see [3-D Secure](https://documentation.ixopay.com/docs/guides/features/3d-secure).

### Optional: Style your hosted fields

One of the benefits of payment.js is that it can be seamlessly integrated into your checkout process. This includes styling the payment form. Below is a sample of how to style the form created in this guide.

For more details on styling, see [Form styling](https://documentation.ixopay.com/docs/reference/integration/payment.js/advanced-guides/form-styling).

Code sample

* Javascript

```html
<html>
  <!-- ... -->
  <body>
    <!-- ... -->
    <script>
      const payment = new PaymentJs();
      // Activate payment.js with your $INTEGRATION_KEY and bind it to the payment fields.
      payment.init("$INTEGRATION_KEY", "cc-number", "cc-csc", (payment) => {
        // style credit card number field
        payment.setNumberStyle({
          border: "1px solid black",
          width: "150px",
        });
        // style CVV field
        payment.setCvvStyle({
          border: "1px solid black",
          width: "150px",
        });
        // add hook for credit card number field
        payment.numberOn("input", (data) => {
          console.log("A credit card number was entered");
        });
      });

      function interceptSubmit() {
        const errorElements = {
          number: document.getElementById("cc-number-error"),
          cvv: document.getElementById("cc-csc-error"),
          month: document.getElementById("cc-exp-month-error"),
          year: document.getElementById("cc-exp-year-error"),
          card_holder: document.getElementById("cc-name-error"),
          // we don't use the integration variant where we have separate fields for the
          // first and last name, just add any errors returned for them to the card_holder
          // error element.
          first_name: document.getElementById("cc-name-error"),
          last_name: document.getElementById("cc-name-error"),
        };
        // reset error text
        Object.values(errorElements).forEach((elem) => (elem.innerHTML = ""));
        // additional data, MUST include card_holder (or first_name & last_name),
        // month and year
        const data = {
          card_holder: document.getElementById("cc-name").value,
          month: document.getElementById("cc-exp-month").value,
          year: document.getElementById("cc-exp-year").value,
        };
        payment.tokenize(
          data,
          // success callback function
          (token, cardData) => {
            document.getElementById("cc-token").value = token; // store transaction token
            document.getElementById("payment-form").submit(); // submit the form
          },
          // error callback function
          (errors) => {
            for (const error of errors) {
              errorElements[error.attribute].innerHTML += `${error.message}<br />`;
            }
          },
        );
      }
    </script>
  </body>
</html>
```

## Next steps

Now that you've integrated IXOPAY platform via hosted fields, you can look into …

* … [testing your setup](https://documentation.ixopay.com/docs/guides/getting-started/testing) to make sure you've set up everything correctly.
* … handling changes to the payments status with [callbacks](https://documentation.ixopay.com/docs/guides/getting-started/callbacks).
* … [recurring payments](https://documentation.ixopay.com/docs/guides/getting-started/recurring-payments) to generate recurring revenue.
* … making your code production-ready by [handling errors](https://documentation.ixopay.com/docs/guides/production/handling-errors).
* … getting more in-depth details about the payment.js features from the ["Hosted fields — payment.js" reference](https://documentation.ixopay.com/docs/reference/integration/payment.js).

## Appendix: Full code sample

Code sample

* Javascript

```html
<html>
  <head>
    <title>Your checkout page</title>
    <script
      data-main="payment-js"
      src="https://gateway.ixopay.com/js/integrated/payment.1.3.min.js"
    ></script>
  </head>
  <body>
    <p>Please enter your credit card details:</p>
    <form id="payment-form" method="POST" action="/checkout" onsubmit="interceptSubmit()">
      <!-- add additional fields for your internal processing if necessary -->
      <input type="hidden" name="cc-token" id="cc-token" />
      <div>
        <label for="cc-name">Card holder</label>
        <input type="text" id="cc-name" name="cc-name" autocomplete="cc-name" />
        <p id="cc-name-error"></p>
      </div>
      <div>
        <label for="cc-number">Card number</label>
        <div id="cc-number"></div>
        <p id="cc-number-error"></p>
      </div>
      <div>
        <label for="cc-csc">CVV</label>
        <div id="cc-csc"></div>
        <p id="cc-csc-error"></p>
      </div>
      <div>
        <label for="cc-exp-month">Expiration Month</label>
        <input type="number" id="cc-exp-month" name="cc-exp-month" autocomplete="cc-month" />
        <p id="cc-exp-month-error"></p>
      </div>
      <div>
        <label for="cc-exp-year">Expiration Year</label>
        <input type="number" id="cc-exp-year" name="cc-exp-year" autocomplete="cc-year" />
        <p id="cc-exp-year-error"></p>
      </div>
      <div>
        <input type="submit" value="Submit" />
      </div>
    </form>
    <script>
      const payment = new PaymentJs();
      // Activate payment.js with your $INTEGRATION_KEY and bind it to the payment fields.
      payment.init("$INTEGRATION_KEY", "cc-number", "cc-csc", (payment) => {
        // style credit card number field
        payment.setNumberStyle({
          border: "1px solid black",
          width: "150px",
        });
        // style CVV field
        payment.setCvvStyle({
          border: "1px solid black",
          width: "150px",
        });
        // add hook for credit card number field
        payment.numberOn("input", (data) => {
          console.log("A credit card number was entered");
        });
      });

      function interceptSubmit() {
        const errorElements = {
          number: document.getElementById("cc-number-error"),
          cvv: document.getElementById("cc-csc-error"),
          month: document.getElementById("cc-exp-month-error"),
          year: document.getElementById("cc-exp-year-error"),
          card_holder: document.getElementById("cc-name-error"),
          // we don't use the integration variant where we have separate fields for the
          // first and last name, just add any errors returned for them to the card_holder
          // error element.
          first_name: document.getElementById("cc-name-error"),
          last_name: document.getElementById("cc-name-error"),
        };
        // reset error text
        Object.values(errorElements).forEach((elem) => (elem.innerHTML = ""));
        // additional data, MUST include card_holder (or first_name & last_name),
        // month and year
        const data = {
          card_holder: document.getElementById("cc-name").value,
          month: document.getElementById("cc-exp-month").value,
          year: document.getElementById("cc-exp-year").value,
        };
        payment.tokenize(
          data,
          // success callback function
          (token, cardData) => {
            document.getElementById("cc-token").value = token; // store transaction token
            document.getElementById("payment-form").submit(); // submit the form
          },
          // error callback function
          (errors) => {
            for (const error of errors) {
              if (error.attribute === "integration_key") {
                alert(
                  "Invalid integration key, lookup the settings in your connector.\n" +
                    "This should never be visible to the customer",
                );
              } else {
                errorElements[error.attribute].innerHTML += `${error.message}<br />`;
              }
            }
          },
        );
      }
    </script>
  </body>
</html>
```