Seamless Checkout
The Connect2Pay Seamless Checkout is a JavaScript library to be included in the merchants checkout page’s HTML. It renders a responsive payment form for seamlessly passing customer’s payment information into Connect2Pay server and process the payment. The objective of this library is to allow merchants to capture user credit card information in the client-side and pass it directly to Connect2Pay API without breaking PCI-DSS standards.
1 - Create token
To be able to use the Seamless Checkout you need to make a Prepare API call and retrieve the customerToken from the API response:
We strongly advise the use of parameter ctrlCallbackURL.
use PayXpert\Connect2Pay\Connect2PayClient;
use PayXpert\Connect2Pay\containers\request\PaymentPrepareRequest;
use PayXpert\Connect2Pay\containers\Order;
use PayXpert\Connect2Pay\containers\Shipping;
use PayXpert\Connect2Pay\containers\Shopper;
use PayXpert\Connect2Pay\containers\Account;
use PayXpert\Connect2Pay\containers\constant\OrderShippingType;
use PayXpert\Connect2Pay\containers\constant\OrderType;
use PayXpert\Connect2Pay\containers\constant\PaymentMethod;
use PayXpert\Connect2Pay\containers\constant\PaymentMode;
use PayXpert\Connect2Pay\containers\constant\AccountAge;
use PayXpert\Connect2Pay\containers\constant\AccountLastChange;
use PayXpert\Connect2Pay\containers\constant\AccountPaymentMeanAge;
$connect2pay = "https://connect2.payxpert.com";
// This will be provided once your account is approved
$originator = "000000";
$password = "Gr3atPassw0rd!";
$c2pClient = new Connect2PayClient($connect2pay, $originator, $password);
$prepareRequest = new PaymentPrepareRequest();
$shopper = new Shopper();
$account = new Account();
$order = new Order();
$shipping = new Shipping();
// Set all information for the payment
$prepareRequest->setPaymentMethod(PaymentMethod::CREDIT_CARD);
$prepareRequest->setPaymentMode(PaymentMode::SINGLE);
// To charge €25.99
$prepareRequest->setCurrency("EUR");
$prepareRequest->setAmount(2599);
// Extra custom data that are returned with the payment status
$prepareRequest->setCtrlCustomData("Give that back to me please!!");
// Where the customer will be redirected after the payment
$prepareRequest->setCtrlRedirectURL("https://merchant.example.com/payment/redirect");
// URL on the merchant site that will receive the callback notification
$prepareRequest->setCtrlCallbackURL("https://merchant.example.com/payment/callback");
$order->setId("ABC-123456");
$order->setShippingType(OrderShippingType::DIGITAL_GOODS);
$order->setType(OrderType::GOODS_SERVICE);
$order->setDescription("Payment of €25.99");
$shopper->setId("1234567WX");
$shopper->setFirstName("John")->setLastName("Doe");
$shopper->setAddress1("Debit Street, 45");
$shopper->setZipcode("3456TRG")->setCity("New York")->setState("NY")->setCountryCode("US");
$shopper->setHomePhonePrefix("212")->setHomePhone("12345678");
$shopper->setEmail("shopper@example.com");
$account->setAge(AccountAge::LESS_30_DAYS);
$account->setDate("20210106");
$account->setLastChange(AccountLastChange::LESS_30_DAYS);
$account->setLastChangeDate("20210106");
$account->setPaymentMeanAge(AccountPaymentMeanAge::LESS_30_DAYS);
$account->setPaymentMeanDate("20210106");
// Set 'true' to trigger SCA challenge
$account->setSuspicious(false);
$shipping->setName("Lady Gogo");
$shipping->setAddress1("125 Main Street");
$shipping->setZipcode("ABC-5678")->setState("NY")->setCity("New York")->setCountryCode("US");
$shipping->setPhone("+47123456789");
$shopper->setAccount($account);
$prepareRequest->setShopper($shopper);
$prepareRequest->setOrder($order);
$prepareRequest->setShipping($shipping);
$result = $c2pClient->preparePayment($prepareRequest);
if ($result !== false) {
// The customer token info returned by the payment page could be saved in session (may
// be used later when the customer will be redirected from the payment page)
$_SESSION['merchantToken'] = $result->getMerchantToken();
// The merchantToken must also be used later to validate the callback to avoid that anyone
// could call it and abusively validate the payment. It may be stored in local database for this.
// Now redirect the customer to the payment page
header('Location: ' . $c2pClient->getCustomerRedirectURL($result));
} else {
echo "Payment preparation error occurred: " . $c2pClient->getClientErrorMessage() . "\n";
}
use PayXpert\Connect2Pay\Connect2PayClient;
$url = "https://connect2.payxpert.com";
// This will be provided once your account is approved
$originator = "000000";
$password = "Gr3atPassw0rd!";
$c2pClient = new Connect2PayClient($url, $originator, $password);
// Set all information for the payment
$c2pClient->setOrderID("ABC-123456");
$c2pClient->setPaymentMethod(Connect2PayClient::PAYMENT_METHOD_CREDITCARD); // or PAYMENT_METHOD_DIRECTDEBIT for SEPA, for example
$c2pClient->setPaymentMode(Connect2PayClient::PAYMENT_MODE_SINGLE);
$c2pClient->setShopperID("1234567WX");
$c2pClient->setShippingType(Connect2PayClient::SHIPPING_TYPE_VIRTUAL);
// To charge €25.99
$c2pClient->setCurrency("EUR");
$c2pClient->setAmount(2599);
$c2pClient->setOrderDescription("Payment of €25.99");
$c2pClient->setShopperFirstName("John");
$c2pClient->setShopperLastName("Doe");
$c2pClient->setShopperAddress("NA");
$c2pClient->setShopperZipcode("NA");
$c2pClient->setShopperCity("NA");
$c2pClient->setShopperCountryCode("GB");
$c2pClient->setShopperPhone("+4712345678");
$c2pClient->setShopperEmail("shopper@example.com");
// Extra custom data that are returned with the payment status
$c2pClient->setCtrlCustomData("Give that back to me please !!");
// Where the customer will be redirected after the payment
$c2pClient->setCtrlRedirectURL("https://merchant.example.com/payment/redirect");
// URL on the merchant site that will receive the callback notification
$c2pClient->setCtrlCallbackURL("https://merchant.example.com/payment/callback");
if ($c2pClient->validate()) {
if ($c2pClient->preparePayment()) {
// The customer token info returned by the payment page could be saved in session (may
// be used later when the customer will be redirected from the payment page)
$_SESSION['merchantToken'] = $c2pClient->getMerchantToken();
// The merchantToken must also be used later to validate the callback to avoid that anyone
// could call it and abusively validate the payment. It may be stored in local database for this.
// Now redirect the customer to the payment page
header('Location: ' . $c2pClient->getCustomerRedirectURL());
} else {
echo "error prepareTransaction: ";
echo $c2pClient->getClientErrorMessage() . "\n";
}
} else {
echo "Validation error occurred: " . $c2pClient->getClientErrorMessage() . "\n";
}
import com.payxpert.connect2pay.client.containers.Account;
import com.payxpert.connect2pay.client.containers.Order;
import com.payxpert.connect2pay.client.containers.Shipping;
import com.payxpert.connect2pay.client.containers.Shopper;
import com.payxpert.connect2pay.client.requests.PaymentRequest;
import com.payxpert.connect2pay.constants.PaymentMode;
import com.payxpert.connect2pay.constants.sca.ShippingType;
import com.payxpert.connect2pay.constants.sca.ShopperAccountAge;
import com.payxpert.connect2pay.constants.sca.ShopperAccountLastChange;
class TestPayment {
public void processPayment() {
// Process a payment of €39.99
PaymentRequest request = new PaymentRequest();
request.setCurrency("EUR").setAmount(3999);
request.setPaymentMode(PaymentMode.SINGLE);
// User will be redirected here when clicking on "Go back to merchant" button
request.setCtrlRedirectURL("http://my.website/somewhere");
// The payment page will do a callback on this URL after the payment is processed
request.setCtrlCallbackURL("http://my.website/payment-callback");
Order order = new Order();
order.setId("1234ABCD").setShippingType(ShippingType.DIGITAL_GOODS);
Shopper shopper = new Shopper();
shopper.setEmail("test@example.com").setFirstName("John").setLastName("Doe")
.setHomePhonePrefix("47").setHomePhone("123456789").setAddress1("Main Street 41")
.setZipcode("123456").setCity("London").setCountryCode("GB");
Account account = new Account();
account.setAge(ShopperAccountAge.BETWEEN_30_60_DAYS).setSuspicious(false)
.setLastChange(ShopperAccountLastChange.BETWEEN_30_60_DAYS);
shopper.setAccount(account);
Shipping shipping = new Shipping();
shipping.setName("Jane Doe").setAddress1("Other Street, 54").setCity("London")
.setZipcode("654321").setCountryCode("GB");
request.setOrder(order);
request.setShopper(shopper);
request.setShipping(shipping);
// Validate the request
try {
request.validate();
} catch (BadRequestException e) {
logger.error("Ooops, an error occurred validating the payment request: " + e.getMessage());
// Handle the error...
}
// Instantiate the client and send the prepare request
// Second argument is the originator ID, third one is the associated API key
Connect2payClient c2p = new Connect2payClient("https://provided.url", "123456", "GreatP4ssw0rd");
PaymentResponse response = null;
try {
response = c2p.preparePayment(request);
} catch (Exception e) {
logger.error("Ooops, an error occurred preparing the payment: " + e.getMessage());
// Handle the error...
}
if (response != null && ResultCode.SUCCESS.equals(response.getCode())) {
// Get the URL to redirect the user to
String redirectURL = response.getCustomerRedirectURL();
if (redirectURL != null) {
// Redirect the user towards this URL, this will display the payment page
}
} else {
// Handle the failure
}
}
}
// Process a payment of €39.99
PaymentRequest request = new PaymentRequest();
request.setOrderId("1234ABCD").setCurrency("EUR").setAmount(3999);
request.setShippingType(ShippingType.VIRTUAL);
request.setPaymentMode(PaymentMode.SINGLE);
// User will be redirected here when clicking on "Go back to merchant" button
request.setCtrlRedirectURL("http://my.website/somewhere");
// The payment page will do a callback on this URL after the payment is processed
request.setCtrlCallbackURL("http://my.website/payment-callback");
// Validate the request
try {
request.validate();
} catch (BadRequestException e) {
logger.error("Ooops, an error occurred validating the payment request: " + e.getMessage());
// Handle the error...
}
// Instantiate the client and send the prepare request
// Second argument is the originator ID, third one is the associated API key
Connect2payClient c2p = new Connect2payClient("https://connect2.payxpert.com", "123456", "GreatP4ssw0rd");
PaymentResponse response = null;
try {
response = c2p.preparePayment(request);
} catch (Exception e) {
logger.error("Ooops, an error occurred preparing the payment: " + e.getMessage());
// Handle the error...
}
if (response != null && ResultCode.SUCCESS.equals(response.getCode()) {
// Get the URL to redirect the user to
String redirectURL = response.getCustomerRedirectURL();
if (redirectURL != null) {
// Redirect the user towards this URL, this will display the payment page
}
} else {
// Handle the failure
}
const paymentPage = require("payxpert")("123456", "GreatP4ssw0rd").connect2pay;
const body = {
"shippingType": "virtual",
"paymentMode": "single",
"amount":500,
"currency":"EUR",
"orderID":"NODEJS TEST",
"ctrlRedirectURL":"http://my.website/somewhere",
"ctrlCallbackURL":"http://my.website/payment-callback"
};
const responseCreatePayment = await paymentPage.createPayment(body);
if (responseCreatePayment.code == "200") {
// Success
}
var client = new Connect2PayClient(OriginatorConfig.ORIGINATOR_ID, OriginatorConfig.ORIGINATOR_PASSWORD);
var request = client.NewRequestCreatePayment();
request.Data.orderID = "ABC-123456";
request.Data.paymentMethod = PaymentMethod.CREDIT_CARD;
request.Data.paymentMode = PaymentMode.SINGLE;
request.Data.shopperID = "RICH_SHOPPER";
request.Data.shippingType = ShippingType.VIRTUAL;
request.Data.operation = Operation.SALE;
request.Data.amount = 1500; // 15 EUR
request.Data.currency = "EUR";
request.Data.orderDescription = "Payment of €15.00";
request.Data.shopperFirstName = "RICH";
request.Data.shopperLastName = "SHOPPER";
request.Data.shopperAddress = "NA";
request.Data.shopperZipcode = "999111";
request.Data.shopperCity = "NA";
request.Data.shopperCountryCode = "GB";
request.Data.shopperPhone = "123-456";
request.Data.shopperEmail = "test@test.test";
request.Data.ctrlCustomData = "Give that back to me please !!";
request.Data.ctrlRedirectURL = "https://merchant.example.com/payment/redirect";
request.Data.ctrlCallbackURL = "https://merchant.example.com/payment/callback";
if (request.Data.Validate())
{
var response = await request.Send();
if (response.IsSuccessfull())
{
Console.WriteLine("Sale operation executed successfully");
Console.WriteLine("Merchant token: " + response.merchantToken);
Console.WriteLine("Customer redirect URL: " + request.getCustomerRedirectURL());
}
}
- Response:
{
"code": "200",
"message": "Request processed successfully",
"customerToken": "1234",
"merchantToken": "4321"
}
2 - Prepare your HTML
In your checkout page you should include a div element with a specified attribute id.
<div id="payment-container">
</div>
Outside the div container, include a script tag with source to our Seamless Checkout library, using the customerToken you have received from step 1. You should add the attributes crossorigin=”anonymous”, “integrity=sha384…”, async=true and data-mount-in={DIV CONTAINER ID}
<div id="payment-container">
</div>
<script async="true" src="https://connect2.payxpert.com/payment/{customerToken}/connect2pay-seamless-v1.5.0.js" data-mount-in="#payment-container"
integrity="sha384-0IS2bunsGTTsco/UMCa56QRukMlq2pEcjUPMejy6WspCmLpGmsD3z0CmF5LQHF5X" crossorigin="anonymous"></script>
Voilà! The credit card form is instantly loaded and rendered into your checkout page.
3 - Process callback
Once payment is successful, your server will receive a server-side notification with a JSON object and all the information regarding the payment. This is why it is very important to use the ctrlCallbackURL parameter in the token creation step!
Client-side callback
Seamless Checkout can trigger a specified callback function and inject the paymentStatus object as parameter.
NEVER use this method for payment confirmation of the order, since the object can be hijacked and values can be modified on the client-side.
ALWAYS rely on server-side callback for this purpose.
The client side Javascript callback should be used to request the real payment status towards the merchant server. To avoid synchronization problem between the client request and the server side callback, the merchant server can use the paymentStatus to actively check the result of the payment.
<!-- Inside the JSON configuration of the Seamless -->
<script type="application/json">
{
"onPaymentResult":"executeMePlease"
}
</script>
<script type="text/javascript">
function executeMePlease(response) {
// This function could be used to query the status of the payment from the server side and update the display accordingly.
console.log(response); // JSON object with the API response
document.getElementById("c2pResultText").appendChild(<p>You did it!!</p>); // You can fetch the result div and modify
}
</script>
Events
Seamless Checkout form can trigger Javascript events that you can use to better integrate the payment flow with your ecommerce:
validForm
This event triggers when customer finishes inputting all valid credit card information, can be catched like this:
document.addEventListener('validForm', function (e) {
console.log("Valid form!")
})
payButtonClick
This event triggers when the payment button is clicked. Can be catched like this:
document.addEventListener('payButtonClick', function (e) {
console.log("Form was submitted! Let's add it to Google Analytics")
})
Customization
The library can be customized in different ways, depending on the merchants’ needs.
<div id="payment-form-container">
<script type="application/json">
{
"payButtonText": "Pay €10.99 now!",
"language": "en",
"fontFamily": "Helvetica"
}
</script>
</div>
Class injection
You can inject external classes directly into the form elements. This is useful is your website is using tools such as Bootstrap.
<div id="payment-form-container">
<script type="application/json">
{
"payButtonText": "Pay €10.99 now!",
"customClasses": {
"cardHolderNameContainer": "form-group",
"cardHolderName": "form-control",
"cardNumberContainer": "form-group",
"cardNumber": "form-control",
"cardExpireDateShortContainer": "form-group",
"cardExpireDateShort": "form-control",
"cardSecurityCodeContainer": "form-group",
"cardSecurityCode": "form-control"
}
}
</script>
</div>
<!-- The configuration above will result: -->
<form class="cardForm__3-0Ac" novalidate="true" id="c2pCreditCardForm">
<div class="form-group" id="c2pCardHolderNameContainer">
<label id="c2pCardHolderNameLabel">
Cardholder Name
</label>
<input type="text" class="formInput__3Sjbj form-control" id="c2pCardHolderName">
</div>
<div class=" form-group" id="c2pCardNumberContainer">
<label id="c2pCardNumberLabel">
Card Number
</label>
<input placeholder="____ ____ ____ ____" pattern="[1-9]/d+" class="formInput__3Sjbj cardNumber__5kHQE form-control" id="c2pCardNumber" type="tel">
</div>
....
....
....
</form>
The skipDefaultStyle parameter will remove any default classes from the elements, so you can freely adapt the form with your own CSS classes.
Using id of top container
You can also customize the form defining the specific CSS for the tags of the container form:
<div id="payment-form-container">
</div>
<style type="text/css">
#payment-form-container > input[type=text] {
background-color: #FFF;
}
</style>
Using ID selector of each element
Every element of the form has its own id, so you can select it and customize and modify as you want:
<style type="text/css">
#c2pCardHolderName {
background-color: blue;
}
</style>
Here’s a list of the elements you can fetch
Element ID |
---|
c2pCreditCardForm |
c2pCardHolderNameContainer |
c2pCardHolderNameLabel |
c2pCardHolderName |
c2pCardNumberContainer |
c2pCardNumberLabel |
c2pCardNumber |
c2pCardIconContainer |
c2pCardInlineFields |
c2pCardExpireDateShortContainer |
c2pCardExpireDateShortLabel |
c2pCardExpireDateShort |
c2pCardSecurityCodeContainer |
c2pCardSecurityCodeLabel |
c2pCardSecurityCode |
c2pSubmitButtonContainer |
c2pSubmitButton |
c2p3DSModal |
c2pResultText |
External Payment Button
This features allows merchant to use the Seamless Checkout solely as a credit card form. For example, if merchant add the credit card form in a different step of the checkout and has their own payment button in their Checkout page, they can configure the Seamless Checkout like:
<!-- Checkout button: -->
<div class="myCheckoutButtonContainer">
<button id="myCheckoutButton">Let's Pay!</button>
</div>
<!-- Inside the JSON configuration of the Seamless -->
<script type="application/json">
{
"externalPaymentButton":"myCheckoutButton"
}
</script
Accepted configuration parameters
Parameter | Accepted Values | Description |
---|---|---|
hideCardHolderName | String | Hides cardholder name field |
onPaymentResult | String | Callback function to be triggered (don’t use parentheses or semicolons) |
noModal | Boolean | If set, breaks out of modal when 3DSecure authentication step is reached. |
fontFamily | String | Custom font if you like to customize the form |
language | en, fr, es | Language of the labels and placeholders |
enableApplePay | Boolean | Set to true to enable Apple Pay |
labels | Object to change the labels of the form | |
labels.cardHolderName | String | Label for the CardHolderName input |
labels.cardNumber | String | Label for the CardNumber input |
labels.cardExpireDateShort | String | Label for the Expiration Date input |
labels.cardSecurityCode | String | Label for the CSC input |
payButtonText | String | Text for the payment button e.g. “Pay Now €10.99” |
placeholders | Object to change the placeholders of the form’s inputs | |
placeholders.cardHolderName | String | Placeholder for the CardHolderName input |
placeholders.cardNumber | String | Placeholder for the CardNumber input |
placeholders.cardExpireDateShort | String | Placeholder for the Expiration Date input |
placeholders.cardSecurityCode | String | Placeholder for the CSC input |
customClasses | Object for customization of the elements | |
customClasses.cardHolderNameContainer | String | Classes will be applied into cardHolderName div container |
customClasses.cardHolderNameLabel | String | Classes will be applied into cardHolderName label |
customClasses.cardHolderName | String | Classes will be applied into cardHolderName input |
customClasses.cardNumberContainer | String | Classes will be applied into cardNumber div container |
customClasses.cardNumberLabel | String | Classes will be applied into cardNumber label |
customClasses.cardNumber | String | Classes will be applied into cardNumber input |
customClasses.cardExpireDateShortContainer | String | Classes will be applied into cardExpireDate div container |
customClasses.cardExpireDateShortLabel | String | Classes will be applied into cardExpireDate label |
customClasses.cardExpireDateShort | String | Classes will be applied into cardExpireDate input |
customClasses.cardSecurityCodeContainer | String | Classes will be applied into cardSecurityCode div container |
customClasses.cardSecurityCodeLabel | String | Classes will be applied into cardSecurityCode label |
customClasses.cardSecurityCode | String | Classes will be applied into cardSecurityCode input |