n26
Version:
Un-official node.js module for interact with your N26 (previously Number26) account
710 lines (639 loc) • 17.8 kB
JavaScript
'use strict';
const moment = require('moment');
const Promise = require('bluebird');
const Card = require('./card.js');
const api = require('./api');
const utils = require('./utils');
/**
* @typedef Promise
*
* Bluebird promise
* See http://bluebirdjs.com/docs/api-reference.html for references
*/
/**
* @typedef account
*
* @property {String} id
* @property {String} iban Account IBAN
* @property {String} bic BIC
* @property {number} usableBalance Usable balance
* @property {number} availableBalance Available balance
* @property {number} bankBalance Bank balance
* @property {String} bankName Bank name
* @property {Boolean} seized
*/
/**
* @typedef addresses
*
* @property {objet} paging
* @property {number} paging.totalResults Total amount of addresses
* @property {Object[]} data
* @property {String} data.id
* @property {String} data.type Type address
* @property {String} data.addressLine1 Address line 1
* @property {String} data.streetName Street name
* @property {String} data.houseNumberBlock Street Number
* @property {String} data.zipCode Zip code
* @property {String} data.cityName City
* @property {String} data.countryName Country
*/
/**
* @typedef cards
*
* @property {objet} paging
* @property {number} paging.totalResults Total amount of addresses
* @property {Card[]} data
*/
/**
* @typedef me
*
* @property {String} id
* @property {String} birthDate Birth date
* @property {String} email Email
* @property {String} gender Gender
* @property {String} title Title (eg: Ph.)
* @property {String} firstName Firstname
* @property {String} lastName Lastname
* @property {String} kycFirstName Firstname on KYC
* @property {String} kycLastName Lastname on KYC
* @property {String} mobilePhoneNumber Mobile phone number
* @property {String} nationality Nationality
* @property {String} passwordHash
* @property {String} shadowID
* @property {String} signupCompleted
* @property {String} cardName
* @property {String} transferWiseTermsAccepted
*/
/**
* @typedef statuses
*
* @property {Number} singleStepSignup
* @property {Number} emailValidationInitiated
* @property {Number} emailValidationCompleted
* @property {Number} phonePairingInitiated
* @property {Number} phonePairingCompleted
* @property {Number} kycInitiated
* @property {Number} kycCompleted
* @property {Number} kycWebIDInitiated
* @property {Number} kycWebIDCompleted
* @property {Number} cardActivationCompleted
* @property {Number} cardIssued
* @property {Number} pinDefinitionCompleted
* @property {Number} bankAccountCreationInitiated
* @property {Number} bankAccountCreationSucceded
* @property {Number} firstIncomingTransaction
* @property {String} smsVerificationCode
* @property {Number} unpairTokenCreation
* @property {Number|String} finoIntegrationStatus='NEVER'
* @property {Number} id
*/
/**
* @typedef transactions
*
* @property {String} id
* @property {String} type
* @property {String} smartLinkId
* @property {String} amount
* @property {String} currencyCode
* @property {String} originalAmount
* @property {String} originalCurrency
* @property {String} exchangeRate
* @property {String} merchantCity
* @property {String} visibleTS
* @property {String} mcc
* @property {String} mccGroup
* @property {String} merchantName
* @property {String} merchantId
* @property {String} recurring
* @property {String} userId
* @property {String} linkId
* @property {String} accountId
* @property {String} category
* @property {String} cardId
* @property {String} pending
* @property {String} transactionNature
* @property {String} partnerAccountIsSepa
* @property {String} partnerName
* @property {String} partnerIban
* @property {String} referenceText
* @property {String} userCertified
* @property {String} smartContactId
* @property {String} partnerBic
* @property {String} confirmed
*/
/**
* @typedef transaction
*
* @property {String} id
* @property {String} type
* @property {String} smartLinkId
* @property {String} amount
* @property {String} currencyCode
* @property {String} originalAmount
* @property {String} originalCurrency
* @property {String} exchangeRate
* @property {String} merchantCity
* @property {String} visibleTS
* @property {String} mcc
* @property {String} mccGroup
* @property {String} merchantName
* @property {String} merchantId
* @property {String} recurring
* @property {String} userId
* @property {String} linkId
* @property {String} accountId
* @property {String} category
* @property {String} cardId
* @property {String} pending
* @property {String} transactionNature
* @property {String} partnerAccountIsSepa
* @property {String} partnerName
* @property {String} partnerIban
* @property {String} referenceText
* @property {String} userCertified
* @property {String} smartContactId
* @property {String} partnerBic
* @property {String} confirmed
* @property {String[]} tags
* @property {Object} [meta]
* @property {String} [meta.memo]
*/
/**
* @typedef transfer
*
* @property {String} id
* @property {String} n26Iban
* @property {String} referenceText
* @property {String} partnerName
* @property {String} partnerIban
* @property {String} partnerBic
* @property {Boolean} partnerAccountIsSepa
* @property {Number} amount
* @property {String} currencyCode
* @property {String} linkId
* @property {String} recurring
* @property {String} visibleTS
*/
/**
* @typedef invitation
*
* @property {String} invited Invited email
* @property {String} status Current status
* @property {Number} reward Reward amount
* @property {Number} created Timestamp
*/
/**
* @typedef barzahlen
*
* @property {String} depositAllowance
* @property {String} withdrawAllowance
* @property {String} remainingAmountMonth
* @property {String} feeRate
* @property {String} cash26WithdrawalsCount
* @property {String} cash26WithdrawalsSum
* @property {String} atmWithdrawalsCount
* @property {String} atmWithdrawalsSum
* @property {String} monthlyDepositFeeThreshold
* @property {Boolean} success
*/
/**
* @typedef statement
*
* @property {String} id
* @property {String} type `pdf` for pdf buffer or `base64` for pdf in base64
* @property {Buffer|String} pdf PDF encoded
*/
/**
* @typedef statements
*
* @property {String} id
* @property {String} url
* @property {Number} month
* @property {Number} visibleTS
* @property {Number} year
*/
/**
* @typedef contact
*
* @property {String} id
* @property {String} name
* @property {String} subtitle
* @property {Object} account
* @property {String} account.accountType
* @property {String} account.iban
* @property {String} account.bic
*/
/**
* @typedef limit
*
* @property {String} limit Limit type, can be ATM_DAILY_ACCOUNT or POS_DAILY_ACCOUNT
* @property {Number} amount
*/
/**
* @typedef category
*
* @property {String} id
* @property {String} base64Image PNG base64 encoded
* @property {String} name Translated category name
*/
/**
* @typedef csv
*
* @description CSV file with "," delimiter
*/
/**
* N26 Account
*/
class Account {
/**
* Create new account instance
*
* @param {Object} auth Object from auth()
*/
constructor(email, password) {
this.logged = false;
this.email = email;
this.password = password;
this.createdAt = null;
this.accessToken = null;
this.expiresIn = null;
this.jti = null;
this.scope = null;
this.tokenType = null;
}
/**
* authentication
*
* @returns {Promise<Account>}
*/
auth() {
return api
.auth(this.email, this.password)
.bind(this)
.then(auth => {
this.logged = true;
this.createdAt = +new Date() / 1000;
this.accessToken = auth.access_token;
this.expiresIn = auth.expires_in;
this.jti = auth.jti;
this.scope = auth.scope;
this.tokenType = auth.token_type;
return this;
});
}
/**
* Get account details
*
* @returns {Promise<account>}
*/
account() {
return utils.callApi(this, 'getAccount');
}
/**
* Get addresses
*
* @return {Promise<addresses>}
*/
addresses() {
return utils.callApi(this, 'getAddresses');
}
/**
* Check barzahlen
*
* @return {Promise<barzahlen>}
*/
barzahlen() {
return utils.callApi(this, 'checkBarzahlen');
}
/**
* Get transaction categories
*
* @return {Promise<category[]>}
*/
categories() {
return utils.callApi(this, 'getCategories');
}
/**
* Get one specific or all cards
*
* @param {String} [cardId] Card Id - Don't use, some info like `status` is missing
*
* @return {Promise<Card>|Promise<cards>}
*/
cards(cardId) {
if (cardId) {
return utils
.callApi(this, 'getCard', cardId)
.then(card => new Card(this, card));
}
return utils
.callApi(this, 'getCards')
.then(cards => cards.map(card => new Card(this, card)));
}
/**
* Get transactions CSV
*
* @param {Date|Number} from From date
* @param {Date|Number} [to=now()] To date
*
* @return {Promise<csv>}
*/
csv(from, to) {
return Promise.try(() => {
if (!from) {
throw new Error('MISSING_PARAMS');
}
from = from instanceof Date ? from.getTime() : from;
to = to instanceof Date ? to.getTime() : to || new Date().getTime();
return utils.callApi(this, 'getCSV', { from, to });
});
}
/**
* Get contacts
*
* @return {Promise<contact[]>}
*/
contacts() {
return utils.callApi(this, 'getContacts');
}
/**
* Get / send invitations emails
*
* @param {String|String[]} [emails] Emails to send an invitation
*
* @return {Promise|Promise<invitation[]>}
*/
invitations(emails) {
if (emails) {
emails = Array.isArray(emails) ? emails : [emails];
return Promise.each(emails, email =>
utils.callApi(this, 'sendInvitations', email)
);
}
return utils.callApi(this, 'getInvitations');
}
/**
* Get / Set limits
*
* @param {Object} [limits]
* @param {Number} [limits.atm] Set daily limit for ATM withdraw
* @param {Number} [limits.pos] Set daily limit for POS payment
*
* @return {Promise|Promise<limit[]>}
*/
limits(limits) {
function validateAmount(amount, max) {
if (!Number.isInteger(amount) || amount < 0 || amount > max) {
return false;
}
return true;
}
return Promise.try(() => {
if (limits) {
const calls = [];
if (limits.atm !== undefined) {
if (!validateAmount(limits.atm, 2500)) {
throw new Error('Limits should be between 0 and 2500');
}
calls.push(
utils.callApi(this, 'setLimits', {
limit: 'ATM_DAILY_ACCOUNT',
amount: limits.atm
})
);
}
if (limits.pos !== undefined) {
if (!validateAmount(limits.pos, 5000)) {
throw new Error('Limits should be between 0 and 5000');
}
calls.push(
utils.callApi(this, 'setLimits', {
limit: 'POS_DAILY_ACCOUNT',
amount: limits.pos
})
);
}
return Promise.all(calls);
}
return utils.callApi(this, 'getLimits');
});
}
/**
* Get information about current user
*
* @param {Boolean} full Return full informations
*
* @return {Promise<me>}
*/
me(full) {
return utils.callApi(this, 'getMe', { full: !!full });
}
/**
* Create or update Meme
*
* @param {String} smartLinkId SmartLinkId
* @param {String} memo Memo text
*/
memo(smartLinkId, memo) {
return utils
.callApi(this, 'getTransactionMeta', smartLinkId)
.then(meta =>
utils.callApi(this, 'createOrUpdateMemo', { smartLinkId, meta, memo })
);
}
/**
* Get statement
*
* @description Return pdf buffer or base64 encoded
*
* @return {Promise<statement>}
*/
statement(id, pdf) {
return Promise.try(() => {
pdf = !!pdf;
if (!id) {
throw new Error('MISSING_PARAMS');
}
return utils.callApi(this, 'getStatement', { id, pdf }).then(result => ({
id,
type: pdf ? 'pdf' : 'base64',
pdf: pdf ? new Buffer(result, 'binary') : result.pdf
}));
});
}
/**
* Get statements
*
* @return {Promise<statements[]>}
*/
statements() {
return utils.callApi(this, 'getStatements');
}
/**
* Get statuses
*
* Return many timestamps about this account
*
* @return {Promise<statuses>}
*/
statuses() {
return utils.callApi(this, 'getStatuses');
}
/**
* Get stats
*
* @param {Date|Number} from From date (native date or timestamp)
* @param {Date|Number} to To date (native date or timestamp)
* @param {String} interval Interval data per "days" / "weeks" / "months" / "years"
*
*/
stats(from, to, interval) {
return Promise.try(() => {
if (!from || !to || !interval) {
throw new Error('MISSING_PARAMS');
}
if (['days', 'weeks', 'months', 'years'].indexOf(interval) === -1) {
throw new Error('BAD_PARAMS');
}
const request = [];
from = moment(
from instanceof Date ? from.getTime() / 1000 : from,
'X'
).startOf(interval);
to = moment(to instanceof Date ? to.getTime() / 1000 : to, 'X').endOf(
interval
);
while (from < to) {
const tempTo = moment(from)
.add(1, interval)
.endOf(interval);
request.push(
utils.callApi(this, 'getStats', {
from: from.valueOf(),
to: tempTo.valueOf()
})
);
from.add(1, interval);
}
return Promise.map(request, slices => {
const slice = slices.slices[0];
return {
from: slice.from,
to: slice.to,
amount: slice.ammount
};
});
});
}
/**
* Get transactions
*
* @param {Object} [options]
* @param {Number} [options.limit] Limit results
* @param {String[]} [options.categories] Filter by categories
* @param {Number} [options.from] "From" timestamp limit
* @param {Number} [options.to] "To" timestamp limit
* @param {String} [options.text] Text search
* @param {String} [options.pending=false] Pending transaction
*
* @return {Promise<transactions[]>}
*/
transactions(options) {
return utils.callApi(this, 'getTransactions', options || {});
}
/**
* Get transaction detail
*
* @param {String} id Transaction number
* @param {Object} options
* @param {Boolean} options.meta With meta
*
* @return {Promise<transaction>}
*/
transaction(id, options) {
return Promise.try(() => {
if (!id) {
throw new Error('MISSING_PARAMS');
}
const data = { id };
if (options && options.meta) {
data.meta = options.meta;
}
return utils.callApi(this, 'getTransaction', data);
});
}
/**
* Create transfer
*
* @param {Object} data
* @param {Number|String} data.pin Credit card pin
* @param {String} data.bic BIC recipient
* @param {String} data.iban IBAN recipient
* @param {String} data.name Recipient name
* @param {Number} data.amount Amount
* @param {String} data.reference Reference
*
* @return {Promise<transfer>}
*/
transfer(data) {
return Promise.try(() => {
if (
!data.pin ||
!data.iban ||
!data.bic ||
!data.amount ||
!data.name ||
!data.reference
) {
throw new Error('MISSING_PARAMS');
}
if (data.reference.length > 135) {
throw new Error('REFERENCE_TOO_LONG');
}
return utils.callApi(this, 'createTransfer', data);
});
}
/**
* Init unpair device
*
* Init unpair process. Will receive an email (ignore it) + sms.
*
* @param {Number} pin
* @param {Number} cardNumber The 10 digits on the card, below name
*
* @return {Promise}
*/
unpairInit(pin, cardNumber) {
return Promise.try(() => {
const that = this;
if (!pin || !cardNumber) {
throw new Error('MISSING_PARAMS');
}
return utils
.callApi(that, 'unpairUpstart')
.then(link => utils.callApi(that, 'unpairVerify', link))
.then(() => utils.callApi(that, 'unpairValidationPin', pin))
.then(() => utils.callApi(that, 'unpairValidationCard', cardNumber))
.then(() => utils.callApi(that, 'unpairValidationSms'));
});
}
/**
* Confirm unpairing
*
* Confirm unpairing
* Use the number on the sms message.
*
* @param {Number} smsNumber The 5 digits received on the phone
*
* @return {Promise}
*/
unpairConfirm(smsNumber) {
return Promise.try(() => {
if (!smsNumber) {
throw new Error('MISSING_PARAMS');
}
return utils.callApi(this, 'unpairValidationSmsVerify', smsNumber);
});
}
}
module.exports = Account;