node-ebics-client/lib/Client.js

259 lines
7.9 KiB
JavaScript
Raw Permalink Normal View History

2018-05-17 15:03:59 +00:00
'use strict';
2018-06-01 13:16:43 +00:00
const $request = require('request');
2018-05-17 15:03:59 +00:00
2018-06-20 09:20:03 +00:00
const constants = require('./consts');
const Keys = require('./keymanagers/Keys');
const defaultKeyEncryptor = require('./keymanagers/defaultKeyEncryptor');
2018-06-15 06:33:41 +00:00
const signer = require('./middleware/signer');
const serializer = require('./middleware/serializer');
const response = require('./middleware/response');
const stringifyKeys = (keys) => {
Object.keys(keys).map((key) => {
keys[key] = keys[key] === null ? null : keys[key].toPem();
return key;
});
return JSON.stringify(keys);
};
2019-11-05 03:54:16 +00:00
/**
* Keys persistent object
* @typedef {Object} KeysObject
* @property {string} A006 - PEM representation of the A006 private key
* @property {string} E002 - PEM representation of the E002 private key
* @property {string} X002 - PEM representation of the X002 private key
* @property {string} bankX002 - PEM representation of the bankX002 public key
* @property {string} bankE002 - PEM representation of the bankE002 public key
*/
/**
* Key storage implementation
* @typedef {Object} KeyStorage
* @property {(data: KeysObject):Promise<void>} write - writes the keys to storage
* @property {():Promise<KeysObject>} read - reads the keys from storage
*/
/**
* Client initialization options
2019-11-05 03:54:16 +00:00
* @typedef {Object} EbicClientOptions
* @property {string} url - EBICS URL provided by the bank
* @property {string} partnerId - PARTNERID provided by the bank
* @property {string} hostId - HOSTID provided by the bank
* @property {string} userId - USERID provided by the bank
* @property {string} passphrase - passphrase for keys encryption
2019-11-05 03:54:16 +00:00
* @property {KeyStorage} keyStorage - keyStorage implementation
* @property {object} [tracesStorage] - traces (logs) storage implementation
* @property {string} bankName - Full name of the bank to be used in the bank INI letters.
* @property {string} bankShortName - Short name of the bank to be used in folders, filenames etc.
* @property {string} languageCode - Language code to be used in the bank INI letters ("de", "en" and "fr" are currently supported).
* @property {string} storageLocation - Location where to store the files that are downloaded. This can be a network share for example.
2019-11-05 03:54:16 +00:00
*/
2018-05-17 15:03:59 +00:00
module.exports = class Client {
2019-11-05 03:54:16 +00:00
/**
*Creates an instance of Client.
* @param {EbicClientOptions} clientOptions
*/
2018-06-20 09:20:03 +00:00
constructor({
url,
partnerId,
userId,
hostId,
passphrase,
keyStorage,
2018-08-31 05:50:18 +00:00
tracesStorage,
bankName,
bankShortName,
languageCode,
storageLocation,
2018-06-20 09:20:03 +00:00
}) {
if (!url)
throw new Error('EBICS URL is required');
2018-06-20 09:20:03 +00:00
if (!partnerId)
throw new Error('partnerId is required');
2018-06-20 09:20:03 +00:00
if (!userId)
throw new Error('userId is required');
2018-06-20 09:20:03 +00:00
if (!hostId)
throw new Error('hostId is required');
2018-06-20 09:20:03 +00:00
if (!passphrase)
throw new Error('passphrase is required');
2018-06-20 09:20:03 +00:00
if (!keyStorage || typeof keyStorage.read !== 'function' || typeof keyStorage.write !== 'function')
throw new Error('keyStorage implementation missing or wrong');
2018-06-20 09:20:03 +00:00
this.url = url;
2018-06-20 09:20:03 +00:00
this.partnerId = partnerId;
this.userId = userId;
this.hostId = hostId;
this.keyStorage = keyStorage;
this.keyEncryptor = defaultKeyEncryptor({ passphrase });
2021-03-30 11:26:35 +00:00
this.tracesStorage = tracesStorage || null;
this.bankName = bankName || 'Dummy Bank Full Name';
this.bankShortName = bankShortName || 'BANKSHORTCODE';
this.languageCode = languageCode || 'en';
this.storageLocation = storageLocation || null;
2018-06-20 09:20:03 +00:00
}
2018-06-27 14:59:35 +00:00
async send(order) {
2018-06-20 09:20:03 +00:00
const isInObject = ('operation' in order);
if (!isInObject) throw new Error('Operation for the order needed');
if (order.operation.toUpperCase() === constants.orderOperations.ini) return this.initialization(order);
2018-06-27 14:59:35 +00:00
const keys = await this.keys();
if (keys === null) throw new Error('No keys provided. Can not send the order or any other order for that matter.');
2018-06-20 09:20:03 +00:00
if (order.operation.toUpperCase() === constants.orderOperations.upload) return this.upload(order);
if (order.operation.toUpperCase() === constants.orderOperations.download) return this.download(order);
throw new Error('Wrong order operation provided');
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
async initialization(order) {
2018-06-27 14:59:35 +00:00
const keys = await this.keys();
if (keys === null) await this._generateKeys();
2018-08-31 05:50:18 +00:00
if (this.tracesStorage)
this.tracesStorage.new().ofType('ORDER.INI');
2018-06-11 12:25:07 +00:00
const res = await this.ebicsRequest(order);
const xml = res.orderData();
2018-06-01 13:16:43 +00:00
2018-07-03 09:48:00 +00:00
const returnedTechnicalCode = res.technicalCode();
const returnedBusinessCode = res.businessCode();
return {
2018-07-04 13:30:16 +00:00
orderData: xml.length ? xml.toString() : xml,
orderId: res.orderId(),
2018-07-03 09:48:00 +00:00
technicalCode: returnedTechnicalCode,
technicalCodeSymbol: res.technicalSymbol(),
technicalCodeShortText: res.technicalShortText(returnedTechnicalCode),
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
businessCode: returnedBusinessCode,
businessCodeSymbol: res.businessSymbol(returnedBusinessCode),
businessCodeShortText: res.businessShortText(returnedBusinessCode),
businessCodeMeaning: res.businessMeaning(returnedBusinessCode),
bankKeys: res.bankKeys(),
};
2018-05-17 15:03:59 +00:00
}
async download(order) {
2018-08-31 05:50:18 +00:00
if (this.tracesStorage)
this.tracesStorage.new().ofType('ORDER.DOWNLOAD');
2018-06-11 12:25:07 +00:00
const res = await this.ebicsRequest(order);
2018-05-17 15:03:59 +00:00
order.transactionId = res.transactionId();
2018-08-31 05:50:18 +00:00
if (res.isSegmented() && res.isLastSegment()) {
if (this.tracesStorage)
this.tracesStorage.connect().ofType('RECEIPT.ORDER.DOWNLOAD');
2018-06-11 12:25:07 +00:00
await this.ebicsRequest(order);
2018-08-31 05:50:18 +00:00
}
2018-05-17 15:03:59 +00:00
2018-07-03 09:48:00 +00:00
const returnedTechnicalCode = res.technicalCode();
const returnedBusinessCode = res.businessCode();
return {
orderData: res.orderData(),
orderId: res.orderId(),
2018-07-03 09:48:00 +00:00
technicalCode: returnedTechnicalCode,
technicalCodeSymbol: res.technicalSymbol(),
technicalCodeShortText: res.technicalShortText(returnedTechnicalCode),
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
businessCode: returnedBusinessCode,
businessCodeSymbol: res.businessSymbol(returnedBusinessCode),
businessCodeShortText: res.businessShortText(returnedBusinessCode),
businessCodeMeaning: res.businessMeaning(returnedBusinessCode),
};
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2018-05-31 09:08:07 +00:00
async upload(order) {
2018-08-31 05:50:18 +00:00
if (this.tracesStorage)
this.tracesStorage.new().ofType('ORDER.UPLOAD');
2018-06-11 12:25:07 +00:00
let res = await this.ebicsRequest(order);
const transactionId = res.transactionId();
const orderId = res.orderId();
order.transactionId = transactionId;
2018-05-31 09:08:07 +00:00
2018-08-31 05:50:18 +00:00
if (this.tracesStorage)
this.tracesStorage.connect().ofType('TRANSFER.ORDER.UPLOAD');
2018-06-11 12:25:07 +00:00
res = await this.ebicsRequest(order);
2018-05-31 09:08:07 +00:00
return [transactionId, orderId];
2018-05-31 09:08:07 +00:00
}
2018-06-11 12:25:07 +00:00
ebicsRequest(order) {
2018-06-27 14:59:35 +00:00
return new Promise(async (resolve, reject) => {
2018-06-20 09:20:03 +00:00
const { version } = order;
2018-06-27 14:59:35 +00:00
const keys = await this.keys();
2018-08-31 05:50:18 +00:00
const r = signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x());
if (this.tracesStorage)
this.tracesStorage.label(`REQUEST.${order.orderDetails.OrderType}`).data(r).persist();
2018-06-20 09:20:03 +00:00
2018-05-17 15:03:59 +00:00
$request.post({
2018-06-01 13:16:43 +00:00
url: this.url,
2018-08-31 05:50:18 +00:00
body: r,
2018-06-01 13:16:43 +00:00
headers: { 'content-type': 'text/xml;charset=UTF-8' },
2018-08-31 05:50:18 +00:00
}, (err, res, data) => {
if (err) reject(err);
const ebicsResponse = response.version(version)(data, keys);
if (this.tracesStorage)
this.tracesStorage.label(`RESPONSE.${order.orderDetails.OrderType}`).connect().data(ebicsResponse.toXML()).persist();
resolve(ebicsResponse);
});
2018-05-17 15:03:59 +00:00
});
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2019-11-01 06:13:53 +00:00
async signOrder(order) {
const { version } = order;
const keys = await this.keys();
return signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x());
}
2018-06-27 14:59:35 +00:00
async keys() {
2018-06-28 08:34:14 +00:00
try {
const keysString = await this._readKeys();
2018-06-27 14:59:35 +00:00
2018-06-28 08:34:14 +00:00
return new Keys(JSON.parse(this.keyEncryptor.decrypt(keysString)));
} catch (err) {
return null;
}
}
2018-06-20 09:20:03 +00:00
async _generateKeys() {
const keysObject = Keys.generate();
await this._writeKeys(keysObject);
2018-06-20 09:20:03 +00:00
}
2018-06-27 14:59:35 +00:00
async setBankKeys(bankKeys) {
const keysObject = await this.keys();
2018-06-20 09:20:03 +00:00
keysObject.setBankKeys(bankKeys);
2018-06-27 14:59:35 +00:00
await this._writeKeys(keysObject);
}
2018-06-20 09:20:03 +00:00
2018-06-28 08:34:14 +00:00
_readKeys() {
return this.keyStorage.read();
}
2018-06-20 09:20:03 +00:00
2018-06-28 08:34:14 +00:00
_writeKeys(keysObject) {
return this.keyStorage.write(this.keyEncryptor.encrypt(stringifyKeys(keysObject.keys)));
2018-06-20 09:20:03 +00:00
}
2018-05-17 15:03:59 +00:00
};