diff --git a/README.md b/README.md index c7ea313..aee5f4f 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ let config = { country: 'fr' } -let connect = client.getAisConnect(null, config) +let connect = await client.getAisConnect(null, config) window.href.location = connect.url; ``` diff --git a/fintecture-client.ts b/fintecture-client.ts index 7d67c59..a7a8fbe 100644 --- a/fintecture-client.ts +++ b/fintecture-client.ts @@ -105,7 +105,7 @@ export class FintectureClient { return this.connect.getPisConnect(accessToken, connectConfig); } - public getAisConnect(accessToken: string, connectConfig: any): IAisConnect { + public async getAisConnect(accessToken: string, connectConfig: any): Promise { return this.connect.getAisConnect(accessToken, connectConfig); } diff --git a/package.json b/package.json index 423f23f..50f44e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fintecture-client", - "version": "1.0.27", + "version": "2.0.0", "description": "Fintecture Open Banking API Gateway enabling secure bank connections and payments", "main": "lib/fintecture-client.js", "types": "lib/fintecture-client.d.ts", diff --git a/spec/connect.spec.ts b/spec/connect.spec.ts index a5f3107..e30d276 100644 --- a/spec/connect.spec.ts +++ b/spec/connect.spec.ts @@ -1,5 +1,4 @@ import { FintectureClient } from '../fintecture-client'; -import { BaseUrls } from './../src/utils/URLBuilders/BaseUrls'; import { IPisSetup, IAisSetup } from './../src/interfaces/connect/ConnectInterface'; import { TestConfig } from './constants/config'; @@ -34,20 +33,17 @@ const client = new FintectureClient({ app_id: TestConfig.appIdMerchant, app_secr describe('Connect', () => { it('#PIS getPisConnect', async (done) => { - const mockConnectUrl = BaseUrls.FINTECTURECONNECTURL_SBX + '/pis?config='; const tokens: any = await client.getAccessToken(); const connectMin = await client.getPisConnect(tokens.access_token, connectPisConfigMin); - expect(connectMin.url).toContain(mockConnectUrl); expect(!!connectMin.session_id).toBe(true); const connectFull = await client.getPisConnect(tokens.access_token, connectPisConfigFull); - expect(connectFull.url).toContain(mockConnectUrl); expect(!!connectFull.session_id).toBe(true); expect(connectFull.url.length).toBeGreaterThan(connectMin.url.length) done(); }); - it('#AIS getAisConnectUrl', (done) => { - const connect = client.getAisConnect(null, connectAisMin); + it('#AIS getAisConnectUrl', async (done) => { + const connect = await client.getAisConnect(null, connectAisMin); expect(!!connect.url).toBe(true); done(); }); diff --git a/src/Connect.ts b/src/Connect.ts index c516a7d..6186cb2 100644 --- a/src/Connect.ts +++ b/src/Connect.ts @@ -1,8 +1,7 @@ -import * as UtilsCrypto from './utils/Crypto.js'; -import { BaseUrls } from './utils/URLBuilders/BaseUrls'; -import * as connectService from './services/ConnectService'; -import { IPisSetup, IPisConnectConfig, IAisConnectConfig, IPaymentPayload, IData, IAttributes, IMeta } from './interfaces/connect/ConnectInterface'; -import { ISessionPayload } from './interfaces/pis/PisInterface'; +import qs from 'qs'; + +import { Endpoints } from './utils/URLBuilders/Endpoints'; +import { IPisSetup, IPaymentPayload, IData, IAttributes, IMeta } from './interfaces/connect/ConnectInterface'; import { IFintectureConfig } from './interfaces/ConfigInterface'; import { Constants } from './utils/Constants.js'; import { PIS } from './Pis'; @@ -10,17 +9,14 @@ import * as apiService from './services/ApiService'; export class Connect { public pis: PIS; - public axios: any; + public axiosInstance: any; public config: IFintectureConfig; public connectConfig: IPisSetup; - private signatureType: string; - constructor(config: IFintectureConfig) { this.pis = new PIS(config); - this.axios = connectService; + this.axiosInstance = apiService.getInstance(config.env); this.config = config; - this.signatureType = 'rsa-sha256'; } /** @@ -29,44 +25,30 @@ export class Connect { * @param {string} accessTOken * @param {payment} State */ - public getAisConnect(accessToken: string, connectConfig: any) { + public async getAisConnect(accessToken: string, connectConfig: any) { this.config = this._validateConfigIntegrity(this.config); - const headers: any = this._buildHeaders(accessToken, 'get', null, this.config.private_key, this.signatureType); - - const config: IAisConnectConfig = { - app_id: this.config.app_id, - signature_type: this.signatureType, - signature: headers['Signature'], + const queryParameters = qs.stringify({ redirect_uri: connectConfig.redirect_uri, - origin_uri: connectConfig.origin_uri, - state: connectConfig.state, - psu_type: connectConfig.psu_type, - country: connectConfig.country, - date: headers['Date'], - request_id: headers['X-Request-ID'], - provider: connectConfig.provider + state: connectConfig.state + }); + const url = `${Endpoints.AISCONNECT}?${queryParameters}`; + + // Extend the headers with Connect specific headers if they are defined + const extraHeaders = { + 'x-provider': connectConfig.provider, + 'x-psu-type': connectConfig.psu_type, + 'x-country': connectConfig.country, + 'x-language': connectConfig.language, }; - if (accessToken) { - config.access_token = accessToken; - } - - const psuType = connectConfig.psu_type ? connectConfig.psu_type : 'retail'; - const country = connectConfig.country ? connectConfig.country : 'fr'; - - const url = `${ - this.config.env === Constants.SANDBOXENVIRONMENT - ? BaseUrls.FINTECTURECONNECTURL_SBX - : BaseUrls.FINTECTURECONNECTURL_PRD - }/ais/${psuType}/${country}`; + const headers = apiService.getHeaders('get', url, null, this.config, null, extraHeaders); - const connect = { - url: `${url}?config=${Buffer.from(JSON.stringify(config)).toString('base64')}`, - } - - return connect; + const { data } = await this.axiosInstance.get(url, { headers }); + return { + url: data.meta.url + }; } /** @@ -81,40 +63,29 @@ export class Connect { const paymentPayload: IPaymentPayload = this._buildPaymentPayload(connectConfig); - const prepare: any = await this.pis.prepare(accessToken, paymentPayload); - - const sessionPayload: ISessionPayload = this._buildSessionPayload(prepare); - - const headers: any = this._buildHeaders(accessToken, 'post', sessionPayload, this.config.private_key, this.signatureType); - - const config: IPisConnectConfig = { - app_id: this.config.app_id, - access_token: accessToken, - signature_type: this.signatureType, - signature: headers['Signature'], - redirect_uri: connectConfig.redirect_uri, + const queryParameters = qs.stringify({ origin_uri: connectConfig.origin_uri, - state: connectConfig.state, - payload: sessionPayload, - psu_type: connectConfig.psu_type, - country: connectConfig.country, - date: headers['Date'], - request_id: headers['X-Request-ID'], - provider: connectConfig.provider + redirect_uri: connectConfig.redirect_uri, + state: connectConfig.state + }); + const url = `${Endpoints.PISCONNECT}?${queryParameters}`; + + // Extend the headers with Connect specific headers if they are defined + const extraHeaders = { + 'x-provider': connectConfig.provider, + 'x-psu-type': connectConfig.psu_type, + 'x-country': connectConfig.country, + 'x-language': connectConfig.language, }; - const url = `${ - this.config.env === Constants.SANDBOXENVIRONMENT - ? BaseUrls.FINTECTURECONNECTURL_SBX - : BaseUrls.FINTECTURECONNECTURL_PRD - }/pis`; + const headers = apiService.getHeaders('post', url, accessToken, this.config, paymentPayload, extraHeaders); - const connect = { - url: `${url}?config=${Buffer.from(JSON.stringify(config)).toString('base64')}`, - session_id: prepare.meta.session_id - } + const { data } = await this.axiosInstance.post(url, paymentPayload, { headers }); - return connect; + return { + session_id: data.meta.session_id, + url: data.meta.url, + }; } private _validatePisConnectConfigIntegrity(connectConfig: any) { @@ -143,14 +114,6 @@ export class Connect { return connectConfig as IPisSetup; } - - private _buildHeaders(accessToken: string, method: string, payload: any, privateKey: string, algorithm: string): any { - const headers = apiService.getHeaders(method, '', accessToken, this.config, payload); - const signingString = UtilsCrypto.buildSigningString(headers, Constants.CONNECTHEADERPARAMETERLIST) - headers["Signature"] = UtilsCrypto.signPayload(signingString, this.config.private_key); - return headers; - } - private _buildPaymentPayload(payment: any) { const attributes: IAttributes = { amount: payment.amount, @@ -184,30 +147,6 @@ export class Connect { return payload; } - private _buildSessionPayload(payment) { - const payload = { - meta: { - session_id: payment.meta.session_id, - }, - data: { - attributes: { - amount: payment.data.attributes.amount, - currency: payment.data.attributes.currency - } - } - } as ISessionPayload; - - if (payment.data.attributes.beneficiary) { - payload.data.attributes.beneficiary = {name: payment.data.attributes.beneficiary.name}; - } - - if (payment.data.attributes.execution_date) { - payload.data.attributes.execution_date = payment.data.attributes.execution_date; - } - - return payload; - } - private _validateConfigIntegrity(config) { if (!config.private_key) { throw Error('private_key must be set to use this function'); diff --git a/src/services/ApiService.ts b/src/services/ApiService.ts index 4475436..a0d471c 100644 --- a/src/services/ApiService.ts +++ b/src/services/ApiService.ts @@ -46,8 +46,14 @@ export const getHeaders = (method: string, url: string, accessToken: string, con headers['Signature'] = Crypto.createSignatureHeader(headers, config, Constants.SIGNEDHEADERPARAMETERLIST); delete headers['(request-target)']; + // Extend with extra headers in case they are not undefined. `undefined` as value of a header + // is not allowed by Node.js if (extraHeaders) { - Object.assign(headers, extraHeaders); + Object.entries(extraHeaders).forEach(([headerName, headerValue]) => { + if (headerValue !== undefined) { + headers[headerName] = headerValue; + } + }) } return headers; diff --git a/src/services/ConnectService.ts b/src/services/ConnectService.ts deleted file mode 100644 index d0dd596..0000000 --- a/src/services/ConnectService.ts +++ /dev/null @@ -1,10 +0,0 @@ -import axios from 'axios'; -import { Constants } from '../utils/Constants'; -import { BaseUrls } from '../utils/URLBuilders/BaseUrls'; - -export const getInstance = (env: string) => { - return axios.create({ - baseURL: - env === Constants.SANDBOXENVIRONMENT ? BaseUrls.FINTECTURECONNECTURL_SBX : BaseUrls.FINTECTURECONNECTURL_PRD, - }); -}; diff --git a/src/utils/URLBuilders/BaseUrls.ts b/src/utils/URLBuilders/BaseUrls.ts index 69807d9..4868b2a 100644 --- a/src/utils/URLBuilders/BaseUrls.ts +++ b/src/utils/URLBuilders/BaseUrls.ts @@ -14,12 +14,8 @@ export class BaseUrls { process.env.FINTECTUREOAUTHURL_DEV || 'https://oauth-sandbox.fintecture.com'; public static readonly FINTECTUREAPIURL_SBX: string = process.env.FINTECTUREAPIURL_DEV || 'https://api-sandbox.fintecture.com'; - public static readonly FINTECTURECONNECTURL_SBX: string = - process.env.FINTECTURECONNECTURL_DEV || 'https://connect-sandbox.fintecture.com'; public static readonly FINTECTUREOAUTHURL_PRD: string = process.env.FINTECTUREOAUTHURL_DEV || 'https://oauth.fintecture.com'; public static readonly FINTECTUREAPIURL_PRD: string = process.env.FINTECTUREAPIURL_DEV || 'https://api.fintecture.com'; - public static readonly FINTECTURECONNECTURL_PRD: string = - process.env.FINTECTURECONNECTURL_DEV || 'https://connect.fintecture.com'; } diff --git a/src/utils/URLBuilders/Endpoints.ts b/src/utils/URLBuilders/Endpoints.ts index 5d3db0d..67b88ee 100644 --- a/src/utils/URLBuilders/Endpoints.ts +++ b/src/utils/URLBuilders/Endpoints.ts @@ -16,4 +16,6 @@ export class Endpoints { public static readonly PIS: string = '/pis/v1'; public static readonly AISPROVIDER: string = '/ais/v1/provider'; public static readonly AISCUSTOMER: string = '/ais/v1/customer'; + public static readonly AISCONNECT: string = '/ais/v2/connect'; + public static readonly PISCONNECT: string = '/pis/v2/connect'; }