swedbank-link-jwt
Version:
Swedbank API (JWT) payment integration helper for Node.js
157 lines (131 loc) • 5.18 kB
JavaScript
// index.js
const axios = require('axios');
const crypto = require('crypto');
const base64url = require('base64url');
const { v4: uuidv4 } = require('uuid');
const fs = require('fs');
class SwedbankLinkJWT {
constructor() {
this.url = 'https://pi.swedbank.com';
this.publicKeyURL = 'https://banklink.swedbank.com/public/resources/bank-certificates/009';
}
base64urlEncode(data) {
return base64url(Buffer.from(data));
}
sign(url, country, sellerId, payload = '', privateKeyPem) {
const header = {
b64: false,
crit: ['b64'],
iat: Math.floor(Date.now() / 1000),
alg: 'RS512',
url: url,
kid: `${country}:${sellerId}`
};
const headerEncoded = this.base64urlEncode(JSON.stringify(header));
const dataToSign = `${headerEncoded}.${payload}`;
const signer = crypto.createSign('RSA-SHA512');
signer.update(dataToSign);
const signature = signer.sign(privateKeyPem);
const signatureEncoded = base64url(signature);
return `${headerEncoded}..${signatureEncoded}`;
}
async checkSignature(headers, body) {
try {
const res = await axios.get(this.publicKeyURL);
const cert = res.data;
const publicKey = crypto.createPublicKey(cert);
const jwsParts = headers['x-jws-signature'].split('..');
if (jwsParts.length !== 2) return 0;
const signature = Buffer.from(jwsParts[1], 'base64');
const dataToVerify = `${jwsParts[0]}.${JSON.stringify(body)}`;
const verifier = crypto.createVerify('RSA-SHA512');
verifier.update(dataToVerify);
return verifier.verify(publicKey, signature);
} catch (err) {
console.error('Signature check error:', err);
return -1;
}
}
async getProvidersList(country, sellerId, privateKeyPem) {
const token = this.sign(`${this.url}/public/api/v2/agreement/providers`, country, sellerId, '', privateKeyPem);
const res = await axios.get(`${this.url}/public/api/v2/agreement/providers`, {
headers: {
'x-jws-signature': token,
'Accept': 'application/json'
}
});
return res.data;
}
generateUUID() {
return uuidv4().replace(/-/g, '');;
}
getLang(locale = 'lv_LV') {
const lang = locale.split('_')[1]?.toLowerCase() || 'en';
return ['lt', 'et', 'lv', 'ru'].includes(lang) ? lang : 'en';
}
getDescriptionLang(code) {
switch (code) {
case 'et':
case 'ee': return 'Tellimus nr.';
case 'lv': return 'Pasūtījuma Nr.';
case 'lt': return 'Užsakymo Nr.';
default: return 'Order no.';
}
}
async paymentRequest({ sellerId, country, bic, redirectUrl, notificationUrl, total, orderId, privateKeyPem }) {
//use this for debugging
//console.log("Seller ID:", sellerId);
//console.log("Country:", country);
//console.log("BIC:", bic);
//console.log("Redirect URL:", redirectUrl);
//console.log("Notification URL:", notificationUrl);
//console.log("Total:", total);
//console.log("Order ID:", orderId);
//const lang = this.getLang(); //use this for switching languages
//const note = this.getDescriptionLang(lang); //use this for switching languages
const lang = 'lv';
const note = 'lv';
const payloadObj = {
amount: total.toFixed(2),
locale: lang,
notificationUrl,
redirectUrl,
currency: 'EUR',
description: `${note} ${orderId}`,
endToEndIdentification: this.generateUUID()
};
const payload = JSON.stringify(payloadObj);
const token = this.sign(`${this.url}/public/api/v2/transactions/providers/${bic}`, country, sellerId, payload, privateKeyPem);
const res = await axios.post(`${this.url}/public/api/v2/transactions/providers/${bic}`, payload, {
headers: {
'x-jws-signature': token,
'Content-Type': 'application/json'
}
});
return res.data;
}
async paymentResponse({sellerId, country, packageId, privateKeyPem}) {
const url = `${this.url}/public/api/v2/transactions/${packageId}/status`;
const token = this.sign(url, country, sellerId, '', privateKeyPem);
const res = await axios.get(url, {
headers: {
'x-jws-signature': token,
'Accept': 'application/json'
}
});
return {
headers: res.headers,
body: res.data
};
}
getStatus(statusCode) {
switch (statusCode) {
case 'EXECUTED': return 'processed';
case 'EXPIRED':
case 'FAILED': return 'failed';
case 'CANCELLED_BY_USER': return 'cancelled';
default: return 'pending';
}
}
}
module.exports = new SwedbankLinkJWT();