Api Products Menu
Digital wallets (for Worldline Greece)

Google Pay™

Introduction

Google Pay™ is a digital wallet platform and online payment system that powers in-app, tap-to-pay, and website purchases. It allows users to make payments online from the web (supported on Google Chrome, Mozilla Firefox, Apple Safari, Microsoft Edge, Opera, and UCWeb UC browsers) and with Android phones, tablets and watches using any credit or debit card saved to their Google Account, including those from Google Play, YouTube, Chrome, or an Android device.

Some of the main benefits of offering Google Pay as a payment method are:

  • A better way to pay: Google Pay™ is a faster, more secure way to pay on sites and in apps using payment methods saved to a Google Account
  • Availability: Google Pay™ is accepted in millions of places around the world. It’s available on Android, iOS, and desktop, and you can use it in multiple browsers, including Chrome, Firefox, and Safari
  • Increased conversions: Google Pay™ delivers frictionless checkout by eliminating the need to type billing and shipping details, increasing conversions by giving shoppers a better way to pay
  • Increased security: Google Pay™ protects your payment information with multiple layers of security, including card network tokenization

User Experience 

Screenshots 

At the checkout, the customer selects the Google Pay™ button at the top of the screen:

The customer then signs into their Google account:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The customer selects their payment card before completing the payment:

 

 

 

 

 

 

 

 

 

 

 

 

 

Requirements / Restrictions

  • Transaction types supported: Payment, Pre-authorization, Refunds, Void.
  • Schemes supported: Visa, Mastercard.
  • Google Pay™ transactions are being processed only through Worldline acquirer.

Important notice: Tokenization is not supported through Google Pay™.

Google Pay for Android Webview

In case you wish to use Google Pay through Android app with Webview, you need to follow the steps described here.

You also need to clarify to Google that you wish to use Cardlink’s existing Google Pay integration through Webview and you are not trying to render a separate Google Pay button outside the payment page.

SCA and PSD2

Google Pay™ supports both CRYPTOGRAM 3DS and PAN ONLY. Cryptogram 3DS, which is a tokenized solution, will always be preferred. Most issuers will accept this as being SCA, and hence not result in the need for completion of 3D-Secure.

In case a non-tokenized solution is used (PAN ONLY) or the issuer declines the transaction for missing SCA, Cardlink’s payment gateway will automatically continue with the 3DS flow to ensure the payment can succeed. 3DS flow is by default enabled to all merchants.

All merchants must adhere to the Google Pay APIs Acceptable Use Policy and accept the terms defined in the Google Pay API Terms of Service.

Google Pay is a trademark of Google LLC.

How to activate Google Pay™

All merchants are by default activated to accept payments for Google Pay™. There is no need for any extra steps or implementation in their end. The respective button appears in the hosted payment page and is ready for use by the customer.

Testing Google Pay™

In order to test Google Pay™ functionality in the demo environment, you need to visit URL https://groups.google.com/g/googlepay-test-mode-stub-data from your browser in order to gain access to the respective test cards.

After that, those test cards will be available at the Google Pay™ account when you execute a transaction.

Google Pay™ Direct

Google Pay direct integration provides the capability to present Google Pay as a distinct payment method in the merchant’s checkout page, without the need to be redirected to Worldline’s payment page.

That implementation utilizes VPOS XML API requests. Further details can be found here.

Below you may find the necessary steps to embed Google Pay direct wallet in your check out page.

Initially, you need to follow the below guide to create a business profile in Google Pay and Wallet Console.

https://developers.google.com/pay/api/web/guides/test-and-deploy/publish-your-integration#create-your-profile

Once completed, you may find the merchant ID at the top right corner of the console page https://pay.google.com/business/console, as shown below.

This value is required and must be provided to Cardlink for proper configuration of the integration.

Following is a description of the steps to follow and implement on merchant’s side to support Google Pay Direct.

A quick review of the steps follows:
1) Addition of element gpcontainer to the point of the html where the Google Pay button should appear
2) Implement signature/digest calculation that will be used for the upcoming requests/responses
3) Creation of script URL to retrieve the googlepaydirect.js from Cardlink’s server
4) Fetching of googlepaydirect.js from Cardlink’s server
5) Display Google Pay button by calling initGooglePay
6) Create “/sale" endpoint to which the js will send the encrypted payment data. That endpoint will then initiate a VPOS XML request (SaleRequest).
If SaleResponse status=PROCESSING (meaning that transaction needs to go through 3D authentication process):
7) Implement function threeDSHandler in js for 3D authentication process
8) Implement REST okUrl and failUrl end point for 3D authentication process return

Step 1. Add html placeholder element

Place <div id="gpcontainer"></div> element to your html payment page, where the Google Pay button will be rendered.

Step 2. Calculate signature/digest to get Cardlink’s script

Develop in your merchant server, calculation of signature for V4 or digest for V2, in order to get JS script from VPOS server, which will be passed to your HTML page, in order to get JS script from VPOS in next step.

First, request library file: vpos-xclient:1.1.161CL from Cardlink for use of classes for creating XML objects as in following code segments.

Sample java code for calculation of signature for V4

Java
…
private static final String V4_SIGN_ALG = "SHA256withRSA";
// get the private key from Cardlink
public static String calculateSignature(byte[] data) {
try {
long s = System.nanoTime();
PrivateKey pk = VPOSXMLClientService.getPrivateKey(PK_STR);
Signature sg = Signature.getInstance(V4_SIGN_ALG);
sg.initSign(pk);
sg.update(data);
byte[] sigBytes = sg.sign();
String sigStr = Base64.encode(sigBytes, 0);
long e = System.nanoTime();
return sigStr;
} catch (Exception e) {
throw new RuntimeException("Error calculating signature", e);
}
}
...

Sample java code for calculation of digest for V2

Java
…
version = "2";
String secret = "secret"; // get secret from Cardlink
Character sep=null;
StringBuilder restScriptData=new StringBuilder(1024);
appendIfFirst(restScriptData, version, sep);
appendIfFirst(restScriptData, mid, sep);
appendIfFirst(restScriptData, date, sep);
restScriptData.append(secret);
String scriptDigestValue=Misc.calculateSHA256AndEncodeBASE64(restScriptData);
queryString = String.format("?version=%s&mid=%s&date=%s&digest=%s",
version, mid, date, URLEncoder.encode(scriptDigestValue, StandardCharsets.UTF_8));
...

Step 3. Create the script URL for getting the googlepaydirect.js script from Cardlink / VPOS

You need to get the URL of VPOS API from Cardlink and then use the following parameters as described.

  • VPOS API script URL sample for V2/V4

https://eurocommerce-test.cardlink.gr/vpos/js/googlepaydirect.js?version=2&mid=0101119349&date=202508191044&digest=9IwkL6kxLs%2BaGQWPtAyfp%2BYfKWqB65UCFhoiPqYPqjk%3D

https://eurocommerce-test.cardlink.gr/vpos/js/googlepaydirect.js?version=4&mid=0101119349&date=202506201306&signature=aUfsR7BSWJ29oZskxXsSn3kK8QFDTK06lw0LCAytUnwPbSGbdgNBX0C6KCwPDdLiZH1TvdgjC0N8eE5GEj5enU%2FjvFD%2FDBGli7aGkfe1dslUD3dvNYBEuKj7ZIdtXGhjmjRi3H5d81KtQKry%2FMb3AfSyDF%2BgNxJyXcQ7WiZd0XVMEPSmO06dhLpAS3luWmHl32s8P6arcZrmq1tVBaGG%2FBcxPWk40ErA1J0sbtpoE7IixBz4H5bl3GVtRtQPhO05wTOneHTSKrUqZf5jAAVNQI86MfVStAFdrleyzkZlazEFqIQUQh3SDBQbsQSxBH0%2BWCsvMcovkW4JyW81LPN%2FBQ%3D%3D

Example URL breakdown

version: version=2/version=4 (depending on implementation)

Cardlink merchant number: e.g., mid=0101119349

date: e.g., date=202506201306

digest/signature: depending on implementation (digest for v2, signature for v4)

Sample JAVA code for creation

Java
String queryString = String.format("?version=%s&mid=%s&date=%s&signature=%s",version, mid, date, URLEncoder.encode(signature, StandardCharsets.UTF_8));

Step 4. Get the googlepaydirect.js script from Cardlink / VPOS

Using the URL you created, you need to fetch (GET) googlepaydirect.js script from your Javascript code.

Sample JS code

Java
const completeScriptUrl = scriptUrl + data.queryString;
// Dynamically load the script
const scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.src = completeScriptUrl;

Step 5. Initialize Google Pay on script load

When script is loaded from Cardlink, initialize with appropriate parameters, calling method initGooglePay(…), to show the Google Pay button.

this.initGooglePay = function (version, merchantServerUrl, orderId, total, currency, mid, threeDSHandler)

Method needs following parameters:

Parameter name Description
version version for VPOS XML: 2.1 or 4.1
merchantServerUrl URL of merchant server
orderId the order ID, created by merchant
total amount in following format: 12.34
currency “EUR"
mid Cardlink assigned merchant number for merchant (Merchant No from backoffice)
threeDSHandler async method you need to implement in JS (described below)

Sample js code for initialization

JS
scriptElement.onload = async () => { // Make the onload function async
console.log("html_script: Script loaded successfully.");
if (typeof GooglePay !== "undefined") {
console.log("Initializing Google Pay...");
try {
googlePay = new GooglePay();
googlePay.initGooglePay(xmlVersion,"/api/wallet",generateRandomId(), total, "EUR", data.mid, threeDSHandler);
await googlePay.initGooglePayClient();
console.log("Google Pay initialized successfully.");
…
  • Calling method initGooglePay(…) will generate GooglePay button.

Step 6. Implement endpoint in Merchant Server “/sale"

Once Google Pay button is clicked, upon render:

1. googlePayDirect.js will make a new request to merchant’s server “/sale” endpoint, with the encrypted payment data.
2. The endpoint will create an XML Sale Request object to call VPOS XML API and VPOS will decrypt the GooglePay payment data to act accordingly.
3. Then, depending on the result, there will be “CAPTURED”, “ERROR”, “REFUSED” or “PROCESSING” status.

Depending on VPOS XML API response, the endpoint will return to the initial request:

1. “CAPTURED" : Map.of(“status", “success"); and then handle accordingly.
2. “PROCESSING" : The data for the processing case, are all extracted by the VPOS XML API SaleResponse and then sent to the client. Details on how to extract them are described below:
Map<String,String> result = new HashMap<>();
result.put(“valid", valid);result.put(“cardType", cardType);
result.put(“cardEncData", cardEncData);
result.put(“orderId", orderId);
result.put(“orderAmount", orderAmount);
result.put(“txId", txId);
result.put(“trExtId", trExtId);
result.put(“trMpiCounts", trMpiCounts);
PROCESSING status means that the transaction needs to go through 3D authentication process, as described below.

Notice that for MPI requests it is required to use version 4.1, so you need to create and maintain a public certificate and calculate signature instead of digest.

3. “ERROR" : Map.of(“status", “error"); and then handle accordingly.
4. “REFUSED" : Map.of(“status", “refused"); and then handle accordingly.

Following is the request created by JS that will be posted to merchant’s server /sale endpoint:

JS
let saleRequest = {
version: this.xmlVersion,
merchantId: this.merchantNo,
orderId: this.requestProperties.orderId,
amount: this.requestProperties.amount,
currency: this.requestProperties.currency,
googlePayResponse: JSON.stringify(token),
};
let body = JSON.stringify(saleRequest);
let saleReqURL = this.merchantServerUrl + "/sale"
fetch(saleReqURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: body
})

These are the elements that consist the XML that is required to be built and sent to VPOS. All of them are required. More information is in code below.

VPOS
Message
MessageId
Version
TimeStamp
RequestMessage (SaleRequest)
Authentication
Mid
OrderInfo
OrderId
OrderAmount
Currency
OrderDesc
WalletInfo
Attribute (name: “googlePaymentData")
PaymentInfo

Following, are examples of requests and responses for SaleRequest & SaleResponse:

SaleRequest with V2.1 digest and SaleResponse

Initial SaleRequest with WalletInfo and googlePaymentData

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753275021449" timeStamp="2025-07-23T15:50:21.451+03:00" version="2.1">
<SaleRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1753275021452</OrderId>
<OrderDesc>Google pay Direct</OrderDesc>
<OrderAmount>44.0</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<PaymentInfo/>
<WalletInfo>
<Attribute name="googlePaymentData">{"description":"Visa •••• 0044","info":{"assuranceDetails":{"accountVerified":true,"cardHolderAuthenticated":false},"billingAddress":{"countryCode":"US","name":"Card Holder
Name","postalCode":"94043"},"cardDetails":"0044","cardNetwork":"VISA"},"tokenizationData":{"token":"{\"signature\":\"MEYCIQCitMXHyUEm3t3ReLNaZSxAG6nmuNNRWEIzbDGdhiYbZgIhAMwRWxUggRCjiAleqcbA4cpNnQHK4t+epiiSk+iKPm/o\",\"intermediateSigningKey\":{\"signedKey\":\"{\\\"keyValue\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYh4HTw+idgqeikahDVPHNtxis6wvE6041bRxjceMQ5mNg0GLrACql06bXsYCsoF6mjMw2DhDQNMSointqGc/tA\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":\\\"1753963292111\\\"}\",\"signatures\":[\"MEUCIFLdaJe7OjUKURXu2VN34m18h4eQu0dhBoNDWMwVBxQIAiEAngpoZDnapuPquPW+Gl/qqigvbVi3XEuFXsU5fxqgYpk\\u003d\"]},\"protocolVersion\":\"ECv2\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"-gBezTaHQZoIUASLTcE0AAl3JwZcXgPkl3q3etL+cly+sL5ZeMK3H9MgRDLd/7ijO1f8Xp3ePFhR53wY54fmeYTjP45GI97eK00TIVN9GcvBJKkKqZEHeggqvPvXsmHmYi2a94jU0dF8bW+Cf6nY3cfYIJ5YsUEcG9klAuqEScLp+QbsawHCAhjTytagndmr+2Ner27AKMx+JQ4GIuNh9GHoz8rvkf0zMCQkrldKZYA9i860dKHCDysFjIGir222lbSO3uCqQBVlTmlaA4p/Wg/uhHooQ3nNzS09SIujXVDKapLnYqtmjuwWM8vql8mM6A4uPQkBmybivP3elB/8rqOhGEA/37SjGVX6sC73hVlgk258sZwab4oyKGrEUo2Rl6smnJyB9HwVxCtEm1SMz+HOjMhbEGK9uarmWI/upMSwhNNFjDzv1BPJ31jefuoh9dp86Vw3IQOqRTj0c4bx7nzrXAOU2HvVigGvtf8IHgijLmBeEtsmaj+LBiyh2W6wCfDnehMMcFYOYwN45igeeh4AZM4oA+wLF93LdqkzfMQ\\\\u003d\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"BKSQdTmVtPb/sx8pa7sDWVFf/ThDaS8nJ+k0xetNgWnkGZX5lgbf7G2l6u3DuU092DPIpboua7SxYrz7z3pP1DE\\\\u003d\\\",\\\"tag\\\":\\\"cW/gGwFaMeo0k7MIxf7sXgHM4u+2EPD765Nqo42TnhI\\\\u003d\\\"}\"}","type":"PAYMENT_GATEWAY"},"type":"CARD"}</Attribute>
</WalletInfo>
</SaleRequest>
</Message>
<Digest>BeGjTPvPFFo5NHnuk5K68BUYPRq8sIrwZ9WmKrLuiFg=</Digest>
</VPOS>

SaleResponse

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message version="2.1" messageId="M1753275021449" timeStamp="2025-07-23T15:50:22.817+03:00">
<SaleResponse>
<OrderId>O1753275021452</OrderId>
<OrderAmount>44.0</OrderAmount>
<Currency>EUR</Currency>
<PaymentTotal>44.0</PaymentTotal>
<Status>PROCESSING</Status>
<TxId>9263958600162</TxId>
<Attribute name="valid">true</Attribute>
<Attribute name="trExtId">O1753275021452</Attribute>
<Attribute name="cardType">visa</Attribute>
<Attribute name="trMpiCounts">0</Attribute>
<Attribute name="cardEncData">UNjnCn4HtpX7ReDRIplPZCoxY2vRgo9iSMKgyyeMghwjyXHblavEOrr+isUFhXp6sF5dfGVYGKSpGobCD6rLZDeSoCyzR5i4kfkb4VKMEJ++3lulFDxHyT2c7+arDOFkrNt73m1d+/AtLpVD2t/arMi/HidTwv7CiVog1XfWnRZyqL1hK0iHrGL2UEBDmOlbso+r0CsJEPTxR6ueZHVs2mramNo7BhYGPoIqm1wkTLcXg6EGQUM4ueaFJnvnMtgJnEXbRHwDAxHzooCxP2A0OJAsnEaaanCpNCZD8uRMJ33iXDzTj2jy+wSJFibA02asBVnh86Due3aeQFElycLep4/P3q2aAP6jTPiwE2XSCuVsUzj7Y0TdFtR9BcqEacatzbb1uDGc3jFY2HhcEZ1045dMmtt5MGdHjXq8vEjVpV3d0zPej7tOT0KCFK6HZZPTcTfpy8wzjzKOWRUk+wAgJi5fpuNTNpibaF6u4dzE6w8dwIAw8wL0NLDEoMu67mie</Attribute>
</SaleResponse>
</Message>
<Digest>xdw66agKfKcBBMKkONXTGCCqBE9os0mQ6/QkSNffkn0=</Digest>
</VPOS>

Second SaleRequest with 3DS data

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753275035061" timeStamp="2025-07-23T15:50:35.061+03:00" version="2.1">
<SaleRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1753275021452</OrderId>
<OrderDesc>GPay ThreeDS results</OrderDesc>
<OrderAmount>44.0</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<PaymentInfo preparedTxId="9263958600162">
<PayMethod>visa</PayMethod>
<ThreeDSecure>
<EnrollmentStatus>Y</EnrollmentStatus>
<AuthenticationStatus>A</AuthenticationStatus>
<CAVV>B5kBAmFISQAAABEwl4IEdXKQc4M=</CAVV>
<XID>VlBPUzg2MDAxNjItMDIxNDUyMDA=</XID>
<ECI>06</ECI>
<Protocol>3DS2.2.0</Protocol>
<Attribute name="TDS2.dsTransID">702209bd-0b12-5b9f-8000-00000000a7e5</Attribute>
</ThreeDSecure>
</PaymentInfo>
<WalletInfo>
<Attribute/>
</WalletInfo>
</SaleRequest>
</Message>
<Digest>QaBUBSPzWPSFWWnz2eh0ZC8y6fQ1HG2Oy74mLBaoSeU=</Digest>
</VPOS>

Second SaleResponse

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message version="2.1" messageId="M1753275035061" timeStamp="2025-07-23T15:50:36.198+03:00">
<SaleResponse>
<OrderId>O1753275021452</OrderId>
<OrderAmount>44.0</OrderAmount>
<Currency>EUR</Currency>
<PaymentTotal>44.0</PaymentTotal>
<Status>CAPTURED</Status>
<TxId>9263958600162</TxId>
<PaymentRef>125102</PaymentRef>
<RiskScore>0</RiskScore>
<Description>OK, CAPTURED response code 00</Description>
<Attribute name="EXTACQUIRERID">026</Attribute>
<Attribute name="valid">true</Attribute>
<Attribute name="trExtId">O1753275021452</Attribute>
<Attribute name="cardType">visa</Attribute>
<Attribute name="trMpiCounts">0</Attribute>
<Attribute name="cardEncData">UNjnCn4HtpX7ReDRIplPZCoxY2vRgo9iSMKgyyeMghwjyXHblavEOrr+isUFhXp6sF5dfGVYGKSpGobCD6rLZDeSoCyzR5i4kfkb4VKMEJ++3lulFDxHyT2c7+arDOFkrNt73m1d+/AtLpVD2t/arMi/HidTwv7CiVog1XfWnRZyqL1hK0iHrGL2UEBDmOlbso+r0CsJEPTxR6ueZHVs2mramNo7BhYGPoIqm1wkTLcXg6EGQUM4ueaFJnvnMtgJnEXbRHwDAxHzooCxP2A0OJAsnEaaanCpNCZD8uRMJ33iXDzTj2jy+wSJFibA02asBVnh86Due3aeQFElycLep4/P3q2aAP6jTPiwE2XSCuVsUzj7Y0TdFtR9BcqEacatzbb1uDGc3jFY2HhcEZ1045dMmtt5MGdHjXq8vEjVpV3d0zPej7tOT0KCFK6HZZPTcTfpy8wzjzKOWRUk+wAgJi5fpuNTNpibaF6u4dzE6w8dwIAw8wL0NLDEoMu67mie</Attribute>
</SaleResponse>
</Message>
<Digest>AomZNcZrLwDkqznUtnWu0OOs6lWKwTNJHJLI+5dMaQE=</Digest>
</VPOS>

SaleRequest with V4.1 digest and SaleResponse

Initial SaleRequest

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753279794662" timeStamp="2025-07-23T17:09:54.663+03:00" version="4.1">
<SaleRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1753279794664</OrderId>
<OrderDesc>Google pay Direct</OrderDesc>
<OrderAmount>33.0</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<PaymentInfo/>
<WalletInfo>
<Attribute name="googlePaymentData">{"description":"Visa •••• 0044","info":{"assuranceDetails":{"accountVerified":true,"cardHolderAuthenticated":false},"billingAddress":{"countryCode":"US","name":"Card Holder Name","postalCode":"94043"},"cardDetails":"0044","cardNetwork":"VISA"},"tokenizationData":{"token":"{\"signature\":\"MEUCIQDhXzgTvHzZY4vqcnKzjZ+JDQtWurjcO/Xk3iMe0iaH7wIgJdQBmFswoJ39JsAe1bYFPVJTnwv5Lu9vfOsWKmyWG7U\\u003d\",\"intermediateSigningKey\":{\"signedKey\":\"{\\\"keyValue\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvIbDSgYLSzrKHarmbRRC9TBvjCI8a4s7eF4MtPWpGkUfMYSr
8p9ZI+R8fMBnt3nq1wt3XN3nObAQ2n6qR0YuKA\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":\\\"1753953893306\\\"}\",\"signatures\":[\"MEYCIQD2bL53lnDUDd3fL1jENsJl7SrQbXjR+A1Mqg3dXCBIpwIhAP2N3QfFhXxGBvFaJyv5gtERIoxhacQ/7UMBLhK2+Qa1\"]},\"protocolVersion\":\"ECv2\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"STijviHGQT7ibwFdi7IEIdzutI4dXnJnBpI6ou/0tsek9M6hT3H6SD9EdsiJT6VNFO17oUDaJzWflVwhTr0W17cYxHNw02u186HKRGMA6bq7Gx/W+YPK8dVS+MNEKvsKmUH58adbnlNZxGtJZ6h9QDoL+j0rpLs3novwChkv8LltOw7yoyAvyNnHJ0ko2D7CFjvU2EdCkSihd3QqNUQDqB8i9tH/elI3Lhj9FX5XBhDXgJLPQfQxVzH55ZN+ZZ70e+G/y+7Vp1wEjxNxy8ej+R5ralTf3QxRPCv3U7I1/g+QqOqMfOu/iG3fFTd1OotHOYQIx0/tsoQR2kUI9WWTyYrz54P/+ivXTFdmgo8fF5FkJEKEm0prKnwma3/8RFv+hHe1LAA/j90FXxQxUAtFOLjLcQPhz+c+6cT03Aw3/b/Z+tnbw8OBIJ4CGtMUExqqU5J/33oPckHffRDa0/eThs/kGYvj2f3oXKXsdZK4QCGkc+xmMe69FaD+r5pvJqQithAp5RwZd5FlaVNoO7uWPcOcAvfih78bW5IDFAFSvw\\\\u003d\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"BLXXgkdDhzBRrSi6GUzGd5yZ+lLktgIpzRpr4jbWXGQM5pv1wpimaPPm91oNEcKrf1p94+wSGEu8gPTYgF8ez+I\\\\u003d\\\",\\\"tag\\\":\\\"TwAD4QVmb2MWATQrZkE/rUTOc8No/+PIwkAXjsG+Br0\\\\u003d\\\"}\"}","type":"PAYMENT_GATEWAY"},"type":"CARD"}</Attribute>
</WalletInfo>
</SaleRequest>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753279794662">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>QsHLdmGGvpYjBfT2m4dlSM4536Ua8rBvQK0hA6g0qRg=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
egKxDWWEjYtDv27iIGxyos64m.....YXefeffyX1Q==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDlTCCAn0CBGdgBQswDQYJKoZI.....7o4bHNBCgUCdD3wEsL5Dch1tUYc6gg
876ddhX64xoH
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

SaleResponse

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753279794662" timeStamp="2025-07-23T17:09:55.933+03:00" version="4.1">
<SaleResponse>
<OrderId>O1753279794664</OrderId>
<OrderAmount>33.0</OrderAmount>
<Currency>EUR</Currency>
<PaymentTotal>33.0</PaymentTotal>
<Status>PROCESSING</Status>
<TxId>9263958600182</TxId>
<Attribute name="valid">true</Attribute>
<Attribute name="trExtId">O1753279794664</Attribute>
<Attribute name="cardType">visa</Attribute>
<Attribute name="trMpiCounts">0</Attribute>
<Attribute name="cardEncData">Q1/5bZqJvrduZcoyZCYwUR.....0Q0OHCnvEpN3bcZc</Attribute>
</SaleResponse>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753279794662">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>t5ctxSZwQHvog6rIXFNWuiIU0qeNweoFJB68yfxlhLY=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
ce+wBBtCZcjw8+2a39ltFyhJboXHSdb0uYZRHvEscGl+4SZ2Jp/s8sBUG....nG3rCugWLT
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIEVTCCAr0CBGQifiUwDQYJKoZIhvcNA......JiPSY2TnYn8lE
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

Second SaleRequest with 3DS data

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753279801778" timeStamp="2025-07-23T17:10:01.778+03:00" version="4.1">
<SaleRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1753279794664</OrderId>
<OrderDesc>GPay ThreeDS results</OrderDesc>
<OrderAmount>33.0</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<PaymentInfo preparedTxId="9263958600182">
<PayMethod>visa</PayMethod>
<ThreeDSecure>
<EnrollmentStatus>Y</EnrollmentStatus>
<AuthenticationStatus>A</AuthenticationStatus>
<CAVV>B5kBAEADiAAAAAzkl4IEdXKQc4M=</CAVV>
<XID>VlBPUzg2MDAxODItNzk0NjY0MDA=</XID>
<ECI>06</ECI>
<Protocol>3DS2.2.0</Protocol>
<Attribute name="TDS2.dsTransID">702209bd-0b12-5b9f-8000-00000000a7e5</Attribute>
</ThreeDSecure>
</PaymentInfo>
<WalletInfo>
<Attribute/>
</WalletInfo>
</SaleRequest>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753279801778">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>cAL/1gVEhgyYOgRm5iASHDOvqq1L0ETqD5PwnHXGCA8=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
BWb6/aEuPKVAQsZEujLTNebEC7gdOzmAJgCiKkT....rTFnHm9p4ej+Uw==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDlTCCAn0CBGdgBQswDQYJKoZIhvcNA....4bHNBCgUCdD3wEsL5Dch1tUYc6gg
876ddhX64xoH
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

Second SaleResponse

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753279801778" timeStamp="2025-07-23T17:10:02.559+03:00" version="4.1">
<SaleResponse>
<OrderId>O1753279794664</OrderId>
<OrderAmount>33.0</OrderAmount>
<Currency>EUR</Currency>
<PaymentTotal>33.0</PaymentTotal>
<Status>CAPTURED</Status>
<TxId>9263958600182</TxId>
<PaymentRef>125123</PaymentRef>
<RiskScore>0</RiskScore>
<Description>OK, CAPTURED response code 00</Description>
<Attribute name="EXTACQUIRERID">026</Attribute>
<Attribute name="valid">true</Attribute>
<Attribute name="trExtId">O1753279794664</Attribute>
<Attribute name="cardType">visa</Attribute>
<Attribute name="trMpiCounts">0</Attribute>
<Attribute name="cardEncData">Q1/5bZqJvrd...N3bcZc</Attribute>
</SaleResponse>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753279801778">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>99PRNAOQArG4QYsndkefRyDwOMxTMQfgdGAQSmLsM4A=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
cHpW8aeuXLQOODS/9rIIV4wZW57rDHtGsYAu...XpX+zJgXOlB7XUEjl
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIEVTCCAr0CBGQifiUwVWbY6um3tJHz+dY3...TnYn8lE
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

Following is java sample code for implementing the /sale enpoint:

  • method/endpoint: saleRequest
JS
@PostMapping(value = "/sale", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, String>> saleRequest(@RequestBody SaleRequest request, HttpSession session) {
…
BigDecimal amount = BigDecimal.valueOf(request.getAmount());
String merchantId = request.getMerchantId();
walletType = WalletType.google;
walletData = request.getGooglePayResponse();
byte[] content = createSaleRequest(merchantId, amount, request.getCurrency(), walletData, walletType);
String xmlSaleRequest = new String(content, StandardCharsets.UTF_8);
String vposUrl = // get vpos xml api url from Cardlink
HttpResponse<String> response = postToVpos(vposUrl, xmlSaleRequest);
String status = XmlUtil.extractXmlValue(response.body(), "Status");
if ("CAPTURED".equalsIgnoreCase(status)) {
return ResponseEntity.ok(Map.of("status", "success"));
}
else if ("PROCESSING".equalsIgnoreCase(status))
{
Map<String,String> res = extractGooglePayProcessingTransaction(response.body());
res.put("status", "processing");
String txId = res.get("txId");
String trExtId = res.get("trExtId");
String paymentTotal = res.get("orderAmount");
String currency = request.getCurrency();
String cardType = res.get("cardType");
String orderId = res.get("orderId");
String calcXid = XIDUtil.calculateXID(txId, trExtId, res.get("trMpiCounts"));
GPayTransactionInfoManager.saveOrderInfo(calcXid, new TrOrderInfo(txId, paymentTotal, currency, cardType, orderId)); // we save the orderinfo in a map, for later usage as needed.
return ResponseEntity.ok(res);
}
else if ("ERROR".equalsIgnoreCase(status)){
... …
}
else if ("REFUSED".equalsIgnoreCase(status)){
... …
}
enum WalletType:
public enum WalletType
{
google
}
  • method: createSaleRequest
JS
public static byte[] createSaleRequest(String mid, BigDecimal amount, String currency, String walletPaymentData, ApiWalletController.WalletType walletType)
throws Exception {
VPOS request = create41BaseRequest(mid); // as described above
Authentication authentication = new Authentication();
authentication.setMid(mid);
RequestMessage saleReq = buildSaleRequest(amount, currency,walletType, walletPaymentData, authentication);
request.getMessage().setSaleRequest(saleReq);
return signAndMarshal(request);
}
  • method: buildSaleRequest
JS
private static RequestMessage buildSaleRequest(BigDecimal amount, String currency,
ApiWalletController.WalletType walletType, String walletPaymentData, Authentication authentication) {
RequestMessage saleReq = new RequestMessage();
saleReq.setAuthentication(authentication);
saleReq.setOrderInfo(buildOrderInfo(amount, currency, "Google pay Direct")); //as described above
WalletInfo walletInfo = new WalletInfo();
AttributeType attributeType = new AttributeType();
attributeType.setName("googlePaymentData");
attributeType.setValue(walletPaymentData);
walletInfo.getAttribute().add(attributeType);
saleReq.setWalletInfo(walletInfo);
PaymentInfo paymentInfo = new PaymentInfo();
saleReq.setPaymentInfo(paymentInfo);
return saleReq;
}
  • method: buildOrderInfo
JS
private static OrderInfo buildOrderInfo(BigDecimal amount, String currency, String orderDesc) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOrderId("O" + System.currentTimeMillis());
orderInfo.setOrderAmount(amount);
orderInfo.setCurrency(currency);
orderInfo.setOrderDesc(orderDesc);
return orderInfo;
}
  • method: extractGooglePayProcessingTransaction
JS
private Map<String,String> extractGooglePayProcessingTransaction(String xml) {
try {
String tagName = "Attribute";
String valid = "";
String cardType = "";
String cardEncData = "";
String orderId = "";
String orderAmount = "";
String txId = "";
String trExtId = "";
String trMpiCounts = "";
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8 ));
Document document =
DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(byteArrayInputStream);
NodeList nodeList = document.getElementsByTagName(tagName);
if (nodeList.getLength() > 0{
for(int i = 0; i < nodeList.getLength(); i++){
Node item = nodeList.item(i);
if (item != null) {
NamedNodeMap attributes = item.getAttributes();
if (attributes != null) {
Node nameAttr = attributes.getNamedItem("name");
if (nameAttr != null) {
String nameValue = nameAttr.getTextContent();
if ("cardType".equals(nameValue)) {
cardType = item.getTextContent();
} else if ("valid".equals(nameValue)) {
valid = item.getTextContent();
} else if ("cardEncData".equals(nameValue)) {
cardEncData = item.getTextContent();
} else if ("trExtId".equals(nameValue)) {
trExtId = item.getTextContent();
} else if ("trMpiCounts".equals(nameValue)) {
trMpiCounts = item.getTextContent();
}
}
}
}
else throw new Exception("attribute missing");
}
}
else {
throw new Exception("error in VPOS response with status: processing");
}
orderId = XmlUtil.extractXmlValue(xml,"OrderId");
orderAmount = XmlUtil.extractXmlValue(xml,"OrderAmount");
txId = XmlUtil.extractXmlValue(xml,"TxId");
Map<String,String> result = new HashMap<>();
result.put("valid", valid);
result.put("cardType", cardType);
result.put("cardEncData", cardEncData);
result.put("orderId", orderId);
result.put("orderAmount", orderAmount);
result.put("txId", txId);
result.put("trExtId", trExtId);
result.put("trMpiCounts", trMpiCounts);
return result;
} catch (Exception e) {
log.error("Error extracting XML in extractGooglePayProcessingTransaction", e);
return new HashMap<>();
}
}
  • method: extractXmlValue
JS
public static String extractXmlValue(String xml, String tagName) {
try {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(byteArrayInputStream);
NodeList nodeList = document.getElementsByTagName(tagName);
Node item = nodeList.item(0);
return (item != null) ? item.getTextContent().trim() : "";
} catch (Exception e) {
log.error("Error extracting XML value for tag: {}", tagName, e);
return "";
}
}

Step 7. Implement function threeDSHandler in js

Function threeDSHandler is passed as a parameter to initGooglePay(..) and its role is to make a request to MPI server for 3DS process. This method will be called after user has clicked Google Pay Button, and the SaleResponse from the previous step was of type “PROCESSING”.

Sample implementation of threeDSHandler

  • method: threeDSHandler

Is implemented as described in the documentation file of MPI at https://developer.cardlink.gr/api_products_categories/direct-integration/#MPI-(3D-authentication)-Interface-v4 (requires log in).

JS
async function threeDSHandler(resp) {
let orderAmount = resp.orderAmount;
let orderId = resp.orderId;
let txId = resp.txId;
let cardEncData = resp.cardEncData;
let trExtId = resp.trExtId;
let trMpiCounts = resp.trMpiCounts;
const xid = await getXid(txId, trExtId, trMpiCounts); // description of XID calculation can be found bellow
if (!xid) {
console.error("XID generation failed, cannot proceed.");
return;
}
const form = await createMPIRequestForm({
cardEncData: cardEncData,
cardType: '1',
deviceCategory: '0', //WWW Browser
purchAmount: orderAmount,
actionUrl: mpiUrlInput.textContent.trim(), ** is the mpi url
exponent: '2',
orderId: orderId,
currency: '978',
merchantId: midInput.textContent.trim(),
okUrl: 'https://merchserver.gr/3ds/success', ** described below
failUrl: 'https://merchserver.gr/3ds/failure', ** described below
xid: xid,
});
form.submit();
}

NOTE: xid Can be calculated on the merchant server and given to the client.

Sample java code for calculation of xid

Java
public static String calculateXID(String trId, String trExtId, String mpiCounts) {
StringBuilder sb = new StringBuilder(20);
sb.append("VPOS");
sb.append(Misc.padCutStringLeft(String.valueOf(trId), '0', 7));
sb.append('-');
sb.append(Misc.padCutStringLeft(trExtId, '0', 6));
sb.append(Misc.padCutStringLeft(mpiCounts, '0', 2));
String xid = Base64.encode(Misc.toBytes(StandardCharsets.ISO_8859_1, sb));
log.info("XID: " + xid);
return xid;
}
  • method: createMPIRequestForm
Java
async function createMPIRequestForm(params) {
const form = document.createElement('form');
form.action = params.actionUrl;
form.method = 'POST';
form.name = 'VEReqForm';
form.id = 'VEReqForm1';
function addField(name, value) {
if (value == null || value === '' ||
(Array.isArray(value) && value.length === 0)) return;
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
}
const description = 'Order ' + params.orderId;
const signPayload = {
mpiVersion: '4.0',
pan: '', // Optional (probably handled via cardEncData)
expiry: '', // Optional (probably handled via cardEncData)
cardEncData: params.cardEncData, // Encrypted card info
devCat: params.deviceCategory,
purchAmount: params.purchAmount,
exponent: params.exponent,
description: description,
currMpi: params.currency,
merchantID: params.merchantId,
xidb64: params.xid,
okUrl: params.okUrl,
failUrl: params.failUrl,
recurFreq: null,
recurEnd: null,
installments: null
};
let signature;
let formattedAmount;
// this is an example of signing the data. Creating an endpoint in merchant server to handle it
try {
const res = await fetch('/api/sign-data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signPayload)
});
if (!res.ok) {
console.error("Failed to sign payload:", res);
return;
}
const json = await res.json();
signature = json.signature;
formattedAmount = json.purchaseAmountFormatted;// get formatted amount string
if (!signature || signature.startsWith("Error")) {
console.error("Invalid signature:", signature);
return;
}
console.log("Signature received:", signature);
console.log("Formatted Purchase Amount:", formattedAmount);
} catch (err) {
console.error("Failed to sign MPI form:", err);
return;
}
addField('version', signPayload.mpiVersion);
addField('cardEncData', signPayload.cardEncData);
addField('deviceCategory', signPayload.devCat);
addField('purchAmount', formattedAmount);
addField('exponent', signPayload.exponent);
addField('description', signPayload.description);
addField('currency', signPayload.currMpi);
addField('merchantID', signPayload.merchantID);
addField('xid', signPayload.xidb64);
addField('okUrl', signPayload.okUrl);
addField('failUrl', signPayload.failUrl);
addField('signature', signature);
[...form.elements].forEach(input => {
console.log(`${input.name}: ${input.value}`);
});
document.body.appendChild(form);
return form;
}
  • endpoint method for signing the data for 3ds
Java
@PostMapping("/sign-data")
public ResponseEntity<SignDataResponse> signData(@RequestBody SignDataRequest request) {
log.info("Sign Data Request: " + request.toString());
try {
double purchAmount = request.purchAmount();
String purchaseAmountFormatted = (purchAmount == 0.0) ? "" :
BigDecimal.valueOf(purchAmount)
.multiply(BigDecimal.valueOf(100))
.setScale(0, RoundingMode.HALF_UP)
.toPlainString();
StringBuilder data = new StringBuilder();
appendIfFirst(data, request.mpiVersion(), ';');
appendIfFirst(data, request.pan(), ';');
appendIfFirst(data, request.expiry(), ';');
appendIfFirst(data, request.cardEncData(), ';');
appendIfFirst(data, request.devCat(), ';');
/*
Per MPI documentation
Max. 12-digit numeric amount in minor units of currency with all
punctuation removed. (Optional for NPA only)
Examples:
Display Amount USD 123.45
Purchase Amount 12345
*/
appendIfFirst(data, purchaseAmountFormatted, ';');
appendIfFirst(data, request.exponent(), ';');
appendIfFirst(data, request.description(), ';');
appendIfFirst(data, request.currMpi(), ';');
appendIfFirst(data, request.merchantID(), ';');
appendIfFirst(data, request.xidb64(), ';');
appendIfFirst(data, request.okUrl(), ';');
appendIfFirst(data, request.failUrl(), ';');
appendIfFirst(data, request.recurFreq(), ';');
appendIfFirst(data, request.recurEnd(), ';');
appendIfFirst(data, request.installments(), ';');
log.info("Data to sign: " + data);
List<String> dataList = Arrays.asList(data.toString().split(";"));
log.info("Data List: " + dataList);
String signature = VPOSXmlRequestCreator.signRequestData(Misc.toUtf8Bytes(data));
SignDataResponse response = new SignDataResponse(purchaseAmountFormatted, signature);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
signing method: signRequestData :
public static String signRequestData(byte[] data) {
try {
return VPOSXMLClientService.signRequestData(data, PK_STR);
} catch(Exception e) {
throw new RuntimeException("Error signing request data", e);
}
}
public record SignDataResponse(String purchaseAmountFormatted, String signature) {
}

Step 8. Return from 3D authentication process (MPI)

You need to implement two REST endpoints – okUrl and failUrl – for previous step, which will handle the response from MPI.

okUrl: ‘https://merchserver.gr/3ds/success’, **need to provide to MPI request
failUrl: ‘https://merchserver.gr/3ds/failure’, **need to provide to MPI request

For 3DS procedure, MPI service needs these two parameters to post response accordingly.

Requirements for implementation of okURL handler.

This okUrl, will receive 3DS data from MPI when 3DS process is successful, and make a second sale request (the first one is triggered by googlePayDirect.js) to VPOS XML API including the 3DS response data.

Here is sample java code for implementing the okURL endpoint:

Java
@PostMapping("/3ds/success")
public ResponseEntity<Void> success(HttpServletRequest request, @RequestBody(required = false) String body) {
if (StringUtils.isNotBlank(body)) {
ThreeDSResp threeDSSuccess = parse3DSBody(body);
TrOrderInfo trOrderInfo = GpayTransactionInfoManager.getOrderInfo(threeDSSuccess.xid()); // have previously stored orderInfo object
HttpResponse<String> vposResp = makeSaleReqWithThreeDSResp(threeDSSuccess, trOrderInfo);
if (vposResp != null) {
String status = XmlUtil.extractXmlValue(vposResp.body(), "Status");
if ("CAPTURED".equalsIgnoreCase(status)) {
request.getSession().setAttribute("confirmationData", trOrderInfo);
request.getSession().setAttribute("trStatus", "CAPTURED");
return ResponseEntity.status(HttpStatus.SEE_OTHER)
.location(URI.create("/3ds/confirmation"))
.build();
}
}
request.getSession().setAttribute("messageTitle", "Payment Error");
request.getSession().setAttribute("messageText", vposResp);
request.getSession().setAttribute("messageBackLink", "/");
return ResponseEntity.status(HttpStatus.SEE_OTHER)
.location(URI.create("/message"))
.build();
}
request.getSession().setAttribute("messageTitle", "Payment Error");
request.getSession().setAttribute("messageText", "We could not complete your transaction.");
request.getSession().setAttribute("messageBackLink", "/");
return ResponseEntity.status(HttpStatus.SEE_OTHER)
.location(URI.create("/message"))
.build();
}
  • method: parse3DSBody
Java
public ThreeDSResp parse3DSBody(String body) {
Map<String, String> paramMap = Arrays.stream(body.split("&"))
.map(p -> p.split("=", 2))
.filter(p -> p.length == 2)
.collect(Collectors.toMap(
p -> URLDecoder.decode(p[0], StandardCharsets.UTF_8),
p -> URLDecoder.decode(p[1], StandardCharsets.UTF_8)
));
return ThreeDSResp.builder()
.version(paramMap.get(KEY_VERSION))
.merchantID(paramMap.get(KEY_MERCHANT_ID))
.xid(paramMap.get(KEY_XID))
.mdStatus(paramMap.get(KEY_MD_STATUS))
.mdErrorMsg(paramMap.get(KEY_MD_ERROR_MSG))
.veresEnrolledStatus(paramMap.get(KEY_VERES_ENROLLED_STATUS))
.paresTxStatus(paramMap.get(KEY_PARES_TX_STATUS))
.iReqCode(paramMap.get(KEY_IREQ_CODE))
.iReqDetail(paramMap.get(KEY_IREQ_DETAIL))
.vendorCode(paramMap.get(KEY_VENDOR_CODE))
.eci(paramMap.get(KEY_ECI))
.cavv(paramMap.get(KEY_CAVV))
.cavvAlgorithm(paramMap.get(KEY_CAVV_ALGORITHM))
.MD(paramMap.get(KEY_MD))
.md(paramMap.get(KEY_MD_LOWER))
.PAResVerified(Boolean.valueOf(paramMap.get(KEY_PARES_VERIFIED)))
.PAResSyntaxOK(Boolean.valueOf(paramMap.get(KEY_PARES_SYNTAX_OK)))
.protocol(paramMap.get(KEY_PROTOCOL))
.cardType(paramMap.get(KEY_CARD_TYPE))
.tds2TransStatus(paramMap.get(KEY_TDS2_TRANS_STATUS))
.tds2ThreeDSServerTransID(paramMap.get(KEY_TDS2_THREE_DS_SERVER_TRANS_ID))
.tds2DsTransID(paramMap.get(KEY_TDS2_DS_TRANS_ID))
.tds2AcsTransID(paramMap.get(KEY_TDS2_ACS_TRANS_ID))
.tds2AcsReferenceNumber(paramMap.get(KEY_TDS2_ACS_REFERENCE_NUMBER))
.tds2MessageVersion(paramMap.get(KEY_TDS2_MESSAGE_VERSION))
.tds2AcsOperatorID(paramMap.get(KEY_TDS2_ACS_OPERATOR_ID))
.signature(paramMap.get(KEY_SIGNATURE))
.sID(paramMap.get(KEY_SESSION_ID))
.build();
}
  • method: makeSaleReqWithThreeDSResp
Java
private HttpResponse<String> makeSaleReqWithThreeDSResp(ThreeDSResp resp, TrOrderInfo trOrderInfo) {
try {
byte[] content = VPOSXmlRequestCreator.createSaleRequest(resp, trOrderInfo);
String xmlSaleRequest = new String(content, StandardCharsets.UTF_8);
String vposUrl = DynamicUrlManager.getVposXmlUrl(resp.merchantID());
HttpResponse<String> response = postToVpos(vposUrl, xmlSaleRequest);
log.info("VPOS Sale Response 3DS: {}", response.body());
return response;
} catch (Exception e) {
log.error("Error in sale request 3DS", e);
return null;
}
}
  • method: createSaleRequest
Java
public static byte[] createSaleRequest(ThreeDSResp threeDSResp, TrOrderInfo trOrderInfo) throws Exception {
log.info("Creating saleReq for ThreeDS");
VPOS request = create41BaseRequest(threeDSResp.merchantID());
Authentication authentication = new Authentication();
authentication.setMid(threeDSResp.merchantID());
RequestMessage saleReq = new RequestMessage();
saleReq.setAuthentication(authentication);
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOrderId(trOrderInfo.orderId());
orderInfo.setOrderAmount(new BigDecimal(trOrderInfo.paymentTotal()));
orderInfo.setCurrency(trOrderInfo.currency());
orderInfo.setOrderDesc("GPay ThreeDS results");
saleReq.setOrderInfo(orderInfo);
WalletInfo walletInfo = new WalletInfo();
AttributeType attributeType = new AttributeType();
walletInfo.getAttribute().add(attributeType);
saleReq.setWalletInfo(walletInfo);
PaymentInfo.ThreeDSecure threeDSecure = new PaymentInfo.ThreeDSecure();
threeDSecure.setXID(threeDSResp.xid());
threeDSecure.setAuthenticationStatus(threeDSResp.paresTxStatus());
threeDSecure.setEnrollmentStatus(threeDSResp.veresEnrolledStatus());
threeDSecure.setECI(threeDSResp.eci());
threeDSecure.setCAVV(threeDSResp.cavv());
threeDSecure.setProtocol(threeDSResp.protocol());
threeDSecure.setTDS2.dsTransID(threeDSResp.(TDS2.dsTransID)());
PaymentInfo paymentInfo = new PaymentInfo();
paymentInfo.setPayMethod(trOrderInfo.payMethod());
paymentInfo.setPreparedTxId(Long.valueOf(trOrderInfo.trId()));
paymentInfo.setThreeDSecure(threeDSecure);
saleReq.setPaymentInfo(paymentInfo);
request.getMessage().setSaleRequest(saleReq);
return signAndMarshal(request);
}

Structure of second SaleRequest XML Object with 3DS data:

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1752179272074" timeStamp="2025-07-10T23:27:52.075+03:00" version="4.1">
<SaleRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1752179263532</OrderId>
<OrderDesc>GPay ThreeDS results</OrderDesc>
<OrderAmount>22.0</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<PaymentInfo preparedTxId="9263958598012">
<PayMethod>visa</PayMethod>
<ThreeDSecure>
<EnrollmentStatus>Y</EnrollmentStatus>
<AuthenticationStatus>A</AuthenticationStatus>
<CAVV>B5kBBxMFJAAAAAiYl4GRdXKQc4M=</CAVV>
<XID>VlBPUzg1OTgwMTItMjYzNTMyMDA=</XID>
<ECI>06</ECI>
<Protocol>3DS2.2.0</Protocol>
<Attribute name="TDS2.dsTransID">702209bd-0b12-5b9f-8000-00000000a7e5</Attribute>
</ThreeDSecure>
</PaymentInfo>
<WalletInfo>
<Attribute/>
</WalletInfo>
</SaleRequest>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
…

Requirements for implementation of failUrl handler

This failUrl will receive 3DS response from MPI when 3DS process fails. The endpoint URL is passed as a parameter as described above, so when 3DS process fails, error data will be returned here as described in the documentation of the MPI service. Therefore, you can return an error message and handle it as is suits your needs, without informing VPOS, meaning that there is no need for a second SaleRequest to VPOS XML API.

Java
@PostMapping("/3ds/failure")
public ResponseEntity<Void> failure(HttpServletRequest request, @RequestBody(required = false) String body) {
if (StringUtils.isNotBlank(body)) {
ThreeDSResp threeDsFailure = parse3DSBody(body);
log.info("Parsed 3DS Failure: {}", threeDsFailure);
reason = "3DS failed, resp was: " + threeDsFailure;
}
request.getSession().setAttribute("messageTitle", "3DS Failure");
request.getSession().setAttribute("messageText", reason);
request.getSession().setAttribute("messageBackLink", "/");
return ResponseEntity.status(HttpStatus.SEE_OTHER)
.location(URI.create("/message"))
.build();
}

Apple Pay

Introduction

Apple Pay is a digital wallet service by Apple Inc. that enables secure and seamless payments across supported devices, like iPhone, iPad, and Mac. Apple Pay is integrated within the payment page to offer customers a fast and convenient payment experience.

User Experience

Apple Pay provides a seamless and intuitive payment experience for customers. When users check out:

  1. They select the Apple Pay option at the payment page. 
  2. Their device prompts them to authenticate using Face ID, Touch ID, or a device passcode. 
  3. The payment details are securely transmitted to the merchant. 
  4. Upon successful transaction processing, the user receives confirmation instantly.

The entire process is designed for speed, security, and ease of use, reducing checkout friction and increasing conversion rates. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Requirements / Restrictions

– Transaction types supported: Payment, Pre-authorization, Refunds, Void.
– Schemes supported: Visa, Mastercard.
– Acquirers supported: Apple Pay transactions are being processed only through Worldline acquirer.

Important notice 1: Apple does not natively support iframe-based payment processing, so Apple Pay is not supported when an iframe is used to display the payment page.

Important notice 2: Tokenization is not supported through Apple Pay.

How to test

From the merchant’s/developer’s side: You need to activate Apple Pay in a newly created or already existing app in Sandbox, so as the respective Apple Pay button to appear in the payment page when the transaction is executed through a compatible device (iOS).

That test merchant number (MID) will also be automatically registered to Apple.

From the customer’s side: Use the below test account in your iOS device.

Password: K^f$JFdDq84e
Important notice: Please DO NOT activate two factor authentication (2FA) during log in.

Test cards: If not already included in the Apple Pay wallet, you should add some of the below.

Scheme/Type PAN Exp. Date CVC
Mastercard 5204245250460049 01/30 111
5204245250522095 01/30 111
5204245251107599 01/30 111
5204245253050839 01/30 111
5204245254718095 01/30 111
Visa Credit 4051069302200121 01/27 340
4761229700150465 01/27 175
4761209980011439 01/27 466
Visa Debit 4761120010000492 01/27 480
4761349750010326 01/27 982
4761262260004228 01/27 501
4761369980320253 01/27 878
4622943120054839 01/27 100
4180620070230189 01/27 111
4123400073320224 01/27 221

You can now choose the desired test card to complete the transaction.

Apple Pay Direct

Apple Pay direct integration provides the capability to present Apple Pay as a distinct payment method in the merchant’s checkout page, without the need to be redirected to Worldline’s payment page.

That implementation utilizes VPOS XML API requests. Further details can be found here.

Below you may find the necessary steps to embed Apple Pay direct wallet in your check out page.

Prior starting working with the following integration, you need to add a specific domain verification file (provided by Cardlink) in the below location under your domain name and it needs to be publicly accessible.

https://[DOMAIN_NAME]/.well-known/apple-developer-merchantid-domain-association

The domain verification file has to be in place before the actual activation of Apple Pay Direct in the payment gateway, so before enabling it in your test app.

Below there is a description of the steps needed to follow and implement on merchant’s side to support Apple Pay Direct.

A quick review of the steps follows:
1) Addition of element applePayDiv to the point of the html where the Apple Pay button should appear
2) Implement signature/digest calculation that will be used for the upcoming requests/responses
3) Creation of script URL to retrieve the applepaydirect.js from Cardlink’s server
4) Fetching of applepaydirect.js from Cardlink’s server
5) Display Apple Pay button by calling initApplePay
6) Create “/start-session” endpoint to which the js will send a request when the customer opens Apple Pay. That endpoint will then initiate the first VPOS XML request (WalletRequest) to receive the wallet data
7) Create “/sale" endpoint to which the js will send the encrypted payment data. That endpoint will then initiate the second VPOS XML request (SaleRequest) for the actual authorization

Step 1. Add html placeholder element

Place <div id="applePayDiv"></div> element to your html payment page, where the Apple Pay button will appear.

Step 2. Calculate signature or digest (V4 or V2) to get Cardlink’s script

First, request library file: vpos-xclient:1.1.161CL from Cardlink for use of classes for creating XML objects as in following code segments.

  • For Signature – V4

Develop in your merchant server, calculation of signature for V4 in order to get JS script from VPOS server, which will be passed to payment page, in order to get JS script from VPOS in next step.

Sample java code:

Java
…
private static final String V4_SIGN_ALG = "SHA256withRSA";
private static final String PK_STR = ""; //private key provided by C:L
public static String calculateSignature(byte[] data) {
46
try {
long s = System.nanoTime();
PrivateKey pk = VPOSXMLClientService.getPrivateKey(PK_STR);
Signature sg = Signature.getInstance(V4_SIGN_ALG);
sg.initSign(pk);
sg.update(data);
byte[] sigBytes = sg.sign();
String sigStr = Base64.encode(sigBytes, 0);
long e = System.nanoTime();
return sigStr;
} catch (Exception e) {
throw new RuntimeException("Error calculating signature", e);
}
}
…
StringBuilder sb = new StringBuilder();
sb.append(version).append(";").append(mid).append(";").append(date).append(";");
byte[] data = Misc.toUtf8Bytes(sb);
String signature = VPOSXmlRequestCreator.calculateSignature(data);
queryString = String.format("?version=%s&mid=%s&date=%s&signature=%s",
version, mid, date, URLEncoder.encode(signature, StandardCharsets.UTF_8));
  • For Digest – V2

Sample java code:

Java
String secret = "secret";
Character sep=null;
String restversion="2";
StringBuilder restScriptData=new StringBuilder(1024);
appendIfFirst(restScriptData, restversion, sep);
appendIfFirst(restScriptData, mid, sep);
appendIfFirst(restScriptData, date, sep);
restScriptData.append(secret);
String scriptDigestValue=Misc.calculateSHA256AndEncodeBASE64(restScriptData);
queryString = String.format("?version=%s&mid=%s&date=%s&digest=%s",
version, mid, date, URLEncoder.encode(scriptDigestValue, StandardCharsets.UTF_8));

Step 3. Create the script URL for getting the applepaydirect.js script from Cardlink / VPOS

  • VPOS API script url sample V2/V4

https://eurocommerce-test.cardlink.gr/vpos/js/applepaydirect.js?version=2&mid=0101119349&date=202508181116&digest=sEP%2F50pEV6bli71X4zgzaIgmz8VrTOPh%2BZPQL5F7%2Bg0%3D

https://eurocommerce-test.cardlink.gr/vpos/js/applepaydirect.js?version=4&mid=0101119349&date=202506201306&signature=aUfsR7BSWJ29oZskxXsSn3kK8QFDTK06lw0LCAytUnwPbSGbdgNBX0C6KCwPDdLiZH1TvdgjC0N8eE5GEj5enU%2FjvFD%2FDBGli7aGkfe1dslUD3dvNYBEuKj7ZIdtXGhjmjRi3H5d81KtQKry%2FMb3AfSyDF%2BgNxJyXcQ7WiZd0XVMEPSmO06dhLpAS3luWmHl32s8P6arcZrmq1tVBaGG%2FBcxPWk40ErA1J0sbtpoE7IixBz4H5bl3GVtRtQPhO05wTOneHTSKrUqZf5jAAVNQI86MfVStAFdrleyzkZlazEFqIQUQh3SDBQbsQSxBH0%2BWCsvMcovkW4JyW81LPN%2FBQ%3D%3D

Example URL breakdown

version: version=2/version=4 (depending on implementation)

Cardlink merchant number: e.g., mid=0101119349

date: e.g., date=202506201306

digest/signature: depending on implementation (digest for v2, signature for v4)

Sample JAVA code for creation

Java
String queryString = String.format("?version=%s&mid=%s&date=%s&signature=%s",version, mid, date, URLEncoder.encode(signature, StandardCharsets.UTF_8));

Step 4. Get the applepaydirect.js script from Cardlink / VPOS

Using the script URL you created, you need to fetch (GET) applepaydirect.js script from your Javscript code.

Sample JS code

Java
const completeScriptUrl = scriptUrl + data.queryString;
// Dynamically load the script
const scriptElement = document.createElement('script');
scriptElement.type = 'text/javascript';
scriptElement.src = completeScriptUrl;

Step 5. Initialize Apple Pay on script load

When script is loaded from Cardlink, initialize with appropriate parameters, calling method initApplePay(…), to show the Apple Pay button.

let initApplePay = function (version, extId, total, currency, displayItemsLabel, totalLabel, mid, url)

Method needs following parameters:

Parameter name Description
version the xml authentication version: 2.1 or 4.1
extId the order ID, created by merchant
total amount in following format: 12.34
currency “EUR"
displayItemsLabel Label to describe the items
totalLabel Label to describe the total amount
mid Cardlink assigned merchant number for merchant (Merchant No from backoffice)
url URL of merchant server

Sample js code

JS
scriptElement.onload = () => {
console.log("html_script: Script loaded successfully.");
// Ensure initApplePay is called only after the script is loaded
if (typeof initApplePay === "function") {
const randomId = generateRandomId(); // Generate the random ID
initApplePay(xmlVersion, randomId, total, "EUR", "itemLabel", "totalLabel", data.mid, "/api/wallet");
…
  • Calling method initApplePay(…) will generate ApplePay button.

Step 6. Implement endpoint in Merchant Server “/start-session”

Using the merchant server URL you provided as a parameter of initApplePay in the previous step, you need to implement an endpoint:

url + “/start-session”

After initialization of ApplePay, when Apple Pay button is clicked by the user, the JS script applePayDirect.js will make a request to this endpoint.

This REST endpoint, will accept following POST request:

POST
let walletRequest = {
version: xmlVersion,
51
validationUrl: validationURL,
merchantId: merchantNo,
amount: requestProperties.amount,
currency: requestProperties.currency,
};
fetch(merchantURL + "/start-session", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify(walletRequest),
})
...

This endpoint, will receive the above request data and then create an XML request and call VPOS XML endpoint. Resulting data, returned from VPOS as walletData, should then be returned as a response to the initial request.

XML structure for wallet request XML message. All elements below are required:

VPOS
Message
MessageId
Version
TimeStamp
WalletRequest
Authentication
Mid
OrderInfo
OrderId
OrderAmount
Currency
OrderDesc
WalletId
Mid

Following, are two examples of requests and responses for WalletRequest:

WalletRequest with V2.1 digest and WalletResponse

WalletRequest

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753275722159" timeStamp="2025-07-23T16:02:02.160+03:00" version="2.1">
<WalletRequest>
<Authentication>
<Mid>0101119349</Mid>
53
</Authentication>
<OrderInfo>
<OrderId>O1753275722160</OrderId>
<OrderDesc>Apple pay Direct</OrderDesc>
<OrderAmount>25.0</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<WalletId>ApplePay</WalletId>
<Mid>0101119349</Mid>
<ValidationURL>https://apple-pay-gateway-cert.apple.com/paymentservices/startSession</ValidationURL>
</WalletRequest>
</Message>
<Digest>fGh3pHUkIwXeW8jCj2K3O9eD30zHI9AXjfFBQ8CtMrg=</Digest>
</VPOS>

WalletResponse

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message version="2.1" messageId="M1753275722159" timeStamp="2025-07-23T16:02:03.551+03:00">
<WalletResponse
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="WalletResponse">
<TxId>0</TxId>
<walletData>
<data>
<key>walletData</key>
<value
xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">{"epochTimestamp":1753275723439,"expiresAt":1753279323439,"merchantSessionIdentifier":"SSHE19A9663755A40CCB29999801C738D4A_916523AAED1343F5BC5815E12BEE9250AFFDC1A17C46B0DE5A943F0F94927C24","nonce":"296c5822","merchantIdentifier":"B4772B3DE66669497E8A56F0811E3E368C457C20F0695675437AE4B5B8EC7ADE","domainName":"merchserver.services.novidea.gr","displayName":"Eurobank","signature":"308006092a66325fbca……..21130ffcc5000000000000","operationalAnalyticsIdentifier":"Eurobank:B4772B3DE66669497E8A56F0811E3E368C457C20F0695675437AE4B5B8EC7ADE","retries":0,"pspId":"B1927F28C02EF75777757868F9FE3136816D8AC42E348F68A0B8D7B6B7768A14"}
</value>
</data>
</walletData>
</WalletResponse>
</Message>
</VPOS>

WalletRequest with V4.1 digest and WalletResponse

WalletRequest

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753280215961" timeStamp="2025-07-23T17:16:55.962+03:00" version="4.1">
<WalletRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1753280215962</OrderId>
<OrderDesc>Apple pay Direct</OrderDesc>
<OrderAmount>28.5</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<WalletId>ApplePay</WalletId>
<Mid>0101119349</Mid>
<ValidationURL>https://apple-pay-gateway-cert.apple.com/paymentservices/startSession</ValidationURL>
</WalletRequest>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753280215961">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>jVOxKYrKMgGVouowdYAE+ZseM0vOZayzsnFncXMsnf8=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
UvecgcyjEG5aFrfamnIx3+Fy0zw784+h9sDyVOW....wzdNiwic3ePk6Pw55wA==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDlTCCAn0CBGdgBQswDQYJKoZ.....NBCgUCdD3wEsL5Dch1tUYc6ggddhX64xoH
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

WalletResponse

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753280215961" timeStamp="2025-07-23T17:16:57.522+03:00" version="4.1">
<WalletResponse
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="WalletResponse">
<TxId>0</TxId>
<walletData>
<data>
<key>walletData</key>
<value
xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">{"epochTimestamp":1753280217243,"expiresAt":1753283817243,"merchantSessionIdentifier":"SSHA3AF2D7A441447CFBCC03C7A29014417_916523AAED1343F5BC5815E12BEE9250AFFDC1A17C46B0DE5A943F0F94927C24","nonce":"9d4bc679","merchantIdentifier":"B4772B3DE66669497E8A56F0811E3E368C457C20F0695675437AE4B5B8EC7ADE","domainName":"merchserver.services.novidea.gr","displayName":"Eurobank","signature":"308006092a864886f70d01b84a66....6c54b61e9080941f0a61e631bc92049daa000000000000","operationalAnalyticsIdentifier":"Eurobank:B4772B3DE66669497E8A56F0811E3E368C457C20F0695675437AE4B5B8EC7ADE","retries":0,"pspId":"B1927F28C02EF75777757868F9FE3136816D8AC42E348F68A0B8D7B6B7768A14"}
</value>
</data>
</walletData>
</WalletResponse>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753280215961">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>RM0m81pOxzXR/OWS1QhNplC/FvanPtP9bd/fqJv0dUg=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
P4frJCHJGVpQ7YJRNnmO2VsSisXYwVW....WPwM4y3OecgsrSSnzUH4MGngN8EjQZbj
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIEVTCCAr0CBGQifiUwDQYJKoZc....1XEmtmcEkJiPSY2TnYn8lE
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

Sample java code for endpoint:

Java
public class MerchantSessionRequest {
private String version;
private String validationUrl;
private String merchantId;
private Double amount;
private String currency;
}
@PostMapping("/start-session")
public ResponseEntity<Map<String, String>> walletRequest(@RequestBody MerchantSessionRequest request) {
String vposXmlVersion = request.getVersion();
BigDecimal amount = BigDecimal.valueOf(request.getAmount());
String currency = request.getCurrency();
String merchantId = request.getMerchantId();
String validationUrl = request.getValidationUrl();
byte[] content = createWalletRequest(vposXmlVersion, amount, currency, "ApplePay", merchantId, validationUrl);
String xmlContent = new String(content, StandardCharsets.UTF_8);
String vposUrl = // get vpos xml api url for production
HttpResponse<String> response = postToVpos(vposUrl, xmlContent);
String walletData = XmlUtil.extractXmlValue(response.body(), "walletData");
if (walletData.startsWith("walletData")) {
walletData = walletData.replaceFirst("^walletData", "").trim();
}
return ResponseEntity.ok(Map.of("walletData", walletData));
…
}
private HttpResponse<String> postToVpos(String uri, String xmlData) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header("Content-Type", "application/xml")
.POST(HttpRequest.BodyPublishers.ofString(xmlData))
.build();
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}

Details of XML request:

  • method: createWalletRequest

public static byte[] createWalletRequest(String version, BigDecimal amount, String currency, String walletId, String mid, String validationURL) throws Exception {
VPOS request = create41BaseRequest(mid);
Authentication authentication = new Authentication();
authentication.setMid(mid);
WalletRequest walletRequest = buildWalletRequest(amount, currency, walletId, mid, validationURL,
authentication);request.getMessage().setWalletRequest(walletRequest);
//sign depending on version
return signAndMarshal(request);
}

  • method: create41BaseRequest

private static VPOS create41BaseRequest(String mid) throws DatatypeConfigurationException {
VPOS request = new VPOS();
com.modirum.mdpay.vpos.services.ext.xmlapi2.mvposjaxb41.Message message = new com.modirum.mdpay.vpos.services.ext.xmlapi2.mvposjaxb41.Message();
message.setMessageId(“M" + System.currentTimeMillis());
message.setVersion(VPOS_VERSION_4_1);
message.setTimeStamp(DatatypeFactory.newInstance()
.newXMLGregorianCalendar((java.util.GregorianCalendar) Calendar.getInstance()));
Authentication auth = new Authentication();
auth.setMid(mid);
request.setMessage(message);
return request;
}

  • method: buildWalletRequest

private static WalletRequest buildWalletRequest(BigDecimal amount, String currency, String walletId, String mid, String validationURL, Authentication authentication) {
WalletRequest walletRequest = new WalletRequest();
walletRequest.setAuthentication(authentication);
walletRequest.setOrderInfo(buildOrderInfo(amount, currency, “Apple pay Direct"));
walletRequest.setWalletId(walletId);
walletRequest.setMid(mid);
walletRequest.setValidationURL(validationURL);
return walletRequest;
}

  • method: buildOrderInfo

private static OrderInfo buildOrderInfo(BigDecimal amount, String currency, String orderDesc) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOrderId(orderID);
orderInfo.setOrderAmount(amount);
orderInfo.setCurrency(currency);
orderInfo.setOrderDesc(orderDesc);
return orderInfo;
}

Information and details for the Message and Authentication parts of XML, can be found here.

Step 7. Implement endpoint in Merchant Server “/sale"

After the previous step, where the walletData was acquired and returned to the initial request from VPOS server, as a next step applePayDirect.js will make a new request, to merchant’s server “/sale” endpoint, with the encrypted payment data this time. The endpoint will create an XML sale request object to call VPOS XML API and VPOS will decrypt the ApplePay payment data to make authorization payment.

Following is the request, create by JS, that will be sent to merchant’s server /sale endpoint:

JS
let saleRequest = {
version: xmlVersion,
merchantId: merchantNo,
orderId: requestProperties.orderId,
amount: requestProperties.amount,
currency: requestProperties.currency,
applePayResponse: JSON.stringify(paymentResponse),
};
let body = JSON.stringify(saleRequest);
let saleReqURL = merchantURL + "/sale"
fetch(saleReqURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: body
})

These are the elements that consist the XML that is required to be built and sent to VPOS. All of them are required, more information is in code below.

VPOS
Message
MessageId
Version
TimeStamp
RequestMessage (SaleRequest)
Authentication
Mid
OrderInfo
OrderId
OrderAmount
Currency
OrderDesc
WalletInfo
Attribute (name: “applePaymentData", value: walletData from incoming request.getApplePayResponse())
PaymentInfo

Sample java code for implementing the /sale endpoint:

Java
public class SaleRequest {
private String version;
private String orderId;
private Double amount;
private String currency;
private String applePayResponse;
private String googlePayResponse;
private String merchantId;
}
@PostMapping(value = "/sale", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, String>> saleRequest(@RequestBody SaleRequest request, HttpSession session) {
…
String vposXmlVersion = request.getVersion();
BigDecimal amount = BigDecimal.valueOf(request.getAmount());
String merchantId = request.getMerchantId();
WalletType walletType WalletType.apple;
String walletData = request.getApplePayResponse();
byte[] content = createSaleRequest(vposXmlVersion, merchantId, amount, request.getCurrency(), walletData, walletType);
String xmlSaleRequest = new String(content, StandardCharsets.UTF_8);
String vposUrl = // get vpos xml api url for production
HttpResponse<String> response = postToVpos(vposUrl, xmlSaleRequest);
String status = XmlUtil.extractXmlValue(response.body(), "Status");
if ("CAPTURED".equalsIgnoreCase(status)) {
return ResponseEntity.ok(Map.of("status", "success"));
}
else if ("ERROR".equalsIgnoreCase(status)){
…
}
else if ("REFUSED".equalsIgnoreCase(status)){
…
}
..
public enum WalletType
{
apple
}
public static byte[] createSaleRequest(String version, String mid, BigDecimal amount, String currency, String walletPaymentData, ApiWalletController.WalletType walletType)
throws Exception {
VPOS request = create41BaseRequest(mid); // as described above
Authentication authentication = new Authentication();
authentication.setMid(mid);
RequestMessage saleReq = buildSaleRequest(amount, currency,walletType, walletPaymentData, authentication);
request.getMessage().setSaleRequest(saleReq);
//sign depending on version
return signAndMarshal(request);
}
private static RequestMessage buildSaleRequest(BigDecimal amount, String currency,
ApiWalletController.WalletType walletType, String walletPaymentData, Authentication authentication) {
RequestMessage saleReq = new RequestMessage();
saleReq.setAuthentication(authentication);
saleReq.setOrderInfo(buildOrderInfo(amount, currency, "Apple pay Direct")); //as described above
WalletInfo walletInfo = new WalletInfo();
AttributeType attributeType = new AttributeType();
attributeType.setName("applePaymentData");
attributeType.setValue(walletPaymentData);
walletInfo.getAttribute().add(attributeType);
saleReq.setWalletInfo(walletInfo);
PaymentInfo paymentInfo = new PaymentInfo();
saleReq.setPaymentInfo(paymentInfo);
return saleReq;
}

Depending on VPOS response, handle the results to your page accordingly.

Following, are two examples of requests and responses for SaleRequest & SaleResponse.

SaleRequest with V2.1 digest and SaleResponse

SaleRequest

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753275788722" timeStamp="2025-07-23T16:03:08.722+03:00" version="2.1">
<SaleRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1753275788722</OrderId>
<OrderDesc>Apple pay Direct</OrderDesc>
<OrderAmount>25.0</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<PaymentInfo/>
<WalletInfo>
<Attribute name="applePaymentData">{"requestId":"1098566906","methodName":"https://apple.com/apple-pay","details":{"billingContact":{"addressLines":["Test","Test"],"administrativeArea":"","country":"Greece","countryCode":"GR","familyName":"Account","givenName":"Test","locality":"Test","phoneticFamilyName":"","phoneticGivenName":"","postalCode":"12345","subAdministrativeArea":"","subLocality":""},"shippingContact":{"familyName":"Account","givenName":"Test","phoneticFamilyName":"","phoneticGivenName":""},"token":{"paymentData":{"data":"SREE86v.....GG3WAD9lXkIhBmgkciC069LujoH9rd0BHJA==","signature":"MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwgAYJKoZIhvcNAQcBAACggDCCA+QwggO7tRjIKwYBBBwG.....Mcv5cOCrY2ZxJLLRJmOhXpu6DYwAAAAAAAA=","header":{"publicKeyHash":"glxz0mA5W2Il6vX0EsGWcle1GON4jfJV3fvFkCdxtDg=","ephemeralPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESFMfnPfujsn9WYRW23kxguu7GR0IqypZVLGaApm/tFcGoNQJH0cC/RcezVVj7SC3eROSTwFOlAdRtjnjAPXfEg==","transactionId":"5aa9abc013abf7875e99aa502487ffcffc49ff0ba62c0473affce336673b3890"},"version":"EC_v1"},"paymentMethod":{"displayName":"MasterCard 0049","network":"MasterCard","type":"credit"},"transactionIdentifier":"5aa9abc013abf7875e99aa502487ffcffc49ff0ba62c0473affce336673b3890"}},"shippingAddress":null,"shippingOption":null,"payerName":"Test Account","payerEmail":null,"payerPhone":null}</Attribute>
</WalletInfo>
</SaleRequest>
</Message>
<Digest>FHdho2VP1KxB5x0tuvFB2yiFWo/2yNqu3ZzwtJi7hMo=</Digest>
</VPOS>

SaleResponse

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message version="2.1" messageId="M1753275788722" timeStamp="2025-07-23T16:03:10.358+03:00">
<SaleResponse>
<OrderId>O1753275788722</OrderId>
<OrderAmount>25.0</OrderAmount>
<Currency>EUR</Currency>
<PaymentTotal>25.0</PaymentTotal>
<Status>CAPTURED</Status>
<TxId>9263958600172</TxId>
<PaymentRef>125106</PaymentRef>
<RiskScore>0</RiskScore>
<Description>OK, CAPTURED response code 00</Description>
<Attribute name="EXTACQUIRERID">026</Attribute>
</SaleResponse>
</Message>
<Digest>tA6xib8MuHNh3wcBAplfeQuJfcoCTBay4ArYMOMWU/8=</Digest>
</VPOS>

SaleRequest with V4.1 digest and SaleResponse

SaleRequest

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
71
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753280227692" timeStamp="2025-07-23T17:17:07.692+03:00" version="4.1">
<SaleRequest>
<Authentication>
<Mid>0101119349</Mid>
</Authentication>
<OrderInfo>
<OrderId>O1753280227692</OrderId>
<OrderDesc>Apple pay Direct</OrderDesc>
<OrderAmount>28.5</OrderAmount>
<Currency>EUR</Currency>
</OrderInfo>
<PaymentInfo/>
<WalletInfo>
<Attribute name="applePaymentData">{"requestId":"2017043584","methodName":"https://apple.com/apple-pay","details":{"billingContact":{"addressLines":["Test","Test"],"administrativeArea":"","country":"Greece","countryCode":"GR","familyName":"Account","givenName":"Test","locality":"Test","phoneticFamilyName":"","phoneticGivenName":"","postalCode":"12345","subAdministrativeArea":"","subLocality":""},"shippingContact":{"familyName":"Account","givenName":"Test","phoneticFamilyName":"","phoneticGivenName":""},"token":{"paymentData":{"data":"s92SZL....y4jBUf06Jf8=","signature":"MIAGCSqGSI...IeCqATbtZknHH+77KUsk+QAAAAAAAA==","header":{"publicKeyHash":"glxz0mA5W2Il6vX0EsGWcle1GON4jfJV3fvFkCdxtDg=","ephemeralPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3jAH47z0CmfQ36YHFDRw0nBqt6rx3dc46rzY12UtNlsfiRtYRZtMF8NGfk8sSANY4RDupObwOkEIRHCYSlQbyg==","transactionId":"e9a4394776e3c4f7b86fc0864062e3d352d049c71ec971748e0711c34b43a049"},"version":"EC_v1"},"paymentMethod":{"displayName":"Visa 0121","network":"Visa","type":"credit"},"transactionIdentifier":"e9a4394776e3c4f7b86fc0864062e3d352d049c71ec971748e0711c34b43a049"}},"shippingAddress":null,"shippingOption":null,"payerName":"Test Account","payerEmail":null,"payerPhone":null}</Attribute>
</WalletInfo>
</SaleRequest>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753280227692">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>zxBHXKGKwlwo5CiLP75iVI9d5HcVz3rv3/Shqq4BoDw=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
Yx6AarjVzKGQCpdXgxQW/tN8LN+dwcPUYRI....q6pLNKravk+F8ZSSwhsIA==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDlTCCAn0CBGdgBQswDQYJKoZIhvcNAQELBQA....ddhX64xoH
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

SaleResponse

XML
<VPOS
xmlns="http://www.modirum.com/schemas/vposxmlapi41"
xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<Message messageId="M1753280227692" timeStamp="2025-07-23T17:17:08.586+03:00" version="4.1">
<SaleResponse>
<OrderId>O1753280227692</OrderId>
<OrderAmount>28.5</OrderAmount>
<Currency>EUR</Currency>
<PaymentTotal>28.5</PaymentTotal>
<Status>CAPTURED</Status>
<TxId>9263958600192</TxId>
<PaymentRef>125125</PaymentRef>
<RiskScore>0</RiskScore>
<Description>OK, CAPTURED response code 00</Description>
<Attribute name="EXTACQUIRERID">026</Attribute>
</SaleResponse>
</Message>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#M1753280227692">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>MJCtaaTemRzmajq2fnMSB+NkoGB+rPhvVRRu7txfdY4=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
RbF7HcPhwceDUT+EKSwUgD+zvbXnGjo9GnG.....1fVT+PQTZuPLszmXWtNvb
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIEVTCCAr0CBGQifiUwDQYJKoZIhvc.....JD1XEmtmcEkJiPSY2TnYn8lE
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</VPOS>

 

IRIS (for Worldline Greece)

IRIS payments

*To download the image, please right click on it and select ‘Save image as’.

IRIS Payments is a real-time payment service that allows users to initiate payments through their bank’s mobile app through QR code or online banking, in a secure, user-friendly environment.

Online shops integrate IRIS Payments to allow customers to pay directly from their bank accounts, bypassing the need for credit or debit cards.

IRIS in Redirect model

For Redirect integration method, IRIS is provided as an option within the payment page.

IRIS is available only for Payment (once off) transactions.

Refund action is not supported through e-commerce platform (API or back office) for IRIS transactions.

Please DO NOT use i-frame for IRIS transactions since there are issues with the connection to the respective e-banking. The connection through i-frame is blocked by the Banks.

Allowed characters you can use in parameter orderDesc (order description) are the below. If you use any other character that is not supported, IRIS transactions cannot proceed.

a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
/ – ? : ( ) . , ‘ +
Space
α ά β γ δ ε έ ζ η ή θ ι ί ϊ ΐ κ λ μ ν ξ ο ό π ρ σ ς τ υ ύ ϋ ΰ φ χ ψ ω ώ
Α Ά Β Γ Δ Ε Έ Ζ Η Ή Θ Ι Ί Ϊ Κ Λ Μ Ν Ξ Ο Ό Π Ρ Σ Τ Υ Ύ Ϋ Φ Χ Ψ Ω Ώ
= ! % * ; # _ $ \ { } [ ]

There is no need for further implementation since the IRIS option is automatically available in the payment page for the payer to choose. The only thing you should take into consideration, is that after a successful IRIS transaction, as part of the response message you will receive parameter payMethod with value ‘IRIS’, which is a new value (check also below).

The only prerequisite is to use the latest payment page which looks like below. If you are using a custom css with changes in colors/shapes/font/logos, please confirm during your tests that IRIS remains functional (check below on how to make test payments with IRIS).

 

If you are using a different/older payment page, you need to follow the below steps:

1. Register and/or log in Sandbox and create a new application with Business partner: Worldline or Cardlink (based on your current payment page) and Integration method: Redirect.
2. Within the new app, go to section “Payment Page" and download the respective xsl/css files of the newest default payment page.
3. Custom those files the way you desire and upload them through the same section.
4. Once the custom payment page has been adjusted to your test mid, we will inform you to proceed with your tests.
5. Confirm through your tests the proper function of IRIS and let us know to adjust the new custom payment page to your production MID as well.

If you wish, you can provide IRIS as a separate payment method in your checkout page and declare that you wish the payment to be made through IRIS by using parameter payMethod=’IRIS’. By doing that, customer directly sees the page with Bank logos and QR generation.

After a successful IRIS transaction, in the response message you will receive the respective parameter payMethod=’IRIS’.

For further information, please check here.

Important notice: If you use one of our CMS plugins here, an updated version is available to support IRIS for Worldline or Cardlink business partner.

How to test IRIS functionality

To begin with, you need to enable IRIS during a new app creation or through an existing app modification.
During new app creation, choose Worldline as Business partner and Redirect or Redirect/Direct as Model.

Post the respective request – using parameter payMethod=’IRIS’ if you wish – to open the payment page.
Choose the IRIS tab on the top right of the page to show IRIS details. If you use payMethod=’IRIS’, then you will be moved there directly.

In test environment, IRIS can be tested end to end only through QR generation. Redirection to e-banking is not supported.
Once you have generated the QR code, you do not use it through a banking mobile app, but you should expect the below results automatically after a few seconds (up to 120), based on the order amount.

Amount ending in Status
9 ERROR
8 REFUSED
anything else CAPTURED

IRIS QR in Direct model

For Direct integration method, IRIS can only be used via QR code.

IRIS is available only for Sales (once off) transactions.

Refund action is not supported through e-commerce platform (API or back office) for IRIS transactions.

Allowed characters you can use in parameter orderDesc (order description) are the below. If you use any other character that is not supported, IRIS transactions cannot proceed.

a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
/ – ? : ( ) . , ‘ +
Space
α ά β γ δ ε έ ζ η ή θ ι ί ϊ ΐ κ λ μ ν ξ ο ό π ρ σ ς τ υ ύ ϋ ΰ φ χ ψ ω ώ
Α Ά Β Γ Δ Ε Έ Ζ Η Ή Θ Ι Ί Ϊ Κ Λ Μ Ν Ξ Ο Ό Π Ρ Σ Τ Υ Ύ Ϋ Φ Χ Ψ Ω Ώ
= ! % * ; # _ $ \ { } [ ]

When IRIS is chosen as a payment method during the checkout, a Sale Request is sent from merchant’s server requesting for the QR code data.

Part of the XML response returned from the payment gateway is the Base64 encoded QR value. This value should be displayed to the buyer as an inline base64 image. Then the payer can scan it using the respective m-banking app.

You may find more details here.
PayMethod and PaymentOption should be taken into consideration regarding the request. Attributes “IRIS-QR" and “IRIS-TXID" regarding the response.

Sample request can be found here.

The transaction is initially in PROCESSING status. When the customer scans the QR within the Bank’s mobile app, the transaction returns to INPAYMENT status and that’s when we start asking DIAS for the result. The maximum validity period of the transaction is 30 minutes.
In order for your system to be informed about the result of the transaction, XML StatusRequest should be used to retrieve transaction’s status.
As long as the status is not final (CAPTURED, REFUSED, ERROR, TIMEDOUT, CANCELED, PROCESSING-TIMEDOUT), then the StatusRequest should be used again.

Relative sample request can be found here.

How to test IRIS functionality

To begin with, you need to enable IRIS QR during a new app creation or through an existing app modification.
During new app creation, choose Worldline as Business partner and Direct as Model.

Use that test mid to post the respective VPOS request as described above. Render the QR encoded data received in the response and present the QR code in your browser.

Implement a flow through which you will consume Status Request API to retrieve the transaction’s result.

The created transaction is automatically completed within a few seconds (up to 120) based on the order amount as follows:

Amount ending in Status
9 ERROR
8 REFUSED
anything else CAPTURED
IRIS (for Nexi Greece)

IRIS payments

 

 

*To download the image, please right click on it and select ‘Save image as’.

IRIS Payments is a real-time payment service that allows users to initiate payments through their bank’s mobile app through QR code or online banking, in a secure, user-friendly environment.

Online shops integrate IRIS Payments to allow customers to pay directly from their bank accounts, bypassing the need for credit or debit cards.

IRIS in Redirect model

For Redirect integration method, IRIS is provided as an option within the payment page.

IRIS is available only for Payment (once off) transactions.

Refund action is not supported through e-commerce platform (API or back office) for IRIS transactions.

Please DO NOT use i-frame for IRIS transactions since there are issues with the connection to the respective e-banking. The connection through i-frame is blocked by the Banks.

Allowed characters you can use in parameter orderDesc (order description) are the below. If you use any other character that is not supported, IRIS transactions cannot proceed.

a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
/ – ? : ( ) . , ‘ +
Space
α ά β γ δ ε έ ζ η ή θ ι ί ϊ ΐ κ λ μ ν ξ ο ό π ρ σ ς τ υ ύ ϋ ΰ φ χ ψ ω ώ
Α Ά Β Γ Δ Ε Έ Ζ Η Ή Θ Ι Ί Ϊ Κ Λ Μ Ν Ξ Ο Ό Π Ρ Σ Τ Υ Ύ Ϋ Φ Χ Ψ Ω Ώ
= ! % * ; # _ $ \ { } [ ]

There is no need for further implementation since the IRIS option is automatically available in the payment page for the payer to choose. The only thing you should take into consideration, is that after a successful IRIS transaction, as part of the response message you will receive parameter payMethod with value ‘IRIS’, which is a new value (check also below).

By default, the respective payment page will have the below view. If you are using a custom css with changes in colors/shapes/font/logos, please confirm during your tests that IRIS remains functional (check below on how to make test payments with IRIS).

If you are using a different/older payment page, you need to follow the below steps:

1. Register and/or log in Sandbox and create a new application with Business partner: Nexi and Integration method: Redirect.
2. Within the new app, go to section “Payment Page" and download the respective xsl/css files of the default payment page.
3. Customize those files the way you desire and upload them through the same section.
4. Once the custom payment page has been adjusted to your test mid, we will inform you to proceed with your tests.
5. Confirm through your tests the proper function of IRIS and let us know to adjust the custom payment page to your production MID as well.

If you wish, you can provide IRIS as a separate payment method in your checkout page and declare that you wish the payment to be made through IRIS by using parameter payMethod=’IRIS’. By doing that, customer directly sees the page with Bank logos and QR generation and can choose how to proceed.

After a successful IRIS transaction, in the response message you will receive the respective parameter payMethod=’IRIS’.

For further information, please check here. Make sure you can handle all possible parameters included in the response message as described here.

Important notice 1: If you use one of our CMS plugins here, an updated version is available to support IRIS for Nexi, without the need to insert IRIS customer code (seller id).

Important notice 2: If you are already using IRIS through one of our plugins with a seller id, please do not install the new version. Also, if you automatically receive updates through the respective CMS marketplace, please do not proceed with the latest update. If you install the new version, the existing IRIS integration will not be functional and you should revert back to the version you previously had.

IRIS combined with XML (direct) integration for card transactions

If you are using XML (direct) integration for card transactions, you need to use Redirect integration for IRIS transactions, which will be by default activated for that purpose. Further technical details regarding Redirect integration can be found here.

How to test IRIS functionality

To begin with, you need to enable IRIS during a new app creation or through an existing app modification.
During new app creation, choose Nexi as Business partner and Redirect or Redirect/Direct as Model.

Post the respective request – using parameter payMethod=’IRIS’ if you wish – to open the payment page.
Choose the IRIS option to show IRIS details. If you use payMethod=’IRIS’, then you will be moved there directly.

In test environment, IRIS can be tested end to end only through QR generation. Redirection to e-banking is not supported.
Once you have generated the QR code, you do not use it through a banking mobile app, but you should expect the below results automatically after a few seconds (up to 120), based on the order amount.

Amount ending in Status
9 ERROR
8 REFUSED
anything else CAPTURED