Release notes
Find out what's new in Payment Orchestration:
Dec 2020: 3DS 2 integration
Jan 2021: Soft declines in 3DS 2
Feb 2021: Lightbox in payment page and Mobile SDKs (beta)
Find out what's new in Payment Orchestration:
Dec 2020: 3DS 2 integration
Jan 2021: Soft declines in 3DS 2
Feb 2021: Lightbox in payment page and Mobile SDKs (beta)
This documentation guides you through our Payment Orchestration Open Payment Gateway (OPG) features for frontend checkout and backend use cases. It provides important information about integration scenarios, testing possibilities, and references.
For a Glossary of Terms, go to the end of this document.
Every potential checkout webpage includes information about the purchase, payment amount, customer details, plus a few additional details. This information is always issued from your server-side system via HTTP POST Request. This is what we call a LIST Request.
To test these features, you need an account for our website. If you do not have an account, please email welcome.germany@payoneer.com and we will create one for you. You can start experimenting with OPG Sandbox as soon as you get your Payment Orchestration account details.
Try your first LIST Request by following these 4 steps:
1. Configure your OPG SandboxYou must generate a Token to authenticate your system against the Payment Gateway API.
To generate a Sandbox token:
We suggest that you use one of the browser plugins listed in Tools for Manual Testing so you can submit JSON POST requests easily by hand.
Use the following values to submit your first LIST Request with the chosen tool:
POST
Content Type
: application/vnd.optile.payment.enterprise-v1-extensible+json
Accept
: application/vnd.optile.payment.enterprise-v1-extensible+json
Find out more about authentication and version headers in API Access.
For your request you should receive a successful HTTP 200 response with a list of available payment methods for this transaction (attribute networks.applicable
).
If you configured your OPG Sandbox as described above, Carte Bleue will not show in this list.
5. Visualize the LIST
Follow the steps below to visualize the content of a generated LIST with a default style, using our Orchestration Platform's Hosted Payment Page:
links.self
attribute. That's the URL of the LIST Resource:
https://api.sandbox.oscato.com/pci/v1/59722056cb4280f9550078e3lffpph128vffgueil7chn475bs
listUrl
to the following URL of the Hosted Payment Page, and open it in a browser:
https://resources.sandbox.oscato.com/paymentpage/v3/responsive.html?listUrl=https://api.sandbox.oscato.com/pci/v1/59722056cb4280f9550078e3lffpph128vffgueil7chn475bs
Note: You have 30 minutes to make a successful CHARGE. After 30 minutes, you will get an error and you should create a new LIST Session (That's also why the link above will not work).
The payment page should look like the example picture below:
This payment page has all necessary capabilities to validate and process payment account input. For example, try using the following test account and then click on the Pay button:
Card number: 5500000000000004
Expiry Date: any date in the future
Security Code: any 3 digit number
Account Holder: John Doe
Using this input above will trigger instant validation, and clicking on Pay will trigger a frontend CHARGE request and should take you to the success URL defined in the LIST request. Alternatively, you can perform the CHARGE request from your backend which we will simulate together in the next step.
6. CHARGE request
The LIST response as generated in step 4. contains information about the current payment session, including all available payment networks and their attributes as seen in the array networks.applicable
. In this example every network has the same attributes, but for our testing we are now only going to look at links.operation
from Mastercard:
"operation": "https://api.sandbox.oscato.com/pci/v1/59722056cb4280f9550078e3lffpph128vffgueil7chn475bs/MASTERCARD/charge"
Notice that this operation endpoint starts with a reference to the current LIST long ID and ends in /MASTERCARD/charge
, which means that for the Mastercard network the next operation after listing it is a charge on the customer account. This operation URL is the endpoint to which you need to submit the CHARGE request via POST
.
In the case of Mastercard, the charge cannot happen without a customer account. So, the CHARGE in this case will carry also a JSON body containing the collected customer account. We can use the same data as done in the step 5. with the hosted payment page, but now mapping them into the appropriate attributes as expected by the payment API.
The CHARGE request will then be done as exemplified in the right pane, using the same authentication and headers as previously done with the LIST request.
A successful CHARGE response should contain a "charged" status code and a redirect URL to the success page as defined in the LIST request.
Play Around
Make another LIST Request, with an incremented transactionId
(it's technically not required but it is good practice to keep them unique) and change the attribute country
to FR
. Submit again. Now you should see a similar list in the response, but this time with PayPal because we configured it earlier for France only.
Also, you can copy the logo URL (attribute links.logo
) of one of the networks and open it in a browser. The logo of that payment method will show.
Take one of the credit cards in the list and open the URL from links.localizedForm
in a browser. You will see a snippet of a simple HTML form without any styling. If you like, you can open the HTML source code. This snippet is another building-block provided by OPG to generate the payment page.
To see an out-of-the-box example of how a payment page is rendered, see our Demo. You can also play around with the country parameter that should go into the LIST Request as well as several parameters offered by our AJAX Integration JavaScript library.
From here on we recommend you to understand our Integration Scenarios and choose the one that better fits your business model. We also have more LIST use cases and backend use cases designed to cover specific checkout flows to fit your application.
{
"transactionId": "tr101",
"country": "DE",
"customer": {
"number": "42",
"email": "john.doe@example.com"
},
"payment": {
"amount": 19.99,
"currency": "EUR",
"reference": "Shop 101/20-03-2016"
},
"style" : {
"hostedVersion":"v3"
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html"
}
}
Example of LIST Response
{
"links": {
"self": "https://api.sandbox.oscato.com/pci/v1/5a4f43dabc12312dfef5752blt5et3mnk2abc123u0fm79bud2"},
"timestamp": "2018-01-05T09:22:31.355+0000",
"operation": "LIST",
"resultCode": "00000.11.000",
"resultInfo": "4 applicable and 0 registered networks are found",
"returnCode": {
"name": "OK",
"source": "GATEWAY" },
"status": {
"code": "listed",
"reason": "listed" },
"interaction": {
"code": "PROCEED",
"reason": "OK" },
"identification": {
"longId": "5a4f43d7148b512dfef5752blt5et3mnk226k45uu0fm79bud2",
"shortId": "02694-35736",
"transactionId": "id_h0010" },
"networks": {
"applicable": [{
"code": "MASTERCARD", ... }]
... }
CHARGE Request on Mastercard Account:
POST to https://api.sandbox.oscato.com/pci/v1/59722056cb4280f9550078e3lffpph128vffgueil7chn475bs/MASTERCARD/charge
With body:
{
"account": {
"number": "5500000000000004",
"expiryMonth": "01",
"expiryYear": "2029",
"verificationCode": "123",
"holderName": "John Doe"
}
}
CHARGE Response:
{
"links": {
"payout": "https://api.sandbox.oscato.com/api/charges/5c1ce635a6cd3d3efee7f84fc/payout",
"self": "https://api.sandbox.oscato.com/api/charges/5c1ce635a6cd3d3efee7f84fc",
"customer": "https://api.sandbox.oscato.com/api/customers/5c0533e6b55f2801861fc6c6u"
},
"timestamp": "2018-12-21T13:10:13.766+0000",
"operation": "CHARGE",
"resultCode": "00000.TESTPSP.000",
"resultInfo": "Approved",
"pspCode": "TESTPSP",
"returnCode": {
"name": "OK",
"source": "PSP"
},
"status": {
"code": "charged",
"reason": "debited"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
...
"redirect": {
"url": "http://localhost:3000/html/success.html",
"method": "GET",
...
}
We suggest that you use one of the browser plugins listed below so that you can easily submit JSON POST requests when you test.
Get your Merchant Code and Payment Token together, and run this command:
curl --user [YOUR MERCHANT CODE]/[YOUR TOKEN]: --header "Accept:application/vnd.optile.payment.enterprise-v1-extensible+json" --data '{"transactionId":"123","country":"DE","payment":{"amount":9.99,"currency":"EUR","reference":"test"},"customer":{"number":123},"callback":{"returnUrl":"https://localhost:3000/success.html","cancelUrl":"https://localhost:3000/cancel.html","notificationUrl":"https://localhost:3000/notify.html"}}' -k https://api.sandbox.oscato.com/api/lists
Note that this request does not use JSON as content type for the input but (implicitly) application/x-www-form-urlencoded
. If successful, the result will be JSON script and start like this (depending on your configuration):
{
"links": {
"self": "https://api.sandbox.oscato.com/pci/v1/5a4fabc1238b512dfef5752blt5et3mnk226k4abc12379bud2"},
"timestamp": "2018-01-05T09:22:31.355+0000",
"operation": "LIST",
"resultCode": "00000.11.000",
"resultInfo": "4 applicable and 0 registered networks are found",
"returnCode": {
"name": "OK",
"source": "GATEWAY"}
...
}
API Types and Authentication
In our API Reference you will find a complete and formal documentation of all API calls. Please note the distinction between Client API and Server API. Endpoints in the Client API are designed to be accessed by frontends, eg web browsers, without authentication. For example the CHARGE Request exists in a frontend accessible version. The Server API is for calls from your servers, such as the LIST Request.
Server API calls require HTTPS Basic Authentication with these credentials:
To generate payment tokens, you access the Merchant Portal (requires access rights). Contact our support team at support@optile.net for help with permissions.
API Changes
In your contract with us we state that new API response parameters can be added in any release without explicit warning. This will not affect the existing functionality and not require further action from you. Your system only needs to be robust against additional response attributes.
For example, if you use Java with the Jackson parser for JSON, please set the FAIL_ON_UNKNOWN_PROPERTIES
to false
(see the official Jackson parser documentation and additional details on Stackoverflow).
Of course we will never rename any request or response parameters and never introduce new mandatory request parameters without the contractual notice periods. So with a robust system as described above you are safe.
Accept and Content Type Headers
For every request, you should set two HTTP headers:
Content Type
indicates the content format of your inputAccept
indicates the content format you expect to receive in the reply
For both please use the value: application/vnd.optile.payment.enterprise-v1-extensible+json
This implies communication via JSON. For the level and version part (enterprise-v1-extensible
in this case) we may allow custom contractual agreements and hence additional values in the future.
CORS support in Sandbox
Although the Sandbox and Live environment APIs are generally the same, there is one slight technical difference to allow for particular testing and demonstration scenarios:
The Sandbox environment will generally allow Cross-origin resource sharing (CORS), by providing the shown additional HTTP headers in the reply >>
The Live environment only allows CORS for the very few Client Payment API calls (see above at Authentication and the API Reference). In all other cases CORS will not return the aforementioned headers -- so a LIST Request from a browser (for example) is not possible.
As long as you do not try to sidestep the rules from the Basic Authentication and try to initiate server-to-server calls from a browser, there is nothing to worry about; you will see no difference between Sandbox and Live environment APIs.
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept, Authorization
This is a simple demo of a dynamically created payment page using optile's AJAX Integration.
Simple integration includes the scenarios shown below:
Integration Scenario | Origin of the Payment Page | Destination of Payment Details | PCI Requirement |
---|---|---|---|
Hosted | Payment Page redirect URL from optile | optile / OPG | SAQ A |
Selective Native with AJAX | Payment Page is generated by the merchant server using optile's AJAX Library + iFrame by optile | optile / OPG | SAQ A |
Display Native with AJAX | Merchant Server using optile's AJAX Library | optile / OPG | SAQ A-EP |
By contrast, advanced integration includes:
Integration Scenario | Origin of the Payment Page | Destination of Payment Details | PCI Requirements |
---|---|---|---|
Selective Native | Payment Page is generated by the merchant server + iFrame by optile | optile / OPG |
SAQ A |
Display Native | The merchant server generates the payment page | optile / OPG | SAQ A-EP |
Pure Native | The merchant server generates the payment page | Merchant Server | SAQ D |
Pure Native with Client Side Encryption | The merchant server generates the payment page | optile / OPG | SAQ A-EP |
Hybrid Scenarios
If you already have integrations with payment providers or are thinking about a parallel setup where optile-powered payment methods are combined with methods of other sources, there are multiple options to achieve this, depending on your specific prerequisites and needs. Please have a look at our Hybrid & Migration Strategies.
The Payment Gateway gives merchants a lot of flexibility. It allows multiple ways to integrate the frontend part of the payment processes (compared to backend interactions, which follow a standard path). Each integration scenario has different advantages, regarding effort, conversion, user experience, and security certification.
We generally recommend the AJAX Integration. It provides very good user-experience with minimum implementation effort, and it can be combined with Selective Native integration to minimize PCI requirements on your side. But a different integration scenario will make more sense for some merchant requirements: for integration with mobile devices or other devices that do not rely on web browser rendering eg Smart TVs, we offer special mobile integration scenarios.
Visualization Comparison of Features and Implementation EffortHosted | Selective Native via AJAX | Selective Native | Display Native via AJAX | Display Native | Pure Native with CSE | Pure Native | |
---|---|---|---|---|---|---|---|
PCI DSS | |||||||
Required SAQ | A | A | A | A-EP | A-EP | A-EP | D |
|
|||||||
Payment Page generation | on optile side | browser | browser or server side | browser | browser (or other client) or server side | server side | server side |
Resulting view | redirect or include in iFrame | native HTML with partial iFrames | native HTML with partial iFrames | native HTML | native HTML or other UI | native HTML or other UI | native HTML or other UI |
Splitting method selection from details entering possible? | yes | yes | yes | yes | yes | yes | yes |
In-page Account Validations | yes | yes | yes | yes | yes | yes | yes |
Payment Details Submission | to OPG | to OPG | to OPG | to OPG | to OPG | to OPG | to merchant server |
|
|||||||
Method LIST Rendering | auto | auto | implement | auto | implement | implement | implement |
In-page Account Validations | auto | auto (configurable) | implement partially, web service provided |
auto (configurable) |
implement, web service provided | implement, web service provided | implement, web service provided |
CHARGE Submission | auto | auto | implement | auto | implement | implement | implement |
Result Processing and Redirection | auto | auto | implement | auto | implement, indicator provided | implement, indicator provided | implement, indicator provided |
Internationalisation | auto | auto | auto | auto | implement, texts provided | implement, texts provided | implement, texts provided |
Styling | default CSS, override recommended | style of shop, CSS override for partial iFrames recommended | style of shop, CSS override for partial iFrames recommended | style of shop, example CSS provided | style of shop | style of shop | style of shop |
1. You initialize the payment from your backend (LIST Request). Set the parameter style.hostedVersion
to v3
to get the most recent version of the hosted payment page (v3). It features a full responsive design and high-resolution payment-method logos. The legacy version (v2) can be accessed by setting style.client
to "RESPONSIVE"
instead. In the right pane there is an example LIST response demonstrating the location of the URL of a hosted payment page.
2. Based on the information given, OPG generates a web page with all the payment methods that can be used to pay this particular transaction. As a reply, your system gets a URL to this generated page, which it typically displays is a large iFrame.
3. Once the customer finishes their interaction within the iFrame and submits the form, the payment data is sent from within the iFrame directly to the OPG. The response will go back to the iFrame, and as a result the iFrame will redirect its parent frame to the merchant's returnUrl (or cancelUrl in rare error cases) with all available status information attached as query parameters to the URL. The right pane shows an example redirect after success.
{
"links": {
"self": "https://api.oscato.com/api/lists/e909b8f7-97fd-463e-9069-313632a9b8cc"
},
"resultInfo": "6 applicable and 0 registered networks are found",
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"redirect": {
"url": "https://resources.sandbox.oscato.com/paymentpage/v3/responsive.html?listId=5e96c3abc1235e11b14543d2lrc25jnqki363v53qmn9747gdf&liveValidation=true&smartSwitch=true",
"method": "GET",
"suppressIFrame": false
}
...
}
https://dev.oscato.com/shop/success.html ?
listUrl=https%3A%2F%2Fapi.sandbox.oscato.com%2Fpci%2Fv1%2F5e96c3e541f95e11b14543d2abc123nqki363v53qmn9747gdf &
shortId=0ab85-63289 &
customerRegistrationId=5e7c86b81dab523005cf2939u &
interactionReason=OK &
resultCode=00000.TESTPSP.000 &
longId=5e96c3faabc1232f3a244fe2c &
transactionId=tr101 &
interactionCode=PROCEED &
amount=10.99 &
reference=Test &
currency=EUR
Selective Native can be implemented using our AJAX Library, or as a native solution. In both cases, the logic behind the implementation is the same; the only difference is that native integration needs the development of your own JavaScript code, while AJAX integration uses a library provided by optile.
Here's an overview of how our AJAX library communicates with the OPG and especially the Selective Native iFrames. You can use the session "AJAX library Integration" as a reference to our AJAX library.
optile's AJAX Integration Library enables seamless integration of the OPG payment checkout into your own websites, with minimum effort.
Essentially, the JavaScript library takes over a major part of the communication with optile's OPG and creates the actual user interface for checking out. You can read about the general communication flow with OPG in our Getting Started Overview. In the AJAX integration scenario, you only need the initial LIST Request implemented in your server system, and a Notification Listener.
A Selective Native with AJAX integration requires only PCI DSS SAQ A compliance. Download the PCI DSS SAQ A requirements. You must sign the self-assessment questionnaire (SAQ) to confirm that you are compliant. This signed SAQ is also your "Attestation of Compliance (AoC)", which may be requested at any time by your acquiring bank.
Selective Native gives a level of security for credit and debit cards in the payment page that means you need only be PCI A compliant, as opposed to PCI A-EP which requires more effort. Compared to the Hosted scenario, Selective Native integration gives more flexibility over the look and feel of the page -- so you can align it more easily to your corporate design and optimal conversion rates.
Selective Native can be implemented natively on your side, or combined with our own AJAX Library. So if you are using our AJAX library, no extra implementation effort is needed; simply change the integration
parameter in the LIST Request to SELECTIVE_NATIVE
. For Hosted Integration Selective Native is not necessary, because the whole hosted page already comes from a PCI-certified optile domain.
Selective Native replaces those parts on the payment page with iFrames, where necessary. This means that all credit and debit card forms will be in small iFrames that come from a fully PCI-certified optile domain. The card data entered by a user cannot be read from the surrounding page. That said, all other parts of the page are yours, with the corresponding visual style. Even the small iFrame forms can be styled by providing a CSS file.
This is how the form inside the iFrame would be embedded into the payment page:
Here we've shown you how to handle the Selective Native iFrames at a high level. Use our AJAX Library Integration as a reference if you want to implement this part on your own.
Communication Flow
Here is an overview of the client-server and inter-frame communication messages when you use our AJAX library:
You initialize the payment from your backend (LIST Request).
Note: This approach uses client-side payment page generation. But if you implement this yourself, you could also generate it on the server-side. For more information on how to build this scenario, go to AJAX Integration.
Here is how communication works in this scenario :
You initialize the payment from your backend (LIST Request).
For more information on how to build this scenario, go to AJAX Integration.
Selective Native can be implemented as a native solution or by using our AJAX Library. In both cases, the logic behind the implementation is the same; the only difference is that a native integration requires the development of your own JavaScript code, while an AJAX integration uses a library provided by optile.
For an overview of Selective Native, go to Selective Native with AJAX.
For a detailed explanation of how to implement Selective Native, go to Selective Native Integration.
For users without web browser rendering (eg native mobile apps) there is Half-Native Integration a variation of Display Native integration.
Display Native Integration
Redirect Networks such as, for example, Sofortüberweisung do not require payment account details in advance. The corresponding form and the subsequent CHARGE request will be empty in this case. Instead, the redirect URL will point to the external page provided by that network (eg the PayPal login screen). After this step, the customer will be redirected back to your system. For you this is transparent, you do not need to do anything about those networks in your implementation explicitly (see also the details on the CHARGE Request). In this scenario the Status Notification is important because the CHARGE reply goes direct to the client and not to the Merchant's server system.
Read more about how to analyze the LIST Response.
Localized FormsTo implement optile's localized forms snippets returned from the original List response, please see Localized Forms section.
{ "transactionId": "tr_1001", "integration": "MOBILE_NATIVE", "channel": "MOBILE_ORDER" "country": "DE", "customer": { "number": "1", "email": "john.doe@optile.net" }, "payment": { "amount": 0.99, "currency": "EUR", "reference": "Shop optile/04-03-2020" }, "style": { "language": "en_US" }, "callback": { "appId": "net.optile.example.sdk", "notificationUrl": "https://dev.oscato.com/shop/notify.html" } }
returnUrl
, summaryUrl
and cancelUrl
. Instead, you need the callback.appId
in the LIST request. It enables a successful return from a provider's website (opened in an external browser) after a successful or a failed payment attempt in a 3rd party website (redirect networks). This applies also for credit/debit card payments which mandate a 3DS authentication which is done in an external website."callback": { "appId": "net.optile.example.sdk", "notificationUrl": "https://dev.oscato.com/shop/notify.html" }
Pure Native Integration
Native integration means you build the payment checkout page yourself, based on the information provided by the LIST response, on your server side. It gives you full control and seamless integration into your pages.
In native scenarios, you can send the payment data submitted by your customers, to your own servers first, instead of having it sent directly to OPG. This depends on whether you want to see sensitive payment data and if you are permitted to do so by your PCI DSS certification.
Pure Native integration lets you pass sensitive data through any of your servers that process cardholder data. In this scenario the merchant must have PCI SAQ D certification.
These requirements must be checked, signed, and submitted for revision if necessary.
For the LIST Request and payment-page rendering, this scenario is like the Display Native scenario except that here, the sensitive payment-account details go from the payment checkout page to your own servers first, where they can be stored or analyzed by your custom logic. From there, the CHARGE Request is submitted to OPG.
Note that under credit-card industry regulations, you are only allowed to process credit-card details on your servers if you have the appropriate PCI DSS certification. This covers not only storage of data but also in-memory processing.
Read more about how to analyze the LIST Response.
Localized FormsIf you wish to implement optile's localized forms snippets returned from the original List response, go to Localized Forms section.
optile will provide you with a public key for encryption in advance. You should store this key because, later on or during the process, the software-client of the user (eg web page in browser, but possibly also mobile clients) will use it.
The payment process is initialized with the LIST Request. You can build the payment page server-side or client-side, just like the other integration scenarios. But, optile's AJAX library is not ideal for building the client-side payment page for client-side encryption, since it submits payment data direct to the OPG instead of your custom endpoint. If you want to use it anyway, you can request a development version of the library and then adjust it to your needs.
For this flow to comply with PCI A-EP level compliance, the payment-account data (eg card data, SEPA IBAN etc) entered by the customer must be encrypted by the client so that only optile can decrypt it. To make that happen, this flow uses the public key referred to above, and the algorithms and data structures described below. Next, you have a choice: you can use the timestamp of encryption and/or the ID of the LIST session for encryption, to assure that the card data is being used for this specific payment session.
In a web context, optile will provide a JavaScript library providing a function that handles the encryption for you. Your client can then send the encrypted data anywhere to your systems. As your server systems will not be able to decrypt the data without access to optile's private key, this flow requires only PCI A-EP certification instead of PCI D. Your system will then send the encrypted account-data to the OPG via the CHARGE Request (in the context of the corresponding LIST session).
In this scenario, there is a simultaneous response to your server systems, with all relevant information. The OPG will also send the Status Notification that your server systems would need in other integration scenarios, but it is less significant here because we are already using reliable server-to-server communication. In a web context, your systems then typically respond to the client with an HTTP redirect, depending on the transaction result.
Here is a view of the process:
Note that under credit-card industry regulations, you must ensure that you have the appropriate PCI DSS certification for processing credit-card details on your servers. This covers not only storage of data, but also in-memory processing. Owing to the nature of the Client-Side Encryption, PCI A-EQ level certification is required.
Read more about how to analyse the LIST Response.
Localized FormsTo implement optile's localized forms snippets returned from the original List response, go to Localized Forms.
The Half-Native integration is typically used for devices or applications that do not rely on web-browser rendering, such as native smartphone or smart TV apps. Here, the data from the LIST Response cannot be integrated direct, because the input forms for the payment methods are described in HTML.
As a consequence there could be a translation client-side from the HTML elements into native UI interaction elements. This would preserve the 'implement once' principle but needs a certain budget. Edge cases that need some extra JavaScript logic, such as interactive forms for installment plan selection, are not straight-forward to translate.
The idea of Half-Native therefore is that you, as a merchant developer, prepare the user-interface snippets that correspond to the HTML form snippets provided by the LIST Response in advance. In other words, you prepare all relevant input forms that may occur beforehand natively, eg in terms of the iOS or Android platform.
After your system issued a LIST Request the client will evaluate the response of the client. Then it only shows those natively prepared input forms that were selected by the Payment Gateway for this payment session.
When your customer enters the necessary payment data, the client typically submits it direct to the Payment Gateway via a CHARGE Request -- in the same way as for the Display Native Scenario. An immediate response and a Status Notification will be sent to your server system.
A corresponding communication sequence looks like this:
Alternatively, the payment session could be initialized as Pure Native and the CHARGE Request issued to your server systems first. But this would affect your required PCI certification.
Another use-case for Half-Native integration is to split the method selection from the actual entering of the account data by the user, ie divide the common payment page into two separate steps.
Here, you could add some simple logic to show the radio buttons and logos on the first part of the payment page, with the second part of the payment page showing the logo and forms of a previously selected payment method with or without the possibility to still switch to a different payment method.
To support this, OPG offers an easy way to store the user selection from the first part in the LIST Session object. This is especially relevant if you want to render the second part as a Hosted Page because it will respect the selected method and show it as preselected (among the other methods).
Every method offered in a LIST Session has the attribute selected
and is also a resource with a separate endpoint. Its URL is given as the links.self
attribute like this:
{
...,
"code": "VISA",
"selected": false,
"links": {
"self": "https://api.sandbox.oscato.com/api/lists/57a06162e4b0a803efa9e1d1l/VISA",
...
}
...
If the user selects one method, your system can issue a PUT or PATCH request to that resource to update its selected
attribute like this:
Example request
https://api.sandbox.oscato.com/api/lists/57a06162e4b0a803efa9e1d1l/VISA
PUT
The selected
status of all other methods in that LIST will become false
as a result. So note that other attributes cannot be changed.
{
"selected": true
}
When executing a LIST request (any variation, POST, GET or PUT), the requested URL can contain the parameter view, with options to enable or disable form representation methods.
For example, a LIST request (POST) could look like:
https://api.sandbox.oscato.com/api/lists?view=jsonForms,-htmlForms
With regard to the query parameters, please note:
jsonForms
is present, the JSON forms are enabled.-htmlForms
is present, the HTML forms are not shown. That means: The attributes links.form
and links.localizedForm
are not present.view=jsonForms,-jsonForms
or view=abc
then the request will fail as invalid.view
it will be ignored.
If JSON forms are enabled (view=jsonForms
), the LIST response object will contain a form representation in JSON format. This means:
localizedInputElements
(not inside the links structure, but outside on the same level)name
(mandatory): the name of the parameter represented by this input field (as used inside an account object in a consecutive CHARGE request)label
(mandatory): the localized human readable label that should be displayed with the input fieldtype
(optional): represents the input type / restrictions that can be enforced by the client. These types are allowed:
string
(default): one line of text without special restrictions. For example: holder namenumeric
: numbers from 0 to 9, space delimiters and dash ("-") are allowed. For example: card numberinteger
: only numbers from 0 to 9. For example: CVCcheckbox
: values "true", "false", or undefined (non-existent).select
: a list of possible values is given in an additional options attribute (see below). For example: expiration date.options
(optional, present for "select" types): a range of objects with attributes:
value
(mandatory)label
(optional. If not present, value should be displayed to the user.)
...,
{
"code": "ONLINE_BANKING_CZ",
"label": "Online Banking for Czech Republic",
"method": "ONLINE_BANK_TRANSFER",
"grouping": "ONLINE_BANK_TRANSFER",
"registration": "NONE",
"recurrence": "NONE",
"redirect": true,
"links": {
"logo": "https://resources.sandbox.oscato.com/resource/network/DEMO_SK/cs_CZ/ONLINE_BANKING_CZ/logo.png",
"self": "https://api.sandbox.oscato.com/pci/v1/5984364ccb425d1b77fc727dlijdpkm1bfr5malib51eacl3f9/ONLINE_BANKING_CZ",
"lang": "https://resources.sandbox.oscato.com/resource/lang/DEMO_SK/cs_CZ/ONLINE_BANKING_CZ.properties",
"operation": "https://api.sandbox.oscato.com/pci/v1/5984364ccb425d1b77fc727dlijdpkm1bfr5malib51eacl3f9/ONLINE_BANKING_CZ/charge",
"validation": "https://api.sandbox.oscato.com/pci/v1/5984364ccb425d1b77fc727dlijdpkm1bfr5malib51eacl3f9/DEMO_SK/cs_CZ/ONLINE_BANKING_CZ/standard/validate"
},
"localizedInputElements": [
{
"name": "bic",
"label": "Banka",
"type": "select",
"options": [
{"value": "GIBACZPXXXX", "label": "?eská spo?itelna, a.s."},
{"value": "FIOBCZPPXXX", "label": "Fio banka, a.s."},
{"value": "KOMBCZPPXXX", "label": "Komercni banka, a.s."},
{"value": "BREXCZPPXXX", "label": "mBank S.A., organiza?ní složka"},
{"value": "RZBCCZPPXXX", "label": "Raiffeisenbank, a.s."},
{"value": "ZUNOCZPPXXX", "label": "Zuno Bank AG"},
]
}
],
"button": "button.charge.label",
"selected": false
},
...
The Interactive Checkout Flow Diagram
Especially for developers we created also a systematic view of all combinations of optile's checkout API flows. Switch between combinations in the selection box at top center. The diagram represents the relationship between a (typical) user journey and the corresponding API requests (with limited details).
Click here to open the Interactive Checkout Flow diagram in a new tab. Loading can take a few seconds, so please be patient...
Regular Checkout
optile supports two main types of a regular checkout:
A valid LIST Request creates a LIST object. It responds with the applicable payment methods (networks) for this checkout and can also be seen as a kind of payment session of the user in which he or she can do multiple CHARGE attempts.
A typical LIST Request looks like this:
https://api.sandbox.oscato.com/api/lists
POST
Explanations for some parameters in the request body:
transactionId
is the alphanumeric identifier for this transaction, assigned by you as a merchant.country
is the "transaction country". It specifies which country-specifc OPG configuration and therefore payment method selection should be used. Typically it is the location of the current shop but it may also be the origin of the customer. Country codes according to ISO 3166-1 alpha-2 are used.integration
defines which integration scenario the payment session is initialized for, see also below. Valid values are: HOSTED
, SELECTIVE_NATIVE
, DISPLAY_NATIVE
(default) and PURE_NATIVE
.customer.number
is the alphanumeric identifier of your customer, assigned by you as a merchant.payment.reference
is the short text that customers will see on their account statement.style.hostedVersion
with v3
will enable new version of payment page (for HOSTED
integration scenarios) designed to support 3DS2 payment flows.style.resolution
when set to 3x
will return logos with 96 px of height for devices with higher PPI. Other possible values are 1x
(32px) and 2x
(64px).callback.returnUrl
is the URL the customer should be redirected to, after the payment was successful, typically on the merchant's site.callback.cancelUrl
is where the customer should be redirected to if he or she cancels on a redirect page (such as PayPal), or a payment attempt was denied and is not recommended to retry (Interaction Code ABORT
).The full data model with all possible parameters can be found in our API Reference.
Read about the LIST Response if you want to render the payment page yourself.
customer
number
and email
, even if you have to use dummy values, because many popular providers require this.
{
"transactionId": "tr101",
"country": "DE",
"integration": "HOSTED",
"customer": {
"number": "42",
"email": "john.doe@example.com"
},
"payment": {
"amount": 19.99,
"currency": "EUR",
"reference": "Shop 101/20-03-2017"
},
"style": {
"hostedVersion": "v3",
"resolution": "3x"
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html"
} }
You can specify the integration scenario in the LIST Request, using the parameter integration
. The selected scenario will impact the operation URLs in the response object. If no integration parameter is specified, the LIST Request will default to Display Native.
These are valid parameters:
HOSTED
(Hosted)SELECTIVE_NATIVE
(Selective Native)DISPLAY_NATIVE
(Display Native, default. Legacy value: NATIVE_WITHOUT_PCI)PURE_NATIVE
(Pure Native. Legacy value: NATIVE_WITH_PCI)https://api.sandbox.oscato.com/api/lists
POST
After a successful CHARGE is executed on a LIST object, the LIST will no longer be valid. This means that any other CHARGE on it will be declined, with the Interaction Code ABORT
and Reason DUPLICATE_OPERATION
. This is to protect customers and merchants from accidentally executing duplicate payments.
To provide a good customer experience, however, you should detect that case (via Interaction Codes passed as GET
query parameters) on the page the customer is redirected to (indicated by the cancelUrl
) and inform the customer that the payment was successful and that only the repetition was declined. Otherwise the customer may think the payment failed.
{
"transactionId": "tr101",
"country": "DE",
"integration": "HOSTED",
"customer": {
"number": "42",
"email": "john.doe@example.com"
},
"payment": {
"amount": 19.99,
"currency": "EUR",
"reference": "Shop 101/20-03-2017"
},
"style": {
"hostedVersion": "v3"
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html"
}
}
With the preselection
sub-parameters, you can dynamically influence the LIST Response as follows:
deferral
: enables Deferred Payments or Payoutsnetworks
: Filters the response to:
REGISTERED
: only accounts that were previously registered for the current customerAPPLICABLE
: only networks that are to be entered by the customer, not registered accounts.ANY
(default): Both kindsdirection
: Specifies the kind of transaction, namely:
CHARGE
(default): A payment transaction with a subsequent CHARGE RequestPAYOUT
: A Payout to the customer
{
...,
"country": "DE",
"preselection": {
"deferral": "DEFERRED",
"networks": "REGISTERED",
"direction": "PAYOUT"
},
"customer": {
...
A valid LIST can be seen as a payment session of the user. To enable payment conversion tracking through optile, and to benefit from dynamic reactions of the payment page, we advise that you do not issue LIST Requests "blindly" but use existing LIST Objects if possible.
The object is a resource hosted by OPG. It is identified by the LIST's longId
and can be requested any time again via HTTP GET
like this:
https://api.sandbox.oscato.com/api/lists/[longId of LIST]
A LIST object remains valid for executing charges until either:
After that, the LIST object will become invalid for security reasons and no further CHARGE will be possible. In Display Native integrations, a GET on an expired LIST resource will return an HTTP 404 (not found) error. In Pure Native integrations, the object can be retrieved but its Status Code will be expired
.
What this means for your implementation:
Use an existing LIST resource while it is still valid. In cases where your customer reloads the payment page, do not issue a new LIST Request, re-use the existing one. Only this will enable proper conversion tracking on your payment checkout through optile. You can double-check the validity through a GET
request on the LIST resource (see above) and/or listen to "expired" notifications. Only issue a new LIST Request if it has expired.
For some business cases it may be necessary to change the information provided in the initial LIST Request, for example if customers change the products in their shopping cart after they have been on the payment page but not yet completed the payment. It means that for the same "user session", an update of an existing LIST resource is required. For a new "user session", generate a new LIST, as described above.
The update is performed REST-style by sending an HTTP PUT
to an existing LIST resource. The data structures payment
, products
and customer
can be replaced as a whole. Other mandatory fields still have to be provided but cannot be changed.
https://api.sandbox.oscato.com/api/lists/ce3eec14-e186-4b7c-94a1-fde802412e69
PUT
Note: LIST session updates can only be performed from the server-side system ("backend") and it needs your payment authentication credentials. After performing a LIST update, you may need to reflect resulting changes to the customer client (eg browser). Therefore, the LIST result should be read by the client again by passing it on from your server system, or by retrieving the LIST object direct from the Payment Gateway again, and showing the updated payment page. Also notice that the JSON structure is basically the same for a LIST request or update.
{
"transactionId": "tr101",
"country": "DE",
"customer": {
"number": "42"
},
"payment": {
"amount": 22.22,
"currency": "EUR",
"reference": "Shop 101/20-03-2017"
}
}
A LIST also holds information indicating which of the methods should be checked when the payment page is loaded. In the LIST Response, each object in networks.applicable
and accounts
, representing a payment method ("network"), has a boolean flag selected
. If set to true
this method should be displayed with a checked radio button and expanded form. optile's Hosted payment page and AJAX library respect this value.
You can modify the flag by sending a PUT
request to the method resource. Its URL is given explicitly in the self
attribute within the method object.
Example request for default checking:
https://api.sandbox.oscato.com/api/lists/57bab64ee4b03ec79899332el/VISA
PUT
As a consequence the selected
property of all other methods in the list will become false
. Note: other properties of a method cannot be changed through this API.
The selected
attributes of all methods will be false
by default, and we advise you to preselect the first method, as our AJAX library and Hosted Payment Page do.
{
"selected": true
}
Sometimes you can improve the checkout experience by making more parameters available to the customer. For example, the products list could have more details about the goods ordered; or the customer might need to clearly specifiy their address. Some information may be required for risk management -- some invoice providers may restrict their offers if billing and shipping addresses are different, for example.
At the moment, the OPG accepts a long list of parameters in the LIST request, which you can find in our API Reference in the Server Payment API dropdown.
Products
The given products
will be shown to the customer in some networks, eg PayPal and Invoices.
product.amount
reflects the price with respect to quantity
. Therefore a quantity
of 2 and amount
of 25.00 means that a single product cost 12.50.
quantity
defaults to 1.
The parameter clientInfo
is optional, as is every sub-attribute within. But, if you provide any part of the object but not userAgent
or acceptHeader
, the following defaults will be used for these attributes if empty:
userAgent
: Mozilla/5.0acceptHeader
: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8These are widely accepted values. But just in case a PSP uses them for additional logic like risk management, we recommend that you set these attributes to the real values from the customer's original HTTP request.
Customer
While customer.number
is the only required parameter, you should also consider customer.email
. This enables basic identification that some providers use for risk management.
Phone numbers can be passed either by providing all three fields, countryCode
, areaCode
and subscriberNumber
, or by providing a single unstructuredNumber
.
You can provide an additional name
structure in the billing section, but the general name defined as child of customer
should be enough in most cases.
Addresses are specified better by the parameter addresses
, which can cover shipping, billing, residential or other relevant address.
One interesting use case is where a customer wants different products to be delivered to different addresses. A merchant could assign certain products to be delivered to the customer's home, some to be delivered to the customer's office, and some even to be collected at a physical store. This scenario can be achieved by several parameters:
id
for every customer addressadditionalAddresses
to specify several addresses with the same type, for example home and office shippingmerchantAddress
(Boolean) to link one of the addresses as the physical store of the merchantshippingAddressId
to link each product item with its address
{
"transactionId": "tr101",
"country": "DE",
"integration": "PURE_NATIVE",
"payment": {
"amount": 34.90,
"currency": "EUR",
"reference": "Shop 101/20-03-13"
},
"products": [{
"code": "B003X6UEXQ",
"name": "Tron Legacy BluRay",
"quantity": 2,
"amount": 25.00,
"shippingAddressId": "addr-id-001"
},{
"code": "C003X6U523",
"name": "The Lawnmower Man DVD",
"quantity": 3,
"amount": 9.90,
"shippingAddressId": "addr-id-002"
}],
"clientInfo": {
"ip": "00.00.111.2",
"ipv6": "0000:0000:0011:0011:1122:1122:0101:0101",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/54.0",
"acceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8"
},
"customerScore": 250,
"customer": {
"number": "002345",
"email": "john.doe@example.com",
"birthday": "2001-12-31T00:00:00.000Z",
"name": {
"title": "Prof.",
"firstName": "John",
"lastName": "Doe"
},
"addresses": {
"billing": {
"street": "Downing Street",
"houseNumber": "10",
"zip": "80333",
"city": "München",
"state": "Bayern",
"country": "DE"
},
"shipping": {
"name": {
"firstName": "John",
"lastName": "Doe"
},
"street": "Baker Street",
"houseNumber": "221b",
"zip": "80333",
"city": "München",
"country": "DE",
"id": "addr-id-001"
},
"additionalAddresses": [
{
"street": "Merchantstr.",
"houseNumber": "1000",
"zip": "80333",
"city": "Munich",
"state": "Bayern",
"country": "DE",
"companyName": "Merchant Ltd",
"id": "addr-id-002"
}
]
},
"phones": {
"company": {"unstructuredNumber": "(0)89 1060 120-00"},
"home": {"unstructuredNumber": "089 5060 120-55"},
"mobile": {"unstructuredNumber": "0177 44004400"}
}
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl":"https://dev.oscato.com/web/acb123"
}
}
Payment Account Validation
As soon as the customer submits their payment account data to the gateway (PRESET call), it can be validated for accuracy of format and also for existence of the account, availability of funds, and other risk checks. This means that if something goes wrong, the shop can give immediate feedback to the customer and offer alternative payment options to minimize user dropout.
This is configurable through the backend, so please contact us. The options are:
LIST Request
As shown in the delayed payment submission diagram, the first step is to submit a LIST Request. For Delayed Payment Submission, an extra LIST parameter is needed: the optional Boolean value presetFirst
. If this is set to true
optile OPG will know that the payment submission should be preset, not charged direct. A summaryUrl
must also be provided in the callback object if presetFirst
is set to true
. This way optile OPG can direct the customer to the desired summary page after the PRESET.
Delayed Payment Submission works with all Integration scenarios. After the LIST response is returned, follow the standard approach for rendering the payment methods, for example Native Integration, Selective Native, or it is rendered by the AJAX library.
{
"transactionId": "tr101",
"integration": "SELECTIVE_NATIVE",
"country": "DE",
"presetFirst": true,
"customer": {
"number": "42",
"email": "john.doe@example.com"
},
"payment": {
"amount": 19.99,
"currency": "EUR",
"reference": "Shop 101/20-03-2016"
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"summaryUrl": "https://dev.oscato.com/shop/success.html",
"notificationUrl": "https://dev.oscato.com/shop/notify.html"
}
}
PRESET Request
When the customer enters their details on the payment page and makes a submission, there should be a PRESET request (to the operation
URL) using the customer's data.
In all integration scenarios except Pure Native, this will go direct to the OPG from the customer's client. It should contain the account
data structure similar to the CHARGE request.
The AJAX library will handle this correctly on button click.
For a Selective Native integration without the AJAX library, the inter-frame communication must be triggered as described there, and an operation_response_event
will be sent to reflect the server response.
{
"account": {
"holderName": "test",
"number": "4111111111111111",
"expiryMonth": "12",
"verificationCode": "333",
"expiryYear": "18"
}
}
The response to a successful PRESET call will show that the customer should be redirected to the summary page URL that was given in the corresponding LIST request. Again, the AJAX library will hande this. When implementing a Hosted or AJAX-dependent integration scenario, you might also want to receive, in the PRESET response, a full masked account data to show your customer which payment card has been used. In these cases, a simple GET on the LIST session can be done (GET on its self link); it will respond to the current session status and the preset account.
The operation
URL of the returned object is where the subsequent CHARGE should be going to (see below).
Since the PRESET step also performs the configured validations if available (see above), the transaction could also be declined at this stage. A provider may, for example, decline to offer Invoice payments for this customer. This will be indicated by the Interaction Codes.
In most cases, the customer should then be returned to the payment page so that the existing LIST resource is reloaded and the remaining payment options presented again. In this example, Invoice may have been removed from the list, but the customer can still select another more secure method.
{
"links": {
"operation": "https://api.sandbox.oscato.net/api/lists/abc123abc123abc123/charge"
},
"resultInfo": "Network and account are preset",
"status": {
"code": "preset",
"reason": "preset"
},
"redirect": {
"url": "https://dev.oscato.com/shop/success.html",
"method": "GET",
"parameters": [
{"name": "shortId","value": "06598-63085"},
{"name": "interactionReason","value": "OK"},
{"name": "resultCode","value": "00000.11.000"},
{"name": "longId","value": "58454da9e4b0a473f4675a96c"},
{"name": "transactionId","value": "tr10687"},
{"name": "interactionCode","value": "PROCEED"}
],
},
"network": "VISA",
"maskedAccount": {
"displayLabel": "41 *** 1111 12 | 18",
"holderName": "test",
"number": "41 *** 1111",
"expiryMonth": 12,
"expiryYear": 18
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"resultCode": "00000.11.000",
"returnCode": {
"name": "OK",
"source": "GATEWAY"
},
"identification": {
"longId": "abcd1234abc123",
"shortId": "06598-63085",
"transactionId": "tr101"
},
"timestamp": "2016-12-05T11:21:13.089+0000"
}
CHARGE Request
At this stage of the process, the customer is on the summary page generated by your shop system. Typically, this page would ask the customer to confirm the payment based on the summary presented. If the customer agrees, your server-side system should then make a CHARGE request using the operation
URL returned in the PRESET response.
It looks like this: https://api.sandbox.oscato.com/api/lists/{LIST-longId}/charge
As the data has already been preset, no account information needs to be provided in the CHARGE Request body. In fact, only an empty JSON body {}
is provided as the request. This is also the reason why the CHARGE can go through your server-side system. There is no sensitive payment-data present here.
After the CHARGE call, your system should redirect the customer to the URL given in the response as redirect.url
. In the success case, this will be the original success URL as specified in the LIST request (the thank-you page).
{
"links": {
"payout": "https://api.sandbox.oscato.net/api/charges/58454db9e4b0a473f4675a97c/payout",
"self": "https://api.sandbox..oscato.net/api/charges/58454db9e4b0a473f4675a97c"
},
"timestamp": "2016-12-05T11:21:29.729+0000",
"operation": "CHARGE",
"resultCode": "00000.TESTPSP.000",
"resultInfo": "Approved",
"pspCode": "TESTPSP",
"returnCode": {
"name": "OK",
"source": "PSP"
},
"status": {
"code": "charged",
"reason": "debited"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"clearing": {
"amount": 19.99,
"currency": "EUR"
},
"identification": {
"longId": "58454db9e4b0a473f4675a97c",
"shortId": "15043-34925",
"transactionId": "tr10687",
"pspId": "VISA.DEBIT.1480889314499"
},
"redirect": {
"url": "https://dev.oscato.com/shop/success.html",
"method": "GET",
.....
There are 2 kinds of registration: Regular and Recurring.
Regular Account Registration
If customers return to your shop, they will see their previously registered payment accounts and do not need to enter their information again. This can increase conversion, especially with credit and debit cards or SEPA direct-debit bank accounts. For credit cards, the verification code (CVV, CVC, etc.) must still be entered by the customer because PCI DSS regulations prohibit the storage of this data. (If the payment provider in the background accepts this, as most acquirer interfaces do, this can also be omitted by using the Dynamic Verification Code feature).
The Dynamic Verification Code feature allows for hiding the CVC field for registered cards on our payment page. In this way, you can submit a CIT payment without entering the CVC. Note that a payment provider needs to support this functionality before it can work on our end. Additionally, you need to use our payment page (so native integrations can't use the feature).
In the 3DS 2 flow, the first payment will always require the CVC code.
Recurring Registration
This kind of registration can be used to trigger Recurring Charges from your backend without customer interaction. This feature must be supported by the Payment Service Provider but does not require the verification code for any subsequent charges.
Payment page with a registered account (regular)
Customer Agreement on Payment Page
You can configure both types of registration. Since they need the customer's agreement, you should provide checkboxes in the payment page. If registration was enabled on optile's side, you simply start a payment session with a regular LIST Request. In AJAX and Hosted integration scenarios, the payment page will then be rendered automatically and the CHARGE Request submitted correctly, so you won't need the following information.
If your system evaluates the LIST Response itself, there will be 2 attributes for each network: registration
and recurrence
, which correspond to the 2 registration types.
Depending on the configuration, these attributes can have the following values:
NONE
No checkbox should be displayed, the network is not registeredOPTIONAL
Checkbox should be displayed and can be checked by the userOPTIONAL_PRESELECTED
Same as OPTIONAL
, but checkbox should be pre-checkedFORCED
No checkbox should be displayed, but the account is registered anywayFORCED_DISPLAYED
Same as FORCED
, but a non-clickable and pre-checked checkbox should be displayedCheckbox Rendering
In the OPTIONAL
case, you can include an additional checkbox next to the network form, like this in the HTML source:
<input type="checkbox" id="allowRecurrenceVISA" name="allowRecurrence" value="true"> <label for="allowRecurrenceVISA">I authorize recurring charges on my account</label>
In the FORCED
case, you can include an additional hidden value, as shown below, if you have a Display Native integration and need to pass the parameter from the client direct to OPG:
<input type="hidden" name="autoRegistration" value="true">
Checkboxes should look similar to those shown on the right. Note: a separate checkbox is needed for each payment network, because some networks do not support account registration and/or recurring charges.
...,
{
"method":"CREDIT_CARD",
"registration":"FORCED",
"code":"VISA",
"label":"Visa",
"recurrence":"FORCED_DISPLAYED",
"links":{
"logo":"https://api.sandbox.oscato.com/resource/network/DEMO/de_DE/VISA/logo.gif",
"operation":"https://api.sandbox.oscato.com/api/lists/2885c861-fae4-4fc1-add4-a856c1012e21/VISA/charge",
"form":"https://api.sandbox.oscato.com/resource/form/VISA/standard.html",
"validation":"https://api.sandbox.oscato.com/api/pci/2885c861-fae4-4fc1-add4-a856c1012e21/DEMO/de_DE/VISA/validate",
"localizedForm":"https://api.sandbox.oscato.com/resource/form/DEMO/de_DE/VISA/standard.html",
"lang":"https://api.sandbox.oscato.com/resource/lang/DEMO/de_DE/VISA.properties"
}
},
...
Payment page with registration checkboxes
Submitting the CHARGE
When the customer submits the payment data, the information indicating which checkboxes have been checked must be passed in the CHARGE Request. This is done through the parameters autoRegistration
and allowRecurrence
, which can have the values true
or false
(default). See the example.
Again, AJAX and Hosted integration scenarios already do this for you.
{
"account": {
"holderName": "Walter Smith",
"number": "4111111111111111",
"verificationCode": "123",
"expiryMonth": "2",
"expiryYear": "2017"
},
"autoRegistration": true,
"allowRecurrence": true
}
Receiving Registration Credentials
A CHARGE request like this will process the initial payment as usual. If the customer has not yet been registered, they will be registered at this stage together with the payment account, and the following Status Notification will be sent:
entity
= customer
: This will be sent only once to your system with both attributes, customerRegistrationPassword
and customerRegistrationId
. These values should be stored on your side because they will enable you to use the payment accounts of this customer from within the Secure Storage again later.entity
= account
: This will contain payment network
and accountRegistrationId
which we will see again when manipulating individual registered payment accounts (as opposed to the whole customer registration). But in most cases you should not store this, because you will receive and use these IDs implicitly in the context of the use cases such as Account Update and Registration Page or Setting a Preferred Payment Account.entity
= payment
: This is the normal notification that only contains the customerRegistrationId
which your system will receive for any payment by a registered customer.Using a Regular Account Registration
If you enabled regular account registration (autoRegistration
flag), the next time the customer does a checkout, you can submit the customerRegistrationId
and -password
with the next LIST Request as shown on the right (see customer.registration
).
Using a Recurring Registration
If the initial CHARGE Request enabled a recurring registration (allowRecurrence
flag), you will be able to trigger subsequent charges from the backend (ie without further customer interaction) using the customerRegistrationId
and -password
. Find out more about Recurring Charges.
{
"transactionId": "tr101",
"country": "DE",
"customer": {
"number": "42",
"email": "john.doe@example.com",
"registration": {
"id": "555dd649e4b086337cd4d18dq",
"password": "h5dhgn7gd3bhlkwykxgnhrd2346nm0ew6d"
}
},
"payment": {
"amount": 19.99,
"currency": "EUR",
"reference": "Shop 101/20-03-2015"
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html"
}
}
As a result, you will get back not only networks.applicable
in the LIST response, but also a list of accounts
, which represent previously registered payment accounts. This contains only regular account registrations, not accounts that were (only) registered for recurring.
These payment accounts should be displayed above the selection of payment methods. The response will provide information such as a localizedForm
and displayLabel
to do this in the regular fashion.
{
...
"timestamp": "2015-03-21T12:57:55.762+0000",
"operation": "LIST",
"resultCode": "00000.11.000",
"resultInfo": "5 applicable and 1 registered networks are found",
...
"accounts": [{
"links": {
"operation": "https://api.sandbox.oscato.com/api/lists/555dd651e4b088681fe/accounts/555dd649e4b086337c/charge",
"logo": "https://resources.sandbox.oscato.com/resource/network/ABC123/de_DE/VISA/logo.png",
"form": "https://resources.sandbox.oscato.com/resource/form/VISA/registered.html",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/ABC123/de_DE/VISA/registered.html",
"lang": "https://resources.sandbox.oscato.com/resource/lang/ABC123/de_DE/VISA.properties"
},
"code": "VISA",
"label": "VISA",
"maskedAccount": {
"displayLabel": "41 *** 1111 02 | 2017",
"holderName": "Martin Tester",
"number": "41 *** 1111",
"expiryMonth": 2,
"expiryYear": 2017
}}
],
"networks": {
"applicable": [{
"code": "VISA",
... }]
}
}
updateOnly
, which exists at root level, must be set to true
In the pane on the right, there is a sample LIST Request with updateOnly
specified.
https://api.sandbox.oscato.com/api/lists
or https://api.live.oscato.com/api/lists
POST
{
"transactionId": "tr1068735466d34",
"country": "DE",
"updateOnly": true,
"customer": {
"number": "4278",
"email": "john.doe@example.com",
"registration": {
"id": "584578ebe4b0583916a67e6ee",
"password": "UbHkTMmYhU1byjDe"
}
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"summaryUrl": "https://dev.oscato.com/shop/success.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html"
}
}
Registered Accounts: Only accounts that are allowed to be updated will be returned. If the account cannot be updated, it will not be shown. Where the payment account is updatable, the operationUrl
returned in the LIST Response will point to an "update" instead of "charge" endpoint (for backwards compatibility with the existing integrations), eg "operation": "https://api.sandbox.oscato.com/pci/v1/5832ec78e4b0b0d86234c04bl/accounts/5832ebe41652164c14b86719a/update"
The associated formUrl
/ localizedFormUrl
points to a form snippet with an expiration date and verification code, or there will be equivalent iFrame
links for Selective Native.
Applicable Networks: Only payment networks with standalone registration capability will be shown. Their operationUrl
points to a "register" instead of "charge" endpoint, eg https://api.sandbox.oscato.com/pci/v1/5832ec78e4b0b0d86234c04bl/SEPADD/register
A new attribute operationType
at the top level of the response object enables the client to see that this is an Account Update Request and then handle any desired follow-on action, such as displaying delete links for saved accounts.
The possible values of the operationType
property are:
CHARGE
PRESET
PAYOUT
UPDATE
{
"links": {
"self": "https://api.sandbox.oscato.com/pci/v1/588f0d4d16526507caceb575lbi1tm58fp1vt9asq425f3u9i3",
"customer": "https://api.sandbox.oscato.com/api/customers/5889b7ab1652e1e7030f7ae8u"
},
"timestamp": "2017-01-30T09:54:21.835+0000",
"operation": "LIST",
"resultCode": "00000.11.000",
"resultInfo": "4 applicable and 5 registered networks are found",
"returnCode": {
"name": "OK",
"source": "GATEWAY"
},
"status": {
"code": "listed",
"reason": "listed"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"identification": {
"longId": "588f0d4d16526507caceb575lbi1tm58fp1vt9asq425f3u9i3",
"shortId": "10055-11601",
"transactionId": "tr10687"
},
"accounts": [
{
"links": {
"form": "https://resources.sandbox.oscato.com/resource/form/MASTERCARD/update.html",
"logo": "https://resources.sandbox.oscato.com/resource/network/ABC123/de_DE/MASTERCARD/logo.png",
"self": "https://api.sandbox.oscato.com/pci/v1/588f0d4d16526507caceb575lbi1tm58fp1vt9asq425f3u9i3/accounts/5889ce431652e1e7030f7b45a",
"lang": "https://resources.sandbox.oscato.com/resource/lang/ABC123/de_DE/MASTERCARD.properties",
"operation": "https://api.sandbox.oscato.com/pci/v1/588f0d4d16526507caceb575lbi1tm58fp1vt9asq425f3u9i3/accounts/5889ce431652e1e7030f7b45a/update",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/ABC123/DE/de_DE/MASTERCARD/update.html",
"validation": "https://api.sandbox.oscato.com/pci/v1/588f0d4d16526507caceb575lbi1tm58fp1vt9asq425f3u9i3/ABC123/de_DE/MASTERCARD/update/validate"
},
"code": "MASTERCARD",
"label": "MasterCard",
"maskedAccount": {
"displayLabel": "51 *** 5100 03 | 2019",
"holderName": "xcvbxcv",
"number": "51 *** 5100",
"expiryMonth": 3,
"expiryYear": 2019
},
"lastSuccessfulChargeAt": "2017-01-26T11:59:59.135+0000",
"selected": false
}
.....
]
"networks": {
"applicable": [{
"code": "PAYPAL",
"label": "PayPal",
"method": "WALLET",
"grouping": "WALLET",
"registration": "OPTIONAL",
"recurrence": "NONE",
"redirect": true,
"links": {
"form": "https://resources.sandbox.oscato.com/resource/form/PAYPAL/standard.html",
"logo": "https://resources.sandbox.oscato.com/resource/network/ABC123/de_DE/PAYPAL/logo.png",
"self": "https://api.sandbox.oscato.com/pci/v1/588f117e16522490c20d0aaflnmhcm4cl9870ebtm1no90tsuv/PAYPAL",
"lang": "https://resources.sandbox.oscato.com/resource/lang/ABC123/de_DE/PAYPAL.properties",
"operation": "https://api.sandbox.oscato.com/pci/v1/588f117e16522490c20d0aaflnmhcm4cl9870ebtm1no90tsuv/PAYPAL/register",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/ABC123/DE/de_DE/PAYPAL/standard.html",
"validation": "https://api.sandbox.oscato.com/pci/v1/588f117e16522490c20d0aaflnmhcm4cl9870ebtm1no90tsuv/ABC123/de_DE/PAYPAL/standard/validate"
},
"button": "button.update.label",
"selected": false
},
....
]
},
"operationType": "UPDATE"
}
accounts
are the saved payment accounts for the customer in question, and networks.applicable
are the payment methods available for this merchant configuration.To demonstrate the Update LIST process, we have split the examples into different documents based on integration type:
In an Update LIST, a customer can also set one of their registered accounts as preferred for future use. See Set preferred payment account.
Native integration requires some extra steps to update a LIST. Below, we show you how to:
Please follow the code examples in the pane on the right.
Step 1. Make the LIST Request passing the parameters updateOnly
= true
and account registration
(id
and password
) object.
{
"transactionId": "tr1068735466d37",
"country": "DE",
"updateOnly": true,
"customer": {
"number": "4278",
"email": "test.Customer@optile.net",
"registration": {
"id": "584578ebe4b0583916a67e6eu",
"password": "UbHkTMmYhU1byjD5"
}
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"summaryUrl": "https://dev.oscato.com/shop/success.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html"
}
}
Step 2. Read the operation links from the applicable networks section of the response.
The operation link is the location of the POST Request that will register the new method. For convenience, a link to a localised form snippet will be provided. This snippet can be presented to the customer so they can enter their new details.
"networks": {
"applicable": [{
"code": "MASTERCARD",
"label": "MasterCard",
"method": "CREDIT_CARD",
"grouping": "CREDIT_CARD",
"registration": "OPTIONAL",
"recurrence": "NONE",
"redirect": false,
"links": {
...
"operation":"https://api.sandbox.oscato.com/pci/v1/58469340e4bae1b32d2fd83fl1r6ee4de1c0srn7loiieafh2e/MASTERCARD/register",
"localizedForm":"https://resources.sandbox.oscato.com/resource/form/ABC123/DE/de_DE/MASTERCARD/update.html",
...
Step 3. After the customer enters the details of the new card, make a POST Request to the operation URL above and register a new MasterCard to the customer based on the new details provided.
{
"account": {
"holderName": "test name",
"number": "5555555555554444",
"expiryMonth": "12",
"verificationCode": "333",
"expiryYear": "18"
}
}
After the request, a response will be shown that is similar to the one below.
{
"resultInfo": "Approved",
"interaction": {
"code": "RELOAD",
"reason": "UPDATED"
}
}
If another LIST Request is repeated with the same body as in Step 1, the newly registered card will be visible in the accounts and can be presented to the customer at the frontend. Hosted and AJAX integrations do this automatically.
....
"resultInfo": "3 applicable and 4 registered networks are found",
....
"accounts": [
{
"links": {
....
},
"code": "MASTERCARD",
"label": "MasterCard",
"maskedAccount": {
"displayLabel": "55 *** 4444 12 | 18",
"holderName": "test",
"number": "55 *** 4444",
"expiryMonth": 12,
"expiryYear": 18
},
....
3D secure
In its standard setting, a customerScore
value below 300 enforces a 3D secure check, a value above 700 disables it, and values in between let the provider decide in accordance with agreed defaults.
CVV/CVC (Dynamic Verification)
For registered credit card accounts, a customerScore
value above 500 will skip CVV/CVC check if the PSP supports it. Case supported, the LIST response will return an empty form snippet for the account, which means that you can charge the customer without requesting CVV/CVC input. This also means that if a registered form snippet is present in the LIST response, you must implement it to collect CVV/CVC input from your customer before charge. Please notice that the OPG will decide for you if a verification code is needed for the currently available network. This dynamic verification logic is rather complex and depends on factors such as routing, network support, and CVC used in previous registration.
In practice, if you follow the implement once principle and always integrate the form snippet as delivered by the OPG, the verification code collection will be completely automated without need for extra logic on your side. The form snippets will always define what customer data is needed when sending a CHARGE request.
"accounts": [{
"links": {
"form": "https://resources.sandbox.oscato.com/resource/form/EMPTY/empty.html",
"logo": "https://resources.sandbox.oscato.com/resource/network/DEMO_HOMERO/en_GB/MASTERCARD/logo2x.png",
"self": "https://api.sandbox.oscato.com/api/lists/5c1cfc3b148b5135a85db1fclttvtsjiauh4sl3ubggidr1sd1/accounts/5c0fc7021968ee2a8574d8dda",
"lang": "https://resources.sandbox.oscato.com/resource/lang/DEMO_HOMERO/en_GB/MASTERCARD.properties",
"operation": "https://api.sandbox.oscato.com/api/lists/5c1cfc3b148b5135a85db1fclttvtsjiauh4sl3ubggidr1sd1/accounts/5c0fc7021968ee2a8574d8dda/charge",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/EMPTY/empty.html"
},
"code": "MASTERCARD",
"label": "Mastercard",
"maskedAccount": {
"displayLabel": "55 *** 4444 07 | 2019",
"holderName": "Tom James",
"number": "55 *** 4444",
"expiryMonth": 7,
"expiryYear": 2019
},
"lastSuccessfulChargeAt": "2018-12-21T12:42:36.946+0000",
"selected": false,
"createdAt": "2018-12-11T14:17:39.022+0000",
"emptyForm": true
}]
Integration
To let your customers set their preferred account, your system must initialize an Update LIST with the parameter updateOnly: true
. As in any other LIST Request, the request body can determine if recurring payment account registrations should be returned by setting the parameter channel
to RECURRING
. Otherwise normal account registrations will be returned.
In any case the LIST Response will show the respective registered accounts, as in the example below.
...
"accounts": [{
"links": {
"iFrame": "https://resources.sandbox.oscato.com/paymentpage/iframe.html?listId=585ab060e4b0f1217e9f4b3alr54bifbfla76esesbesj4k7ot&accountId=585a98bae4b091f43e2a0295a",
"logo": "https://resources.sandbox.oscato.com/resource/network/DEMO_SK/de_DE/VISA/logo.png",
"self": "https://api.sandbox.oscato.com/pci/v1/585ab060e4b0f1217e9f4b3alr54bifbfla76esesbesj4k7ot/accounts/585a98bae4b091f43e2a0295a",
"lang": "https://resources.sandbox.oscato.com/resource/lang/DEMO_SK/de_DE/VISA.properties"
},
"code": "VISA",
"label": "Visa",
"maskedAccount": {
"displayLabel": "40 *** 1112 08 | 2018",
"holderName": "Jane Doe",
"number": "40 *** 1112",
"expiryMonth": 8,
"expiryYear": 2018
},
"selected": false,
"iFrameHeight": 35
},
...
],
...
This list should be visualized for the user in the common way. See LIST Response, Update LIST. To make a payment account the preferred one, your system makes a simple POST request to the setpreferred endpoint of that account. The URL is the self
link as given in the response plus an appended /setpreferred
.
So the request for the example above would be:
https://api.sandbox.oscato.com/pci/v1/585ab060e4b0f1217e9f4b3alr54bifbfla76esesbesj4k7ot/accounts/585a98bae4b091f43e2a0295a/setpreferred
POST
{}
Further details on the behavior of setpreferred
To get all of the customer's registered payment accounts, and enable delete, your system sends a LIST request with the additional "allowDelete": true
parameter, as shown in the pane on the right.
Note:
registration.id
and registration.password
must be sent in the LIST request.allowDelete
parameter can also be used to switch off the possibility of account deletion on the Account Update and Registration Page by setting it to false
. Otherwise account deletion will be available by default.When using our Hosted page or AJAX library, every registered account will render an additional "Delete" button at their side, as shown below. Naturally, you can modify its style via your custom CSS stylesheet.
When a customer clicks the Delete button, the following dialog appears:
The button-label and user-messages are localized, so if you are using the payment page for a language supported by optile, they will show correctly.
LIST Request with allowDelete
{
"transactionId": "tr101",
"country": "DE",
"integration": "HOSTED",
"allowDelete": true,
"customer": {
"number": "42",
"email": "john.doe@example.com",
"registration": {
"id": "5a5cd871abc123409a2b4abcu",
"password": "abc8Yqaabc123C2s"
}
},
"payment": {
"amount": 19.99,
"currency": "EUR",
"reference": "Shop 101/20-03-2016"
},
"style" : {
"hostedVersion":"v3"
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html"
}
}
customerRegistrationPassword
in the LIST requests. This acts as one of the encryption keys of sensitive data. If, for example, there is a standard account registration with one provider (meaning the provider token is stored and used by optile), but the provider experiences downtime, optile will use the customerRegistrationPassword
that was provided by the merchant to access an alternative provider (if configured for the merchant), even if it does not have an account registration yet.customerRegistrationId
only, then existing provider registrations can be used. But to access Secure Storage, decrypt sensitive account data, and do failover routing without existing provider registrations, the customerRegistrationPassword
should be stored safely by your system and always supplied for payment sessions.
The JavaScript library of the AJAX Integration can be used to render the payout page. This results in a flow similar to the payment checkout:
The main difference is that you provide the parameter preselection.direction
with the value PAYOUT
in the LIST Request. See example.
This shows that a LIST for PAYOUT (as opposed to CHARGE) is requested. The effect is that all forms and endpoints for further actions that are returned will be coined towards a payout.
Apart from that, the flow is the same. The subsequent PAYOUT Request is also structured as a CHARGE and will be handled by the JavaScript library, unless you chose a native integration.
...
"country": "DE",
"preselection": {
"direction": "PAYOUT"
},
"customer": {
...
After a LIST is initialized for payout using the parameter preselection.direction
= PAYOUT
, any payment method returned in the list response can be deployed for credit operations. The payout URL can be found in the operation
parameter and it will have this structure:
https://api.sandbox.oscato.com/api/lists/{listId}/{network}/payout
https://api.live.oscato.com/api/lists/{listId}/{network}/payout
The credit is completed by sending a POST request to the operation URL, and sending the proper payment account in the JSON body. This process works in a similar way to a charge operation in a regular LIST request.
POST to https://api.sandbox.oscato.com/api/lists/5a218abc123b512afef4f87glf4djckdjaih37abc1231n9ft9/MASTERCARD/payout
Basic Authentication
{
"autoRegistration": false,
"allowRecurrence": false,
"account": {
"holderName": "John Doe",
"number": "5500000000000004",
"expiryMonth": "02",
"expiryYear": "2019",
"verificationCode": "555"
}
}
The LIST for PAYOUT can also be requested in deferred mode, meaning that the customer submission of account details (the PAYOUT Request) does not yet trigger the money transfer. Instead, a consecutive CLOSING Request is needed, typically issued by the Merchant system after a staff member has reviewed the transaction. At this point, the transaction will be made through the configured providers.
For technical information on how to initialize and close deferred transactions, see Deferred Payments. The only difference for payouts is that there will be no reservation of funds on the payer's side.
Including the JavaScript Library
The optile JavaScript Library (as known as AJAX Library) is used with Selective Native and Display Native integration scenarios to render payment components returned by API responses into your checkout page. With the library you can integrate a payment page in these scenatios with much less effort, since it implements all frontend payment features and only a single call is necessary to initialize it.
The most recent AJAX Integration package can be downloaded here (login required): v3-3.18.0 (~1.9MB).
The zip file contains the following folders and files:
build
: The completely built and ready-to-use JavaScript and CSS files. The files should be put on the same folder level as your checkout HTML pages.example
: An example HTML file in two versions, one linking the minified resources (JS & CSS) the other the development versions.node_modules
: Node (npm) dependencies required to build the AJAX library.src
: development source (based on node.js with the browserify addon).package.json
: list of build and runtime dependencies, and development scripts.README.md
: A starting point for developers (node.js experience recommended).The main folder contains these files:
File | Description |
---|---|
op-payment-widget-v3*.js |
The readily built and readable JavaScript library. It comes in standard, versioned and minified versions. We recommend merchants to use versioned files. |
op-payment-widget-v3*.css |
The responsive CSS for the widget. It comes in standard, versioned and minified versions. We recommend merchants to use versioned files. |
widget*.css |
CSS file needed to style the iFrames for Selective Native integrations. It comes in standard, versioned or minified versions. |
widget-card-view*.css |
CSS file needed to style the iFrames for Selective Native integrations using our card view. It comes in standard, versioned or minified versions. |
responsive.html |
The HTML source of optile's hosted payment page, can be used as example to get started with development. |
The example
folder contains various implementation examples to help you getting started with our library.
You can integrate the respective JavaScript and CSS files into the header of an HTML page like demonstrated on the right pane. Make sure the HTML header contains the tag <meta charset="UTF-8">
for correct loading of the payment page.
Note that you also have to include the jQuery JavaScript framework yourself. If you want to support legacy browsers like Internet Explorer 6-8, Opera 12.1x or Safari 5.1+, we recommend a 1.12.x version for maximum compatibility (the latest 1.x version at the time of writing is 1.12.4).
We also recommend to set the viewport
meta tag to enable the responsive UX, see example on the right.
<head>
<code class="language-markup"><meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"></code>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="../op-payment-widget-dist/op-payment-widget-2.1.18.min.js"></script>
<code class="language-markup"><link rel="stylesheet" type="text/css" href="../op-payment-widget-dist/op-payment-widget-2.1.18.min.css"></code>
<!-- ... -->
</head>
The LIST Request needs to be initiated from your backend, because it needs to contain your sensitive OPG credentials. These should never be processed through the customer's browser. Read about this call in our LIST Request article.
As a result a LIST object will be generated in the OPG. This object contains all information to generate the payment checkout user interface (i.e., payment page). The only important part of the LIST Response is the link to this object (containing its long ID) or its actual longId attribute.
On the right pane you can find an example LIST Response with the link to the object >>
{
"links": {
"self": "https://api.sandbox.oscato.com/pci/v1/5fb254c5c4ffdf5cbab77723luv2g4f46tkplecjj7a2brv123"
},
...
"identification": {
"longId": "5fb254c5c4ffdf5cbab77723luv2g4f46tkplecjj7a2brv123",
...
}
}
This URL to "self" or the contained LIST object ID (in this example: 5fb254c5c4ffdf5cbab77723luv2g4f46tkplecjj7a2brv123
) has to go back to the customer's browser and from there to the AJAX Library. There are three ways of doing this:
listUrl
or listId
.http://www.example.com/checkout/payment.html?listUrl=https%3A%2F%2Fapi.sandbox.oscato.com%2Fpci%2Fv1%2F5fb254c5c4ffdf5cbab77723luv2g4f46tkplecjj7a2brv123
http://www.example.com/checkout/payment.html?listId=5fb254c5c4ffdf5cbab77723luv2g4f46tkplecjj7a2brv123
listUrl
or listId
parameter in the initialization call of the library.In order to have the payment checkout forms automatically injected into the current HTML DOM, you have to initialize the library.
You do so by specifying the HTML element you want to inject into first. Typically this is a <div>
element. In the example it has the Id paymentNetworks
and is referenced by jQuery syntax $('#paymentNetworks')
. Then you trigger the checkoutList
call from there.
An example call looks like this >>
$('#paymentNetworks').checkoutList({
payButton: "submitBtn",
payButtonContainer: "submitBtnContainer",
listUrl: listResponse.links.self,
smartSwitch: true,
developmentMode: false });
When calling checkoutList
you can provide additional options to configure and tune the payment checkout, listed below:
baseUrl |
String | optional |
The URL of the optile OPG service that will handle all the AJAX calls. It depends on the OPG version and enviroment you are using.
Sandbox:
Live:
Not required if |
listId |
String | optional |
The ID of the LIST object as returned by the LIST Response. If not provided in the JavaScript call, the library will look for a GET query parameter called
Not required if |
listUrl |
String | optional |
The URL to the generated LIST object as returned by the LIST response. If not provided in the JavaScript call, the library will look for a GET query parameter calles listUrl and use its value if found.
This value overrides any given |
widgetCssUrl |
String | optional |
If your application stores CSS files in a different directory level than your checkout page, use this parameter to reference the location of |
payButton |
String | mandatory | The ID of the HTML element of the submit button defined by merchant. |
payButtonContainer |
String | optional | The ID of the HTML element that contains the submit button. Used with methods that require proprietary provider buttons, which will then replace the standard payment button dynamically. |
cardView |
Boolean | optional |
Default: |
fullPageLoading |
Boolean | optional |
Default: |
smartSwitch |
Boolean | optional |
Default: |
summaryPage |
Boolean | optional |
Default: |
abortFunction |
Function | optional |
Custom function to deal with
Attention: even if an |
proceedFunction |
Function | optional |
Custom function to deal with
Attention: even if a
Furthermore, intrinsic redirect networks cannot have their behavior changed. When using a custom |
iFrameScaleFunction |
Function | optional |
For selective native integrations using custom styling via external CSS file, it might be needed to also adapt the container iFrames height to properly fit customized input fields for payment methods, saved accounts or preset accounts.
If declared by the merchant, the function should return a float value which will be used to scale the standard iFrame height size as returned in the parameter
If the |
doOperation |
Function | optional |
If the AJAX library is initialized in Summary Page mode (see summaryPage attribute) this callback function can be implemented to execute any operation once the user press the submit button. This function must be declared within a summaryPageHandler container object. |
onResult |
Function | optional |
If the AJAX library is initialized in Summary Page mode (see summaryPage attribute) this callback function can be implemented to handle responses This function must be declared within a summaryPageHandler container object. from operations executed after the user presses the submit button. |
summaryPageHandler |
Object | optional |
If the AJAX library is initialized in Summary Page mode (see summaryPage attribute) this object will contain doOperation and/or onResult callbacks implemented by you. |
Property
|
Type
|
M / O
|
Description
|
---|
If the AJAX Integration library is correctly initialized and provided with a valid listId
it will fetch all resources needed to build the payment forms from OPG automatically and inject it into the current HTML DOM.
Note that a generated LIST object expires after 30 minutes. Also it will only allow one successful Charge. See LIST Request for details.
Some hints to finalize your payment checkout page:
returnUrl
/ cancelUrl
/ external PSP page) or get a response from your custom functions. In the case the browser goes to an external page for payment, they will still in the end be directed to the given returnUrl
or cancelUrl
.Your backend will get a secure asynchronous notification about the current transaction status, which your system should listen to. See Status Notification for additional information.
<!DOCTYPE html>
<html>
<head>
<title>Hosted Payment Page</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script type="text/javascript" src="op-payment-widget-v3.min.js"></script>
<link rel="stylesheet" type="text/css" href="op-payment-widget-v3.min.css">
<style>
@media only screen and (max-device-width: 736px){
body {
background-color: #F2F2F2;
}
}
</style>
<script type="text/javascript">
function initPaymentPage() {
var dict = {};
checkoutList('paymentNetworks', {
payButton: 'submitBtn',
payButtonContainer: 'submitBtnContainer',
baseUrl: '"https://api.sandbox.oscato.com/pci/v1/"',
});
}
document.addEventListener('DOMContentLoaded', function (event) {
initPaymentPage();
});
</script>
</head>
<body>
<div id="paymentNetworks" class="payment-networks-container">
</div>
<div id="submitBtnContainer" class="submit-buttons-container">
<button id="submitBtn" type="button"></button>
</div>
</body>
</html>
If you wish to implement natively the optile's localized forms snippets returned from the original LIST Response, please refer to the Native Form Rendering article for details.
During a regular payment flow, the optile widget builds a list of payment methods and listens to a button-submit to process the next step (for example, capture a payment). This button is designed and implemented by the merchant, so to make this possible, it must have the ID submitBtn
, which is linked to the AJAX initialization function checkoutList
when building the payment page. However, providers such as PayPal impose the use of their own payment button, which has specific styling elements that cannot be changed by merchants. If you include such a provider in the list of available networks, you have the additional challenge of switching between a merchant's and a provider's payment buttons.
To solve this challenge, the optile AJAX library replaces this button on demand: if a user selects a provider like PayPal, its proprietary button is loaded. If any other method is clicked, the merchant button is loaded. This is done by defining a <div>
with ID submitButtonContainer
which will be used as container to submitBtn
and the provider's button. The checkoutList
function must be initialized with the parameter payButtonContainer
pointing to this container defined by the merchant, as seen in the second example in the pane on the right.
After these initialization steps, the payment page will look like the example below:
|
|
Left: Submit button for other payment networks. Right: PayPal proprietary button.
From an integration point of view, this is the only change needed to start using PayPal checkout. On the Provider Contract configuration side, a new parameter JAVASCRIPT_INTEGRATION
should be set to true
, but you can request this change direct from our support team if you wish.
<div id="submitBtnContainer">
<button hidden="hidden" id="submitBtn" type="button"></button>
</div>
JavaScript Initialization:
$("#paymentNetworks").checkoutList({
payButton: "submitBtn",
payButtonContainer: "submitBtnContainer",
...
});
Delayed Payment Submission (Summary Page)
optile's Delayed Payment Submission offers a unique solution: payment data is collected and cached ('PRESET') so the merchant has the chance to build a summary of the order before triggering the next step. With payment services that provide their own libraries, the summary page presents another two-fold challenge: First, the placement of the payment button has to happen there; and second, the proper process communication between optile and these third party libraries must be handled.
To overcome these obstacles, the optile AJAX widget (the same one used for the Payment Page) must also be included in the merchant Summary Page and properly initialized with a different set of parameters. Its current implementation supports PayPal checkout, Apple Pay and Google Pay, and future updates will cover new methods. As you saw in the payment page, the library makes use of the initialization function checkoutList
, but now with different parameters and extra handler functions:
summaryPage:true
tells the library that the current context is a summary pagesummaryPageHandler
: optional functions that can be implemented by the merchant for better control of the payment process
doOperation
: after an end-user clicks the provider-specific payment button, it will be triggered to handle the CHARGE process request to the OPG.onResult
: handles result object after success or error sent back from the OPG.See the pane on the right for examples of these functions.
function loadSummaryPage(inputData) {
$("#paymentNetworks").checkoutList({
payButton: "submitBtn",
payButtonContainer: "submitBtnContainer",
listUrl: inputData.links.self,
summaryPage: true,
summaryPageHandler: {
doOperation: function(data, callBack) {
$.ajax({
type: data.method,
dataType: "json",
data: JSON.stringify(data.operationData ? data.operationData : {}),
contentType: "application/json",
url: data.url
}).done(callBack)
},
onResult: summaryPageHandlerOnResult
}
});
}
Summary Page Handler Functions
The idea behind the summary-page handler functions is to give merchants the opportunity to intervene in the payment process, by implementing their own way of handling the trigger and result processing of a transaction. In this way, you can introduce custom logic that starts other parallel processes in your systems; and you get better control of the payment flow without needing to rely only on the functionality provided by optile. For example, with doOperation
a merchant can send a signal to its backend saying that an item is being taken from stock. With onResult
, you can define custom behavior of your user-interface based on the possible Interaction Codes sent by the OPG.
PayPal exception handling
There are some exceptional scenarios in which the merchant needs to cancel the payment operation mid-transaction. For example, it's possible that after triggering a CHARGE via doOperation
a merchant sees that the item being ordered is no longer available. Ideally, the payment operation would be stopped before the end-user executes the payment on PayPal, meaning the PayPal popup should be closed by a trigger from the merchant systems. Since the introduction of PayPal checkout.js
, such control is expected to happen in the frontend. To make this happen, the library has an exception handler that closes the popup. In practice, a merchant can trigger a popup-close by throwing a Javascript exception: throw new error
.
For example, a merchant backend system can return an error to the frontend, which would then throw an error during doOperation
execution to close the popup.
function handlerOnResult(data) {
console.log("Result Data: ", data);
switch (data.interaction.code) {
case "PROCEED":
$("#paymentNetworks").empty();
$("#submitBtnContainer").hide();
showGlobalSuccessMessage("Payment is done!")
break;
case "ABORT":
$("#paymentNetworks").empty();
$("#submitBtnContainer").hide();
if (data.error instanceof Error) {
showGlobalErrorMessage("Payment is aborted by Client!")
} else {
showGlobalErrorMessage("Payment is aborted!");
}
break;
case "TRY_OTHER_ACCOUNT":
case "RETRY":
showGlobalErrorMessage("interaction." + data.interaction.code + "." + data.interaction.reason);
break;
case "TRY_OTHER_NETWORK":
case "RELOAD":
$("#paymentNetworks").empty();
$("#submitBtnContainer").html('');
//loadSummaryPage(data);
window.setTimeout(function() {
showGlobalErrorMessage("interaction." + data.interaction.code + "." + data.interaction.reason);
}, 2000);
break;
}
}
networks.applicable
. If you have registered payment accounts (eg credit card) from a customer, these will be contained in accounts
(as masked data).
If you use Pure Native integration, the response will contain a few extra attributes -- see below. The idea is that this advanced data will be handled only by the merchant's server systems, whereas the Display Native data (above) could safely be passed to a browser for rendering the page in the frontend.
We will provide all the resources needed for display and charge of every payment network. With these resources you can easily generate a payment page of your own, the one the customer sees, within the frame of your corporate design.
The important links are:
localizedForm
: Links to an HTML form snippet detailing what information is needed from the customer to charge this network (eg credit card number). The language of the labels is dictated by the LIST Request's country
or style.language
parameters. The input fields depend heavily on the network. Their name attributes already correspond to the parameter names needed for the consecutive CHARGE. Only the ${formId}
placeholder in "id" attributes needs to be replaced before displaying the form -- to avoid conflict with existing elements on the page.operation
: The information entered on the form should be submitted to this OPG URL. It differs from network to network. Note: For Pure Native scenarios, you would submit to your own server first, and from there to the OPG. For Display Native scenarios, the submission comes directly from the browser. We recommend an AJAX request instead of form submission for this.logo
: URL of a logo that can be displayed for this networkvalidation
: The custom validation service we provide for this network. The form data can be posted here asynchronously to receive live feedback on whether the information is considered valid.You create the payment checkout page (most likely but not necessarily in HTML) dynamically and per transaction, with this data. Do not hard-code payment-network forms to keep the power of the 'implement once' principle. When the page is built, deliver it to the client (ie the browser), which displays it to your customer.
Visualization (red are parameters or resources to fetch; green are calls to execute):
{
"links": {
"self": "https://api.sandbox.oscato.com/api/lists/abcd1234abcd1234abcd1234abcd1234"},
"timestamp": "2017-07-13T08:09:18.788+0000",
"operation": "LIST",
"resultCode": "00000.11.000",
"resultInfo": "1 applicable and 0 registered networks are found",
"returnCode": {"name": "OK", "source": "GATEWAY"},
"status": {"code": "listed", "reason": "listed"},
"interaction": {"code": "PROCEED", "reason": "OK"},
"identification": {
"longId": "abcd1234abcd1234abcd1234abcd1234",
"shortId": "abcde-12345",
"transactionId": "id000001"},
"networks": {
"applicable": [{
"code": "MASTERCARD",
"label": "MasterCard",
"method": "CREDIT_CARD",
"grouping": "CREDIT_CARD",
"registration": "OPTIONAL",
"recurrence": "NONE",
"redirect": false,
"links": {
"form": "https://resources.sandbox.oscato.com/resource/form/MASTERCARD/standard.html",
"logo": "https://resources.sandbox.oscato.com/resource/network/ABC123/en_EN/MASTERCARD/logo2x.png",
"self": "https://api.sandbox.oscato.com/api/lists/abcd1234abcd1234abcd1234abcd1234/MASTERCARD",
"lang": "https://resources.sandbox.oscato.com/resource/lang/ABC123/en_EN/MASTERCARD.properties",
"operation": "https://api.sandbox.oscato.com/api/lists/abcd1234abcd1234abcd1234abcd1234/MASTERCARD/charge",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/ABC123/DE/en_EN/MASTERCARD/standard.html",
"validation": "https://api.sandbox.oscato.com/pci/v1/abcd1234abcd1234abcd1234abcd1234/ABC123/en_EN/MASTERCARD/standard/validate"
},
"button": "button.charge.label",
"selected": false
}]
},
"operationType": "CHARGE"
}
In Native intregrations, and in non-PCI applicable payment networks in Selective Native integrations, the LIST Responses will contain localized form snippets that can be used to integrate into your payment page.
Below is an overview of how to do this for the Native and Selective Native integration scenarios.
Getting snippets from OPGTo retrieve the relevant localised form snippets, your system will make a LIST request specifying the integration type and country. See the example on the right.
{
"transactionId": "tr10687",
"country": "GB",
"integration": "DISPLAY_NATIVE",
"updateOnly": false,
"customer": {
"number": "11",
"email": "john.doe@example.com",
"registration": {
"id": "586e73261652031c165ed473u",
"password": "1Xz5NpCk7Comh737"
}
},
"payment": {
"amount": 19.99,
"currency": "EUR",
"reference": "tesRef1234"
},
"callback": {
"returnUrl": "https://myReturnUrl.com/pending.html",
"cancelUrl": "https://myReturnUrl.com/cancel.html",
"notificationUrl": "https://myReturnUrl.com/notification.html"
}
}
Developers who have chosen a Native or Selective Native integration will have total control over where they present the form snippets. What follows serves only as a demonstration of how to do this; you are free to implement this at the frontend as you wish.
After retrieving the LIST Response, the associated payment networks should be presented at the frontend programmatically so that methods can be added or removed on the merchant configuration portal without the need for changes in the payment frontend of the integrated site.
In this simple example, all payment methods in a radio button list are displayed. When the radio button is selected, some JavaScript will fire and the associated paymentInputPlaceholder div
containing the localised form snippet will be displayed. Some information from the LIST Response data values in these HTML controls are also populated; this is so they can be accessed later in the process. In this example, a Pay button is used for triggering the selected payment request; it is placed beneath the radio buttons.
<!-- Loop through the applicable networks returned in the LIST Response.
The below uses MVC with HTML5 but you are free to implement this as you wish this is for ILLUSTRATION ONLY!
-->
foreach (var network in Model?.ApplicableNetworks?.OrderBy(x => x.label))
{
<!-- Div to hold the title of the payment method and a radio button group to allow this be selected-->
<div class="PaymentItem row" style="border:0px solid black;">
<div class="col-xs-9 PaymentHeaderItem">
<div class="col-xs-3" style="vertical-align:middle;">
<span>
<input type="radio" name=paymentNetworks
data-recurrence="@network.recurrence"
data-networkMethod="@network.method"
value="@network.code.Replace(" ", "")" />
<img src="@network.links.logo" />
</span>
</div>
<div class="col-xs-6" style="vertical-align:middle;">
@network.label
</div>
</div>
<!-- The placeholder that will be displayed when the associated radio button is selected -->
<div class="paymentInputPlaceholder" id="@network.code.Replace(" ", "")">
<div class="col-xs-9 paymentTitle">
@{
// These are variables associated with displaying the 'register for later' checkbox
bool registrationPossible = network.registration.ToLower() != "none";//none means the network does not support registration
bool displayEnabled = network.registration.ToLower() != "forced";// forced means the backend is configured to always register this payment option, no user selection allowed
bool registrationOptionalChecked = network.registration.ToLower() == "optional_preselected";
bool checkedValue = (displayEnabled == true || registrationOptionalChecked == true);
}
<!-- Display the check box if it is a method that supports registration and the user is logged in -->
@if (User.Identity.IsAuthenticated && registrationPossible)
{
<div style="vertical-align:text-bottom">
<label style="vertical-align:bottom">
<input type="checkbox"
id="@(network.code.Replace(" ", "") + "Register")"
value=@checkedValue
enabled=@displayEnabled >
@Resource.RegisterPaymentMethod <!--Local Resource containing 'Register this paymnet method' text-->
</label>
</div>
}
<!-- As this sample uses a selective native integration there may be credit cards that we
need to display in a frame as opposed to a form to avoid being in PCI scope -->
@if (network.method.Equals("credit_card", StringComparison.OrdinalIgnoreCase))
{
<iframe id="@(network.code.Replace(" ","")+"Frame")" frameborder="0" seamless=""
src="@network.links.iFrame" height="@(network.iFrameHeight+10)"
style="width:90%; overflow:hidden; border:0px groove black"></iframe>
} else {
<!-- Display the contents of the localised form here. I have built a form around the snippet
but that may not be totally necessary and is up to the implementing party to design this as they wish.
Important points to note though is that I'm noting the validation, self and operation links form the
LIST Response here for future use in my jquery/JavaScript
-->
<form id="@(String.Format("{0}{1}",network.code.Replace(" ",""),"Form"))"
action="@network.links.operation" method="post"
class="formSnippet"
data-snippetId="@(String.Format("{0}{1}", network.code, "Snippet"))"
data-listSelf="@network.links.self"
data-validationUrl="@network.links.validation">
<div class="optileHtmlSnippet"
id="@(String.Format("{0}{1}", network.code, "Snippet"))"
data-code="@(network.code.Replace(" ",""))"
data-src="@network.links.localizedForm" >
</div>
</form>
}
</div>
</div>
</div>
}
<!-- Submit button for all payment options -->
<div class="row" style="padding-top:10px; float: left;">
<button class="submitBtn btn btn-primary" value="Pay">@Resource.Pay</button>
</div>
As noted earlier, localised form snippets contain placeholders to identify the associated form. Before these snippets can be properly used, these snippets must be replaced with the relevant value.
To do this, inject the name of the payment method into the localised form at the frontend. Although there are different approaches for doing this, in this example you simply place all localised forms inside a div named ‘optileHtmlSnippet
' then on the document load calling function, replace all form name placeholders with the form name. You can find the function opTemplateEngine
we used in this example in the JavaScript file ‘template-engine.min.js
// Load htmlSnippets into placeholders
$(".optileHtmlSnippet").each(function () {
/* Get the custom attribute from the div that contains the address of the localised
form returned in the LIST Response */
var src = $(this).attr("data-src");
var currentElement = $(this);
$.get(src, function (response) {
var code = currentElement.attr("data-code");
/* Load the form snippet into the div with the placeholders replaced
The opTemplateEngine is another JavaScript file that contains this logic */
var endHtmlSnippet = opTemplateEngine(response, {formId:code});
// Set the current elements html to the html where placeholders have been replaced
currentElement.html(endHtmlSnippet);
});
});
After this step is applied, the form snippet will now have the placeholders replaced. See the example below for SepaDD.
Before:
<div id="${formId}-row1" class="row row1"> <div class="col col1"><label for="${formId}-iban">IBAN</label></div> <div class="col col2"><input id="${formId}-iban" type="text" name="iban" class="text iban" autocomplete="off"></div> <div class="col col3"><span id="${formId}-iban-message" class="message"></span></div> </div>
After:
<div id="SEPADD-row1" class="row row1"> <div class="col col1"><label for="SEPADD-iban">IBAN</label></div> <div class="col col2"><input id="SEPADD-iban" type="text" name="iban" class="text iban" autocomplete="off"></div> <div class="col col3"><span id="SEPADD-iban-message" class="message"></span></div> </div>
If you wish to display checkboxes that let the user specify the payment method to be saved for future use, you must render this at the frontend, outside the form snippet.
In this example, a checkbox is rendered to handle this. Before rendering the checkbox and setting the desired value, the original LIST Response should be checked for each payment method to see if registration is supported. This is because some payment methods do not support registration.
The value in the LIST Response that illustrates this is the Registration property of the applicable network.
{
...
"code": "SEPADD",
"label": "SEPA",
"method": "DIRECT_DEBIT",
"grouping": "DIRECT_DEBIT",
"registration": "OPTIONAL",
"recurrence": "NONE",
"redirect": false,
...
}
The possible values of this registration value can be seen in the console API but can also be summarised as:
Some logic must be in place in the frontend, to decide if the checkbox is rendered or not.
@{
// These are variables associated with displaying the 'register for later' checkbox
bool registrationPossible = network.registration.ToLower() != "none";//none means the network does not support registration
// forced means the backend is configured to always register this payment option, no user selection allowed
bool displayEnabled = network.registration.ToLower() != "forced" && network.registration.ToLower() != "forced_displayed";
bool registrationOptionalChecked = network.registration.ToLower() == "optional_preselected";
bool checkedValue = (displayEnabled == true || registrationOptionalChecked == true);
}
<!-- Display the check box if it is a method that supports registration and the user is logged in -->
@if (User.Identity.IsAuthenticated && registrationPossible) {
<div style="vertical-align:text-bottom">
<label style="vertical-align:bottom">
<input type="checkbox"
id="@(network.code.Replace(" ", "") + "Register")"
value=@checkedValue
enabled=@displayEnabled>
@ Resource.RegisterPaymentMethod
<!--Local Resource containing 'Register this payment method' text-->
</label>
</div>
}
This information can then be retrieved on the Charge and included as part of the CHARGE Request.
//Get register value
var registerCheckbox = $('input[name=paymentNetworks]:checked').val() + "Register";
var allowPaymentRegistration = $('#' + registerCheckbox).prop('checked');
This uses the same approach as above, but uses instead the recurrence property in the LIST Response to drive the logic.
ValidationThere was a validation link in the original LIST Response for each applicable payment method. In this example, this information is loaded as a data value attribute in the snippet div.
On form submit, a jQuery function is needed to perform validation and should be implemented if the selected payment method is not an iFrame. Refer to Selective Native for iFrame validation in a Selective Native integration.
In this example, the implementation of the localised form validation is simply an Ajax call to validate the information contained in the selected form snippet. If there are errors, they will be rendered at the appropriate place in the form. Again, please note this example is for illustration only; you are free to implement this any way you choose.
var validationPassed = validateFormInput(validationUrl, data, formSnippetId);
// Use the validation URL from the LIST Response to validate the submitted form data
function validateFormInput(validationUrl, data, formId) {
// if there is no data to validate then mark it as passed
if(data == null) {
return true;
}
var jsonString = (JSON.stringify(data))
var result = null;
$.ajax({
url: validationUrl,
async: false, // forcing the validation to fire before continuing with form processing
type: 'post',
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: jsonString,
success: function (response) {
result = response.valid;
if(!response.valid) {
// Loop through each field's validation response
$.each(response.messages, function(formInput) {
var snippetInputId = "#" + formId + "-" + formInput + "-message";
if(response.messages[formInput].type.toLowerCase() == "error") {
/* Display this error in the relevant place - the message placeholder will
always be '${formId}-{inputFieldName}-message' */
$(snippetInputId).text(response.messages[formInput].message);
$(snippetInputId).css({ "color": 'red'});
} else {
$(snippetInputId).text("");
}
});
}
},
error: function (response) {
console.log("FAILURE trying to run validation : " + JSON.stringify(response));
result = false;
}
});
return result;
}
Payment page rendering
Whichever integration scenario you have chosen, your system starts with the LIST request. If you are using the Delayed Payment Submission flow with a Summary Page before purchase, you can find out more under Delayed Payment Submission (this is easier to integrate because you don't need a proprietary button on the Payment Page). In other cases, your system renders the Payment Page by using the building components taken from the LIST response and should place the (regular) payment button in a dedicated container (eg <div>
). When the user selects one of these proprietary "frontend" methods, your system should call a generic JavaScript code from optile (see below) which will place the proprietary provider button into this container. (If you are using the full AJAX library, this container corresponds to the one given as the payButtonContainer
parameter in the checkoutList
initialization call). At the same time, your system should hide the normal payment button. The user will probably notice that the button has been replaced, but this is unavoidable because these kinds of providers don't let you use your own generic pay button.
JavaScript initialization on method selection
Your system now needs to know if it should call a JavaScript function when the user selects a method. For this, it looks into the HTML form snippet that was returned by the LIST response for this method or registered account. In any event, the form snippets should be integrated into your payment page, in accordance with our dynamic payment page rendering standards. Keep in mind that the form may not be visible to the customer (eg for PayPal) but it should still be loaded. If this snippet contains a <fieldset>
tag with the attribute initcustomsubmitbtn
then this gives you the JS function name (as a string) to call.
You can easily find the tag with this attribute (if it exists) with a CSS selector like fieldset[initcustomsubmitbtn]
. When used in a JavaScript implementation that supports these selectors, this returns all fieldset elements that contain the attribute named initcustomsubmitbtn
.
If there is no matching tag then there is no proprietary button for this method, and the regular payment button should be shown again.
<fieldset initcustomsubmitbtn="functionName">
...
</fieldset>
If the function is given, it should be called as soon as the user selects the corresponding method. In other words, your system calls eval
with the value of initcustomsubmitbtn
and 5 parameters.
The 5 parameters are:
eval("functionName")(networkObject, buttonContainer, paymentCallback, operationUrl, listResponse)
networkObject
: The JS object from the LIST response for the selected network. See the example.
{
"code": "PAYPAL",
"label": "PayPal",
"method": "WALLET",
"grouping": "WALLET",
"registration": "OPTIONAL_PRESELECTED",
"recurrence": "NONE",
"redirect": true,
"links": {...},
"button": "button.charge.label",
"selected": false,
"contractData": {...}
}
buttonContainer
: a pointer to an HTML button container, as shown in the example on the right. Could also be a special "inner" container that only holds the proprietary buttons. See Button display below.paymentCallback
: is called to handle the payment response. It should take only one argument which will contain the (CHARGE or PRESET) response from the OPG.operationUrl
: is an optional string, if passed, it will be used instead of the url available in networkObject
.listResponse
: is an optional parameter containing extra information such as currency and amount.
<div id="submitBtnContainer">
<button id="submitBtn" style="display: none;" type="button">My button</button>
<span id="paymentLoadingIcon" class="payment-in-progress" style="display: none;"> </span>
</div>
Calling the initialization function will create the proprietary button and bind any subsequent click event on it. Naturally, it does not execute the payment at this point. This function acts as middleware between the payment page and the provider's proprietary JavaScript.
A click on the provider button will trigger the payment process as per the behavior defined in its own JavaScript code. The result will be given asynchronously to your given callback function (in the format of an OPG CHARGE or PRESET response) so that you can (typically) redirect the user accordingly.
On mobile devices, PayPal (for example) typically redirects immediately on mobile devices. On a desktop, the result is delivered once the user has completed the checkout in the provider popup. For your implementation, however, it makes no difference. It should just follow the redirect or react on issues (based on the Interaction Codes).
Button display
As mentioned above, your system should hide the normal payment button when the user selects a method that has its own proprietary button. We suggest you use CSS to make sure that all buttons appear at the same place so there is consistency for the user. However, they will be different, because they are provider-specific. Providers usually do not allow the buttons to be changed visually.
In a nutshell, every time the user selects a new method, your system must evaluate again which buttons to show or hide. As a general rule, the decision goes like this:
Every time the selected payment method changes, hide all buttons inside the container. Note that they cannot consist of a <button>
tag but could simply be a <div>
for example. Then either call the JS function to show the proprietary button, or show the generic pay button. You could also group all proprietary buttons into another (inner) container within the (outer) container (in which the regular payment button is located). This gives you clear separation between regular and proprietary buttons. In this case you would pass the inner container to the function given in initcustomsubmitbtn
.
Delayed Payment Submission
You should include optile's AJAX library in the Summary Page, as described under https://www.optile.io/opg#1842448 (again, look under Delayed Payment Submission). This will render any proprietary payment button, if required, or just handle the button click for you. In any case it leaves you with full rendering possibilities, so you should use it on the Summary Page even if you don't use it for Payment Page rendering. It does not make a difference at this point which integration scenario was used to render the Payment Page earlier as long as the LIST object contains a preset payment account.
You only need to implement the Frontend CHARGE Request yourself in Native Integration scenarios.
The customer's sensitive payment-account data originates from the customer's client. That's why we call it a Frontend CHARGE (as opposed to triggering a Recurring Charge which would be a Backend Charge). Under PCI regulations, the sensitive data takes different paths depending on the Native Integration scenario that's in place:
Display Native: The data is transferred from the payment checkout page in the customer's client (eg browser) direct to the OPG. Your server-side systems never touch the data and therefore don't have to be PCI-certified.
Pure Native: The data is transferred from the payment checkout page to your own server-side systems. From there it is submitted to OPG (server to server). This means your system may store the data itself. In the case of credit cards, your system must comply with some PCI DSS standards and you must be certified.
The endpoint for the CHARGE Request is given as operation
attribute for the method in question, in the LIST Response.
https://api.sandbox.oscato.com/api/lists/ce3eec14-e186-4b7c-94a1-fde802412e69/VISA/charge
POST
Content Type
: application/vnd.optile.payment.enterprise-v1-extensible+json
Accept
: application/vnd.optile.payment.enterprise-v1-extensible+json
{
"account": {
"holderName": "John Doe",
"number": "4111111111111111",
"verificationCode": "123",
"expiryMonth": "12",
"expiryYear": "2021"
}
}
{}
, not an empty request body.
The CHARGE Response is returned to the system that issued the request. This is often the customer client (eg browser); in Pure Native scenarios it is typically the merchant backend. At the same time, a Status Notification is sent from the OPG to the merchant backend (as specified in the notificationUrl
), containing the same status information. It can be used as a reliable source of status information for the merchant. On the other hand, the CHARGE reply should be evaluated in terms of feedback to the customer and how to direct them further. A CHARGE response can contain redirect information for methods that require the customer to finish a payment on their own platforms.
HTTP/1.1 200 OK
Content-Type: application/vnd.optile.payment.simple-v1-extensible+json; charset=utf-8
Content-Length: <length>
JSON
{
"links": {
"payout": "https://api.sandbox.oscato.com/api/charges/59f756bfa6cd3d345d2d39a3c/payout",
"self": "https://api.sandbox.oscato.com/api/charges/59f756bfa6cd3d345d2d39a3c"
},
"timestamp": "2017-10-30T16:43:43.723+0000",
"operation": "CHARGE",
"resultCode": "00000.TESTPSP.000",
"resultInfo": "Approved",
"pspCode": "TESTPSP",
"returnCode": {
"name": "OK",
"source": "PSP"
},
"status": {
"code": "charged",
"reason": "debited"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"clearing": {
"amount": 450.00,
"currency": "EUR"
},
"identification": {
"longId": "59f756bfa6cd3d345d2d39a3c",
"shortId": "14052-58040",
"transactionId": "id_h0017",
"pspId": "VISA.DEBIT.1509106088565"
},
"redirect": {
"url": "http://localhost:3000/html/success.html",
"method": "GET",
"parameters": [
{"name": "shortId", "value": "14052-58040"},
{"name": "interactionReason", "value": "OK"},
{"name": "resultCode", "value": "00000.TESTPSP.000"},
{"name": "longId", "value": "59f756bfa6cd3d345d2d39a3c"},
{"name": "transactionId", "value": "id_h0017"},
{"name": "interactionCode", "value": "PROCEED"},
{"name": "amount", "value": "450.00"},
{"name": "reference", "value": "sandbox testing"},
{"name": "currency", "value": "EUR"}]
},
"customer": {
"number": "69",
"email": "john.doe@example.com",
"name": {
"firstName": "John",
"lastName": "Doe"
}
},
"network": "VISA",
"maskedAccount": {
"displayLabel": "41 *** 1111 02 | 2020",
"holderName": "John Doe",
"number": "41 *** 1111",
"expiryMonth": 2,
"expiryYear": 2020
}
}
Some payment methods will require the customer to be redirected to their own web platform to authorize a purchase. Many alternative PSPs and especially 3D Secure methods rely on redirection, therefore it is important to understand how the CHARGE response represents them.
In such cases, a CHARGE can be considered a two-step process. It will initially stay in pending state and will only move to a successful (or failed) state after the end customer finishes interacting with it. From an integration perspective it means you should evaluate first the Interaction Code and Reason of the response and parse the redirect URL accordingly.
A positive CHARGE response of redirect methods will initially be in pending state, which is represented by status.code = pending
and interaction.reason = PENDING
. Its redirect
object will contain parameters to inform how your systems should handle the redirection as follows:
url
: the base URL to which the request should be sent to.method
: the request method used. It can be either a POST
or a GET
.parameters
: an array of parameters to be sent with the request. In case of a POST
redirect, the parameters should be sent as in a form submission with header Content-type = application/x-www-form-urlencoded
, in a GET
redirect the parameters should be attached to the main URL as query string elements. If account placeholders are used (see next item on this list) the following key-value pairs can be present and should be replaced by the respective input form data collected from the end user.
holderName: ${account.holderName}
cardNumber: ${account.number}
expiryMonth: ${account.expiryMonth}
expiryYear: ${account.expiryYear}
verificationCode: ${account.verificationCode}
containsAccountPlaceholders
: a boolean that indicates if any of the parameters above contain placeholders for payment account data. Some PSPs will expose payment data in full details as redirect parameters, so to enable merchants that have PCI certification SAQ-A to still process such redirects, the account-related parameters will contain placeholders that our Ajax library replaces with input data from the frontend iFrames. For merchants that have are on higher levels of PCI certification SAQ A-EP or SAQ-D, these placeholders need to be filled with data collected in the frontend through regular forms.After the user completes the purchase through the redirect window, a regular CHARGE response containing either a success or error case will follow. Note that these responses also contain redirect URLs to the success or error pages defined by your shop, and should be parsed by the exact same method described above.
"status": {
"code": "pending",
"reason": "debit_requested"
},
"interaction": {
"code": "PROCEED",
"reason": "PENDING"
},
...
"redirect": {
"url": "https://preprod-tpeweb.paybox.com/cgi/RemoteMPI.cgi",
"method": "POST",
"parameters": [
{
"name": "IdMerchant",
"value": "510525147"
},
{
"name": "IdSession",
"value": "5d654a3eb55f28722d5fb03ac"
}
...
]
}
Code snippet used to parse a redirect
if(response['redirect'] != null) {
if(response['redirect']['method'].toLowerCase() == "get") {
//Redirect to provided redirect url and provide any params
var parameters = response["redirect"]["parameters"];
var redirectUrl = response['redirect']['url'];
if(parameters != null) {
redirectUrl = redirectUrl+"?"+jQuery.param(parameters);
}
window.location.href = redirectUrl;
} else {
//Make a post using redirect parameters as input
var postData = keyPairToJson(response["redirect"]["parameters"]);
$.redirectPost(response['redirect']['url'], postData);
}
}
After a successful charge request, the response body will also contain a masked account used for the charge. This can be used for giving the end customer a better summary of the payment process. The data can be found inside the maskedAccount
parameter in the response body. See the example in the pane on the right.
It is important to note that when using client-side charges (via our AJAX library, for example) the masked account data will not be sent on the response.
Redirect after SuccessA typical successful CHARGE response looks like this:
If the request is accepted and the Interaction Code in the response indicates PROCEED
, the response will contain the redirect
structure with information on where the user should be redirected. Only in Pure Native scenarios, where you did not provide the callbacks in the LIST Request, must you generate the redirect in your backend yourself.
There are two reasons for redirection after a successful request:
Regardless of the reason, the task of your system is simply to redirect the customer's browser according to the information provided in the redirect
section.
The key-value pairs provided in the parameters
section should be attached as GET-Parameters to the given url
. This enables the next page to display additional information about the payment status. For example, it may provide data for the remittance account in the case of invoice or prepaid payment networks (see Status Notifications for a list of possible keys relating to remittance accounts).
For Hosted Integrations in an iFrame especially, the suppressIFrame
flag indicates whether the redirect should happen in the top frame of the browser (true
), hence suppressing a potential iFrame. This is required for explicit redirect methods, such as PayPal, or redirects to one of your pages. It should therefore be the default, as it is in optile's hosted page and AJAX library. Only if false
is explicitly given should the redirect be in the current frame. This can be useful for hosted white label pages by third-party providers.
The bottom response parameters timestamp
, operation
, resultCode
, and identification
are intended for enterprise merchants only; you may not need them.
If there was a potentially recoverable error during payment, the following Interaction Codes may appear in the response:
TRY_OTHER_NETWORK
TRY_OTHER_ACCOUNT
RETRY
In this case, no redirect
section is contained in the CHARGE response >>
In this case you should inform the customer of the failed payment. Depending on the Interaction Code the customer should be asked to select another payment method or provide correct account information.
TRY_OTHER_NETWORK
Re-read the LIST object and render the payment page again accordingly, because the used network is probably not available anymore.TRY_OTHER_ACCOUNT
A different payment account should be requested from the customer (it can be the same method, eg Visa credit card, but a different account number would be expected). The LIST Object has not changed.RETRY
There is some other problem that may require updated account details, or to retry this later. The LIST Object has not changed.
{
"info": "Invalid holder name",
"interaction": {
"code": "RETRY",
"reason": "UNKNOWN"
}
}
When integrating our services natively, localized messages can be exhibited by mapping the interaction codes returned by OPG with the respective values provided by our live environment in a resource JSON file:
https://resources.sandbox.oscato.net/resource/lang/MERCHANT_CODE
/language
/checkout.json
In this URL, two parameters need to be adapted:
MERCHANT_CODE
: your Merchant Codelanguage
: a language code composed of ISO 639 two-letter language codes and ISO 3166 two-letter country codes separated by an underscore character. Similar to RFC 1766.The resource file lists the interaction codes and reasons, with their related descriptions as specified in the table Possible Interaction Code Values, plus translation for other page elements such as button labels and titles. An extract of the file from https://resources.sandbox.oscato.net/resource/lang/DEMO_SK/en/checkout.json is shown in the right pane.
We are continuously working to provide more languages in these resource files, but where a specific language is still not available, OPG will automatically fall back to English.
{
...
"button.delete.label": "Delete",
"button.operation.CHARGE.label": "Pay",
...
"interaction.RETRY.ACCOUNT_NOT_ACTIVATED.title": "Payment failed",
"interaction.TRY_OTHER_ACCOUNT.EXCEEDS_LIMIT.text": "The limit on this account has been exceeded. Please use another payment method.",
"interaction.RETRY.ACCOUNT_NOT_ACTIVATED.text": "Your payment method isn't yet activated. Please activate and try again or use another method.",
"interaction.TRY_OTHER_ACCOUNT.INVALID_ACCOUNT.text": "Please verify the data you entered is correct and try again, or use another payment method.",
"interaction.TRY_OTHER_NETWORK.BLOCKED.title": "Payment failed",
...
}
For some methods with a multi-step flow, eg Installment, the Interaction Code RELOAD
will be returned. As with TRY_OTHER_NETWORK
the LIST should be reloaded in this case and displayed again. See ACTIVATE Request for background information.
In some cases (duplicate payment, system failure, fraud detection, session expiration, etc.) the response will advise to interrupt payment process via Interaction Code ABORT
.
This will be coupled with a redirect to merchant's Cancel Page, if the cancelUrl
parameter was given in the LIST Request.
As with successful payment processing, if a redirect
section is found in the response, you should simply redirect the customer's browser accordingly.
If the reason was a duplicate payment (interaction reason DUPLICATE_OPERATION
), ie a Charge on a List object after there was already a successful Charge on that List, we recommend that you add a hint for the customer on the Cancel Page saying that the payment was already done and that only the repetition failed. Otherwise the customer may think the entire payment was denied.
{
"info": "Allowable retries exceeded",
"interaction": {
"code": "ABORT",
"reason": "FRAUD"
},
"redirect": {
"url": "https://dev.oscato.com/shop/cancel.html",
"method": "GET",
"parameters": [
{"name": "transactionId", "value": "tr101"},
{"name": "returnCode", "value": "FAILED"},
{"name": "interactionCode", "value": "ABORT"},
{"name": "interactionReason", "value": "FRAUD"},
{"name": "referenceId", "value": "aa1aa-1bb-123-aac-abc"},
{"name": "longId", "value": "aa1aa-1bb-123-aac-abc"},
{"name": "shortId", "value": "14335-64676"}
],
"suppressIFrame": false
}
}
The validation response contains user-friendly messages that should be displayed in front of the input fields, for example "Correct", "Invalid expiration year", "Missing card number" etc. The messages appear in the language that was specified in the LIST Request.
You can get the validation API endpoint from links object within the LIST Response. The endpoint is unique for each of the payment networks returned.
{
"networks": {
"applicable": [{
"code": "VISA"
...
"links": {
...
"validation": "https://api.sandbox.oscato.com/pci/v1/0a15894e-732c-4afb-a6ec-2edd7be4edb8/DEMO/de_DE/VISA/standard/validate"
}
},
...
],
...
}
POST
Accept
: application/vnd.optile.payment.simple-v1-extensible+json
Content-Type
: application/vnd.optile.payment.simple-v1-extensible+json
{
"holderName": "Jane Doe",
"expiryYear": "2019",
"expiryMonth": "3",
"verificationCode": "123",
"number": "4111111111111111"
}
The JSON parameters to be sent are equal to the input field names of the HTML payment form snippets linked in the LIST Response. For example, the validation request for a Visa credit card should have parameters that match the form snippet on the right pane (notice that the name
attribute is equivalent to the parameter name to be sent in the JSON request body:
Commonly used parameter names are listed below (this list is not exhaustive and may change with new payment networks):
holderName
number
expiryMonth
expiryYear
verificationCode
bic
iban
<input id="VISA-holderName" type="text" name="holderName" class="text holderName" autocomplete="off">
<select id="VISA-expiryYear" name="expiryYear" class="select expiryYear">
<select id="VISA-expiryMonth" name="expiryMonth" class="select expiryMonth">
<input id="VISA-verificationCode" type="text" name="verificationCode" class="text verificationCode" autocomplete="off">
<input id="VISA-number" type="text" name="number" class="text number" autocomplete="off">
Validation passed
HTTP 200 OK
"valid": true
is only returned if all parameters are valid.
{
"valid": true,
"messages": {...}
}
Empty field value or invalid input value:
HTTP 200 OK
Missing input { "valid": false, "messages": { "expiryYear": { "message": "Please enter year of expiration" "type": "ERROR" }, ... } }
Invalid field value
{ "valid": false, "messages": { "holderName": { "message": "Card Holder Invalid!" "type": "ERROR" }, ... }
Invalid format of JSON in the request:
HTTP 500
{
"interaction": {
"reason": "SYSTEM_FAILURE"
"code": "ABORT"
},
"resultInfo": "Internal error: Unexpected character [...]"
}
{
HTTP 500
{
"interaction": {
"reason": "SYSTEM_FAILURE"
"code": "ABORT"
},
"resultInfo": "Internal error: Unrecognized field [...]"
}
If a method uses the Activation flow and you do not rely on the optile's AJAX or Hosted Integration to take care of it, your implementation should detect this case as follows.
The initial handling of the payment page stays as is. The provided form will be shown to the customer, the entered data submitted to the operation
endpoint given in the LIST Response.
If the method uses the Activation Flow you will notice that the endpoint ends in activate instead of charge, but you do not need to detect that in your implementation. The part your system should pay attention to will come in the response.
If the customer can proceed with the checkout, the response will be something like >>
{
"resultInfo": "Calculation performed successfully.",
"interaction": {
"code":"RELOAD",
"reason":"ACTIVATED"
}
}
The Interaction Code RELOAD
tells your system to reload the LIST Object (via GET
on the LIST Resource) and generate the payment page again with its content. The provided form of the activated network has now changed and will request additional information from the customer.
In order to not confuse the customer, the selected
flag for this method will now be true
, indicating that this method should be preselected and expanded when generating the payment page again.
From there the regular CHARGE process applies again: The form content gets submitted to the operation
endpoint given in the LIST Object (which will now end in charge), and the response should be evaluated.
URL: https://api.sandbox.oscato.com/api/lists
{
"integration" : "DISPLAY_NATIVE",
"transactionId" : "76jknb5qh39rt4r2vqqoeitte91",
"country" : "DE",
"callback" : {
"returnUrl" : "https://dev.oscato.com/shop/success.html",
"cancelUrl" : "https://dev.oscato.com/shop/cancel.html",
"notificationUrl" : "https://dev.oscato.com/shop/notify.html"
},
"customer" : {
"number" : "012345",
"email" : "whitelist-test@payolution.com",
"name": {
"firstName":"Markus",
"lastName" :"Mustermann"
},
"addresses": {
"billing":{
"street": "Schlossstraße",
"houseNumber":"26",
"city":"Berlin",
"zip":"12163",
"country":"DE"
}
}
},
"clientInfo":{
"ip":"127.0.0.1"
},
"payment" : {
"reference" : "shop 3490/20-05-14",
"amount" : 500,
"currency" : "EUR",
"invoiceId" : "1110-234-918735",
"longReference" : {
"essential" : "Shop #3490 ",
"extended" : "Thank you for your purchanse!",
"verbose" : "Support at +49 89 3330303"
}
}
}
Expected Response
{
"links": {
"self": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l"
},
"timestamp": "2016-11-14T10:41:04.919+0000",
"operation": "LIST",
"resultCode": "00000.11.000",
"resultInfo": "1 applicable and 0 registered networks are found",
"returnCode": {
"name": "OK",
"source": "GATEWAY"
},
"status": {
"code": "listed",
"reason": "listed"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"identification": {
"longId": "582994c0e4b0b0d86230c524l",
"shortId": "15738-35982",
"transactionId": "76jknb5qh39rt4r2vqqoeitte91"
},
"networks": {
"applicable": [{
"code": "INSTALLMENT_SEPA",
"label": "Kauf auf Raten",
"method": "DIRECT_DEBIT",
"grouping": "DIRECT_DEBIT",
"registration": "NONE",
"recurrence": "NONE",
"redirect": false,
"links": {
"form": "https://resources.sandbox.oscato.com/resource/form/INSTALLMENT_SEPA/standard.html",
"logo": "https://resources.sandbox.oscato.com/resource/network/SHOP/de_DE/INSTALLMENT_SEPA/logo.png",
"self": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/INSTALLMENT_SEPA",
"lang": "https://resources.sandbox.oscato.com/resource/lang/SHOP/de_DE/INSTALLMENT_SEPA.properties",
"operation": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/INSTALLMENT_SEPA/activate",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/SHOP/DE/de_DE/INSTALLMENT_SEPA/standard.html",
"validation": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/SHOP/de_DE/INSTALLMENT_SEPA/standard/validate"
},
"button": "button.activate.label",
"selected": false
}]
}
}
URL: https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/INSTALLMENT_SEPA/activate
Method: POST
{
"account" : {
"customerBirthDay" : "1",
"customerBirthYear" : "1991",
"customerBirthMonth" : "1",
"optIn" : "true"
}
}
Expected Activation Response
{
"resultInfo": "Calculation performed successfully.",
"interaction": {
"code": "RELOAD",
"reason": "ACTIVATED"
}
}
URL: link.self (you find that link in the LIST Response)
Example: https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l
Method: GET which return the Installment Plans
{
"links": {
"self": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l"
},
"resultInfo": "1 applicable and 0 registered networks are found",
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"networks": {
"applicable": [{
"code": "INSTALLMENT_SEPA",
"label": "Kauf auf Raten",
"method": "DIRECT_DEBIT",
"grouping": "DIRECT_DEBIT",
"registration": "NONE",
"recurrence": "NONE",
"redirect": false,
"links": {
"form": "https://resources.sandbox.oscato.com/resource/form/INSTALLMENT_SEPA/activated.html",
"logo": "https://resources.sandbox.oscato.com/resource/network/SHOP/de_DE/INSTALLMENT_SEPA/logo.png",
"self": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/INSTALLMENT_SEPA",
"lang": "https://resources.sandbox.oscato.com/resource/lang/SHOP/de_DE/INSTALLMENT_SEPA.properties",
"operation": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/INSTALLMENT_SEPA/charge",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/SHOP/DE/de_DE/INSTALLMENT_SEPA/activated.html",
"validation": "https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/SHOP/de_DE/INSTALLMENT_SEPA/activated/validate"
},
"button": "button.charge.label",
"selected": true,
"formData": {
"installments": {
"originalPayment": {
"amount": 500,
"currency": "EUR"
},
"plans": [{
"id": "c363bb5d-f831-4c98-8482-cdbc8abc00a4",
"schedule": [{
"amount": 167.84,
"date": "2016-12-04T23:00:00.000+0000"
},{
"amount": 167.84,
"date": "2017-01-04T23:00:00.000+0000"
},{
"amount": 167.84,
"date": "2017-02-04T23:00:00.000+0000"
}],
"currency": "EUR",
"installmentFee": 0,
"totalAmount": 503.52,
"nominalInterestRate": 4.95,
"effectiveInterestRate": 5.05,
"creditInformationUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d0PcuR2eVe5q/CreditInformation.pdf",
"termsAndConditionsUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d15t21a5zw8r/TermsAndConditions.pdf",
"dataPrivacyConsentUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d2t7hcATneYD/DataPrivacyConsent.pdf"
},{
"id": "c03e403e-a1c0-4082-8dcd-42399e5665ea",
"schedule": [{
"amount": 84.43,
"date": "2016-12-04T23:00:00.000+0000"
},{
"amount": 84.43,
"date": "2017-01-04T23:00:00.000+0000"
},{
"amount": 84.43,
"date": "2017-02-04T23:00:00.000+0000"
},{
"amount": 84.43,
"date": "2017-03-04T23:00:00.000+0000"
},{
"amount": 84.43,
"date": "2017-04-04T22:00:00.000+0000"
},{
"amount": 84.43,
"date": "2017-05-04T22:00:00.000+0000"
}],
"currency": "EUR",
"installmentFee": 0,
"totalAmount": 506.58,
"nominalInterestRate": 4.95,
"effectiveInterestRate": 5.06,
"creditInformationUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d3iBeZYNpTI5/CreditInformation.pdf",
"termsAndConditionsUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d4k3JJ3ilyEp/TermsAndConditions.pdf",
"dataPrivacyConsentUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d5PIp6I9jORR/DataPrivacyConsent.pdf"
},{
"id": "4fda7671-8565-4dc8-9105-2c2a54fadfbe",
"schedule": [{
"amount": 56.63,
"date": "2016-12-04T23:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-01-04T23:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-02-04T23:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-03-04T23:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-04-04T22:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-05-04T22:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-06-04T22:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-07-04T22:00:00.000+0000"
},{
"amount": 56.63,
"date": "2017-08-04T22:00:00.000+0000"
}],
"currency": "EUR",
"installmentFee": 0,
"totalAmount": 509.67,
"nominalInterestRate": 4.95,
"effectiveInterestRate": 5.05,
"creditInformationUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d6bWXLcTWp5Q/CreditInformation.pdf",
"termsAndConditionsUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d7zXd7FoIceb/TermsAndConditions.pdf",
"dataPrivacyConsentUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d89Jj4fIYI72/DataPrivacyConsent.pdf"
},{
"id": "17ee461d-8e1d-4f8f-b1d2-95ff3ad24e91",
"schedule": [{
"amount": 42.74,
"date": "2016-12-04T23:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-01-04T23:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-02-04T23:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-03-04T23:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-04-04T22:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-05-04T22:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-06-04T22:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-07-04T22:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-08-04T22:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-09-04T22:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-10-04T22:00:00.000+0000"
},{
"amount": 42.74,
"date": "2017-11-04T23:00:00.000+0000"
}],
"currency": "EUR",
"installmentFee": 0,
"totalAmount": 512.88,
"nominalInterestRate": 4.95,
"effectiveInterestRate": 5.08,
"creditInformationUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6d9ETOlzIfSMw/CreditInformation.pdf",
"termsAndConditionsUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6daYpicMFJ0eA/TermsAndConditions.pdf",
"dataPrivacyConsentUrl": "https://documents.sandbox.oscato.com/SHOP/PAYOLUTION/582994d8c3a614600a9bb6dbV4AgV5fise/DataPrivacyConsent.pdf"
}]
}
}
}]
}
}
URL: https://api.sandbox.oscato.com/pci/v1/582994c0e4b0b0d86230c524l/INSTALLMENT_SEPA/charge (use the operation URL from the Reloaded LIST in Step3)
Method: POST
CHARGE Request Example >>
{
"account" : {
"installmentPlanId" : "c03e403e-a1c0-4082-8dcd-42399e5665ea",
"holderName" : "Jane Doe",
"bankCode" : "PBNKDEFF",
"iban" : "DE89370400440532013000"
}
}
If one chooses to implement Sepa Instalments in a Selective Native or Native implementation, then there is a small amount of effort required to render the process on the front end.
After the initial LIST Request is made with Selective Native or Native as the implementation parameter the LIST Response will contain a series of applicable networks each with an associated localized form (or frame if it is selective native and a credit card network). These html snippets (or iframes in selective native) should then be used to render the front end presented to the customer for each payment network.
These localised forms will contain {form-id} placeholders that need to be replaced with the network.code (highlighted in fig 1) value of the payment network. This can be accomplished easily using JavaScript. See the section 'Populating form snippets' in the Localized Form document for a simple approach to illustrate this. Also ‘network code
' and ‘self
' link should be saved as data attributes in the html element displaying the localised form as they will be needed later in the sepa-installment process.
After this replacement happens a form asking the customer to enter his/her date of birth will be displayed. If the year of birth field is empty it means that the JavaScript in the html snippet didn't execute and you should recheck your placeholder injection.
On submit the front end should make a call to the operation url returned in the previous LIST Response (illustrated earlier in this article). Opposite is some sample javascript to illustate this submit process.
// .... Other code here.....
var data = $(this).serializeFormJSON($(formId).serializeArray());
var postRequest = {};
postRequest.account = data;
// Note the optIn checkbox saves as ‘on' rather than true but request expects a boolean
if(postRequest.account.optIn != null)
postRequest.account.optIn = true;
var jsonString = (JSON.stringify(postRequest))
$.ajax({
url: actionUrl, // the operation link for this network from original LIST
type: 'post',
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: jsonString,
success: function (response)
{
// .... Other code here.....
//******* Serialise a form's values *******
$.fn.serializeFormJSON = function (a) {
var o = {};
//var a = this.serializeArray();
$.each(a, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
On the call-back the response should be checked to see if it was successful. If the response was successful it will prompt that the form should be reloaded. This can be accomplished by using the self
link for the payment network returned in the original response (see example above).
If it was unsuccessful then this information can be displayed to the user.
After the reload ((*) in JavaScript sample opposite) the form will now be in an activated state and the operation from the new RELOAD
response will be the end CHARGE URL (see sample JSON earlier in this section). This can be used for the end submit action for the next form request. The ACTIVATION (reload) response will also contain a series of instalments that need to be used to populate dropdown values in the new localised form html snippet.
Repeat the {form-id}
replacement process to load the network code into the localised form snippet and additionally at this stage, because the activated SEPA-Instalment form needs the drop down of potential rates to be populated and handled, an additional piece of JavaScript is required ((**) in JavaScript sample opposite). This is accomplished by finding the name of the postCreate
function in the form snippet that is associated with the localised snippet's fieldset
. There is a placeholder for formData
there that can be populated with the instalments returned by the last LIST Request ((***) in JavaScript sample opposite).
At this stage the completely rendered form snippet will be displayed on the frontend and the included JavaScript will handle the form changes.
$.get(listSelf, function (response) {
// (*) Set new form action url
submitAction = response.links.operation; // url to submit to
srcToLoad = response.links.localizedForm; // get form source
formData = response.formData; // get form data
// Load localised activated form
$.get(srcToLoad, function (response) {
var formToPostToName = $('input[name=paymentNetworks]:checked').val() + "Form";
var selectedForm = '#' + formToPostToName;
var snippetId = $(selectedForm).attr("data-snippetId");
var elementToUpdate = $('#' + snippetId);
// (**) Load the form snippet into the div
var htmlToInjectIntoSnippet = opTemplateEngine(response, {formId:code});
// Find the name of the postcreate function to call. This will always be in the fieldset of the injected localised form if it is present
var postCreateFunctionName = $('#' + code + "-fieldset").attr("postCreate");
if(postCreateFunctionName != null) {
// (***) Inject the postcreate function call with the formData from the response
htmlToInjectIntoSnippet = htmlToInjectIntoSnippet.replace("<script>", "<script> " + postCreateFunctionName + "(" + JSON.stringify(formData) + ");");
}
elementToUpdate.html(htmlToInjectIntoSnippet);
});
});
On submittal of the above form it's data should be serialised and submitted as part of the CHARGE
Request. The process will then be complete. See opposite for sample Javascript to handle this.
// Submit
var data = $(this).serializeFormJSON($(formId).serializeArray());
// ...... other code ......
var postRequest = {};
postRequest.account = data;
// ...... other code ......
var jsonString = (JSON.stringify(postRequest))
$.ajax({
url: actionUrl,
type: 'post',
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: jsonString,
success: function (response)
{
// ...... handle response here ......
If you implement this yourself, you should first be aware of the behavior of the LIST Response when it generates the payment page (client- or server-side).
For each method in accounts
or networks.available
that falls under the scope of PCI DSS (ie credit and debit cards), the LIST Response will not provide the links form
and localizedForm
. This means that your system will not be able to include the HTML snippets for the corresponding forms, even if you forget to change the implementation.
Instead, it will provide the URL to the iFrame in links.iFrame
and the suggested height of the iFrame (in px) in the iFrameHeight
attribute. So an object representing a payment method would look something like this:
Your system should place the iFrame into the payment page with these parameters >>
...
"networks": {
"applicable": [{
"code": "VISA",
"label": "Visa",
"method": "CREDIT_CARD",
"grouping": "CREDIT_CARD",
"registration": "OPTIONAL",
"recurrence": "NONE",
"redirect": false,
"links": {
"iFrame": "https://resources.sandbox.oscato.com/paymentpage/iframe.html?listId=57c03359e4b0c50a7e8007cfl&network=VISA",
"logo": "https://resources.sandbox.oscato.com/resource/network/UBUY/de_DE/VISA/logo.png",
"self": "https://api.sandbox.oscato.com/api/lists/57c03359e4b0c50a7e8007cfl/VISA",
"lang": "https://resources.sandbox.oscato.com/resource/lang/UBUY/de_DE/VISA.properties"
},
"button": "button.charge.label",
"selected": false,
"iFrameHeight": 165
},
...]
},
...
Web browsers will not allow direct access into the iFrames from the outside (the surrounding page). But in the standard version of Selective Native, the submit button is still outside, on the payment page. We therefore need predefined messages that can be exchanged between the frames.
Briefly, the essential messages are:
validation_response_event
Sent from the iFrame to the outer page to reflect whether there is valid account information entered so that the outer page can enable/disable the Pay button (for optimized user experience).payment_action
Sent from the outer page to the iFrame as soon as the Pay button is pressed, telling the iFrame to do a final validation and submit the payment operation request.operation_response_event
Returns the response of a payment operation request from the iFrame to the outer page.To provide an excellent user experience, Live Validation of the data entered by the user should be enabled (in the iFrames it is enabled by default), and the Pay button should be disabled for as long as the entered data is not formally valid.
This means that your page should decide whether to enable or disable the button, every time the user selects a payment method and the payment page is generated. If the selected method has a plain form (without iFrame) and contains a <fieldset>
element or it contains an iFrame form, disable the button for now. If there is data entered already, do a validation right away (see below). Otherwise, enable the button which is required for methods that do not require any input on this page (eg PayPal).
Your page should do a Live Validation of the entered data for methods with plain forms, for example when the user stops typing for a moment or leaves the input field. This is particularly important because your payment button will be disabled in the first place in these cases. If the response signals "valid": true
then the payment button should be enabled, otherwise disabled (again) and the hints shown next to the corresponding input field(s).
Inter-Frame communication comes into play if the selected method is subject to PCI and therefore provides an iFrame instead of a plain embeddable form. By default, the iFrame will do Live Validation itself and display hints accordingly in the form. The iFrame will also send the result to the outer page in the form of an validation_response_event
. Your page should listen to this message and enable/disable the payment button accordingly.
Here is some JavaScript / jQuery example code >>
Note that event.data["response"]
contains the full data structure of the Live Validation Response. This means, for example, that event.data["response"]["valid"]
corresponds to the valid
attribute from the direct API response.
You can disable Live Validation of the iFrames by requesting them with a liveValidation=false
query parameter. For example:
https://resources.sandbox.oscato.com/paymentpage/iframe.html?listId=57c03359e4b0c50a7e8007cfl&network=VISA&liveValidation=false
// The expected origin of the iFrames:
// Differentiate between Live and Sandbox here!
var opg_origin = 'https://resources.sandbox.oscato.com/';
// Listener for validation_response_event message:
window.addEventListener("message", function (event) {
if (event.origin === opg_origin) {
if (event.data["type"] === "validation_response_event") {
var response = event.data["response"];
if (response["valid"]) {
// Adjust the ID of your submit button accordingly:
$("#submitBtn").prop("disabled", false);
} else {
$("#submitBtn").prop("disabled", true);
}
}
}
}
With Smart Switch your customers do not have to select their credit card brand, it will be detected automatically.
To enable this feature with Selective Native you open the iFrame of a credit card form with the added parameters smartSwitch=true
and networks
(plural, not network
) with a list of credit cards codes (corresponding to the credit cards of your configuration). For example:
https://resources.sandbox.oscato.com/paymentpage/iframe.html?listId=57c03359e4b0c50a7e8007cfl&smartSwitch=true&networks=VISA,MASTERCARD,AMEX
An iFrame that is integrated like this will detect the credit card scheme of the entered number, during typing, and send the detection result to the parent frame in the form of a detection_response_event
. It can be consumed as the other response events and always has the following structure >>
The method
attribute will reflect the detected method from the list that was given as URL parameter. If none of those were detected, the value will be null
.
{
"type": "detection_response_event",
"response": {
"method":"VISA"
}
}
The payment_action
message should be sent from the outer page to the iFrame when the Pay button is pressed, telling it to do a final validation (using the same call as Live Validations). If final validation succeeds, submit the card data direct to the OPG for payment. At this point, you should disable the Pay button and, ideally, show a spinner to indicate that the user now needs to wait for feedback. When the response arrives, the result will be sent as a separate message from the iFrame to the outer page again.
If the validation fails, there will be a validation_response_event
message as shown above. These situations should be very rare if you implemented the enabled/disabled Pay button correctly. This is because the button will only be enabled if the validation succeeded already (see above), although it could still happen, eg due to race conditions. In any case there is nothing to do at this point because the Pay button should be disabled after the payment action, and stay disabled. Only the updated validation hints will be shown in the iFrame.
If the validation succeeds, the iFrame will execute the payment operation (eg CHARGE) and the response will be sent as operation_response_event
message from the iFrame to the outer page. This means that on the page there should be a listener for this event, taking the response data and reloading or redirecting the user according to CHARGE Response.
Only one response event will be sent as a result of a payment_action
.
Here is some JavaScript / jQuery example code you can use to get started. It is an extension of the above example code >>
For message type operation_response_event
, event.data["response"]
will hold the entire CHARGE Response. That means you can get the Interaction Code with event.data["response"]["interaction"]["code"]
, or the potential redirect URL with event.data["response"]["redirect"]["url"]
and so forth.
With this data at hand, all that is left to do is react to the response, as described under CHARGE Request.
Advanced Use CasesSpecific Actions:
Besides the payment_action
message, the following messages can also be sent to the iFrame:
validation_action
Will trigger an explicit Validation of the payment account data currently present in the form and will cause a validation_response_event
as soon as the validation was completed (see above).operation_action
Will trigger the payment operation directly (i.e., without extra validation) with the currently entered payment account data and this will cause an operation_response_event
as soon as the operation was completed (see above). This could be used instead of the payment_action
if the iFrame does Live Validation in order to avoid the delay of one extra validation.
Since the payment_action
we have described combines these two actions, two different response events can result.
In principle, all requests are subject to race conditions. Therefore your page can assign IDs to actions that will be returned with the corresponding response event. This way your implementation could detect messages that overtook other messages.
You can set and read this ID in the attribute action_id
, so you could:
selected_iframe.postMessage({"type": "validation_action", "action_id": "42"}, opg_origin);
You can access the value in the response event through event.data["action_id"]
and thereby match the response to its original request.
If your implementation does not make repeated action requests, this would not be neccessary.
// The expected origin of the iFrames:
// Differentiate between Live and Sandbox here!
var opg_origin = 'https://resources.sandbox.oscato.com/';
// Sending the opg_submit message on button click:
// Replace "#submitBtn" with the actual ID of your submit button here!
$("#submitBtn").onclick = function() {
// Get iFrame for the currently selected radio button. Only works for non-grouped forms:
selected_iframe = $("#paymentNetworks input[type=radio][name=paymentType]:checked ~ .formContainer iframe").contentWindow;
selected_iframe.postMessage({"type": "payment_action"}, opg_origin);
// Disable Pay button and show a spinner to indicate to the user that this may take a while...
}
window.addEventListener("message", function (event) {
if (event.origin === opg_origin) {
// Listener for operation_response_event message:
if (event.data["type"] === "operation_response_event") {
var response = event.data["response"];
if (response["interaction"]["code"] == "PROCEED" || response["interaction"]["code"] == "ABORT") {
// Continue with redirecting
// ...
} else {
// Continue with reloading and displaying error message
// ...
}
// Now the validation listener from above follows:
} else if (event.data["type"] === "validation_response_event") {
var response = event.data["response"];
if (response["valid"]) {
// Adjust the ID of your submit button accordingly:
$("#submitBtn").prop("disabled", false);
} else {
$("#submitBtn").prop("disabled", true);
}
}
}
});
The frame snippets presented to the user can be styled and translated. To do this, simply pass in the desired language
and cssOverride
URL in the style attribute in the original LIST Request. Afterwards, the frame will have its contents translated and style applied. See the screenshot below for a simple example of where the frame has been translated to French and has been styled to a dark gray background and heavy bold text.
"style":
{
"language": "fr_FR",
"cssOverride": "https://ADDRESS_HERE/CSS_FILE_NAME.css",
},
The css file in this example simply contains the following:
body
{
background-color: darkgray;
font-weight: 900;
}
If you wish to use Selective Native to integrate other payments methods (other than credit cards) then you could use optile's localized forms snippets returned from the original LIST Response. Find out more about implemeting these localized forms.
Because the frame that handles payment does not contain checkboxes for registering payments or allowing an input payment method to be used in recurring payments, these must be handled outside the frame.
The checkbox must be rendered after the frame. Then, on submit, the values of these checkboxes can be used in the frame's post message request to specify whether this payment method is to be registered or used for recurring charges. In the example opposite the values allowRecurringPayment
and allowPaymentRegistration
will have been retrieved from the checkboxes via JavaScript.
For a broader overview, go to displaying registration checkboxes in Localized Forms.
// make post to frame
frameToPostTo.postMessage({
"type": "payment_action",
"request": {
"allowRecurrence": allowRecurringPayment,
"autoRegistration": allowPaymentRegistration }},
opg_origin);
The integration packages are available as zip files here:
Inside each file, next to the root files with development meta information, you will find these folders:
op-client-side-encryption1.x.y.min.js
The versioned filename which we recommend to use in order to easily keep track of version changes.op-client-side-encryption.min.js
A copy without versioning in its file name to represent the latest version. Could be used for on-the-fly upgrades of the library, which requires more careful management.The integration starts with a LIST Request, but with CSE you must use the same integration parameter as for Pure Native integration:
"integration": "PURE_NATIVE"
If you submit encrypted account data using any other integration scenario (including the default), you will get this error: "Unrecognized JSON property 'encryptedAccount'…"
After the user has entered their payment-account data, the web client is expected to build a JSON object from it and then encrypt it. The attribute names contained in this object are the same as the input field names used in the equivalent HTML form from optile's form-based API. In other words, the object should have the same attributes as those in the optile account object used for CHARGE Requests.
The most common attributes for Charges are:
This payload should be represented as shown on the right pane.
{
"holderName": "John Doe",
"number": "5500000000000004",
"verificationCode": "123",
"expiryMonth": "12",
"expiryYear": "2022"
}
The minified JavaScript encryption library can be included in your web page, either by loading it direct in a <script>
tag, or through require
or import
. In technical terms, the library provides an asynchronous API to encrypt user data in accordance with the standards below.
The library exports the function: opEncrypt(accountPayload, publicKey, [timestamp], [listId])
This function returns a Promise Object (also called a "thenable"), which resolves to the encrypted string through its callback. This string is the encrypted data structure that you can send through your server to the OPG. We chose this implementation path to make the library compatible for a wide variety of use-cases, including server-side use and advanced client-use scenarios that benefit from an asynchronous programming style.
Input ParametersaccountPayload
: the JavaScript object (not a string) with any of the attributes available in the optile account object used for CHARGE RequestspublicKey
: the public key in JSON Web Key (JWK) format (as JavaScript object), which you already received from optiletimestamp
(also known as generationTime - optional): the timestamp of the encryption. If it is not given, or given as null, the current client time is used as default and added to the payload. Input format is ISO standard, eg "2017-09-26T08:41:01.859Z"listId
(optional): the longId
of the OPG LIST session on which the Charge would be executed. As it is an optional parameter, listId
will also not be included into the payload if it is not given.The function returns a Promise instance which resolves to a string containing the JSON Web Encryption (JWE) encoded account data. This string is the data that can be passed on to the OPG.
function submitCard(timestamp, listId) {
// listId needs to identify the LIST session to be used for this transaction
// timestamp is used to identify the encryption time
opEncrypt(
{
"holderName": document.getElementById("holderName").value,
"number": document.getElementById("number").value,
"verificationCode": document.getElementById("verificationCode").value,
"expiryMonth": document.getElementById("expiryMonth").value,
"expiryYear": document.getElementById("expiryYear").value
},
{
"kty": "RSA",
"n": "2UEvEj1-cUxF5ri-pXUhHIn-QNxiz6B4lWWvnK-RqZrMQQ_VSYiUfgkosydIp4-zrKzI5PbkWar1dYo3PcAfkZn1ZNTdxC4sMb3TEWJmbKZHh9C6fD4YsY6euTrSBKMC-aa1a7r0nUEzFQ6-7bKAfoOQKkrvIq6ecbTlcqNOAJX6aFJJ1bWmp7gTtG4ncYgtf_Q8nszgSMbe8hiIly4nwYwAxtdjxuOypcEx7I350VdCzTz-eN0rAnclT_-cv8P8J-JkS3yJVUlWeKaGeTtZB5-ZJHpDEUpOb9yc_NHKuxj5L9A-FemVUq606tBTG92yQU711Crf03CQ8-jbZyudkNYVQ",
"e": "AQAB"
},
timestamp,
listId
).then((encrypted_payload) => {
// encrypted_payload contains the data to be sent to your server.
// Send via asynchronous HTTP, for example,
// or as a form submit with a hidden field "optile_encrypted":
document.getElementById("optile_encrypted").value = encrypted_payload;
document.getElementById("sendform").submit();
}).catch((error) => {
// handle possible errors during encryption
console.log(error);
});
}
Your server sends the payment-account data in an (authenticated) CHARGE Request to the OPG. The parameter to hold that data string is called encryptedAccount
(which replaces the account
object of a regular CHARGE Request). Note: registration flags are still given, unencrypted.
The LIST session for this CHARGE transaction needs to correspond to the listId
used during encryption. For security, the encrypted card data will not work with any other LIST sessions. The response from the OPG will be the same as with an unencrypted CHARGE Request.
Never send unencrypted card data to your server systems.
{
"encryptedAccount": "VGhpcyBpcyB0aGUgZW5jcnlwdGVkIGFjY291bnQgZGF0YSBpbi...",
"allowRecurrence": true
}
If you want to build your own encryption client, you will need these extra details. You don't need these details if you simply want to use the JavaScript encryption library provided by optile.
The encrypted content is transmitted as a RFC7516 JWE (JSON Web Encryption) data structure in JWE Compact Serialization format. All of the user's input (see above) is encoded in key/value pairs in the encrypted JWE JSON payload. optile only supports AES256-GCM with RSA-OAEP as the encryption scheme. Therefore, the JOSE header will always look like this:
{ "alg": "RSA-OAEP", "enc": "A256GCM" }
It's base64 representation will accordingly always be:
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ
As specified by RFC7516...
…are each base64-encoded and concatenated using a "." character as separator. The resulting string is the one that must be sent to your server and then passed on to the OPG.
Payout requests generally only need a payment
data structure that includes the amount
to be refunded. With some PSPs, you may also be able to submit an empty payout request translating to a full refund. But with other PSPs this does not work for partial refunds that have been issued beforehand—therefore, we do not recommend it. To stay on the safe side, always specify the amount to be refunded.
In some cases you may have to specify which products are refunded (eg if you use invoice payments). To do that, you can specify a products
array after the payment
structure, similar to the product information you provide in an extended LIST Request. It is important to note that most PSPs will give you an error if you try to refund products that were not specified during the initial checkout.
First partial refund example
Suppose the initial checkout Charge had an amount
of 19.99 € and got the longId
value 26d5b179-a3b5-4aa3-ad98-3074b41e7d2b
. From there, you build the payout URL and post an amount less than the original amount (partial refund) as follows:
https://api.sandbox.oscato.com/api/charges/26d5b179-a3b5-4aa3-ad98-3074b41e7d2b/payout
POST
{
"payment": {
"amount": 5.00,
"currency":"EUR",
"reference":"Refund 472"
}
}
Example Response
{
"interaction":{
"reason":"OK",
"code":"PROCEED"
},
"resultInfo":"Approved",
"links":{
"self":"https://api.sandbox.oscato.com/api/payouts/fced7bd9-5489-4466-988c-83d2548a075a"
}
}
Note that in Pure Native integrations you will get a more elaborate response like:
{
"links": {
"self": "https://api.sandbox.oscato.com/api/payouts/54aea1bae4b005fbaf09f5f4o"
},
"timestamp": "2015-01-08T15:26:51.838+0000",
"operation": "PAYOUT",
"resultCode": "00000.WIRECARD.0",
"resultInfo": "Approved",
"status": {
"code": "paid_out",
"reason": "refund_credited"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"clearing": {
"amount": 5,
"currency": "EUR"
},
"identification": {
"longId": "54aea1bae4b005fbaf09f5f4o",
"transactionId": "tr101",
"providerId": "C979816142073082263267"
}
}
We issue a second refund that covers the remainder of the Charge amount—after which the customer will have been fully refunded.
Example Requesthttps://api.sandbox.oscato.com/api/charges/26d5b179-a3b5-4aa3-ad98-3074b41e7d2b/payout
POST
If your PSP does not support partial or multiple refunds for a one payment transaction, you should receive an error like: "Multi-refund is not supported".
{
"payment": {
"amount": 14.99,
"currency": "EUR",
"reference": "Refund 473"
}
}
Example Response
{
"interaction":{
"reason":"OK",
"code":"PROCEED"
},
"resultInfo":"Approved",
"links":{
"self":"https://api.sandbox.oscato.com/api/payouts/bef25df3-0d24-412c-805c-fd686821ef6e"
}
}
If your refund amount exceeds the amount originally charged, you will get an error from the OPG. To demonstrate, we issue a third "partial" refund on the same Charge, that was already fully refunded.
Example Requesthttps://api.sandbox.oscato.com/api/charges/26d5b179-a3b5-4aa3-ad98-3074b41e7d2b/payout
POST
Where you need to create an 'exceeding Refund' and your PSP supports it, the validation on the OPG should be turned off. Please contact us in this case.
{
"payment": {
"amount": 20.00,
"currency":"EUR",
"reference": "Refund 474"
}
}
Example Response
{
"links": {
"self": "https://api.sandbox.oscato.com/api/payouts/54aea1dce4b005fbaf09f5f5o"
},
"timestamp": "2015-01-08T15:27:24.854+0000",
"operation": "PAYOUT",
"resultCode": "50100.11.000",
"resultInfo": "Effective balance of charge process is less than requested payout amount",
"status": {
"code": "failed",
"reason": "refund_failed"
},
"interaction": {
"code": "RETRY",
"reason": "UNKNOWN"
},
"identification": {
"longId": "54aea1dce4b005fbaf09f5f5o",
"transactionId": "tr101"
}
}
Some PSPs ask you to list the products which are to be refunded. Make sure the product.code
and amount
(ie quantity
) of the refunded products match those from the original CHARGE Request. If the Payment Gateway finds a product with the same code
but the amount
does not match, it will return an error.
https://api.sandbox.oscato.com/api/charges/26d5b179-a3b5-4aa3-ad98-3074b41e7d2b/payout
POST
The products list behaves exactly as in the extended LIST Requests. The quantity
is optional and defaults to 1, and the amount
is the sum after considering the quantity.
The response and behavior in respect of partial ('multi') refunds is equivalent to those from simple Refund scenarios.
{
"payment": {
"amount": 22.40,
"currency": "EUR",
"reference": "Refund 202/01"
},
"products": [{
"code": "B003X6UEXQ",
"name": "Tron Legacy BluRay",
"quantity": 1,
"amount": 12.50
},{
"code": "C003X6U523",
"name": "The Lawnmower Man DVD",
"quantity": 1,
"amount": 9.90
}]
}
Cancellation
Refunds can be applied if a Charge has been processed by the provider. But there are situations where a Charge is queued on the provider side (or optile side) before it gets processed. The reason for this is often a provider-internal batch processing schedule. In this situation, the provider typically allows you to cancel this transaction. In optile's status space, such a transaction will have Status Code pending
and the Status Reason processing_scheduled
.
Canceling such a transaction means it will not be processed by the provider—so generally no processing fees will apply, and the end customer will not see any related transaction on their account statements (as opposed to refunds where a debit and a credit will typically be visible). In most cases, a cancellation can only be done completely. If you want to return a partial amount, you can wait until the Charge was processed and then do a partial refund instead.
To request the cancellation, send an HTTP DELETE request (without a body) to the CHARGE resource (as accessible by its links.self
URL).
Example Request
https://api.sandbox.oscato.com/api/charges/26d5b179-a3b5-4aa3-ad98-3074b41e7d2b
DELETE
A successful cancellation will return an HTTP 2xx return code. Otherwise it means it failed.
A cancellation can fail if, for example, the transaction was already locked by the provider because it is about to be processed. The best strategy in such a situation is to wait until the payment is processed, then issue a normal refund.
Note: This cancellation is similar to the cancellation of a Deferred Charge, but the underlying situation/status of the transaction is different.
In every integration scenario, you will get the customerRegistrationId
and customerRegistrationPassword
via Status Notification when you register a customer for the first time (by registering their first payment account).
The customerRegistrationPassword
will only be sent ONCE by a notification with entity
=customer
by which it should be stored. But the customerRegistrationId
is still sent with entity
=payment
.
If a customer has payment accounts registered for recurring charges, you can trigger those recurring charges by posting to:
/api/customers/<customerRegistrationId>/charge
You do not have to specify the channel
, it is set to RECURRING
by default for this type of call.
Provide the password via customer.registration.password
. Registration ID is passed via URL path and is not required in this scenario.
URL: https://api.sandbox.oscato.com/api/customers/aff7a069-b77a-4023-b5ae-ad4f48596ce4/charge
POST
hasRecurringAccount=true|false
in the notification with entity=customer
. If this customer has given the recurring mandate (via allowRecurrence=true
in the CHARGE Request) and not revoked it (by removing the recurring account) hasRecurringAccount
will show true
; otherwise false
.
true
does not guarantee the a recurring charge will work (or has ever worked), it just means that a mandate was given.true
, ie no change)regId
and regPwd
) will be sent. Soon afterwards, another notification with updated hasRecurringAccount
info may also follow.
{
"transactionId": "S-321-012",
"country": "DE",
"payment": {
"amount": 9.90,
"currency": "EUR",
"reference": "Your Subscription 321"
},
"customer": {
"email": "john.doe@example.com",
"registration": {
"password": "h5dhgn7gd3bhlkwykxgnhrd2346nm0ew6v"
}
},
"callback": {
"returnUrl": "https://my.ubuy.com/shop/success.html",
"cancelUrl": "https://my.ubuy.com/shop/cancel.html",
"notificationUrl": "https://ubuy.com/shop/notification.html"
}
}
pending
and the reason code retry_scheduled
.PROCEED
- SCHEDULED
, because on merchant side no action has to be taken. The returnCode.name
will indicate why the transaction was unsuccessful to allow statistical analysis.autoRetryAfter
instead of retryAfter
, but in the same format.declinesCount
in the notification will state the number of rejections that have occurred for this payment request yet, starting with 1.declined
and the reason code debit_declined
.autoRetryAfter
or retryAfter
value will be returned.declinesCount
in the notification will state the number of rejections that have occurred for this payment request.charged
and the reason code debited
, as with a normal successful ChargePROCEED
and OK
.autoRetryAfter
or retryAfter
value will be returned.declinesCount
in the notification will state the number of rejections that have occurred for this payment request until now (and therefore in total).The precise retry schedules can be individually fine-tuned in their configuration depending on the Interaction Reason Code.
declinesCount
parameter is provided to inform the merchant about the number of retry attempts made for the payment. Once this number reaches 10 (system default), the payment account is automatically blocked by the system. The logic of increasing and resetting the count is described in this chapter below. This logic applies not only for retry attempts made by ADM but for merchant initiated recurring charges as well.
Blocking an account after 10 attempts is default system behaviour, but there is a possibility to switch off this logic, so the account will not be blocked. In order to switch off retry attempts count, please contact our support team.
The defaults are:
Interaction Reason Code | Retries schedule plan |
---|---|
EXCEEDS_LIMIT | Retries take place on the [1st], the [15th], then the [1st] and the [15th] of the next month of the initial transaction date. |
UNKNOWN |
The retry schedule is planned after these days of the initial transaction date: [+1d] [+2d] [+3d] [+4d] [+7d] |
TEMPORARY_FAILURE |
The retry is scheduled after hours and days of the initial transaction time & date as follows: [+2h] [+4h] [+6h] [+8h] [+10h] [+12h] [+16h] [+20h] [+1d] [+2d] [+3d] [+4d] [+5d] |
In some cases it makes sense to stop the OPG from repeating charges, e.g., if payments have arrived through another channel. You can do so by sending an HTTP DELETE
to the Charge resource (in OPG1: the retry entity associated with the Charge resource).
Example Request
DELETE
As a reply you will get an HTTP 200
status code with an empty body. This indicates that the OPG will not repeat the scheduled charges for the referenced initial Charge any more.
This will also trigger a Status Notification with these values (among others):
statusCode
= canceled
reasonCode
= debit_canceled
interactionCode
= ABORT
interactionReason
= UNKNOWN
If you dig into the logic of ADM in the context of Fallback Routing (i.e., different providers that can be used for recurring charges), you will run into some conceptual challenges, because different providers could return different decline reasons.
Here is how OPG handles a situation like this under the hood:
Let's assume a recurring CHARGE for a registered customer that has 2 registered payment accounts (VISA and MASTERCARD). Let say that VISA has two PSP registrations for recurring charges, PROVIDER1 and PROVIDER2; and MASTERCARD only one via PROVIDER1.
In the first step, the OPG will try to process via VISA card, as in this example, this network has higher priority for some reason. If the processing via PROVIDER1 fails, the OPG will analyse the result. If the failure is 'recoverable' (Result Codes like 'provider not available' or 'provider internal error') the OPG will try to process via PROVIDER2. If the error is not recoverable the OPG will skip the processing via the other route (other provider registration) of the current registered account (in this case VISA) and will try to process with another registered account (in this case MASTERCARD). The OPG will also analyse the error result code to apply this information to the denial state of the registered account.
As an example, let us say that processing with VISA card was tried and failed for both PSP registrations. For PROVIDER1 with Result Code 'provider not available' (recoverable) and for PROVIDER2 with 'exceeds limit' (not recoverable). OPG will calculate an interaction code for the last try (here: PROVIDER2) and use it to increase the payment account's denials counter by 1 and change the account's denial state. This will also block the payment account if its denials counter reaches its limit (system default: 10). If the payment was successful the OPG would reset the account's denial counter to 0.
After that the OPG will try to process the transaction with the other registered account (MASTERCARD). Let say this try also fails with the Result Code 'provider not available' . As before, the OPG will derive the interaction code, use it for denial management, and return it to the merchant.
Now the transaction processing failed for both registered accounts VISA with Result Code 'exceeds limit' (second result code, coming from PROVIDER2) and MASTERCARD with 'provider not available'. The last try will be taken as final execution result. Based on this result the OPG will derive the final interaction code and use the ADM to find the suggested next retry time. As a result ADM could return: 'in 2 hours'. The OPG will increase the rejection counter for this payment process (which is separate from the payment account mentioned before) and will schedule the retry event, via the scheduler component, in 2 hours.
After 2 hours the scheduler will fire a retry-event and we will try to process this CHARGE again. If this try also fails, the OPG will retry with the same logic again until ADM blocks the next retry and finishes this CHARGE process with actual denial handler's 'failure result interaction code'.
The reservation has a different commitment and duration depending on the payment network and/or the issuing bank. For credit cards, a reservation ("preauthorization") typically lasts 30 days whereas a successful closing of this amount is guaranteed for 10 days. For PayPal the reservation lasts 29 days although for a direct debit a successful closing is not guaranteed.
The same process is used for invoice payments. Typically the closing is called as soon as the products are shipped out to the customer. In retail businesses this can take days or even weeks. For Open Invoice Providers this is the signal that the payment is due, whereupon they should transfer it to you and start waiting for the invoice payment of the end customer.
The diagram below shows the deferred payment based on the AJAX Integration scenario:
Initialize a Deferred Payment
If a payment network supports deferrals then it needs to be configured in case you want the deferred payment to be enabled. The configuration would be done by our staff individually per merchant (within the limitations of that network).
You can enable the payment networks that are in "deferred", "non-deferred" or "any" mode, as explained below in the LIST Request, by adding the preselection.deferral
parameter with one of the following values:
DEFERRED
Only networks which support deferred payments are listed. They are set in deferred mode for subsequent chargesNON_DEFERRED
(default) All networks which support immediate payment collection are listed. They are set in non-deferred mode when a CHARGE Request hits themANY
All networks are returned. If they support both modes, they are returned in deferred mode.
Note: The system default is set to "non-deferred"; therefore, if a payment network is configured to be in "deferred" mode and the value is not specified in the preselection.deferral
parameter in the LIST Request, then the LIST Response would not include this payment network.
{
"transactionId": "T345",
"country": "DE",
"integration": "PURE_NATIVE",
"customer": {
"number": "42",
"email": "john.doe@example.com"
},
"payment": {
"amount": 29.99,
"currency": "EUR",
"reference": "ubuy T345/20-08-2013"
},
"preselection": {
"deferral": "DEFERRED"
},
"callback": {
"returnUrl": "https://www.ubuy.com/shop/success.html",
"cancelUrl": "https://www.ubuy.com/shop/cancel.html",
"notificationUrl": "https://www.ubuy.com/backend/notify"
}
}
As with a normal LIST Request, you will get back all available networks together with their operation URL, which in Native Integration you interpret according to LIST Response.
It is possible to see the status code in the PURE_NATIVE integration scenario only, not with the other integration scenarios for security reasons. You could use the PURE_NATIVE integration parameter for testing reasons, but normally you would use the integration scenario that applies to your case.
Make the Reservation
Now you can submit the customer's account details to the operation
URL of the selected network. If the network is in deferred mode, this results in the "reservation" of the amount and a DEFERRED CHARGE object will be created. The Status Notification for this will return the statusCode
= preauthorized
.
This DEFERRED CHARGE could either be canceled or closed in a second step, see below. Both actions are triggered from server-side.
For security, only in "Pure Native" integration scenarios will your backend receive the required closing URL directly in the LIST reply as shown on the right side. In other cases it can be constructed, see below.
{
"interaction": {
"reason": "OK",
"code": "PROCEED"
},
"redirect": {
"method": "GET",
"parameters": [{
"name": "transactionId",
"value": "tr345"
},
...
],
"url": "https://dev.oscato.com/shop/success.html",
"suppressIFrame": false
},
"resultInfo": "Approved",
"links": {
"self": "https://api.dev.oscato.com/api/charges/06ae3329-a7ea-42aa-9bd1-52f7987f3727",
"closing": "https://api.dev.oscato.com/api/charges/06ae3329-a7ea-42aa-9bd1-52f7987f3727/closing"
}
}
Cancel the Reservation
Whenever your system realizes that a payment is no longer required, because, for example, you cannot deliver the goods, it should cancel the (remaining) reservation. Otherwise your customer may not be able to use the blocked funds on their account until they expire (ie after 10 days). For some payment networks (Open Invoice methods, for example) these requested funds may never expire. Future payments may therefore lead to rejections if high amounts are left open.
To cancel a reservation, issue an HTTP DELETE
request on the DEFERRED CHARGE resource. In "Pure Native" scenarios you get the URL to "self" already with the CHARGE reply.
Generally it is constructed like this:
https://api.sandbox.oscato.com/api/charges/[longId of DEFERRED CHARGE]
As a result, the reservation will be canceled through the provider and the transaction will transition to the status code canceled
/ preauthorization_canceled
. This will be reflected in the response, which returns the DEFERRED CHARGE object again. Note that this DEFERRED CHARGE object is not actually deleted, you can fetch its status any time and it remains visible in My Transactions in the portal, for example.
It is possible to close a payment partially and cancel the remaining amount. To do that, you would have to delete/cancel the DEFERRED CHARGE after the partial closing has been made. This would automatically cancel the rest of the amount.
This order should be in line with most merchants' workflows, especially since cancellations of preauthorizations are not time-critical.
Close the Transaction
To complete (capture) the transaction and actually trigger the payment, you issue a POST
request from your backend to the closing URL. It can be constructed like this:
https://api.sandbox.oscato.com/api/charges/[longId of DEFERRED CHARGE]/closing
In simple cases, the request contains only an empty JSON object {}
which means that the closing will be performed on the full amount.
But the closing can also be performed partially and multiple times. You can do this if, for example, an order has to be split into different shipments. In this case, you can submit a different amount and also a list of products covered by this payment.
{
"payment": {
"amount": 10.99,
"currency": "EUR",
"reference": "Partial Delivery tr345/1"
},
"products": [{
"code": "B003X6UEXQ",
"name": "Tron Legacy BluRay",
"amount": 10.99
}]
}
A successful CLOSING operation will create a CHARGE object on the optile side, which represents the actual capture of the funds. It has a different ID from the original DEFERRED CHARGE object, and your system will receive a response and a notification about its status. If your system issues multiple partial closings, the resulting CHARGE objects will all have different IDs.
A CHARGE object in the response directly includes its own PAYOUT link. Your system should issue PAYOUTS on the ID(s) of the corresponding resulting CHARGE(s), not on the preceding DEFERRED CHARGE object.
For invoice payments, a partial CLOSING is the signal to expect less money transferred, eg due to a return of goods by the customer.
{
"links": {
"payout": "https://api.sandbox.oscato.com/api/charges/5955242a16525bcc727abeedc/payout",
"self": "https://api.sandbox.oscato.com/api/charges/5955242a16525bcc727abeedc"
},
"timestamp": "2016-08-29T16:00:43.315+0000",
"operation": "CLOSING",
"resultCode": "00000.TESTPSP.000",
"resultInfo": "Approved",
"pspCode": "TESTPSP",
"returnCode": {
"name": "OK",
"source": "PSP"
},
"status": {
"code": "charged",
"reason": "debited"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"clearing": {
"amount": 10.99,
"currency": "EUR"
},
"identification": {
"longId": "5955242a16525bcc727abeedc",
"shortId": "06015-18360",
"transactionId": "T345",
"pspId": "MASTERCARD.DEBIT.1498572745446"
},
"customer": {
"number": "42",
"email": "john.doe@example.com"
}
}
Chargebacks are by definition asynchronous. Therefore, the OPG will send a Status Notifications to inform your system about them. This means simplified implementation for you, because every payment provider delivers this information differently, and often only passively, meaning active polling is needed.
Before an actual chargeback takes place, a payment provider can also request clarifications from the merchant, or announce a scheduled chargeback while still giving the merchant a chance to intervene.
These stages are modeled and described under Status and Reason Codes. Below is an representation of their possible transitions:
In general, certain Return Codes from providers are considered recoverable errors, for example, technical failures or configuration problems. Others, like stolen credit card, exist independently of the provider and are not considered recoverable in this context. See Return Codes for a full list.
If a recoverable error is detected, the payment gateway will look for an alternative provider contract that is also configured to be used to process this transaction. This means it needs to support the same parameters, like selected payment network and given country.
Frontend ChargesFor frontend charges with customer interaction, fallback routing is straightforward. If the provider declines the transaction for a recoverable reason, the payment data as provided by the customer will be used to immediately try again with another provider. If one attempt succeeds or all available routes fail, the CHARGE Response and Status Notification with the result from the last attempt will be sent to the merchant.
Backend ChargesCharges without customer interaction, ie Recurring Charges, are handled differently because the Payment Gateway is not allowed to store credit card verification numbers (CVC and CVV) and cannot therefore simply turn to a new provider as a fallback. From a providers' perspective, it would be an initial payment attempt requiring the verification number, which cannot be provided.
Therefore the payment gateway depends on past registrations by that customer, with alternative providers. If those are present, they will be used.
If no route through available registrations succeeds, the payment gateway will fall back to alternative registered payment accounts, another credit card that is stored, for example. If those are found, the whole story will be repeated with these accounts, ie looking for alternative providers again.
Note: Asynchronous ProvidersSince a direct reply with error information is required instantaneously for fallback routing, the logic does not work after an asynchronous provider or payment network is contacted. 'Asynchronous' in this context means only a temporal pending status is returned to the payment gateway, the final result arrives later. This is the case for providers like Adyen or Dotpay, or for redirection networks where the customer is sent to an external page.
But if no technical connection to the asynchronous provider could be established in the first place, owing to a provider downtime for example, Fallback Routing will be triggered because the OPG is able to act instantly.
The OPG supports additional requirements which are often needed by merchants in the gambling industry, and certain wallet payment methods, PayPal for example.
1:1 RelationshipThis feature can be enabled by our support team for a specific payment method and merchant division. We will ensure that:
This means that a payment transaction with the corresponding payment method will be declined by the Payment Gateway if:
Note: For some wallet payment methods (eg PayPal) the account ID used can always be changed by the customer during checkout on the web pages of the method. This means that the Payment Gateway can only decline a transaction after the customer has clicked "Pay" on the wallet page.
Examples:The communication between your system and the Payment Gateway would look like this:
First your system gets a CHARGE Response and corresponding Status Notification with:
PROCEED
PENDING
When the customer has completed the checkout on the wallet site, your system will get another Status Notification. If successful, there will be the usual PROCEED/OK indication. If the transaction is declined owing to these configured restrictions, it will contain:
TRY_OTHER_NETWORK
NETWORK_FAILURE
...plus a human readable error message in the resultInfo
attribute.
No. If a customer tries to pay with the payment method and does not succeed, they can repeat the payment with the same method again but a different account ID (eg PayPal email).
The payment account IDs used are only recorded while the feature is turned on. In other words, the feature only checks against payments in the past that were made while the feature was turned on.
Example:The feature is turned on and John pays in (for the first time) with his PayPal account john_doe@supermail.com.
Then the feature is turned off again by the merchant.
Next, John tries a payment with his second PayPal account, johnny432@mail.org. The payment succeeds.
Also Mary, with her seperate gambling account, makes a first-time payment with her PayPal account mary123@coolmail.net, and the payment succeeds.
After that, the restriction feature gets turned on again.
John can now only make payments with his PayPal account john_doe@supermail.com again (or some other method, of course). johnny432@mail.org would be declined. But if he had a second gambling account, he could use his PayPal account johnny432@mail.org.
Mary can make another payment with any PayPal account. Also, anybody else could use Mary's PayPal account mary123@coolmail.net for a payment into their gambling account.
The feature can be turned on and off per division separately. The feature will double-check all payments of all divisions where the feature is enabled.
Example:Merchant M has 3 Divisions: D1, D2 and D3. The feature is enabled for D1 and D2, but not D3.
Mary pays with her regular PayPal account mary123@coolmail.net in D1. When she also tries to pay with this PayPal account in her seperate gambling account in D2, it will be declined because PayPal account mary123@coolmail.net was used already in a different gambling account at Merchant M.
But she can use a different PayPal account now to pay in D2, eg peterpoor@fastmail.com.
Note: The underlying assumption in this example is that the two seperate gambling accounts of Mary in D1 and D2 each have a different customer.number
delivered to the Payment Gateway. If it were the same, she could pay in D2 with the same PayPal account as in D1, and only with that PayPal account.
In any case, Mary can use her PayPal account from D1, mary123@coolmail.net again to pay in D3 because the feature is not enabled there.
No. Technically the Payment Gateway will never confirm the execution of the payment, so there will be no completed transaction in the wallet method account.
Transaction ID Types
A transaction object contains these IDs:
longId
The optile generated ID (uniqueness guaranteed) for consecutive operationsshortId
An optile generated string that's rather short and "almost unique" (i.e. not guaranteed for all times) that can be used for simplified human to human communication, e.g., over the phonetransactionId
(mandatory for LIST requests) The primary, merchant-assigned ID (no uniqueness required or checked by optile)invoiceId
(optional) An additional merchant assigned ID, mostly used by invoice based payment methodspspId
An ID assigned by the payment provider (if the transaction reached the provider)institutionId
An ID assigned by the banking institution in the background (if the transaction reached the institution and the ID is revealed by the PSP in front of it)
You will find these attributes inside the identification
section of a transaction object (for authenticated server-to-server requests), except invoiceId
which is inside the payment
section.
Transaction Object Types
Type | Created through... | Allows Merchant IDs | Allows followup actions |
---|---|---|---|
LIST | LIST request |
|
Read, update, cancel, and: On a payment page: CHARGE or PRESET or ACTIVATE On an account update page: UPDATE, REGISTER |
DEFERRED CHARGE | CHARGE on a LIST (in deferred mode) |
|
Read, CLOSING, cancel |
CHARGE | CHARGE on a LIST, CLOSING on a DEFERRED CHARGE or Standalone Charge |
|
Read, PAYOUT, cancel (in few statuses) |
DEFERRED PAYOUT | PAYOUT on a CHARGE (in deferred mode) |
|
Read, CLOSING, cancel |
PAYOUT | PAYOUT on a CHARGE, CLOSING on a DEFERRED PAYOUT |
|
Read |
ACTIVATE | ACTIVATE on a LIST (for few methods) |
|
Read. The followup CHARGE is issued on the LIST object. |
Note that the operations PRESET and cancel do not create separate transaction objects, that's why there are no corresponding objects listed here. They are only modifications on existing resources.
...
"identification": {
"longId": "5a7da245148b514ac4b3946ec",
"shortId": "04040-80938",
"transactionId": "tr10004",
"pspId": "VISA.DEBIT.1518007534249"
},
...
Other ID and Object Types
Apart from the resources that represent payment transactions there are also other object types and IDs, most notably for customer and payment account registrations:
customerRegistrationId
. They can be used to issue Recurring Charges.accountId
. They will be used implicitly for payments with registered accounts or updates or deletions of accounts. They can be also used explicitly to set preferred accounts. Or, after a LIST with "channel": "RECURRING"
there can be Recurring Charges issued directly to a registered account.
Technically the notifications are normal HTTP(S) GET requests with appended query parameters (as if they would come from a browser). They are sent to a URL defined by you, either by configuration or passing a notificationUrl
parameter in the LIST Request. In other words, the OPG acts like it would request a web page from your server system and delivers the status parameters this way.
When a payment operation is triggered, there will be both, a synchronous reply to that request, and an asynchronous notification. In AJAX, Hosted and Display Native integrations the reply will arrive at the customer's client (e.g., browser), whereas the notification will directly arrive at your servers. Therefore it is safe to trust the notifications, as they never go through the customer's client.
The client should simply react on the reply, displaying the result to the customer. Your backend should trust the notification (only in Pure Native scenarios this difference is obsolete, because the reply also goes to the backend and can be used instead of the first notification).
There is one exception though: If an asynchronous PSPs is used (e.g., Adyen), the Interaction Code in the reply could be PENDING
(and also a notification with the Status Code pending
will arrive). In this case the customer should be asked to wait. Typically after a few seconds (in rare cases maybe even half a minute) a new notification will arrive at your server with an updated Status Code. Only for these cases there should be some kind of synchronization with the customer's client.
It is important to listen to status notifications, also after a payment is completed, using the Status Codes to note events like chargebacks.
Status Entities
Note that a notification can concern different things as indicated by the value of the entity
attribute:
payment
: The payment transaction (relevant for successes, chargebacks, etc.)customer
: The customer registration (as a consequence of account registration)account
: The customer's account registration (network name and account registration ID, as a consequence of account registration)session
: The frontend payment session (relevant when deciding whether to issue a new LIST Request or update an old one)
In the simplest implementation you will only need the payment
notifications. Therefore do not confuse the entities.
Name | Type | Appearance | Description |
---|---|---|---|
transactionId |
String | 1 | ID as given by you. Stays the same for the whole transaction "chain", for example the initial LIST and consecutive CHARGE and CLOSING operations. |
longId |
String | 1 | ID of the specific transaction object (LIST, CHARGE, DEFERRED CHARGE, PAYOUT) as assigned by OPG (uniqueness guaranteed). |
shortId |
String | 1 | Short ID of the transaction as assigned by OPG. Intended for simplified support communication. |
entity |
String | 1 |
Data entity described by this notification. Either payment , customer , account , or session . Each entity has different Status Code spaces. |
statusCode |
String | 1 | The transaction's current Status Code |
reasonCode |
String | 1 | Additional information about the transaction status, see Status Codes |
interactionCode |
String | 1 | How your system should react, see Interaction Codes |
interactionReason |
String | 1 | Additional information on the suggested reaction, see Interaction Codes |
returnCodeName |
String | 0..1 |
Detailed provider response in optile's unified Return Code space. Intended for detail analysis in hindsight. Return Codes are subject to change. Therefore the stable interactionCode and statusCode should be used for runtime processing instead. |
returnCodeSource |
String | 0..1 |
Source of the transaction outcome, in most cases the GATEWAY itself or the connected PSP behind it, see Return Codes |
pspCode |
String | 0..1 | The PSP used for processing |
institutionCode |
String | 0..1 | The banking institution (that may be behind the PSP) used for processing. |
resultCode |
String | 1 | Combined result code containing component and qualifier, see Result Codes. We recommend to use the simpler Status and Interaction Codes for most use cases, or the unified Return Codes for detailed analysis instead. |
resultInfo |
String | 1 |
Textual result info to the relevant operation. If returnCodeSource = GATEWAY the message is generated by the OPG, if returnCodeSource = PSP or an institution name, the message contains the original error string sent by the PSP or institution. In most cases resultInfo will not be generated by the OPG but from a PSP or institution. Mostly intended for debugging. |
customerRegistrationId |
String | 0..1 | Identifies the customer registered on OPG side. If your system provides this in future requests, the registered payment accounts of the customer can be accessed. |
customerRegistrationPassword |
String | 0..1 |
Your key to OPG's Secure Storage space for a registered customer. It's sent only once, on new registration of the customer (with |
hasRecurringAccount |
String | 0..1 |
Indicates if a registered customer gave a recurring mandate that is still valid, so that you know whether you can do recurring charges on that customer. This is a boolean attribute (values true or false ) included in the notification with entity=customer . |
network |
String | 0..1 | Code of the payment network used in processing |
amount |
Decimal | 0..1 | Processed payment amount |
currency |
String | 0..1 | Processed payment currency |
clearingAmount |
Number | 0..1 | Cleared amount of the operation confirmed by a PSP or a financial institutution. Depending on the type of operation either authorized or captured amount is sent. Amount provided as decimal delimiter, for example: 12.99 |
clearingCurrency |
String | 0..1 | The currency of the cleared amount. The value format is displayed as per ISO-4217, for example "EUR" |
reference |
String | 0..1 | Payment reference text used (as given by merchant) |
retryAfter |
Datetime | 0..1 | Suggestion when you should trigger a rejected Recurring Charge again. |
autoRetryAfter |
Datetime | 0..1 | When the OPG will retry this rejected Recurring Charge again, in case ADM is enabled. |
rejectionCount |
Integer | 0..1 | How often this Recurring Charge attempt has been rejected already. Starts with 1 at first rejection. |
notificationId |
String | 1 |
Identifier for this notification. Stays the same for repetitions of a Notification due to the guaranteed delivery. Can be used to detect duplicates. To determine the order of Notifications use timestamp . |
timestamp |
Datetime | 1 | Stays the same for repetitions of a Notification due to the guaranteed delivery. Can be used to determine the order of Notifications (reception order could be broken due to due to network issues for example). |
previousStatusCode |
String | 1 | Provided when the status of the transaction changed. Indicates the previous Status Code so that the precise status transition can be derived. |
previousReasonCode |
String | 1 |
Same as |
Note: not all parameters will always be present since status notifications could be pushed in different cases. For example, in case an account is registered, a status notification would be pushed without the "amount" parameter since it is independent from the amount. Also, the order of parameters is not fixed.
HTTP 200 OK
. Otherwise the Payment Gateway's guaranteed message delivery will repeat them (see below), therefore putting unnecessary load on our system and yours.https://ubuy.com/listener/notifications.do?transactionId=yourTransactionID&resultCode=00000.OPTILE.000&statusCode=charged&reasonCode=debited&referenceId=427a2bc8-216f-4a89-bf15-1edf90b800b8&longId=427a2bc8-216f-4a89-bf15-1edf90b800b8&shortId=07999-56867×tamp=2012-03-21T12:27:24.791+01:00&network=VISA&amount=29.70¤cy=EUR&reference=payment_reference_from_provider&interactionCode=PROCEED&interactionReason=OK¬ificationId=2255968806107622
mandate
data: Was introduced for SEPA network and returns information about the official mandate data that was used for a transaction.
remittanceAccount[n]
data: Provide this to your customers so they can complete their payment in case of invoice or prepaid methods. Only the fields required for the payment will be provided. For example a bank account together with a reference text (aka descriptor) could be identified, where the customer should transfer their money to. There could be more than one possible remittance account (indicated by [n] below, starting with remittanceAccount[0]
).
Name | Type | Appearance | Description |
---|---|---|---|
mandate.reference |
String | 0..1 | Mandate reference used for this SEPA transaction. Can be different from the value suggested by you if the PSP uses his own IDs. |
mandate.creditorId |
String | 0..1 | Creditor ID used for this transaction. Can be different from your ID if the PSP uses his own ID. |
mandate.authentication.date |
String | 0..1 | When the mandate was issued according to you. |
mandate.authentication.city |
String | 0..1 | Where the mandate was issued according to you. |
remittanceAccount[n].networkCode |
String | 0..1 | |
remittanceAccount[n].holderName |
String | 0..1 | Account holder name |
remittanceAccount[n].iban |
String | 0..1 | International Bank Account Number |
remittanceAccount[n].bic |
String | 0..1 | ISO 9362 (SWIFT) bank identification code |
remittanceAccount[n].number |
String | 0..1 | Account number |
remittanceAccount[n].bankCode |
String | 0..1 | National bank code |
remittanceAccount[n].bankName |
String | 0..1 | Bank name |
remittanceAccount[n].accountId |
String | 0..1 | An ID of the account that is not a number, e.g. email for PayPal accounts |
remittanceAccount[n].reference |
String | 0..1 | Descriptor for the money transfer (German "Verwendungszweck") |
remittanceAccount[n].comment |
String | 0..1 | A dynamic note from the provider that could contain legal information and therefore should be displayed. |
remittanceAccount[n].termDays |
String | 0..1 | Number of days the customer has by contract to transfer the money (in case of invoice: after the goods were delivered). |
If you have not implemented the notification listener already, do not worry about these values, which are either deprecated or not fully in place, yet.
Name | Type | Appearance | Description |
---|---|---|---|
returnCode |
String | 0..1 |
Deprecated, use interactionCode and interactionReason instead. Don't confuse with returnCodeName . |
referenceId |
String | 0..1 |
Deprecated, same as longId |
referredTransactionPassword |
String | 0..1 | Password for sensitive data temporary saved during current transaction |
The delivery of notifications is guaranteed (for any interface version). This means:
Please consider the following implications of guaranteed delivery:
notificationId
, which will stay the same across all repetitions.PENDING
Interaction Reason) fails, and a few seconds later a new notification (e.g., with OK
Interaction Reason) is successfully delivered, the first one will be re-sent 2 minutes later. In the given example this means the PENDING
notification would arrive after the OK
message. This information, of course, is not correct anymore. Therefore the notification order should be checked based on their timestamp
. For example, the timestamp of the last notification could be saved and only notifications with higher timestamp will be processed in the future.Note: These examples are unlikely, especially example 2, but they could occur. Therefore we recommend a clean implementation regarding these cases in the long run.
Also, if some kinds of notifications are ignored by your system (e.g., with entity
= session
, because you do not need payment conversion tracking), please let the system still return an HTTP 200 OK, so that the guaranteed delivery does not take effect.
It's importanty that you can recognize the notifications as valid in cases when a malicious party tries to spoof your servers by generating false notifications. You can do it in one of the three available ways:
If, for notification security reasons, you allow-list our IP's, note that we stop supporting static IP's as of April 1, 2021. Use one of the methods mentioned above to make sure your notifications are secure.
HTTP 200 OK
. Otherwise the Payment Gateway's guaranteed message delivery will repeat them, This will put unnecessary load on our system and yours.
Custom Parameters (shared secrets)
You can add custom name=value
pairs to the list of parameters in the notification URL by passing them in the LIST request callback.notificationUrl
definition. Any number of parameters added to the notification URL in the LIST request will be sent back once the notification arrives in your servers. On the right pane we can see the example of a token
containing the value 512938z1b176598113bA1b3
sent through a LIST and later received as a notification parameter.
Use the shared secret method to control if the notifications sent by the OPG are expected by your systems. You can achieve it by adding a token to URLs provided to the Orchestration Platform and checking that notifications received through these URLs have the same token.
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"summaryUrl": "https://dev.oscato.com/shop/summary.html",
"notificationUrl": "https://dev.oscato.com/notification?token=512938z1b176598113bA1b3"
}
Example Notification URL
https://dev.oscato.com/notification?token=512938z1b176598113bA1b3&accountStatus=registered&recurrence=false&customerRegistrationId=5d...
Additional headers
You can add more metadata to your transactions by providing custom headers to the transaction LIST request. The headers will be returned as a list in the notification you receive from our orchestration platform. The headers can be added on the transaction or division level.
You can use this additional information to separate divisions in your systems, or for analytical or security purposes.
There is no limitation to the number of headers other than that of your HTTPS server.
There are also few limitations on the content of the header and we accept everything that doesn't overwrite OPG original headers. For example, we don't allow user agent number to be in the header. If there is a risk that the original header will be overwritten, the additional header is dropped by the system.
"callback": { "returnUrl": "http://example.com", "summaryUrl": "http://example.com", "cancelUrl": "http://example.com", "notificationUrl": "http://example.com", "appId": "string", "notificationHeaders": [ { "name": "string", "value": "string" } ]
POST and GET notifications
You can choose to receive notifications as a GET (the default) or POST request. In GET, the data will be placed in the query URL and in POST, in the body. Fore security reasons, we advise selecting POST notifications.
After an operation, the OPG provides different information in form of codes or messages that can be used by the merchant systems to understand what might be the next operation step and what is the current state of an operation. These codes and messages should not be presented to end customers due to their technical content.
There are 4 different code types sent during a payment session: Interaction codes, status codes, return codes, and result codes, plus a longer message delivered through the result info field.
The matrix below summarizes them:
Code | Description |
---|---|
Interaction Codes | Informs what the frontend system or backend shop system should do after an operation. Example: proceed after a successful payment. |
Status Codes | Describes the current status of an operation. Example: charged after a successful transaction. |
Return Codes | Unified detail representation of payment providers' responses for deeper analysis. |
Result Codes | Contains the payment provider's native code, if available. Can be useful for further investigations of the nature of a specific operation result. |
Result Infos | Text message as complement to the codes. If returnCodeSource = GATEWAY the message is generated by the OPG, if returnCodeSource = PSP or an institution name, the message contains the original error string sent by the PSP or institution. In most cases resultInfo will not be generated by the OPG but from a PSP or institution. |
In the next sections you can find detailed information about all codes described above, and in which situations they should be used.
The interaction code is actually composed of two parts: the code itself and a reason, which expands its meaning and can be used for more refined decision.
Interaction Codes are designed to lead a merchant shop system to the appropriate action in a payment session. However, they are not a comprehensive reflection of all the statuses a payment may have. This information is present in the Status Codes, which bring a deeper understanding of the current status of a transaction. Keep in mind that the Status Codes don't need to be evaluated during a session, but they can be helpful case a merchant wants to investigate the details of a particular scenario while troubleshooting or testing.
Possible Interaction Code ValuesCode | Reason | Description |
---|---|---|
PROCEED |
The operation worked as expected. |
|
OK |
The operation was completed successfully, for example a successfully initialized LIST or executed CHARGE. Therefore you should check the payment status now. |
|
PENDING |
The status is still unknown. Wait for the next notification. |
|
SCHEDULED |
Scheduled for specific point in time, not yet submitted. For example ADM retry or scheduled for batch processing. |
|
TAKE_ACTION | You as a merchant need to take action to proceed with this transaction, typically manually confirm or decline it on the provider side where it could be held back for reasons such as risk or others. | |
ABORT |
The operation failed and there is no point in retrying. If a CHARGE fails like this, also alternative payment attempts in the same LIST session should not be allowed. Do not confuse this with the "aborted" status. |
|
NO_NETWORKS |
The LIST does not have any applicabe networks (methods) or registered accounts anymore, because they were all declined. (In backend cases this would be SYSTEM_FAILURE) |
|
FRAUD |
Fraud detected, do not allow any more Charge/Payout attempts (for frontend RISK_DETECTED may be returned instead) |
|
RISK_DETECTED |
Blocked by risk management, it's suggested to not trust this customer. |
|
DUPLICATE_OPERATION |
Probably a duplicate frontend Charge was issued on a LIST, meaning that the payment was already successful the first time. Then you should not show an error but indicate the successful payment to the user. For recurring backend charges a duplicated request may be declined by the provider, you should check your system logic. |
|
INVALID_ACCOUNT |
Invalid account (backend denial handling) |
|
CHARGEBACK |
A previously successful payment was revoked by customer |
|
INVALID_REQUEST | The request to the OPG was not valid and therefore could not be executed. There is nothing the end customer can do about it. It has to be fixed on the merchant side. | |
SYSTEM_FAILURE |
There is a technical issue with this operation which the end customer cannot resolve. It could be on the merchant side, for example an invalid request (that was not covered by the code above), OPG side, or provider side. If the Update of a LIST fails like this, the LIST itself may still be valid. |
|
TRY_OTHER_NETWORK |
The customer should choose a different network (method). The previously attempted method is removed from the LIST. Reload it, so that the method will not be present anymore. |
|
BLOCKED |
Network (AKA "method") is blocked by merchant configuration or denial handler (e.g., due to numerous chargebacks) |
|
NETWORK_FAILURE |
Currently there is no working route for this network available or the network is down globally. This includes misconfiguration on OPG or provider side. |
|
INVALID_REQUEST |
This method or provider needs data that is not or incorrectly provided by the request (e.g., missing billing address etc.). |
|
RISK_DETECTED |
A restriction to (possibly more secure) methods has taken place due to suspicion of fraud . |
|
ADDITIONAL_NETWORKS |
The system has detected more (sub-) networks the customer needs to choose from. |
|
TRY_OTHER_ACCOUNT |
The payment account exists, but was not accepted. Offer to try another account, same network (AKA "method") is allowed. Reload the LIST so the used account will not show anymore in case it was a registered one. |
|
BLOCKED |
Account is blocked by merchant configuration or payment provider (e.g., due to excessive retries or chargeback) |
|
BLACKLISTED |
Account is blacklisted by external source (e.g., PSP or scoring provider) |
|
CUSTOMER_ABORT |
The customer has chosen to abort this operation. |
|
INVALID_ACCOUNT |
Invalid account data, account unknown, deactivated, etc. |
|
EXCEEDS_LIMIT |
This payment account does not provide sufficient funds. |
|
EXPIRED_ACCOUNT |
Account is expired, a different account should be used (applies to frontend payments). |
|
RETRY |
Retry with this network, but with corrected account data (typically frontend) or at another time (typically backend). |
|
TRUSTED_CUSTOMER |
Specific data is incorrect and a trusted customer is detected. It would be safe to give a hint what field contains an error, based on the Return Code. |
|
UNKNOWN_CUSTOMER |
Specific data is incorrect but the customer is unknown (maybe fraud). The user should get a general message about account error without hints. Applies to frontend only, backend equivalent is DECLINED. |
|
ACCOUNT_NOT_ACTIVATED |
Payment account exists, but needs to be activated first. |
|
EXPIRED_SESSION |
LIST or third party payment session has expired, restart if customer is available. |
|
DECLINED |
Account data seems correct, but payment is not accepted due to various reasons (check Return Code). Rejections and Failures (as opposed to Declines) would instead be NETWORK_FAILURE, SYSTEM_FAILURE, etc. |
|
EXPIRED_ACCOUNT |
Account is expired. Applies to backend payments. Retry the Recurring Charge after the expiration date was updated or registered accounts were added. |
|
EXCEEDS_LIMIT |
Account is valid but at the moment has exceeded its limits, i.e., there are insufficient funds (applies to backend operations, ADM is recommended) |
|
TEMPORARY_FAILURE | Provider, network ("method") or adapter experiences a temporary failure. Occurs mostly for backend, where a retry later is recommended. Since this is not possible in frontend, this situation would often cause a TRY_OTHER_NETWORK there. | |
STRONG_AUTHENTICATION | As per 3DS 2 strong customer authentication (SCA) is required to make the transaction successful; other retries are possible. | |
VERIFY | An unexpected error, currently only generated by optile's resources and frontend libraries, has occurred. Your system should wait for a backend notification and/or verify the transaction status with optile. | |
COMMUNICATION_FAILURE | A communication failure occurred on the frontend (e.g. network connectivity on a mobile device was lost during payment). Your order system should consolidate the transaction state with optile's backend. Your frontend should display a message to inform the end-customer (e.g. An error has occurred and that they should check their order status on the webshop). | |
RELOAD | The LIST object was updated based on the submitted data. Reload and display the resulting payment page again. | |
ACTIVATED | A method has been used that requires a multi-step flow, e.g. Installments. See ACTIVATE Request. This is not an error but expected behavior for some methods. |
The whole Status is composed of a Status Code and a Reason Code, which provides further information inside a group of Codes.
Note that a status can concern different scopes as indicated by the value of the entity
attribute:
payment
: The payment transaction (relevant for successes, chargebacks, etc.)customer
: The customer registration (as a consequence of account registration)account
: The account registration under a customer (also result of account registration)session
: The frontend payment session (relevant when deciding whether to issue a new LIST Request or update an old one)Status Codes are complemented by Interaction Codes that offer recommendations on what to do next in a frontend payment session, especially to reduce declines in problematic situations.
Conceptually the status codes can be seen in a hierarchy as visualized below:
Please note that the diagram above, while being a general classification of payment statuses, does not represent the actual transition flow expected in a payment session. In fact, many PSPs can initially reject a payment request (due to internal, identified errors) and later accept it. Also, initially approved transactions can move in a later stage to a rejected state (example chargebacks). For this reason, the OPG will always listen to the PSPs' backend notifications and update the transaction statuses accordingly, even transitions between such "positive" and "negative" states.
Payment Statuses
Concerning entity
= payment
Status Code | Reason Code | Comment |
---|---|---|
charged |
debited |
Charge successful. Also applies to Closings of Deferred Charges but not Deferred Charges themselves (see below). |
^ |
debited_partial |
A Charge or Closing that was successful but partially refunded. |
^ |
payment_received |
Charge successful (after customer had to complete the payment separately, such as with the Sofort Banking method) |
^ |
paid_out_partial |
A previously charged transaction has been partially paid out. |
paid_out |
refund_credited |
Payout completed. This can apply to a Refund transaction or the initial Charge when completely paid out. |
^ |
paid_out |
^ |
^ |
credited |
^ |
pending |
debit_requested |
Operation pending (e.g., because customer has been redirected to a PSP confirmation page or the PSP processes the request asynchronously). In most cases you can wait for further notification. Only processing_paused may require action, please check! |
^ |
refund_requested |
^ |
payout_requested |
^ | |
^ |
payment_demand_requested |
^ |
^ |
payment_demanded |
^ |
^ |
credit_requested |
^ |
^ |
registration_requested |
The payment process is waiting for an account registration to be complete. |
^ |
retry_scheduled |
A recurring payment attempt failed, but optile has scheduled a retry for the future. This status and behaviour is a part of Automated Denial Management. |
^ |
processing_scheduled |
The payment request was received and is currently queued on provider or optile side with the possibility to cancel it before it gets processed (see Refunds & Cancelations). |
^ |
processing_paused |
The payment request was received and is currently held back on provider side. Typically the merchant needs to manually confirm this transaction with the provider to proceed. |
failed |
debit_failed |
The processing was denied (maybe not even attempted), because a technical problem is present (on provider or maybe merchant side). Examples: Downtime, wrong request format, missing account data (assuming this is due to a faulty implementation on merchant side) |
^ |
refund_failed |
^ |
^ |
payment_demand_failed |
^ |
^ |
credit_failed |
^ |
declined |
debit_declined |
The processing was denied, because seemingly there is no willingness or ability of the account holder to actually pay. Examples: Insufficient Funds, Risk Management, Blacklisting, Invalid account data (assuming this is due to incorrect input from customer) |
^ |
refund_declined |
^ |
^ |
payment_demand_declined |
^ |
^ |
credit_declined |
^ |
rejected |
rejected |
The processing was denied, because it is not allowed due to current configuration or general agreement with the provider. Examples: Currency or Country not allowed, payment feature not enabled. |
aborted |
debit_aborted |
Operation aborted by customer (do not confuse with interaction code ABORT ) |
^ |
payment_demand_aborted |
For example, a prepayment is not done within 3 months, or actions on redirected page not completed within 30 minutes. |
charged_back |
charged_back |
Amount charged back from the merchant account due to customer complaint or dispute. |
information_requested |
information_requested |
The merchant is notified that a chargeback will take place. There is enough information to conclude that a charge to the customer was incorrect. |
information_requested |
notification_of_chargeback |
Charge disputed by customer: e.g., customer claimed a transaction cancelation directly at the PSP. The PSP requests information (from the merchant) about the transaction to accept or reject the claim. |
dispute_closed |
dispute_closed |
DEPRECATED - Starting from OPG v2.0.6 a transaction in status information_requested can only transition to charged_back or back to its previous status (most likely charged ). |
^ |
chargeback_canceled |
DEPRECATED - See above. |
canceled |
debit_canceled |
Operation canceled by merchant |
^ |
refund_canceled |
^ |
^ |
payment_demand_canceled |
^ |
^ |
receipt_canceled |
^ |
^ |
credit_canceled |
^ |
Concerning entity
= payment
The following statuses can occur for Deferred Charges or related Closings, in addition to the statuses above.
Status Code | Reason Code | Comment |
---|---|---|
charged |
closed |
A Deferred Charge that was completely closed. Closings themselves, however, will be marked as debited instead, see above. |
^ |
closed_partial |
A Deferred Charge that was partially closed |
preauthorized |
preauthorized |
Preauthorization successful. Amount is likely to be available for closing. |
^ |
debited_partial |
A successful Deferred Charge has been partially closed. |
pending |
preauthorization_requested |
Operation pending (e.g., because customer has been redirected to a PSP confirmation page or the PSP processes the request asynchronously). Wait for further notification. |
^ |
preorder_issued |
^ |
^ |
preorder_requested |
^ |
^ |
cancelation_requested |
^ |
failed |
preauthorization_failed |
Operation failed due to technical reasons |
^ |
preorder_failed |
^ |
declined |
preauthorization_declined |
Operation declined by institution |
^ |
preorder_declined |
^ |
aborted |
preauthorization_aborted |
Operation is aborted by customer |
expired |
preauthorization_expired |
Preauthorization reference has expired |
^ |
request_expired |
Request operation has expired (no valid response from PSP) |
canceled |
preorder_canceled |
Operation canceled |
^ |
preauthorization_canceled |
^ |
Concerning entity
= payment
The following statuses can occur for PRESET flows in Delayed Payment Submission and Express Checkout, in addition to the statuses above. Note that in Delayed Payment Submission the preset account is part of the LIST object which will not change the status. Therefore these statuses only occur on the PRESET call itself in this case, and are visible if issued from the backend.
Status Code | Reason Code | Comment |
---|---|---|
preset |
preset |
The Preset is complete and ready for a CHARGE. |
pending |
preset_requested |
The Preset is still incomplete. In Express Checkout the user may have to select further data. |
^ |
confirmation_requested |
Occurs in Express Checkout only, when the Preset needs a CONFIRM to be completed. |
failed |
preset_failed |
Operation failed due to technical reasons |
rejected |
preset_rejected |
Operation failed due to configuration issues. |
declined |
|
Operation declined by payment provider |
expired |
preset_expired |
The completed or still incomplete Preset expired and can no longer be used for consecutive operations. |
canceled |
preset_canceled |
Operation was canceled (typically by the merchant) |
Concerning entity
= customer
The customerRegistrationPassword
will be sent exactly once after an account registration with such a notification. customerRegistrationId
will be returned for other notifications as well.
Status Code | Reason Code | Comment |
---|---|---|
registered |
registered |
Customer is registered |
pending |
registration_requested |
Registration requested; wait for further notification |
^ |
deregistration_requested |
Operation pending; wait for further notification |
^ |
registration_cancelation_requested |
^ |
failed |
registration_failed |
Operation failed due to technical reasons |
declined |
registration_declined |
Operation declined by institution |
aborted |
registration_aborted |
Operation is aborted by customer |
expired |
registration_expired |
Registration reference has expired |
canceled |
registration_canceled |
Operation canceled |
Concerning entity
= account
Status Code | Reason Code | Comment |
---|---|---|
registered |
registered |
Account is registered |
deregistered |
deregistered |
De-registration is done successfully |
Concerning entity
= session
Status Code | Reason Code | Comment | Entity |
---|---|---|---|
expired |
list_expired |
An initiated List expired, i.e., its reference is no longer valid and no Charge can be executed through it anymore. |
session |
listed |
listed |
The List is valid and a Charge can be executed. | Only in List reply |
pending |
charge_requested |
The List is in use, but the Charge state is unknown for now (i.e., because the customer is redirected to the PSP page to close the payment). |
session |
rejected |
list_rejected |
The List could not be generated due to the current configuration (e.g., missing methods or providers for the requested country or currency). |
session |
failed |
list_failed |
The List has failed due to a technical problem and no action is possible for this List. |
session |
canceled |
list_canceled |
The List has been canceled (by the merchant) and no action is possible for this List. |
session |
ended |
list_used |
The List has been used for payment and no action is possible for this List. |
session |
^ |
ended |
The List is in the end-state and will be removed/deleted. |
session |
Result Codes describe the outcome of an operation in detail. To simplify, the OPG performs a mapping of the Return Code to interpret the Interaction Codes, which we recommend to use in most cases.
A Result Code consists of three groups separated by dots:
Component and qualifier are called Investigation Info and help to analyze error conditions. Result codes are complemented by Result Info messages, which contain original payment service provider information, when available.
For example:
Result Code: 00100.102.000
Return Code = 00100 - Invalid request
Component code = 102 - Merchant HTTP Gateway Listener
Qualifier = 000 - no additional information
returnCode
: numerical representation, shown as part of the Result Code.returnCodeName
: human readable representation, see table below.returnCodeSource
: indicates where in the chain the response was generated. Together with the pspCode
and institutionCode
this should make clear, which organization should be contacted in case of transaction specific questions. Its value can be either of:
GATEWAY
(Payment Gateway, first level)PSP
(Connected payment service provider, second level)INSTITUTION
(The banking institution that may be behind the payment service provider)The table below includes all Return Codes of the Payment Gateway with their numbers, names, descriptions and if they are considered recoverable in the context of Fallback Routing.
Number | Code Name | Recoverable | Description |
---|---|---|---|
0 | OK | Approved | |
1 | OK_PENDING | Pending, wait for updated status | |
2 | OK_SCHEDULED_FOR_BATCH | Pending due to transaction queuing, wait for updated status | |
5 | OK_TEST_MODE | OK / Production level test mode | |
6 | OK_CUSTOMER_ACTION_REQUIRED | Customer needs to take action, eg manually confirm on provider side. The merchant does not need to inform the customer, this is taken care of. | |
7 | OK_MERCHANT_ACTION_REQUIRED | Merchant needs to take action, eg manually confirm on provider side, or inform the customer to take action. | |
8 | OK_PROVIDER_ACTION_REQUIRED | Provider needs to take action, eg manually review and approve the transaction. | |
11 | ABORTED_BY_CUSTOMER | Aborted, operation is aborted by customer | |
14 | CANCELED | Canceled, operation is canceled (typically by merchant) | |
15 | CHARGEDBACK | Charged back, charge back was received | |
16 | IN_DISPUTE | Dispute process has been initialized | |
17 | OPERATION_REQUEST_EXPIRED | Requested operation is expired | |
10050 | OPERATION_NOT_SUPPORTED | x | Operation is not supported |
10100 | WORKFLOW_TEMPLATE_NOT_FOUND | No workflow for operation | |
10103 | INVALID_TRANSACTION_STATE | Operation is invalid for current process state | |
10105 | DUPLICATED_TRANSACTION | Same transaction has been already submitted | |
10520 | MAX_REQUESTS_OCCURRED | x | Transaction is rejected because of heavy load from one host |
10530 | INSTITUTION_NOT_SUPPORTED | x | Institution is not supported |
10531 | INVALID_INSTITUTION | x | Institution is unknown |
10532 | CONTACT_INSTITUTION | Contact institution | |
11000 | SERVICE_ERROR | x | Error occurs during request processing |
11005 | THIRD_PARTY_SERVICE_ERROR | x | Error from third party service on provider side |
11010 | TRANSACTION_IN_USE | Transaction is blocked by other request | |
11011 | CURRENCY_CONVERTER_INVALID_RATE | x | Currency converter - invalid currency rate |
11012 | CURRENCY_CONVERTER_MISSING_RATE | x | Currency converter - missing currency rate |
11013 | TERMINAL_IN_USE | x | Terminal is locked by other request |
11014 | CURRENCY_CONVERTER_ERROR | x | Currency converter service error |
11020 | ADAPTER_NOT_AVAILABLE | x | Provider adapter is not available |
11021 | ADAPTER_COMMUNICATION_ERROR | x | Communication error with adapter |
11025 | ADAPTER_ERROR | x | An error in adapter |
12000 | REQUEST_ERROR | x | Adapter can not create a valid request for communication with target service, so no service communication took place |
12010 | COMMUNICATION_ERROR | x | Communication error with target service because of socket connect timeout, read timeout or IOException |
12015 | TEMPORARY_UNAVAILABLE | x | Payment service temporary unavailable |
13000 | RESPONSE_ERROR | Cannot parse or understand provided response | |
13010 | API_VERSION_ERROR | x | Service API version is not compatible with version used by a service-client |
13050 | INVALID_REQUEST | x | The request has wrong or in an unexpected format, or it is not accpted by a reason like 'cannot process empty request', 'missing signature', 'no action type found in request' and so on |
20100 | INVALID_CONFIGURATION | x | Configuration is invalid, no detail information given |
20150 | INVALID_PROVIDER_CONTRACT | x | Provider configuration is invalid |
20151 | INVALID_PROVIDER_CREDENTIALS | x | Invalid provider credentials |
20152 | MISSING_PROVIDER_CREDENTIALS | x | Missing provider credentials |
20160 | NO_ROUTE_FOR_NETWORK | x | No route to provider |
20161 | NO_NETWORK_BY_PROVIDER | x | No network by provider |
20162 | NOT_ALLOWED | x | Requested action is not allowed by contract |
20163 | AMOUNT_NOT_ALLOWED | x | Requested amount is not allowed by contract |
20164 | COUNTRY_NOT_ALLOWED | x | Country is not allowed by contract |
20165 | CURRENCY_NOT_ALLOWED | x | Currency is not allowed by contract |
20180 | METHOD_NOT_SUPPORTED | x | Requested method is not supported |
20200 | MERCHANT_UNKNOWN | Merchant is not known on OPG side | |
30000 | INVALID_REFERENCE | x | Invalid transaction- or other unspecified- reference |
30001 | INVALID_CUSTOMER_REGISTRATION_REFERENCE | x | Invalid customer registration reference |
30002 | INVALID_ACCOUNT_REGISTRATION_REFERENCE | x | Invalid payment account registration reference |
30003 | MISSING_REFERENCE | x | Missing transaction- or other unspecified- reference |
30004 | EXPIRED_REFERENCE | x | Expired transaction- or other unspecified- reference |
30005 | USED_REFERENCE | x | Used transaction- or other unspecified- reference |
30006 | MISSING_CUSTOMER_REGISTRATION_REFERENCE | x | MIssing customer registration reference |
30007 | MISSING_ACCOUNT_REGISTRATION_REFERENCE | x | Missing payment account registration reference |
30008 | CANCELED_REFERENCE | x | Canceled (voided or invalidated) transaction- or other unspecified- reference |
30009 | CANCELED_ACCOUNT_REGISTRATION_REFERENCE | x | Canceled (voided or invalidated) payment account registration reference |
30010 | INVALID_CUSTOMER_REGISTRATION | x | Invalid data in customer registration |
30011 | INVALID_REGISTERED_ACCOUNT | x | Invalid data in registered account |
45000 | DECLINED | x | Declined |
45010 | INVALID_ACCOUNT | Invalid card or account | |
45011 | EXPIRED_ACCOUNT | Expired card or account | |
45012 | RESTRICTED_ACCOUNT | Restricted card or account | |
45013 | INCOMPLETE_ACCOUNT | Incomplete account, some attribute missing | |
45014 | LOCKED_ACCOUNT | Account is locked | |
45015 | TEST_ACCOUNT | Test account | |
45016 | NOT_ACTIVATED_ACCOUNT | Not activated account | |
45017 | LOST_OR_STOLEN_CARD | Lost or stolen card | |
45018 | PICK_UP_CARD | The card is requested by an issuer to be retrieved | |
45020 | NON_SUFFICIENT_FUNDS | Not sufficient funds / exceeds limit | |
45025 | ACCOUNT_SURPASSED_LIMIT | The account exceeds limit (daily limit, weekly limit, amount of transactions count, amount of single transaction and so on) | |
45065 | CUSTOMER_AUTHENTICATION_REQUIRED | By providing this code, the issuing bank indicates that this transaction could be accepted if it's repeated with Strong Customer Authentication | |
45100 | TRANSACTION_EXCEEDS_LIMIT | Transaction's amount is above limit | |
45300 | SECURITY_VIOLATION | x | Security violation |
45340 | ALLOWABLE_VERIFICATION_TRIES_EXCEEDED | Allowable retries exceeded | |
45341 | NOT_AUTHENTICATED_ACCOUNT | Password or pin/verification code does not match | |
45400 | RISK_MANAGEMENT_VIOLATION | x | Declined by risk management system |
45405 | RISK_VELOCITY_CHECK | x | Declined by velocity check |
45406 | RISK_REPEATED_REVERSALS | x | Declined because of often payment reversals from customer side |
45407 | RISK_AVS_CHECK | x | Declined by Address Verification System |
45408 | RISK_SUSPECTED_FRAUD | Suspected fraud | |
45409 | RISK_BLACKLIST | Account or customer is blacklisted | |
50000 | VALIDATION_ERROR | x | Data (not necessarily account-related) does not pass the validation logic, feedback is without specific information |
50005 | INVALID_DATA | x | Invalid data not necessarily account-related |
50010 | INVALID_BANK_CODE | Invalid bank code | |
50011 | INVALID_BANK_NAME | Invalid bank name | |
50012 | INVALID_IBAN | Invalid IBAN | |
50013 | INVALID_BIC | Invalid BIC | |
50015 | INVALID_ACCOUNT_NUMBER | Invalid account number | |
50016 | INVALID_HOLDER_NAME | Invalid holder name | |
50017 | INVALID_BANK_LOCATION | Invalid bank location | |
50018 | INVALID_BANK_LOCATION_ID | Invalid bank location ID | |
50030 | INVALID_COUNTRY | Invalid country | |
50050 | INVALID_EXPIRY_MONTH | Invalid expiry month | |
50051 | INVALID_EXPIRY_YEAR | Invalid expiry year | |
50052 | INVALID_EXPIRY_DATE | Invalid expiry date | |
50053 | INVALID_VERIFICATION_CODE | Invalid verification code | |
50070 | INVALID_LOGIN | Invalid login | |
50080 | INVALID_PASSWORD | Invalid password | |
50081 | INVALID_AUTHENTICATION_DATA | Invalid 3d secure or other authentication data | |
50095 | INVALID_EMAIL | Invalid email | |
50100 | INVALID_AMOUNT_FORMAT | Invalid amount format | |
50105 | INVALID_URL_FORMAT | Invalid URL format | |
50120 | INVALID_CURRENCY_FORMAT | Invalid currency format | |
50130 | INVALID_REFERENCE_TEXT | Invalid reference text | |
50170 | INVALID_BILLING_ADDRESS | Invalid billing address | |
50190 | INVALID_SHIPPING_ADDRESS | Invalid shipping address | |
50300 | INVALID_PRODUCT_INFO | Invalid product info | |
51005 | MISSING_DATA | x | Missing data (not necessarily account-related), no details given |
51009 | MISSING_ACCOUNT | No account information provided | |
51010 | MISSING_BANK_CODE | Missing bank code | |
51011 | MISSING_BANK_NAME | Missing bank name | |
51012 | MISSING_IBAN | Missing IBAN | |
51013 | MISSING_BIC | Missing BIC | |
51015 | MISSING_ACCOUNT_NUMBER | Missing account number | |
51016 | MISSING_HOLDER_NAME | Missing holder name | |
51017 | MISSING_BANK_LOCATION | Missing bank location | |
51018 | MISSING_BANK_LOCATION_ID | Missing bank location ID | |
51030 | MISSING_COUNTRY | Missing country | |
51050 | MISSING_EXPIRY_MONTH | Missing expiry month | |
51051 | MISSING_EXPIRY_YEAR | Missing expiry year | |
51052 | MISSING_EXPIRY_DATE | Missing expiry date | |
51053 | MISSING_VERIFICATION_CODE | Missing verification code | |
51070 | MISSING_LOGIN | Missing login | |
51080 | MISSING_PASSWORD | Missing password | |
51081 | MISSING_AUTHENTICATION_DATA | Missing 3d secure or other authentication data | |
51095 | MISSING_EMAIL | Missing email address | |
51100 | MISSING_AMOUNT | Missing amount | |
51120 | MISSING_CURRENCY | Missing currency | |
51130 | MISSING_REFERENCE_TEXT | Missing reference text | |
51170 | MISSING_BILLING_ADDRESS | Missing billing address | |
51190 | MISSING_SHIPPING_ADDRESS | Missing shipping address | |
51300 | MISSING_PRODUCT_INFO | Missing product (shopping cart) info | |
99999 | UNKNOWN | x | Result code is unknown |
Please note that we don't keep track of updates of PSP test card numbers, so the table below might not be entirely correct. To avoid false negatives while testing, please make sure that you get the PSPs' Sandbox test numbers directly from their developer documentation. We link some of these PSPs docs directly under their name in the table below.
Test Account Numbers Credit CardsPayment Service Provider | VISA | MasterCard | American Express | Diner's Club | Discover | Maestro |
---|---|---|---|---|---|---|
Holder name: any Expiry date: 10 / 2020 CVV: 737 Number: 4111 1111 1111 1111 |
Holder name: any Expiry date: 03 / 2030 CVC: 737 Number: 5555 5555 5555 4444 |
Holder name: any Expiry date: 03 / 2030 CID: 7373 Number: 3700 0000 0000 002 |
Holder name: any Expiry date: 03 / 2030 CVV2: 737 Number: 3600 6666 3333 44 |
Holder name: any Expiry date: 03 / 2030 CID: 737 Number: 6011 6011 6011 6611 |
Holder name: any Expiry date: 03 / 2030 CVC: 737 Number: 6771 7980 2100 0008 |
|
Authorize.Net |
Holder name: any Expiry date: any date in the future CVV: any 3-digit number Number: 4007000000027 |
Holder name: any Expiry date: any date in the future CID: any valid 4-digit number Number: 370000000000002 |
Holder name: any Expiry date: any date in the future CVV2: any valid 3-digit number Number: 38000000000006 |
Holder name: any Expiry date: any date in the future CID: any 3-digit number Number: 6011000000000012 |
||
Concardis |
Holder name: any Expiry date: 12 / 2020 CVV: 123 Number.: 4111 1111 1111 1111 |
Holder name: any |
||||
eMerchantPay |
Holder name: any Expiry date: 01 / 2020 CVV: 123 Number.: 4111 1111 1111 1111 |
Holder name: any Expiry date: 01 / 2020 CVV: 123 Number.: 5500 0000 0000 0004 |
||||
Ingenico |
Holder name: any, min. 3 chars Expiry date: any date in the future CVV: any 3-digit number Number e.g.: 4111 1111 1111 1111 |
Holder name: any, min. 3 chars Expiry date: any date in the future CVV: any 3-digit number Number e.g.: 5544444444444444 |
||||
optile Dummy Adapter - TestPSP |
Holder name: any, min. 3 chars
Notice: For 3D Secure tests, use 4111 1111 1111 1301 card number. |
Holder name: any, min. 3 chars
5102 8819 1749 4147 -> |
Holder name: any, min. 3 chars Expiry date: any date in the future CVV: any 4-digit number Numbers e.g.: 3400 0000 0000 009 3700 0000 0000 002 |
Holder name: any, min. 3 chars Expiry date: any date in the future CVV: any 3-digit number Number: 3000 0000 0000 04 |
Holder name: any, min. 3 chars Expiry date: any date in the future CVV: any 3-digit number Number: 6011 0000 0000 0004 |
|
Secure Trading |
Holder name: any Number.: 4111 1100 0000 0112 (decline) |
Holder name: any Number.: 5100 0000 0000 0412 (decline) |
||||
Six |
Holder name: any Expiry date: 12 / 2018 CVV: 596 Number: 4761 7390 9000 0088 |
Holder name: any Expiry date: 12 / 2025 CVV: 123 Number: 5413 3300 8960 0010 |
||||
Telecash/IPG |
Holder name: any Expiry date: 10 / 2020 CVV: 123 Number: 4035874000424977 |
Holder name: any Expiry date: 10 / 2020 CVV: 123 Number: 5426064000424979 |
||||
Wirecard |
Holder name: any Expiry date: any date in the future CVV: any 3-digit number Number: 4200000000000000 |
Holder name: any Expiry date: any date in the future CVC: any 3-digit number Number: 5500000000000000 |
Holder name: any Expiry date: any date in the future CID: any valid 4-digit number Number: 370000000000000 |
Holder name: any Expiry date: any date in the future CVV2: any valid 3-digit number Number: 38000000000000 |
Holder name: any Expiry date: any date in the future CID: any 3-digit number Number: 6011000000000000 |
Holder name: any Expiry date: any date in the future CVC: any 3-digit number Number: 675940000000000002 |
WorldPay |
Holder name: Sansa Stark, |
Holder name: Tom James, |
PSP | Adyen | Wirecard | Sofortüberweisung | optile dummy adapter |
---|---|---|---|---|
Bank Account |
Account Number: 1234567890 Bank Code: 12345678 Holder name: any |
Account Number: 12345678 Bank Code: 70070010 First Name: John F Last Name: Doe |
Bank Code (BLZ): 88888888 Account Number: any number PIN: any 4-digit number |
Account Number e.g.: 1111111 or
IBAN: DE02120300000000202051 |
The Payment Gateway enables you to test the "happy path" (a success is returned) as well as negative responses (e.g. denials) and edge cases (e.g. chargebacks) in the Sandbox environment. You configure the "Test Adapter" (TESTPSP) in the background and this way you can easily double check the integration of your system(s) with the Payment Gateway.
The Test Adapter will respond according to the given holder name or payment amount and therefore confront your system with different kinds of behavior as shown in the table below. Note that depending on the features you implemented you only need a subset of test cases. The features are identified by codes that are also used throughout the documentation.
The provided cases are primarily intended for automated testing. For 'redirect' methods such as PayPal there will be a redirect to a dummy page provided, containing buttons that simulate different behavior (successful payment or customer abort). However, they only take reliably effect if none of the following test cases are used. If one of these test cases is triggered, no button should be clicked, and the OPG will proceed with status transitions according to the test case. Clicking a button in this case would result in conflicting status notifications.
Notes:Test Case # | Holder Name | Payment Amount | Feature Code | Test Case Name | sync or async provider | Test Steps / Calls | Charge statusCode (reasonCode) result(s) | Charge interaction Code (Reason) result(s) | Note |
---|---|---|---|---|---|---|---|---|---|
101 | testcase 101 | 1,01 | CHECKOUT | Successful frontend payment | sync | LIST, CHARGE | charged | PROCEED (OK) | |
102 | testcase 102 | 1,02 | CHECKOUT | Failed frontend payment (technical reason) | sync | LIST, CHARGE | failed | TRY OTHER NETWORK | (1) |
103 | testcase 103 | 1,03 | CHECKOUT | Declined frontend payment (business reason) | sync | LIST, CHARGE | declined | RETRY | (1) |
104 | testcase 104 | 1,04 | CHECKOUT | Successful frontend payment | async | LIST, CHARGE | pending, charged | PROCEED (PENDING), PROCEED (OK) | |
105 | testcase 105 | 1,05 | CHECKOUT | Failed frontend payment (technical reason) | async | LIST, CHARGE | pending, failed | PROCEED (PENDING), ABORT | |
106 | testcase 106 | 1,06 | CHECKOUT | Declined frontend payment (business reason) | async | LIST, CHARGE | pending, declined | PROCEED (PENDING), RETRY | |
107 | testcase 107 | 1,07 | CHECKOUT | Payment expired due to customer inactivity (e.g., prepayment not done, actions on redirected page not completed) | async | LIST, CHARGE | pending, aborted | PROCEED (PENDING), ABORT | |
108 | testcase 108 | 1,08 | CHARGEBACK | - |
LIST, CHARGE,charged_back |
CHARGE success, charged_back | PROCEED (OK) | ||
109 | testcase 109 | 1,09 | CHARGEBACK | - | LIST, CHARGE, information_requested, charged_back | CHARGE success, information_requested, charged_back | PROCEED (OK) | ||
110 | testcase 110 | 1,10 | CHARGEBACK | - | LIST, CHARGE, information_requested, charged | CHARGE success, information_requested, charged (debited) | PROCEED (OK) | ||
111 | testcase 111 | 1,11 | REDIR | Customer gets redirected for frontend charge and completes payment | async | LIST, CHARGE | pending, charged | PROCEED (PENDING), PROCEED (OK) | (2) |
112 | testcase 112 | 1,12 | REDIR | Customer gets redirected for frontend charge and cancels payment | async | LIST, CHARGE | pending, aborted | PROCEED (PENDING), ABORT | |
119 | testcase 119 | 1,19 | CHARGEBACK | - |
LIST, CHARGE, charged_back, charged |
CHARGE success, charged_back, charged (debited) |
PROCEED (OK) | ||
120 | testcase 120 | 1,20 | CHECKOUT | Failed frontend payment (business reason) | sync | LIST, CHARGE | failed | TRY_OTHER_NETWORK | |
121 | testcase 121 | 1,21 | CHECKOUT | sync |
LIST, CHARGE |
failed | TRY_OTHER_ACCOUNT | ||
122 | testcase 122 | 1,22 | CHARGEBACK | - | LIST, CHARGE, information_requested, charged_back |
CHARGE success, information_requested (notification_of_chargeback), charged_back |
PROCEED (OK) |
(1) In real world the interaction code could vary
(2) Outcome depends on customer interaction, which can also be done with the provided test adapter. Redirections always mean asynchronous communication.
Test Case # | Holder Name | Payment Amount | Feature Code | Test Case Name | sync or async provider | Test Steps / Calls | Charge statusCode result(s) | Charge interaction Code (Reason) result(s) | Note |
---|---|---|---|---|---|---|---|---|---|
201 | testcase 201 | 2,01 | PAYOUT-TX | Successful full refund of a previous transaction | sync | LIST, CHARGE, PAYOUT | charged, paid_out | PROCEED (OK), PROCEED (OK) | |
202 | testcase 202 | 2,02 | PAYOUT-TX | Failed full refund of a previous transaction | sync | LIST, CHARGE, PAYOUT | charged, failed |
PROCEED (OK), RETRY |
|
203 | testcase 203 | 2,03 | PAYOUT-TX | Declined full refund of a previous transaction | sync | LIST, CHARGE, PAYOUT | charged, declined | PROCEED (OK), RETRY | |
204 | testcase 204 | 2,04 | PAYOUT-TX | Successful full refund of a previous transaction | async | LIST, CHARGE, PAYOUT | pending, charged, pending, paid_out | PROCEED (PENDING), PROCEED (OK), PROCEED (PENDING), PROCEED (OK) | |
205 | testcase 205 | 2,05 | PAYOUT-TX | Failed full refund of a previous transaction | async | LIST, CHARGE, PAYOUT | pending, charged, pending, failed |
PROCEED (PENDING), PROCEED (OK), PROCEED (PENDING), ABORT |
|
206 | testcase 206 | 2,06 | PAYOUT-TX | Declined full refund of a previous transaction | async | LIST, CHARGE, PAYOUT | pending, charged, pending, declined |
PROCEED (PENDING), PROCEED (OK), PROCEED (PENDING), RETRY |
|
207 | testcase 207 | 2,07 | PAYOUT-PART | Successful partial refund of a previous transaction | sync | LIST, CHARGE, PAYOUT(partial) | charged, paid_out | PROCEED (OK), | |
208 | testcase 208 | 2,08 | PAYOUT-PART | Failed partial refund of a previous transaction | sync | LIST, CHARGE, PAYOUT(partial) | charged, failed | PROCEED (OK), | |
209 | testcase 209 | 2,09 | PAYOUT-PART | Declined partial refund of a previous transaction | sync | LIST, CHARGE, PAYOUT(partial) | charged, declined | PROCEED (OK), RETRY | |
210 | testcase 210 | 2,10 | PAYOUT-PART | Successful partial refund of a previous transaction | async | LIST, CHARGE, PAYOUT(partial) | pending, charged, pending, paid_out | PROCEED (OK), | |
211 | testcase 211 | 2,11 | PAYOUT-PART | Failed partial refund of a previous transaction | async | LIST, CHARGE, PAYOUT(partial) | pending, charged, pending, failed | PROCEED (OK), | |
212 | testcase 212 | 2,12 | PAYOUT-PART | Declined partial refund of a previous transaction | async | LIST, CHARGE, PAYOUT(partial) | pending, charged, pending, declined | PROCEED (OK), RETRY | |
213 | testcase 213 | 2,13 | PAYOUT-MULTI | Successful multiple refund of a previous transaction | sync | LIST, CHARGE, PAYOUT(partial), PAYOUT (full) | charged, paid_out | PROCEED (OK), | |
214 | testcase 214 | 2,14 | PAYOUT-MULTI | Failed multiple refund of a previous transaction | sync | LIST, CHARGE, PAYOUT(partial), PAYOUT (full) | charged, failed | PROCEED (OK), ABORT | |
215 | testcase 215 | 2,15 | PAYOUT-MULTI | Declined multiple refund of a previous transaction | sync | LIST, CHARGE, PAYOUT(partial), PAYOUT (full) | charged, declined | PROCEED (OK), RETRY | |
216 | testcase 216 | 2,16 | PAYOUT-MULTI | Successful multiple refund of a previous transaction | async | LIST, CHARGE, PAYOUT(partial), PAYOUT (full) | pending, charged, pending, paid_out | PROCEED (OK), | |
217 | testcase 217 | 2,17 | PAYOUT-MULTI | Failed multiple refund of a previous transaction | async | LIST, CHARGE, PAYOUT(partial), PAYOUT (full) | pending, charged, pending, failed | PROCEED (OK), ABORT | |
218 | testcase 218 | 2,18 | PAYOUT-MULTI | Declined multiple refund of a previous transaction | async | LIST, CHARGE, PAYOUT(partial), PAYOUT (full) | pending, charged, pending, declined | PROCEED (OK), RETRY |
Make sure to differentiate between the initial CHARGE and subsequent CLOSING transactions.
Test Case # | Holder Name | Payment Amount | Feature Code | Test Case Name | sync or async provider | Test Steps / Calls | CHARGE / CLOSING statusCode result(s) | CHARGE / CLOSING interaction Code (Reason) reply | Note |
---|---|---|---|---|---|---|---|---|---|
401 | testcase 401 | 4,01 | DEFER | Successful deferred Charge | sync | LIST, CHARGE | CHARGE: preauthorized | PROCEED (OK) | Credit Cards or Direct Debit |
402 | testcase 402 | 4,02 | DEFER | Failed deferred Charge | sync | LIST, CHARGE | CHARGE: failed | ABORT | |
403 | testcase 403 | 4,03 | DEFER | Declined deferred Charge | sync | LIST, CHARGE | CHARGE: declined | RETRY | |
404 | testcase 404 | 4,04 | DEFER | Successful deferred Charge | async | LIST, CHARGE | CHARGE: pending, preauthorized | PROCEED (PENDING) | |
405 | testcase 405 | 4,05 | DEFER | Failed deferred Charge | async | LIST, CHARGE | CHARGE: pending, failed | PROCEED (PENDING) | |
406 | testcase 406 | 4,06 | DEFER | Declined deferred Charge | async | LIST, CHARGE | CHARGE: pending, declined | PROCEED (PENDING) | |
407 | testcase 407 | 4,07 | DEFER | Demanded Charge (e.g., prepayment) that expired due to missing customer payment | async | LIST, CHARGE | CHARGE: pending, request_expired | PROCEED (PENDING) | |
408 | testcase 408 | 4,08 | REDIR & DEFER | Successful deferred Charge with Redirect network | async | LIST, CHARGE | CHARGE: pending, preauthorized | PROCEED (PENDING) | e.g., PayPal |
409 | testcase 409 | 4,09 | DEFER | Deferred Charge that expired due to missing Closing | sync | LIST, CHARGE | CHARGE: preauthorized, preauthorization_expired | PROCEED (OK) | |
410 | testcase 410 | 4,10 | DEFER | Successful deferred Charge and full Closing | sync | LIST, CHARGE, CLOSING (full) | CLOSING: charged | PROCEED (OK) | |
411 | testcase 411 | 4,11 | DEFER | Successful deferred Charge, failed full Closing | sync | LIST, CHARGE, CLOSING (full) | CLOSING: failed | ABORT | |
412 | testcase 412 | 4,12 | DEFER | Successful deferred Charge, declined full Closing | sync | LIST, CHARGE, CLOSING (full) | CLOSING: declined | RETRY | |
413 | testcase 413 | 4,13 | DEFER | Successful deferred Charge and full Closing | async | LIST, CHARGE, CLOSING (full) | CLOSING: pending, charged | PROCEED (PENDING) | |
414 | testcase 414 | 4,14 | DEFER | Successful deferred Charge, failed full Closing | async | LIST, CHARGE, CLOSING (full) | CLOSING: pending, failed | PROCEED (PENDING) | |
415 | testcase 415 | 4,15 | DEFER | Successful deferred Charge, declined full Closing | async | LIST, CHARGE, CLOSING (full) | CLOSING: pending, declined | PROCEED (PENDING) |
(1) CANCEL is not a transaction itself, therefore the status of the corresponding CHARGE will be affected and returned.
Testcases for Recurring Charges:Test Case # | Holder Name | Payment Amount | Feature Code | Test Case Name | s/a | Test Steps / Calls | statusCode result(s) | CHARGE interaction Code (Reason) reply |
---|---|---|---|---|---|---|---|---|
701 | testcase 701 | 7,01 | RECUR | Successful Recurring Registration | sync | LIST, CHARGE, CHARGE (recurring) |
Init. CHARGE: charged CUSTOMER: registered Rec. CHARGE: charged |
Init. CHARGE: PROCEED (OK) Rec. CHARGE: PROCEED (OK) |
Test Case # | Holder Name | Payment Amount | Feature Code | Test Case Name | s/a | Test Steps / Calls | statusCode result(s) | CHARGE interaction Code (Reason) reply |
---|---|---|---|---|---|---|---|---|
751 | testcase 751 | 7,51 | REG | Successful Standalone Registration | async | LIST for Update, REGISTRATION |
Init. REGISTRATION: pending, CUSTOMER: registered |
Initial REGISTRATION: PROCEED (PENDING), Final: PROCEED (OK) |
When running this test, please send a LIST request for account registration, and register the account setting the parameter autoRegistration = true
.
Please note that these cases are applicable only to recurring charges.
Test Case # | Holder Name | Payment Amount | Feature Code | Test Case Name | s/a | Test Steps / Calls | statusCode result(s) | CHARGE interaction Code (Reason) reply |
---|---|---|---|---|---|---|---|---|
801 | testcase 801 | 8,01 | ADM-EXCEEDS_LIMIT | sync |
Recurring CHARGE, (ADM - failed), (ADM - failed), (ADM - successful) |
CHARGE failed, CHARGE failed, CHARGE success |
ADM for interaction RETRY - EXCEEDS_LIMIT |
|
802 | testcase 802 | 8,02 | ADM-EXCEEDS_LIMIT-FAILED | sync |
Recurring CHARGE, (ADM - failed), (ADM - failed), (ADM - failed) |
CHARGE failed | ADM for interaction RETRY - EXCEEDS_LIMIT | |
803 | testcase 803 | 8,03 | ADM-DECLINED | sync |
Recurring CHARGE, (ADM - failed), (ADM - failed), (ADM - successful) |
CHARGE failed, CHARGE failed, CHARGE success | ADM for interaction RETRY - DECLINED | |
804 | testcase 804 | 8,04 | ADM-DECLINED-FAILED | sync |
Recurring CHARGE, (ADM - failed), (ADM - failed), (ADM - failed) |
CHARGE failed | ADM for interaction RETRY - DECLINED | |
805 | testcase 805 | 8,05 | ADM-TEMPORARY_FAILURE | sync |
Recurring CHARGE => (ADM - failed) => (ADM - failed) => (ADM - successful)
|
CHARGE failed, CHARGE failed, CHARGE success | ADM for interaction RETRY - TEMPORARY_FAILURE | |
806 | testcase 806 | 8,06 | ADM-TEMPORARY_FAILURE-FAILED | sync |
Recurring. CHARGE => (ADM - failed) => (ADM - failed) => (ADM - failed)
|
CHARGE failed | ADM for interaction RETRY - TEMPORARY_FAILURE |
Here you find abstract definitions of the OPG payment APIs in OpenAPI ("Swagger") 2.0 format using YAML. You can use them to generate models for your API clients in a multitude of programming langues, for example Go, Java, NodeJS, Objective C, Perl, PHP, Python, Ruby, and others, using swagger-codegen.
Note there is a difference in models between the "server-to-server" communication, where your server makes requests against OPG (for example the LIST request), and the "client-to-server" communication, where a web browser or mobile app makes requests (for example the CHARGE request).
Notes:
Please find available for download:
To check our FAQs, please refer to our Help Center.
Make sure to register or sign in to the Help Center to be able to view the protected FAQs. If you could not find an answer to your question, please do not hesitate to submit a ticket with your request.
Payment Page
Static Payment Page: Most merchants have a "hard-coded" payment page, or their own selection logic which payment methods should be displayed for an individual transaction. We assume that this is typically handled on server-side. Provider routing is rarely implemented.
Use routing recommendations only: With the help of optile's Open Routing feature the legacy payment page can be used, only the decision which provider should be used for a payment request would be based on optile's LIST response. This requires all payment method forms and provider integrations to be already present on your system.
Static Redirects / Static Forms with Standalone Charge: The legacy payment page will be used, so the payment page forms and selection has to be present on your side. However, the payment request for all or some payment methods will be done through optile, using the Standalone Charge feature. This is very simple for methods that require a redirect only. For others the corresponding input forms have to be accurately modeled. In any case the configured possibilities on optile side need to always support the choices presented by the merchant's payment page. As a result there is a technical provider-independence for the optile methods, but the payment page is still limited to the methods coded in your system, so it's not "implement once".
Server-side method list merge: Here optile is configured to offer payment methods that complement the legacy ones. Therefore optile's LIST response will be read out and its methods merged with those that are still handled by your system. This requires visual merge logic and a case distinction on submit on your system, but if generically implemented this is already an "implement once" solution, meaning you could add methods and providers on runtime. Also, a step-by-step migration of legacy payment methods is easy, because you can remove your system's representation of them on the page and add them to optile instead.
Hybrid Page with AJAX library: Also in this approach there will be a mix of legacy and optile methods on the payment page. Instead of orchestrating this on your server-side, however, the optile AJAX library will inject the optile methods on client side and will also support the distinction on submit (going to legacy or optile endpoints). Therefore it requires only minimum implementation effort on your side. See the separate chapter on Hybrid AJAX page for a detailed documentation.
Own page intelligence / scoring: You are basically using optile's dynamic payment page. However, you employ your own (or third-party) customer scoring system to dynamically select the payment methods to be shown for each session. You can use this information to further limit the optile-originating set of methods via the preselection.networkCodes
array in the LIST request. In theory you could also filter on your side, but for integration scenarios that have a CHARGE request directly from the client this leaves a loophole for proficient end customers to still other methods. If you also employ your own provider routing logic, you can continue doing so by using optile's Open Routing functionality and override optile's routing recommendation in the CHARGE request. In any case.
Fail-over payment page: You are using optile's full payment page (optionally with the aforementioned own intelligence or scoring), but you keep your legacy page as a fallback, which you could switch to in case optile was not reachable at some point (which you will hopefully and probably not experience).
The idea is that you keep your existing method list, submit button, and logic. With the help of optile's AJAX library you append optile's methods to your payment method list (visually). For optile's methods you use optile's submit logic (also through the AJAX library) which is technically triggered by a separate submit button. However both, your legacy submit button and optile's submit button have exactly the same look and they get interchanged automatically and without user's notice, depending on which payment method is selected. If you prefer, you can also extend your existing submit button instead of having two that interchange. See the variation below step 4 for that option.
In any case you can keep your existing methods and logic. The addition of optile methods will only require one small change in your HTML (step 1), one server-side initialization call against OPG (step 2), and one JavaScript callback function from your side (step 3). Step 4 depends on which buttons you want to use. The rest will be taken care of by optile's AJAX library.
Step-by-Step Guide
1. Integrate optile's payment method list (through its AJAX library)
Integrate optile's AJAX library into your payment page as described in AJAX Library Integration. The placeholder for optile's methods (the paymentNetworks
div
) should go directly below (or above) your existing method list. Adjust the CSS in a way that the optile method list appears in the same visual style as your legacy list. Hide the radio buttons, add borders, add highlight styling for selected entries using CSS selectors, adjust sizes or whatever else is necessary. In the initialization call to the library add the deselectLegacyMethods
function (see step 3.2). The payButton
ID identifies the button that should be used for optile methods, not your legacy button (see step 4 how you can spare this and extend your own button instead).
2. Integrate optile's LIST request
In order to initialize optile's payment method list your system first needs to make an (authenticated) LIST request from your server-side to the OPG. This should happen when the user navigates to the payment page. As integration
parameter you can use SELECTIVE_NATIVE
or DISPLAY_NATIVE
.
The resulting LIST ID should then be used to initialize the AJAX library on the payment page. The library will in turn fetch the payment methods available through optile's logic and the corresponding resources, and will render the list into the placeholder from step 1.
3. Combine the lists
Currently we have two lists which both can have one payment method selected. Now we need some logic that makes sure that in total only one method can be selected. This means when the user selects a method in one list, any potential selection in the other list has to be removed.
Let's look at the two cases:
3.1. If the user selects any method from the legacy list make your system call this function in optile's AJAX library: deselectDynamicMethods()
- This will remove any existing selection in the optile list. Also it will hide optile's submit button, if it was given in the initialization. Make sure you show your legacy button in this case.
3.2. If the user selects any method from the optile list, the AJAX library will detect this and invoke the deselectLegacyMethods
callback function that you indicated in the init call. You should implement this function in a way that is deselects all methods in your legacy list and, if applicable, hides your legacy button. optile's AJAX library will automatically show the optile button, if it was given in the init call.
4. The submit button
If you go with the two button option you have a very clear separation of the submit flows. Make sure both buttons look exactly the same and hide the correct one on page load. Detect when the user selects a legacy method, show your legacy button and invoke optile's JavaScript function from 3.1. which will also hide the optile button. Your callback function from 3.2. should in turn hide your legacy button while optile's AJAX library will show the optile button, if given in the init call.
As a result, a click on the currently visible button will either trigger your existing logic, or it will be an optile submit to OPG.
Variation: Extend your submit button
In case you already have a listener to your submit button you could also extend its logic instead of having a separate optile button.
In this variation you don't have a separate optile button on your payment page, and therefore you don't pass the payButton
ID to the AJAX libraries' init call. As a consequence the JavaScript functions from 3.1. and 3.2. would still run as described, making sure only one entry in both payment method lists is selected, but now they don't show or hide submit buttons any more. Reflect this in your implementation of the callback function from 3.2.
The listener on your button, however, now needs to check before data submission, if a legacy or an optile method is selected. In case of a legacy method it can just proceed as before. In case of an optile method it should invoke the AJAX libraries' function: optilePaymentAction()
This will perform a validation first, and on success execute the transaction with OPG. If you want to skip the validation step, you can also call optileOperationAction()
instead (which we don't recommend in general).
As a result, there is one button which invokes a different submission logic, depending on the selected method.
$('#paymentNetworks').checkoutList({
baseUrl: "https://api.sandbox.oscato.com/pci/v1/",
listId: someVariable,
deselectLegacyMethods: yourCallbackFunctionName
});
Since most PSPs require hard-coding on the merchant side to integrate different payment methods, we are often asked:
"How do I integrate this method or this provider?"
The answer for optile is in general: Simply stick to the unified flow of the LIST request and CHARGE request (and others as laid out in this documentation) and let your system listen to the Notifications. optile's API unification will make it work for all methods and providers.
Having said that, there are cases where a method or provider requires LIST attributes that are otherwise optional, or has other specifics regarding their technical integration. We try to give you an overview here. Please note, however, that also payment provider APIs can behave differently for different setups, so thorough testing with all combinations of providers and methods is always required.
Refer to the information provided to comply with the PSD2 directive and upgrade to 3DS 2.
3D Secure 2 is a solution for Strong Customer Authentication (SCA) that is required under PSD2 for card payments. 3D Secure 2 offers extensive fraud protection, user convenience, and technology. It will be effective as of January 1, 2021 (and September 14, 2021 in the UK).
Strong Customer Authentication (SCA) is a set of requirements introduced by the EU Revised Directive on Payment Services (PSD2).
SCA is a multi-factor authentication based on the use of two or more elements categories as possession (something a user owns, such as a mobile phone), inherence (for example biometrics), and knowledge (something a user knows such as passcode). These categories are independent from each other, so that the breach of one form of authentication doesn't compromise the reliability of the others. SCA is designed to protect the confidentiality of the authentication data.
SCA is only required when both the cardholder's issuing bank and the merchant's acquirer are located in the EEA region. If either of these parties is outside the EEA, then the SCA regulation does not apply.
PSD2 (the second Payment Services Directive) is an EU Directive on the regulation of payment services and payment service providers. PSD2 applies to payments in EU/EEA currencies between payment providers in the EU/EEA.
The main benefit of 3D Secure 2 is frictionless flow. For merchants and card issuers it means a far greater opportunity to easily authenticate transactions to identify high risk transactions and reduce fraud.
Customers, on the other hand, can complete the 3D Secure authentication process without being redirected from the checkout flow if the transaction is deemed eligible by their bank.
A merchant provides risk data together with a transaction
An issuer performs risk analysis and decides whether the challenge is required. If the risk is considered to be low, no challenge is presented to customer. If the risk is considerable, the bank will respond with a request for user authentication (the challenge). The challenge can be presented in the customer application, or in the bank's application on a mobile device.
The implementations of 3DS 2 requires merchants and card issuers to exchange more data, including cardholder's key addresses, browser language, or location data. This increase in data shared between merchants and issuers results in better fraud assessments and reduction in the rate of false declines.
Additionally, Visa and Mastercard have both created policies where merchants who utilize 3DS 2 can receive a liability shift in the event of fraud related chargebacks when they attempt authentication.
3DS 1.0 |
3DS 2.0 |
---|---|
Static passwords, security |
Eliminates static passwords |
Relies on browser only |
Supports different |
15 data elements |
150 data elements exchanged |
Guest checkout only |
Supports guest checkout with additional use cases (wallets, tokenization, etc.) |
For the time being, no transaction should be declined by the issuer because 3DS 1 is used.
However, Mastercard Europe will double their authentication fees for 3DS 1. This increased fee will not apply to 3DS 2 as per January 1, 2021.
In October 2021, Visa will remove liability protection from 3DS 1. The liability shift, which protects your customers from fraud, will remain for 3DS2.
In October 2022, Mastercard is planning to sunset 3DS 1. Customers will then need to process all authenticated transactions through 3DS 2 after this date.
Additionally, 3DS1 might not work with recurring and card-on-file transactions. The first transaction in a recurring sequence and card-on-file transactions must go through SCA which is not guaranteed by using 3DS 1. If SCA is not used, the subsequent transactions might fail.
For the mentioned reasons, we highly advise implementing 3DS 2 as soon as possible.
If this is the case, and your acquirer and the issuer are based in the EEA, your transactions might start failing.
If your acquirer is based outside the PSD2 zone, you are fine, and you don't need to integrate any of the 3DS versions.
PSD2 exemptions
There're several exemptions from PSD2. In the following cases transactions may require SCA depending on the issuers' decision and acquirer fraud rates:
These transactions don't require SCA:
To get an exemption for a recurring transaction you need to use the recurring flow with optile. For your initial transaction, use WEB_ORDER with "allowRecurrence": true
and for the following ones transactions channel=RECURRING
with the parameters:"registration": {"id": customerregistrationid,"password": password}
For details, see the example on the right.
Note that the first recurring transaction will require SCA.
To trigger MOTO transaction with OPG, use one of the following values for the channel
parameter in the LIST request:
CALLCENTER_ORDER
MAIL_ORDER
Applied exemption |
Issuer |
Liability |
No exemption |
Does not support 3DS |
Issuer |
Low Value transactions |
Checks if number of transactions < or =5 and accepts exemption |
Acquirer |
No exemption |
Applies low value exemption |
Issuer |
Low Risk transactions |
Accepts exemption |
Acquirer |
No exemption |
Applies low risk exemption |
Issuer |
No exemption |
Performs transaction risk analysis / requests challenge (if proceeding number of low-value transactions =5) |
Issuer |
MIT first or card-on-file |
Requests challenge |
Issuer |
Outside the EU/EEA
If your acquirer and the issuer are based outside the PSD2 zone, you don't need to comply with the PSD2 directive.
Example of the MIT LIST request
{
"transactionId": "optile_test2",
"country": "ES",
"channel": "RECURRING",
"division": "Default",
"customer": {
"number": "136440382",
"email": "bob.lynch@gmail.com",
"birthday": 1535528731146,
"name": {
"firstName": "Joe",
"lastName": "Blow"
},
"registration": {
"id": "5fc8fd078270c916be080d68u",
"password": "2EODNSNUQe6Wu1dq"
}
},
"payment": {
"amount": 1.03,
"currency": "EUR",
"reference": "430003"
},
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"summaryUrl": "https://dev.oscato.com/shop/summary.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl": "https://dev.oscato.com/c05dton"
}
}
Mastercard is planning to decommission 3DS1 in October 2022, while VISA in Europe will remove liability shift starting October 2021. However, each country has their own deadlines for PSD2 migration.
Find out more about PSD 2 deadlines in the selected European countries:
BaFin has recommended to issuers to follow a rump-up plan between mid-January 2021 and mid-March 2021 introducing soft declines as per the instructions below:
Soft declines are officially applied since October 1, 2020 and progressively ramping up until March 31, 2021. The latest communicated transition plan follows the soft-decline timeline below:
Travel and hospitality MCCs are exempted until March 31, 2021.
The transition phase for the introduction of SCA in 2021 looks as per below:
The migration plan target is December 31, 2020.
The soft-decline plan is as follows:
The Danish FSA expects that strong customer authentication will be used for all payments by January 11, 2021 at the latest.
Soft declines have been gradually introduced starting October 2020, and The Dutch National Bank has confirmed the December 31, 2020 as the migration deadline.
In order to upgrade to 3DS 2, you need to provide the issuer with more data so that transaction risk can be assessed. To complete the upgrade process, you'll need to do the following:
Step 1. Add the required parameters
Step 2. Integrate a redirect
Step 3. Activate 3DS 2 in your merchant portal
Step 4. Test the transaction flow on sandbox before rolling out to production.
Step 5. Activate 3DS 2 in your merchant portal for the production environment.
Step 1
Add the required parameters to the LIST and CHARGE requests in optile's API
To ensure frictionless flow, it's a good idea to provide the optional parameters, too. See the optional parameters section for details.
Required parameters per provider
You need to provide all the listed parameters.
If you use optile's payment page, you're good to go, as the parameters marked with Y(es) will be provided for you, and you only need to manually provide the N(o) parameters.
Parameter |
Provided by optile |
|
Y |
|
Y |
|
Y |
|
Y |
|
N |
Parameter |
Provided by optile |
clientInfo.browserScreenHeight |
Y |
clientInfo.browserScreenWidth |
Y |
clientInfo.javaEnabled |
Y |
clientInfo.language |
Y |
clientInfo.timezone |
Y |
|
Y |
|
N |
|
N |
No new required parameters.
No new required parameters.
No new required parameteres.
No new required parameters.
Parameter |
Provided by optile |
(called |
N |
Parameter |
Provided by optile |
|
N |
|
N |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
N |
Parameter |
Provided by optile |
|
N |
|
N |
Parameter |
Provided by optile |
|
only for recurring ( |
N |
|
|
N |
|
only if |
Y |
|
only if |
Y |
|
only if |
Y |
|
only if |
Y |
|
only if |
N | |
only if |
N | |
only if |
Y | |
only if |
Y | |
only if |
Y | |
only if |
Y |
Parameter |
Provided by optile |
Required for native integration; optional for hosted. |
N |
products[].quantity |
N |
Parameter |
Provided by optile |
|
N |
|
N |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
Parameters required only if |
N |
Parameters required only if |
N |
Parameters required only if |
N |
Parameters required only if |
N |
Parameter |
Provided by optile |
|
Y |
|
Y |
|
Y |
|
Y |
clientInfo.browserScreenHeight |
Y |
clientInfo.browserScreenWidth |
Y |
clientInfo.ip | Y |
clientInfo.userAgent | Y |
clientInfo.acceptHeader | N |
callback.returnURL |
N |
customer.addresses.shipping.city | N |
customer.addresses.shipping.country | N |
customer.addresses.shipping.houseNumber | N |
customer.addresses.shipping.street | N |
customer.addresses.shipping.zip | N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
Parameter |
Provided by optile |
callback.returnURL |
N |
Parameter |
Provided by optile |
channel
This parameter is required for recurring transactions. Set the |
N |
Parameter |
Provided by optile |
callback.returnURL |
N |
callback.notificationURL |
N |
For the detailed description of the parameters, see API reference
Integrate the redirect
In 3DS 2 the identity of the end user (a customer) may need to be verified using a passive, biometric, or two-factor authentication approach. The CHARGE response should provide a PSP-generated URL that enables the shopper to complete the required authentication.
You need to pass the redirect object to the frontend and handle the result. When the authentication is performed, the customer will again be redirected to one of your callback URLs from the LIST session.
If you use our native integration, you can use the information to learn how to implement the redirect.
If DDC is required for a transaction, then optile's adapter initiates a corresponding handler page. The handler page receives the 3DS2 redirect from the provider as input. The provider 3DS2 page is passed inside the parameters object in the redirect value.
The URL to the redirect page and the parameters are passed via the GET call to the handler page and the handler page then redirects the browser to the issuer's challenge page.
The URL parameters must be passed as strings. Follow these steps to get the correct redirect URL:
Remove the backslash characters from the JSON parameters redirect.
For example, convert the original response:
"{\"url\":\"
to this:
"{"url":"
2. Encode the cleaned up JSON into a URL. This means all special characters must be percent-encoded.
For example, convert this:
"{"url":
"https://secure-test.worldpay.com/shopper/3ds/ddc.html",
"method":"POST",
"parameters":
[{
"name":"Bin","value":"555555"},
{"name":"JWT",
"value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNTY5NzFiYS1mYjViLTRmYWMtYmY4Mi0zZThlNWRmMzUxMWYiLCJpYXQiOjE2MDc1NTA0MTQsImlzcyI6IjViZDllMGU0NDQ0ZGNlMTUzNDI4Yzk0MCIsIk9yZ1VuaXRJZCI6IjViZDliNTVlNDQ0NDc2MWFjMGFmMWM4MCJ9.ES5ONlEZs3f8Ce_lQUKiwLvxDvuD-x_3_xUkCurFrvU"
},
{"name":"ContinueUrl",
"value":"https%3A%2F%2Fapi.sandbox.oscato.com%2Fdecision%2FWORLDPAY%2FLOTTOLAND%2Fdo.html%3Ftype%3Daccept%26wpProcess%3D5fd145ce8270c91ca105e758c%26wpRedirectType%3Dpending%26source%3Dddc"
}],
"source":"PSP","type":"PROVIDER"}"
into:
%22%7B%22url%22%3A%22https%3A%2F%2Fsecure-test.worldpay.com%2Fshopper%2F3ds%2Fddc.html
%22%2C%22method%22%3A%22POST
%22%2C%22parameters%22%3A%5B%7B%22name%22%3A%22Bin
%22%2C%22value%22%3A%22555555%22%7D%2C%7B%22name%22%3A%22JWT
%22%2C%22value%22%3A%22eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNTY5NzFiYS1mYjViLTRmYWMtYmY4Mi0zZThlNWRmMzUxMWYiLCJpYXQiOjE2MDc1NTA0MTQsImlzcyI6IjViZDllMGU0NDQ0ZGNlMTUzNDI4Yzk0MCIsIk9yZ1VuaXRJZCI6IjViZDliNTVlNDQ0NDc2MWFjMGFmMWM4MCJ9.ES5ONlEZs3f8Ce_lQUKiwLvxDvuD-x_3_xUkCurFrvU
%22%7D%2C%7B%22name%22%3A%22ContinueUrl%22%2C%22value%22%3A%22https%253A%252F%252Fapi.sandbox.oscato.com%252Fdecision%252FWORLDPAY%252FLOTTOLAND%252Fdo.html
%253Ftype%253Daccept%2526wpProcess%253D5fd145ce8270c91ca105e758c%2526wpRedirectType%253Dpending
%2526source%253Dddc%22%7D%5D%2C%22source%22%3A%22PSP
%22%2C%22type%22%3A%22PROVIDER%22%7D%22
3. Append these parameters into the main url and pass a GET request to the handler page:
https://resources.sandbox.oscato.com/3ds2/handler/testpsp?
redirect=%22%7B%22url%22%3A%22https%3A%2F%2Fsecure-test.worldpay.com%2Fshopper%2F3ds%2Fddc.html
%22%2C%22method%22%3A%22POST%22%2C%22parameters%22%3A%5B%7B%22name%22%3A%22Bin
%22%2C%22value%22%3A%22555555%22%7D%2C%7B%22
name%22%3A%22JWT%22%2C%22value%22%3A%22eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNTY5NzFiYS1mYjViLTRmYWMtYmY4Mi0zZThlNWRmMzUxMWYiLCJpYXQiOjE2MDc1NTA0MTQsImlzcyI6IjViZDllMGU0NDQ0ZGNlMTUzNDI4Yzk0MCIsIk9yZ1VuaXRJZCI6IjViZDliNTVlNDQ0NDc2MWFjMGFmMWM4MCJ9.ES5ONlEZs3f8Ce_lQUKiwLvxDvuD-x_3_xUkCurFrvU
%22%7D%2C%7B%22name%22%3A%22ContinueUrl%22%2C%22value%22%3A%22https%253A%252F%252Fapi.sandbox.oscato.com%252Fdecision%252FWORLDPAY%252FLOTTOLAND%252Fdo.html%253Ftype%253Daccept%2526wpProcess%253D5fd145ce8270c91ca105e758c%2526wpRedirectType%253Dpending%2526source%253Dddc
%22%7D%5D%2C%22source%22%3A%22PSP%22%2C%22type%22%3A%22PROVIDER%22%7D%22&language=en_EN
This will bring the handler page with the provider's 3DS2 redirect in it. The handler page redirects the browser to the issuer's challenge page fort he customer to enter the challenge information.
The issuer's challenge page then redirects the customer to the optile's decision endpoint, which, in turn, redirects the customer to your success page or the failure page.
{
"resultInfo": "Operation successful.",
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"redirect": {
"url": "https://dev.oscato.com/shop/success.html",
"method": "GET",
"parameters": [
{
"name": "shortId",
"value": "11926-61834"
},
{
"name": "interactionReason",
"value": "OK"
},
{
"name": "resultCode",
"value": "00000.PAYBOX.00000"
},
{
"name": "longId",
"value": "5fd0af7bc4ffdf5b3caa7212c"
},
{
"name": "transactionId",
"value": "AL_1607511931226"
},
{
"name": "interactionCode",
"value": "PROCEED"
},
{
"name": "amount",
"value": "19.99"
},
{
"name": "reference",
"value": "Shop 101/20-03-2016"
},
{
"name": "currency",
"value": "EUR"
}
],
"type": "RETURN"
},
"links": {
"redirect": "https://api.sandbox.oscato.com/redirect/5fd0af7b41f95e7ed2c8cd75lsqmo3aebrmeqbmd87fgs0ph08/5fd0af7bc4ffdf5b3caa7212c"
}
}
{
"resultInfo": "Pending, you have to check the status later",
"interaction": {
"code": "PROCEED",
"reason": "PENDING"
},
"redirect": {
"url": "https://3ds-test.wirecard.com/acs?reqid=A4DFFD8EA86C108709B02D9B23AC263CEEA60C8C",
"method": "POST",
"parameters": [
{
"name": "PaReq",
"value": "eJxtkEFLAzEQhf/Lnt3tJDubTXptEQoKYqteepnNzLYL3UaTqIj43w30UBCv37w37/G+qwNleaGvXaRz2qyrZbWyDgGNMtB34LAzfd+Dqm6qOR2eJaYpnItKNVBQlLd3SXk3zbI5T3miUzlp0KA0OAUalS2qfIwi6+1W4ofEa9DILY3eSW099jWOoGqHw1CzAKOQc+KxuB9iyMGH0zVbN5d08unp8a6AY86vablf7BctpzqXRs3nFMVT5MaHeb8oymuPe8nHwBfnX7qmTAU7HkSzsYgsMo7AjEOnUMB2rRIP2rXInvE/+22Ic3nRGmoZoBN0KAaGgYDQ2DKKBfJaVz+/TP9zFQ=="
},
{
"name": "TermUrl",
"value": "https://api.sandbox.oscato.com/decision/WIRECARD/DEMO_AL/do.html?type=accept&requestId=5fd0a5d01dab521d2296f2b9c"
},
{
"name": "MD",
"value": ""
}
],
"suppressIFrame": true,
"type": "PROVIDER"
},
"links": {
"redirect": "https://api.sandbox.oscato.com/redirect/5fd0a5d041f95e7ed2c8caf8lt1s6p38i44co3b8jvub7uif1c/5fd0a5d0c4ffdf5b3caa6fc6c"
}
}
The redirect is a standard browser redirect to a new page. It's not different from a standard redirect used in optile to redirect to the payment page or a success page.
If you're using optile pure native integration, the specifics of your redirect integration will depend on the frontend. Refer to https://www.optile.io/reference#operation/payWithPaymentNetwork for the detailed description of the redirect object.
Step 3
When you're tests have succeeded and you're ready to start using 3DS 2, you need to activate it in the merchant portal.
Go to your merchant portal.
Open Provider Contracts.
Select an existing contract with a provider or create a new contract.
For the 3DS2_SUPPORTED option select True.
Step 4
Use your PSP test accounts to test the integration. For example:
|
Step 5
Go live
Repeat step 3 for the production environment and try the integration in production.
Example of a CHARGE request response:
"redirect":
{ "url": "https://resources.sandbox.oscato.com/3ds2/handler/worldpay",
"method": "GET",
"parameters": [
{
"name": "redirect",
"value": "{\"url\":\"https://secure-test.worldpay.com/shopper/3ds/ddc.html\",
\"method\":\"POST\",\"parameters\":[{\"name\":\"Bin\",\"value\":\"555555\"
},
{
\"name\":\"JWT\",
\"value\":
\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNTY5NzFiYS1mYjViLTRmYWMtYmY4Mi0zZThlNWRmMzUxMWYiLCJpYXQiOjE2MDc1NTA0MTQsImlzcyI6IjViZDllMGU0NDQ0ZGNlMTUzNDI4Yzk0MCIsIk9yZ1VuaXRJZCI6IjViZDliNTVlNDQ0NDc2MWFjMGFmMWM4MCJ9.ES5ONlEZs3f8Ce_lQUKiwLvxDvuD-x_3_xUkCurFrvU\"
},
{
\"name\":\"ContinueUrl\",\"value\":\"https%3A%2F%2Fapi.sandbox.oscato.com%2Fdecision%2FWORLDPAY%2FLOTTOLAND%2Fdo.html%3Ftype%3Daccept%26wpProcess%3D5fd145ce8270c91ca105e758c%26wpRedirectType%3Dpending%26source%3Dddc\"}],
\"source\":\"PSP\",\"type\":\"PROVIDER\"
}"
},
{
"name": "language", "value": "en_EN"
}
],
"type": "3DS2-HANDLER"
}
You can form the URL with this algorithm:
var redirectUrl = response.redirect.url;
foreach (param in response.redirect.parameters) {
if (redirectUrl.contains("?")) {
redirectUrl += "&";
} else {
redirectUrl += "?";
} /// THE ONLY CORRECT WAY
redirectUrl += urlencode(param.name) +
"=" + urlencode(param.value)
}
3DS 2 doesn't change the user flow for transactions. From the customer's perspective, the only thing that is different is the spinner that shows during the device data collection phase (after the CHARGE response has been generated). This is when the transaction data is being verified and the decision is made whether or not a challenge should be presented.
From the technical perspective, the flow is the same for all integration types.
A merchant sends the LIST request with the required parameters.
optile's payment page, or merchant's frontend, or merchant's backend sends the CHARGE request.
The payment provider communicates to optile what should happen next:
Device data collection (DDC -collection of information about a remote device for the purpose of identification)
3DS challenge
transaction completion (success/failure)
After DDC results have been submitted, the payment provider communicates next steps to optile (challenge/no challenge). The customer is redirected either to "Thank you" page, provided by merchant, or to 3DS challenge page. After completion of the challenge, the customer is redirected to the merchant's success or failure page.
For 3DS 2 we use standard optile notifications. For details, see Interaction codes.
We advise you not to leave these fields blank or fill them with static information. If a field is optional, leave it blank and don't populate it with a space. Additionally, make sure the Merchant Category Code (MCC) is correctly filled in and reflects your business.
To ensure a frictionales flow, it's a good idea to provide optional parameters as well as the required ones.It also speeds up the process of adding a new provider because most likely they'll need the same set of parameters to be passed.
You need to provide all the listed parameters.
If you use optile's payment page, you're good to go, as the parameters marked with Y(es) will be provided for you, and you only need to manually provide the N(o) parameters.
Parameter |
Provided by optile |
---|---|
|
Y if not provided, customer.email will be used |
|
Y |
|
Y |
|
N |
|
N |
Parameter |
Provided by optile |
---|---|
|
Y if not provided, customer.email will be used |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y (for registered customers) |
|
optile will calculate the parameter from other data points. This parameter doesn't require any actions from merchants. |
Parameter |
Provided by optile |
---|---|
|
Y if not provided, customer.email will be used |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
N |
|
N |
Parameter |
Provided by optile |
---|---|
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
N |
|
N |
Parameter |
Provided by optile |
---|---|
customer.deliveryEmail |
Y |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y (for registered customers) |
|
N |
|
N |
|
N |
|
|
---|---|
|
N |
Parameter |
Provided by optile |
---|---|
|
Y if not provided, customer.email will be used |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N (
|
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
N ( |
|
Y ( |
|
Y |
|
Y ( |
|
Y ( |
|
Y ( |
|
Y ( |
|
N ( |
|
N ( |
|
N (for registered customers) ( |
|
N ( |
Parameter |
Provided by optile |
---|---|
|
Y if not provided, customer.email will be used |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y (for registered customers) |
|
N |
|
N |
|
N |
|
N |
Parameter |
Provided by optile |
---|---|
|
Y if not provided, customer.email will be used |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y (for registered customers) |
|
N |
|
N |
Parameter |
Provided by optile |
---|---|
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
N |
|
N |
|
N |
Shipping name matches account name indicator |
optile will calculate the parameter from other data points. This parameter doesn't require any actions from merchants. |
Parameter |
Provided by optile |
---|---|
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
Y |
|
N |
|
N (to be provided for native integration only) |
|
N |
Parameter |
Provided by optile |
---|---|
|
Y if not provided, |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
|
Y (for registered customers) |
|
N |
|
N |
|
N |
|
N |
|
N |
|
N |
Parameter |
Provided by optile |
---|---|
|
N |
Parameter
|
Provided by optile
|
---|---|
|
N |
preselection.challengeIndicator |
N |
Parameter
|
Provided by optile
|
---|---|
|
N |
customer.addresses.shipping.street | N |
customer.addresses.shipping.houseNumber | N |
customer.addresses.shipping.zip | N |
customer.addresses.shipping.city | N |
customer.addresses.shipping.state | N |
customer.addresses.shipping.country | N |
customer.addresses.billing.street | N |
customer.addresses.billing.houseNumber | N |
customer.addresses.billing.zip | N |
customer.addresses.billing.city | N |
customer.addresses.billing.state | N |
customer.addresses.billing.country | N |
Parameter |
Provided by optile |
---|---|
customer.deliveryEmail |
Y |
customer.accountInfo.timestamp |
N |
customer.accountInfo.creationDate |
N |
customer.accountInfo.passwordChangeDate |
N |
riskData.shipping.type |
N |
clientInfo.language |
Y |
clientInfo.ip |
Y |
ThreeDsChallenge parameter) |
N |
|
N |
products[].quantity |
N |
customer.phones.mobile |
N |
customer.number |
N |
customer.addresses.shipping.street |
N |
customer.addresses.shipping.zip |
N |
customer.addresses.shipping.city |
N |
customer.addresses.shipping.country |
N |
customer.addresses.shipping.name |
N |
customer.addresses.shipping.companyName |
N |
customer.addresses.billing.street |
N |
customer.addresses.billing.zip |
N |
customer.addresses.billing.city |
N |
customer.addresses.billing.country |
N |
customer.addresses.billing.name |
N |
customer.addresses.billing.companyName |
N |
For the detrailed description of the parameters, see API parameters.
A Soft decline happens when an issuing bank confirms the card used exists but for some reason can't accept the transaction. The reasons might be (but are not limited to) the following:
Transactions can also be soft declined on the PSP/Acquirer side.
Soft decline means that this transaction could be accepted, if the issue, which caused a soft decline, was removed.
Soft decline code
Transactions are usually soft declined by the issuing bank with a variety of codes that depend on the scheme used. OPG tags these transactions with its own soft decline code of
45065, CUSTOMER_AUTHENTICATION_REQUIRED
. By providing this code, the issuing bank indicates that this transaction could be accepted if it's repeated with Strong Customer Authentication.
Orchestration platform and soft declines
When a PSP sends us a soft decline ( "Authentication required" ) response code, we do retry with SCA on customer present transactions, if challenge flow was not initiated during this transaction.
We also generate the RETRY/STRONG_AUTHENTICATION
interaction code for the merchant to know that SCA is required and automatically initiate the retry mechanism. The re-try information is logged in data.retryInfo
and can be accessed in the Technical Monitor.
To make sure transactions passing smoothly, it's a good idea to include challengeIndicator = CHALLENGE_REQUESTED_MANDATE
in the LIST request for transactions when the same cardholder is trying to pay again after after a soft decline response code has been received.
Recurring transactions
Customers are not present during recurring transactions, so you need to ensure that upon receiving the CUSTOMER_AUTHENTICATION_REQUIRED
soft decline code for the RECURRING
channel you trigger a new payment sequence through the WEB_ORDER
or MOBILE_ORDER
channel. You could do it, for example, by providing a new payment link to customer via email.
Soft decline timelines
For details on the deadlines per country, see https://www.optile.io/opg#21108832. Note that the dates might still change.
In most 3DS2 scenarios, the CHARGE request returns a response with interaction.code PROCEED
and redirect.type = 3DS2-HANDLER
. In this case, the widget will render the 3DS2 challenge in a lightbox, instead of a full page redirect. That gives a better user experience as the challenge feels like it is occurring within the merchant's shop instead of a completely different site. This also allows the widget to call merchant custom functions for initiating redirect following the 3DS2 process.
What do I need to do to start using the lightbox?
If you're already using our payment page widget (selective native or display native implementation), upgrade to version 3.18.0 or later and the full page redirect will automaticlly change to lightbox.
If you implement the orchestration platform natively, read on to get the technical details that will help you integrate the lightbox.
If you use single-page application and haven't used our widget yet, read on to underdstand the technical details of how the lightbox work.
If you use hosted implementation, consider switching to selective or display native implementation to have the lightbox available.
---------------------------
Prerequisites:
What's the flow?
How does it work in practice?
interaction.code PROCEED
and redirect.type 3DS2-HANDLER
then the widget renders a lightbox.How do I set the lightbox size?
You can specify certain sizes of 3DS2 window from the payment service provider (and in turn the issuer) with the challengeWindowSize
parameter. The parameter is offered by the payment page widget v3 in the CHARGE response: style.challengeWindowSize
.
The possible sizes are:
You can provide values for the style.challengeWindowSize
parameter already when making your initial server-side LIST request (for details, see LIST request). In this case, the lightbox will be displayed in that size.
If no values for the style.challengeWindowSize
are provided, then the lightbox will be displayed in a default size of 500x600.
If the screen size is too small (e.g. mobile device) for the default or the selected size, the lightbox will be displayed as a fullscreen-sized iframe.
How do I work with z-index?
Depending on the design of your merchant shop, you might need to adjust different z-index property values. The default value can be overwritten by creating a custom style. It will overwrite any z-index value you have. See the example below:
.op-payment-widget-lb-overlay {
...
z-index: 3;
...
}
Implementation details
The main changes to the 3DS2 handler page to enable this flow are:
Implementation of additional parameters which can be appended to the URL used to open the handler page in an iframe (the lightbox). The purpose of these additional parameters is to:
inform the handler page that it is being loaded within an iframe instead of a separate window, and
pass some data needed for processing the result message that the handler page will return to the parent window.
Implementation of a logic for handling final redirects (to merchant payment success or failure page) from the Payment Gateway where the parent window is messaged instead of initiating a full page redirect. This is because a full page redirect that occurs within the handler page inside a lightboxed iframe will only redirect within the iframe, but not initiate a full page redirect in the parent window (merchant's shop page).
Full page redirect vs lightbox
Lightbox mode | Full redirect mode | |
---|---|---|
CHARGE response |
CHARGE returns a response with interaction.code PROCEED and redirect.type 3DS2-HANDLER |
CHARGE returns a response with interaction.code PROCEED and redirect.type 3DS2-HANDLER |
Opening page |
The handler page is opened using the Additionally: These parameters are appended as additional query string parameters to the querystring when opening it in iframe:
|
The handler page is opened using the redirect.url (and redirect.parameters encoded as query string) from the CHARGE response |
Behavior on final redirect |
If
|
The handler page will initiate a full page redirect to either the merchant success or cancel page.
Parent window will not be informed of this redirect.
|
Opening the lightbox
The format of the URL used when opening the handler page in lightbox mode should look like in the following way:
{redirect.url received in CHARGE response}?{query string built from the redirect.parameters received in the CHARGE response}&isLightbox=true&targetOrigin={merchant shop origin}&suppress={value of redirect.suppressIFrame from CHARGE response}
Listener for the final redirect message
Within the parent application (payment widget v3 or merchant shop), there should be a listener for the final redirect message, for example:
window.addEventListener('message', function (event) {
if (redirectUrl.includes(event.origin)) {
var message = JSON.parse(event.data);
var redirect = { ...message.data.redirect, suppressIFrame: message.suppress };
// Merchant shop redirect logic here...
}
});
Scenario | 3DS2 success case | 3DS2 error case | |
Non-preset-first flow(for example, regular CHARGE call) |
Merchant provided no custom proceedFunction or abortFunction when initiating payment widget |
Full page redirect to the redirect.url received in message => should be merchant's success page |
Full page redirect to the redirect.url received in message => should be merchant's cancelled payment page |
Merchant provided a custom proceedFunction but not an abortFunction |
The custom proceedFunction is called with the data from the handler page redirect message |
The custom proceedFunction is called with the data from the handler page redirect message |
|
Merchant provided a custom proceedFunction and a custom abortFunction |
The custom proceedFunction is called with the data from the handler page redirect message |
If the interaction code in the handler page message is specifically ABORT, then we call the
abortFunction
Otherwise, the custom
proceedFunction is called with the data from the handler page redirect message |
|
Preset-first flow (for example summary page) |
Merchant provided no custom onResult function |
Full page redirect to the redirect.url received in message => should be merchant's success page |
Full page redirect to the redirect.url received in message => should be merchant's cancelled payment page |
Merchant provided a custom onResult function |
The custom onResult is called with the data from the handler page redirect message |
The custom onResult is called with the data from the handler page redirect message |
proceedFunction
when initiating the payment page v3 widget and that the function handles the following interaction codes: PROCEED, RETRY, ABORT and has default handling for any unrecognised interaction codes.onResult
function within an summaryPageHandler
object, handling these codes: PROCEED, RETRY, ABORT and that the function has default handling for any unrecognised interaction codes.In the following examples we will use a Wirecard sandbox contract with 3D Secure enabled. The following accounts can be used for testing 3D Secure scenarios on Wirecard:
VISA:
Card Number: 4012000300001003
CVV: 003
Expiry Date: Any valid date
3D Secure Password: wirecard
Holder Name: Any
Mastercard:
Card Number: 5413330300001006
CVV: 006
Expiry Date: Any valid date
3D Secure Password: wirecard
Holder Name: Any
This example illustrates the PURE_NATIVE integration type where a CHARGE request with account information is passed from the merchant's system to the OPG. To ensure that 3D Secure processing is enabled in this example, we explicitly configured a separate division called "3Dsecure" with PSP contracts that have 3D Secure enabled on the PSP side (please contact our support to help you with contract configuration). As an alternative, passing a customerScore
in the LIST with values ranging from 0 - 1000 can filter out some contracts depending on the configuration, and enable or disable the security features like 3D Secure check. Passing a low number in the customerScore
field should leave only 3D Secure contracts in the routing.
clientInfo
object during a LIST request.
{
"transactionId": "3d-0001234",
"country": "DE",
"integration": "PURE_NATIVE",
"division": "3Dsecure",
"callback": {
"returnUrl": "https://dev.oscato.com/demoshop/summary.html",
"cancelUrl": "https://dev.oscato.com/demoshop/payment-failure.html",
"notificationUrl": "https://dev.oscato.com/demoshop/optile-notifications"
},
"customer": {
"number": "123",
"email": "buyer123@example.com"
},
"customerScore": 200,
"clientInfo": {
"ip": "83.171.174.105",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:55.0) Gecko/20100101 Firefox/55.0",
"acceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
},
"payment": {
"reference": "demoshop 0001234-123",
"amount": 25.99,
"currency": "EUR"
}
}
Response:
{
"links": {
"self": "https://api.sandbox.oscato.com/api/lists/59c10807cb425c2661d45481lmn83pf5uk1775tukqc41v52j9"
},
"timestamp": "2017-09-19T12:05:27.646+0000",
"operation": "LIST",
"resultCode": "00000.11.000",
"resultInfo": "2 applicable and 0 registered networks are found",
"returnCode": {
"name": "OK",
"source": "GATEWAY"
},
"status": {
"code": "listed",
"reason": "listed"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"identification": {
"longId": "59c10807cb425c2661d45481lmn83pf5uk1775tukqc41v52j9",
"shortId": "16244-09875",
"transactionId": "3d-0001234"
},
"networks": {
"applicable": [{
"code": "VISA",
"label": "Visa",
"method": "CREDIT_CARD",
"grouping": "CREDIT_CARD",
"registration": "OPTIONAL",
"recurrence": "NONE",
"redirect": false,
"links": {
"form": "https://resources.sandbox.oscato.com/resource/form/DEMOSHOP/standard.html",
"logo": "https://resources.sandbox.oscato.com/resource/network/DEMOSHOP/de_DE/VISA/logo.png",
"self": "https://api.sandbox.oscato.com/api/lists/59c10807cb425c2661d45481lmn83pf5uk1775tukqc41v52j9/VISA",
"lang": "https://resources.sandbox.oscato.com/resource/lang/DEMOSHOP/de_DE/VISA.properties",
"operation": "https://api.sandbox.oscato.com/api/lists/59c10807cb425c2661d45481lmn83pf5uk1775tukqc41v52j9/VISA/charge",
"localizedForm": "https://resources.sandbox.oscato.com/resource/form/DEMOSHOP/DE/de_DE/VISA/standard.html",
"validation": "https://api.sandbox.oscato.com/pci/v1/59c10807cb425c2661d45481lmn83pf5uk1775tukqc41v52j9/DEMOSHOP/de_DE/VISA/standard/validate"
},
"button": "button.charge.label",
"selected": false
},
{
"code": "MASTERCARD",
...
}]
},
"operationType": "CHARGE"
}
If the request was successful (interaction.code = PROCEED
) and the redirect.url
contains a URL that does not lead to one of the "callback" URLs provided during the LIST, it means that the credit card supplied by a customer is 3D Secure enrolled and a PA request is generated.
At this point the merchant's system should redirect the client's browser to a URL from a "redirect" block of CHARGE response using a suggested HTTP "method" and HTTP "parameters". In the example above, these are the POST
method and PaReq
, TermUrl
, and MD
parameters. The property suppressIFrame
, if set to true
, indicates that a redirect cannot be performed within the IFrame: either a full page redirect should be executed or a new window/tab is opened.
This action should result in the rendering of a 3D Secure page by the customer's issuer bank system; in our case that is a Wirecard demo page for 3D Secure check, as shown in the figure below.
The customer is then asked to enter additional validation (SMS-tan, 3D Secure password, etc.) and continue with the payment or cancel the transaction.
In case of Wirecard, the test contract password is: wirecard
If everything was successful, the customer is automatically redirected to the returnUrl
of a "callback" from the original LIST request with additional parameters from the OPG system, e.g.:
https://dev.oscato.com/demoshop/summary.html?shortId=10127-51825&interactionReason=OK&resultCode=00000.WIRECARD.0&longId=59c10d47cb427abc123263c9c&transactionId=3d-0001234&interactionCode=PROCEED&amount=25.99&reference=demoshop+0001234-123¤cy=EUR¬ificationId=2436093064195617&referenceId=59c10d47cbabc123be3263c9c×tamp=2017-09-19T14%3A47%3A34.156%2B02%3A00
If a payment has failed (due to 3D Secure check or during Authorization/Capture), the customer is automatically redirected to the cancelUrl
of a "callback" from the original LIST request with additional parameters from the OPG system, e.g.:
https://dev.oscato.com/demoshop/payment-failure.html?shortId=08706-31667&interactionReason=INVALID_ACCOUNT&resultCode=45010.WIRECARD.525&longId=59c112b4cb4abc123c1a7a69c&transactionId=3d-0001234&interactionCode=TRY_OTHER_ACCOUNT¬ificationId=2436364692588779&referenceId=59c112b4cb42f93abc123a69c×tamp=2017-09-19T14%3A52%3A05.785%2B02%3A00
{
"account": {
"holderName": "Testy Threed",
"number": "4012000300001003",
"expiryMonth": 1,
"expiryYear": 2019,
"verificationCode": "003"
}
}
Response:
{
"links": {
"self": "https://api.sandbox.oscato.com/api/charges/59c10d47cb427396be3263c9c"
},
"timestamp": "2017-09-19T12:27:53.844+0000",
"operation": "CHARGE",
"resultCode": "00001.WIRECARD.0",
"resultInfo": "Pending, you have to check the status later",
"pspCode": "WIRECARD",
"returnCode": {
"name": "OK_PENDING",
"source": "PSP"
},
"status": {
"code": "pending",
"reason": "debit_requested"
},
"interaction": {
"code": "PROCEED",
"reason": "PENDING"
},
"identification": {
"longId": "59c10d47cb427396be3263c9c",
"shortId": "10127-51825",
"transactionId": "3d-0001234",
"pspId": "C660018150582407317218"
},
"redirect": {
"url": "https://c3-test.wirecard.com/acssim/app/bank",
"method": "POST",
"parameters": [{
"name": "PaReq",
"value": "eJxtU9tuozAQfe9XIN6LL2CMI+Oq3ewlD9lNt6kq7RsLo4IacGpgQ/br11CybQIWSMzMGebM+Iy86cqd8wdMXegqdomHXQeqVGdF9Ry7j9sv15Hr1E1SZclOVxC7R6jdG3Ult7kBWD5A2hpQV449cg11nTyDU2Sxy4KQM1/YJ/B5QEUYEOy+4Qbs5vYnvL7bg29koSwJj0p0Ms9BazBpnlTNuXsIJenr3eq7ItQPWCjRaE5xJZjVUjGMMbEvlujNMQVWSQnqqTCQJiZzllBq5yHXe4mGwBSf6rZqzFFF1JY/GVNYa3Yqb5r9AqHD4eAdxgJeqkuJ+uB5x2i+Zblpe3c9R6QrMvV7d0/v//7arF/yFTzpbvuS3f342unt51UsUY+Y5mVJA4piwrEgwiHBgvIF8yUa/DMDL/seFWWeEPjDsZc3hqY5+5H17SlXCIkunDOTbY2xsjwqwSM72pM1BUK3tzLtfyzR/++Lec4PTn76NquptLHa4CwUViyMR4zy0KeM+5QEHPshi3qlDaBZNoWVABGYDHSKSz1I9LGqpfa+Fv29D/tklw2db9s/Ym7rMQ=="
},
{
"name": "TermUrl",
"value": "https://api.sandbox.oscato.com/decision/WIRECARD/DEMOSHOP/do.html?type=accept&requestId=59c10d4880ae1fa446fb3b6dc"
},
{
"name": "MD",
"value": ""
}],
"suppressIFrame": true
},
"customer": {
"number": "123",
"email": "buyer123@example.com"
}
}
If the request was successful (interaction.code=PROCEED
) and the redirect.url
contains a URL that lead to one of the "callback" URLs provided during LIST, it means that the credit card supplied by a customer is not 3D Secure enrolled or cannot be validated via a 3D Secure process at the time and CHARGE was processed without a 3D Secure authentication.
The interaction code may also indicate an issue with the payment; if this is the case and if redirect
is present, the redirect.url
will lead to cancelUrl
from "callback" provided during LIST.
At this point the merchant's system may redirect the client's browser to a URL from the redirect
block of CHARGE response using a suggested HTTP "method" and HTTP "parameters" or it may follow any other logic of successful payment processing as it is done during a non-3D Secure flow.
{
"account": {
"holderName": "Testy Threed",
"number": "4111111111111111",
"expiryMonth": 1,
"expiryYear": 2019,
"verificationCode": "003"
}
}
Response:
{
"links": {
"payout": "https://api.sandbox.oscato.com/api/charges/59c117dfcb427396be3263efc/payout",
"self": "https://api.sandbox.oscato.com/api/charges/59c117dfcb427396be3263efc"
},
"timestamp": "2017-09-19T13:13:04.615+0000",
"operation": "CHARGE",
"resultCode": "00000.WIRECARD.0",
"resultInfo": "AVS Unavailable.",
"pspCode": "WIRECARD",
"returnCode": {
"name": "OK",
"source": "PSP"
},
"status": {
"code": "charged",
"reason": "debited"
},
"interaction": {
"code": "PROCEED",
"reason": "OK"
},
"clearing": {
"amount": 25.99,
"currency": "EUR"
},
"identification": {
"longId": "59c117dfcb427396be3263efc",
"shortId": "10818-81133",
"transactionId": "3d-0001235",
"pspId": "C874211150582678450819"
},
"redirect": {
"url": "https://dev.oscato.com/demoshop/summary.html",
"method": "GET",
"parameters": [{
"name": "shortId",
"value": "10818-81133"
},
{
"name": "interactionReason",
"value": "OK"
},
{
"name": "resultCode",
"value": "00000.WIRECARD.0"
},
{
"name": "longId",
"value": "59c117dfcb427396be3263efc"
},
{
"name": "transactionId",
"value": "3d-0001235"
},
{
"name": "interactionCode",
"value": "PROCEED"
},
{
"name": "amount",
"value": "25.99"
},
{
"name": "reference",
"value": "demoshop 0001235-123"
},
{
"name": "currency",
"value": "EUR"
}]
},
"customer": {
"number": "123",
"email": "buyer123@example.com"
}
}
Paying with installments comes with special risks. One of the consequences is a two-step form for the user, see ACTIVATE Request for technical details.
IP Address Required
Most installment providers will require the current IP address of the customer, so please provide it in the LIST Request as clientInfo.ip
.
Customer Name, Billing and Shipping Addresses
Typically, customer name and addresses are also required.
There are two NetworkConfiguration flags that you can set to not include the corresponding method in the LIST Response under the following circumstances:
true
this method will not be displayed if the LIST Request contained both, customer.address.shipping
and customer.address.billing
explicitly and any of the fields are not equal by string comparison (this includes the case when a field is undefined in one address, but exists in the other).true
this method will not be displayed if the LIST Request contained both, customer.address.shipping.name
and customer.address.billing.name
explicitly and any of the fields inside the name
objects are not equal by string comparison (this includes the case when a field is undefined in one name object, but exists in the other).Note: If the Name check excludes the method, also the Address check will exclude it, but not vice versa. In that sense the Address check is more strict. The Name check would still allow a different address as long as the recipient name is equal.
To make use of this feature, be sure to provide the customer name(s) explicitly in the address
objects, not in the generic customer.name
object.
This configuration can be applied to any other method, too, but for installments it is especially important.
URL to Data Privacy Guideline
In the first step of the installment form the user will give their consent to data privacy guidelines that allow the provider to do checks about the customer with external parties. These guidelines are linked, and this URL needs to be configured on the OPG side. Please contact our support about the dataPrivacyConsentUrl as part of the NetworkConfiguration as long as this option is not available in the portal. We recommend that you provide generic guidelines that fit to any of your providers. If you have only one installment provider you can also use a URL provided by them.
Base URL for Contract PDF
In the second step of the installment form there is a link to a PDF, which officially summarizes the conditions for the user. This is generated by the provider, but to make it more trustworthy, it should come from your domain. Therefore we offer you to configure a Document Proxy Base URL for your Division, e.g.:
http://www.example.com/docs.
The link in the customer form will then point to that URL with a parameter bedu
attached. bedu
is for base64 encoded document url. This could result in:
http://www.example.com/docs?bedu=aHR0cHM6Ly9yZXNvdXJjZXMubGl2ZS5vc2NhdG8uY29tL3Jlc291cmNlL2RvYy81N2UzOWIyYjQyL2NyZWRpdC5wZGY=
When this URL is requested your server should decode the parameter value from base64, which again contains a URL, in this example:
https://resources.sandbox.oscato.com/resource/doc/57e39b2b42/credit.pdf
From this URL the PDF should be streamed to download for the customer. Note that it is an optile domain, which again acts as a proxy because some installment providers require additional authentication to retrieve the PDF.
If you do not configure your own Document Proxy Base URL, the optile proxy will be used for the link. This is not perfect, because it is an unknown domain for the user, but functionally, it will work.
The bottom line is that we recommend to build your own document proxy. All it has to do is decode the bedu
parameter and stream the PDF from whatever URL is encoded there.
There are some fields that are mandatory for an Intercard payment (in addition to optile's mandatory fields):
Mandatory fields for Klarna Pay Now:
style.language
: a language code composed of ISO 639 two-letter language codes and ISO 3166 two-letter country codes separated by an underscore character. Similar to RFC 1766. Customer's locale.country
: ISO 3166 alpha-2 country code.payment
amount
: Non-negative, minor units. Total amount of the order, including tax and any discounts.curreny
: ISO 4217 currency code.products
name
: String, any descriptive item name.quantity
: Non-negative. Quantity of items of this type.amount
: Minor units. Includes tax and discount.netAmount
: Minor units. Includes tax and discount. Same value as amount above.taxAmount
: Minor units. Total tax amount for this product.taxRatePercentage
: Tax rate. Ratio between tax_amount and net_amount.The parameters marked with an asterisk above are not mandatory as per Klarna API, but it is considered good practice to include them in payment requests.
That means that a transaction on MuchBetter will stay on statusCode: pending
until the customer logs in the app and approves the payment, and will change to statusCode: charged
after confirmation.
By MuchBetter's own implementation guidelines, a merchant is required to include the following message after requesting for a payment:
On a similar pattern, when implementing payouts a merchant is required to include the following message after request:
successUrl
or cancelUrl
you provided in the LIST Request.mandate
parameter structure). Please note that the mandate reference should be shorter than 35 characters and alpha numeric.payment.reference
parameter a message in the format "Shop-URL, Shop-Hotline/Support e-mail, and transaction ID". See example in the right pane.mandate
values being used by the PSP for this transaction (because depending on your contract it may use its own values).More information about the Mandate:
Mandate {
reference
(string):
The SEPA mandate reference (aka Mandate ID) proposed by the merchant has to be unique in the scope of the merchant's creditorId
creditorId
(string, optional):
The SEPA creditor ID of the merchant could also be configured for some PSPs within a contract which is optional. In case when a PSP uses its own creditor ID, it will be returned in the reply and the status notification
authentication
(MandateAuthentication, optional): Required by some PSPs for SEPA transactions
Below we present a generic diagram for the payment flow:
{
"transactionId": "93acf8c4-dbb3-411c-b9fb-abc123c3f7aa",
"country": "DE",
"channel": null,
"division": null,
"callback": {
"returnUrl": "https://dev.oscato.com/shop/success.html",
"cancelUrl": "https://dev.oscato.com/shop/cancel.html",
"notificationUrl":"https://dev.oscato.com/shop/notify.html",
"summaryUrl": null
},
"customer": {
"number": "123ABC",
"email": "john.doe@example.com",
"birthday": null,
"name": {
"title": null,
"firstName": "FIRST_NAME",
"middleName": null,
"lastName": "SECOND_NAME",
"maidenName": null
},
"addresses": null,
"phones": null,
"registration": null
},
"clientInfo": null,
"payment": {
"reference": "www.shop.com, contact support@shop.com - TiD 123456789",
"amount": 89,
"currency": "EUR",
"invoiceId": null,
"longReference": null
},
"products": null,
"updateOnly": false,
"presetFirst": false,
"style": {
"language": "en",
"theme": null,
"cssOverride": null,
"client": null
},
"preselection": {
"deferral": "ANY"
},
"extraElements": null,
"mandate": {
"reference": "Authorization for shop.com",
"creditorId": null,
"authentication": {
"time": "543836748822",
"city": null
}
},
"installment": null
}
There is one specific feature is supported for Payolution – partial cancellation of invoice.
This feature serves the following business case – the merchant or a customer delete some of the items from the basket before the delivery.
In such a case the partial cancellation of the invoice should be performed.
Normally an HTTP DELETE request (without a body) to the CHARGE resource has to be sent. But this call can only trigger full cancellation.
In order to perform partial cancellation of the invoice, Payout request has to be called for the payment (preauthorization) longId. The call is the same as for Refund (please refer to this chapter for refund and cancellation description) with the difference that for refund Payout has to be sent on Capture longId, not for preauthorization longId. In case of not captured (not delivered yet) invoice for Payolution adapter, the Payout call will trigger partial or full cancellation. If the invoice is captured, the Payout call will trigger Refund.
Restrictions:
There are some fields that are mandatory for an Intercard payment (in addition to optile's mandatory fields):
finalOperation = true
in the request. For example, considering a preauthorization of 20,00 Euro, if a capture of 5,00 Euro is signed with finalOperation = true
, no more additional capture steps can be executed. This parameter must be passed in the CLOSING request as shown in the example on the right side. You can also find this parameter documented in our API Reference.finalOperation
. This means that a refund can be marked as final, even though its value is lower than the total captured amount, and no more additional refunds will be allowed. The parameter must be passed in the PAYOUT request body.checkout.js
. Other than handling the payment session, this library also delivers a PayPal-approved payment button which follows their own styling guidelines and must be used by a merchant in order to be completely certified to use PayPal services. You can read about optile's AJAX library handling provider buttons or how to integrate provider buttons natively, in any case following optile's "implement once" abstraction.
{
"payment": {
"reference": "order nr. 04842",
"amount": 5.00,
"currency": "EUR",
"invoiceId": "O-21072017/035/0001637"
},
"finalOperation": true
}
Send a LIST request for CZ (Twisto operates only in Czech Republic) and in the response search for the network object code TWISTO_INVOICE
. This object contains all needed data to create and initialize a special form for Twisto. The right pane has an example of such LIST response.
The network should contain contractData
with a publicKey
which will be used later to initialize the Twisto.js
library.
{
"code": "TWISTO_INVOICE",
"label": "Buy now, pay in 14 days",
"method": "BANK_TRANSFER",
"grouping": "BANK_TRANSFER",
"registration": "NONE",
"recurrence": "NONE",
"redirect": false,
"links": {
"preloadForm": "https://resources.integration.oscato.com/resource/form/TWISTO_INVOICE/preload_standard.html",
"form": "https://resources.integration.oscato.com/resource/form/TWISTO_INVOICE/standard.html",
"logo": "https://resources.integration.oscato.com/resource/network/TWS-1562603071238/cs_CZ/TWISTO_INVOICE/logo2x.png",
"self": "https://api.integration.oscato.com/api/lists/5d78bcef6c730308b7375d93lsrq3nje71cn2kimga8bika73e/TWISTO_INVOICE",
"lang": "https://resources.integration.oscato.com/resource/lang/TWS-1562603071238/cs_CZ/TWISTO_INVOICE.properties",
"operation": "https://api.integration.oscato.com/api/lists/5d78bcef6c730308b7375d93lsrq3nje71cn2kimga8bika73e/TWISTO_INVOICE/charge",
"localizedForm": "https://resources.integration.oscato.com/resource/form/TWS-1562603071238/CZ/cs_CZ/TWISTO_INVOICE/standard.html",
"validation": "https://api.integration.oscato.com/pci/v1/5d78bcef6c730308b7375d93lsrq3nje71cn2kimga8bika73e/TWS-1562603071238/cs_CZ/TWISTO_INVOICE/standard/validate"
},
"button": "button.charge.label",
"selected": false,
"contractData": {
"publicKey": "test_pk_g6f2cxsn5j824ugxgbhoxnct1c3uf6zs50tgzanj08k9y7yhz3"
}
}
When the customer is ready and clicks the "Pay" button, submit an empty CHARGE request to the network's operation
U