Initiate Payments
This guide shows how to integrate to the PIS API. It will contain information on how to initiate a domestic private account to account payment.
Variables and constants used in the guide
Name | Description |
---|---|
accessToken | An access token with scope "paymentinitiation private" . |
TOKEN_URL | The token URL. For production, use https://auth.openbankingplatform.com/connect/token |
bic | Will contain the BIC of the bank that the user selected. |
CLIENT_ID | The Client ID of the application you created in the Developer Portal. |
CLIENT_SECRET | The secret that was generated when you created an application. If you did not save that value, you need to generate a new secret. |
paymentAmount | The transaction amount |
paymentCurrency | The transaction currency |
psuUserAgent | The User-Agent from the user's request. |
psuIpAddress | The user's IP address |
xRequestID | Most requests require the header X-Request-ID , which is a uuid. This will be a unique identifier of your request and will be useful in case you need support. Make sure to create a new GUID for every individual request. In this guide, we assume that you store this value in the variable xRequestID . |
1. Create Payment Initiation
First we need to create a payment initiation. This is where we specify the details of the transaction.
Endpoint
POST /psd2/paymentinitiation/v1/payments/domestic
Request headers
Accept: "application/json",
Authorization: "Bearer " + accessToken,
Content-Type: "application/json",
PSU-IP-Address: psuIpAddress,
PSU-User-Agent: psuUserAgent,
X-BicFi: bic,
X-Request-ID: xRequestID,
We need to construct parts of the request body a bit differently depending on what bank we are sending the money from (the debtor) and to (the creditor). This is because the banks differs a bit in what format they want the account numbers.
For that we can create the helper functions getCreditorAccount
and getDebtorAccount
, which returns the body parameters formatted in the correct way depending on the bank of the sender (the debtor).
This assumes that you have the objects creditor
and debtor
that contains the neccessary information.
// generates the body parameter debtorAccount
function getDebtorAccount(debtor, bic){
switch (bic) {
case "HANDSESS":
case "NDEASESS":
return {
bban: debtor.bban,
currency: debtor.currency,
};
default:
return {
iban: debtor.iban,
currency: debtor.currency,
};
}
}
// generates the body parameter creditorAccount
function getCreditorAccount(creditor, bic){
switch (bic) {
case "HANDSESS":
return {
clearingNumber: creditor.clearingNumber,
bban: creditor.bban,
currency: creditor.currency,
};
default:
return {
iban: creditor.iban,
currency: creditor.currency,
};
}
}
Request body
{
instructedAmount: {
currency: paymentCurrency,
amount: paymentAmount,
},
creditorName: creditor.name,
creditorAccount: getCreditorAccount(creditor, bic),
remittanceInformationUnstructured:
"This text shows up on the transaction, both for creditor and debtor",
debtorAccount: getDebtorAccount(debtor, bic),
}
Result
// The paymentID will be used in the coming requests
paymentID = response.body.paymentId
2. Start Payment Initiation Authorisation Process
Next we need to create an authorisation for the payment initiation we just created.
Endpoint
POST /psd2/paymentinitiation/v1/payments/domestic/${paymentID}/authorisations
Request headers
Accept: "application/json",
Authorization: "Bearer " + accessToken,
Content-Type: "application/json",
PSU-IP-Address: psuIpAddress,
X-BicFi: bic,
X-Request-ID: xRequestID,
Result
// Contains the id that represents at least one of Mobilt BankID (mbid), Mobile BankID on this device (mbid_same_device),
// Mobile BankID on another device (mbid_animated_qr_image).
authenticationMethodID = response.body.scaMethods[0].authenticationMethodId;
// URL that will be used in the call to Update PSU Data for Payment Initiation (next step).
authoriseTransactionUri = response.body._links.authoriseTransaction.href;
//Resource identification of the related SCA,
// will be used in the final step of the Redirect approach.
paymentAuthorisationID = response.body.authorisationId;
3. Update PSU Data for Payment Initiation
This request triggers the authorisation flow.
If the ASPSP supports Animated QR, the response.body.scaMethods array will contain an element with authenticationMethodId mbid_animated_qr_image
.
When multiple choices are given, always use mbid_same_device
(or mbid
if mbid_same_device
is not available) if authorisation is to be performed on the same device, and mbid_animated_qr_image
when using different devices.
Endpoint
POST authoriseTransactionUri
Request headers
Accept: "application/json",
Authorization: "Bearer " + accessToken,
Content-Type: "application/json",
PSU-IP-Address: psuIpAddress,
X-BicFi: bic,
X-Request-ID: xRequestID,
Request body
{
authenticationMethodId: authenticationMethodID
}
Result
The first thing we need to check in the response is the SCA method used by the bank (Decoupled or Redirect), which will be extracted from the response headers.
// "DECOUPLED" or "REDIRECT"
scaApproach = response.headers.aspsp-sca-approach;
We are interested in different values from the response depending on if we got Decoupled or Redirect.
If the SCA flow is Decoupled, and you chose mbid
in the previous step, you need to get the autoStartToken
.
autoStartToken = response.body.challengeData.data[0]
You can the construct the full QR code like this:
bankIdLink = "https://app.bankid.com/?autostarttoken=" + autoStartToken + "&redirect=null"
If your users only uses desktops, the above is enough. You will display the QR code in the browser and poll the status of the Payment Initiation Authorisation to see when it's finalised.
Make sure to re-render the image if the autoStartToken
changes.
Use the bankIdLink
to and ask the user to open the link on its mobile device,
you'll need to adjust the bankIdLink
slightly if you want to support users on a smartphone. If you want the user to return to your website after using Mobilt BankID, redirectUriAfterDecoupledAuthentication
should point to where you want the user to be redirect to.
If you have an application, redirectUriAfterDecoupledAuthentication
should be set to null
, this will open the previously opened application once the Mobilt BankID session is done.
bankIdLink = "https://app.bankid.com/?autostarttoken=" + autoStartToken + "&redirect=" + redirectUriAfterDecoupledAuthentication
If the SCA flow is Decoupled, and you chose mbid_same_device
, generate the bankIdLink
in the same way as outlined for mbid
and open the link.
If the SCA flow is Decoupled, and you chose mbid_animated_qr_image
, you'll need to display the B64 encoded image animatedQR
and instruct the user to scan it within the Mobilt BankID application.
The image is only valid for 1 second, and must be refreshed by calling Get Payment Initiation Authorisation SCA Status repeatedly.
// Example value: "data:image/png;base64, + B64"
animatedQR = response.body.challengeData.image
In case of Redirect approach, you need to extract the link to our auth server (which in turn will redirect to the bank's external authentication page) and replace the placeholders with the relevant values.
redirectLinkToBank = response.body._links.scaOAuth.href
Replace the following placeholders in redirectLinkToBank
in the following way:
"[CLIENT_ID]"
should be replaced by your CLIENT_ID
.
"[TPP_REDIRECT_URI]"
is the URI you want us to redirect to after we get confirmation from the bank that the user has authenticated. This URI has to be whitelisted for your application in the Developer Portal.
"[TPP_STATE]"
is a convenience field for you to put in anything you want, for example something that identifies this session. It's important that you can identify the correct session after the PSU is redirected back again.
We now have what we need to let the user authorise the payment intiation. The flow will differ completely between Decoupled and Redirect, so the intructions will be separated.
4a. Decoupled
If using desktop, you use the bankIdLink
to generate a QR code, or the ready-made PNG QR in animatedQR
, that you present in your UI. Call the Get Payment Initiation Authorisation SCA Status endpoint until scaStatus
is failed
or finalised
.
If the data in response.body.challengeData.data[0]
or response.body.challengeData.image
changes, you must re-render the image displayed to the user.
When the user has successfully authenticated and authorized the payment, the status of the Payment Initiation Authorisation will be finalised
. To know the status of the Payment Initiation, you should poll the OPE API endpoint Get Payment Initiation Status.
Endpoint
GET /psd2/paymentinitiation/v1/payments/domestic/{paymentID}/authorisations/{paymentAuthorisationID}
Request headers
Accept: "application/json",
Authorization: "Bearer " + accessToken,
Content-Type: "application/json",
PSU-IP-Address: psuIpAddress
X-BicFi: bic,
X-Request-ID: xRequestID,
Result
scaStatus = response.body.scaStatus;
// if mbid or mbid_same_device
autoStartToken = response.body.challengeData.data[0];
// if mbid_animated_qr_image
animatedQR = response.body.challengeData.image;
4b. Redirect
First you need to route the user to redirectLinkToBank
. When the user has authenticated, the bank will route the user back to the URI you replaced "[TPP_REDIRECT_URI]"
with. Once there, you extract the URL parameters code
and scope
.
To finalise the payment, you make the following request:
Endpoint
POST TOKEN_URL
Request headers
Content-Type: "application/x-www-form-urlencoded",
X-PaymentId: paymentID,
X-PaymentAuthorisationId: paymentAuthorisationID,
Request body
{
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: code,
redirect_uri: redirectURI, // Will be the same value as what you replaced [TPP_REDIRECT_URI] with in the end of step 4.
scope: scope,
grant_type: "authorization_code"
}
Result
accessToken = response.body.access_token
If you receive an access token it means that the request was successful.
5. Get Payment Initiation Status
The last thing to do is to check the status of the Payment Initiation.
Endpoint
GET /psd2/paymentinitiation/v1/payments/domestic/{paymentID}/status
Request headers
Accept: "application/json",
Authorization: "Bearer " + accessToken,
Content-Type: "application/json",
PSU-IP-Address: psuIpAddress,
PSU-User-Agent: psuUserAgent
Result
transactionStatus = response.body.transactionStatus
The Payment Initiation can have a number of different statutes. Read more about them in the Endpoint details.
Currently we want to check if the payment was rejected. In that case, transactionStatus
will have the value "RJCT"
.
If not, then we are done.