Skip to main content

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.

Reference

For more details on payment.js, check out the in-depth article on payment.js 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 <head> 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 <script>-tag.

<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 <form>. payment.js handles the card number and CVV fields and will replace them with secure iframes.

Build the form with the following elements:

  • <input> elements for
    • Card holder name (or separate fields for first name and last name)
    • Expiration year
    • Expiration month
  • <div> or <span> 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.

...
<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() 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.

    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.

<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(). We extract the required data from the <form> 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 <form> and submit it to our backend. In the case of an error, add error handling to the error callback and display it to the customer.

<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() method.

The errors are described in detail in Error handling.

<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 <form> 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 transaction, which when successful, will immediately transfer funds. Depending on your business model a Preauthorize transaction - followed by a Capture transaction - might be more appropriate.

Authentication

For details on how to authenticate API calls to the IXOPAY platform, see Authentication.

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 --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 '{
"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"
}'

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 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 page.

For details on how to handle unsuccessful transaction results, see Handling errors.

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

{
"success": true,
"uuid": "d94c0d72f3a36e21f16e",
"purchaseId": "20240514-d94c0d72f3a36e21f16e",
"returnType": "FINISHED",
"paymentMethod": "Creditcard"
}
3-D Secure

Sometimes 3D Secure authentication is required for credit card payments. For details on how to handle 3D Secure transaction results, see 3-D 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.

Code sample
<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 …

Appendix: Full code sample

Code sample
<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>