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.
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
<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.
- 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()
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.
- Navigate to the edit view of your connector.
- In the Base Data section on the left, find the entry called Public Integration Key (e.g. for payment.js).
- 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.
- Javascript
<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.
- Javascript
<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.
- Javascript
<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 += ``;
}
},
);
}
</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.
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.
For details on how to authenticate API calls to the IXOPAY platform, see Authentication.
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
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"
}'
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
$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);
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())
.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 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"
}
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
- Javascript
<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 += ``;
}
},
);
}
</script>
</body>
</html>
Next steps
Now that you've integrated IXOPAY platform via hosted fields, you can look into …
- … testing your setup to make sure you've set up everything correctly.
- … handling changes to the payments status with callbacks.
- … recurring payments to generate recurring revenue.
- … making your code production-ready by handling errors.
- … getting more in-depth details about the payment.js features from the "Hosted fields — payment.js" reference.
Appendix: Full code sample
Code sample
- Javascript
<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 += ``;
}
}
},
);
}
</script>
</body>
</html>