@fabrix/spool-cart
Version:
Spool - eCommerce Spool for Fabrix
652 lines (651 loc) • 25.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@fabrix/fabrix/dist/common");
const errors_1 = require("@fabrix/spool-sequelize/dist/errors");
const lodash_1 = require("lodash");
const moment = require("moment");
const enums_1 = require("../../enums");
class AccountService extends common_1.FabrixService {
publish(type, event, options = {}) {
if (this.app.services.EventsService) {
options.include = options.include || [{
model: this.app.models.EventItem.instance,
as: 'objects'
}];
return this.app.services.EventsService.publish(type, event, options);
}
this.app.log.debug('spool-events is not installed, please install it to use publish');
return Promise.resolve();
}
resolvePaymentDetailsToSources(customer, paymentDetails, options) {
options = options || {};
const Source = this.app.models['Source'];
return Source.sequelize.Promise.mapSeries(paymentDetails, detail => {
if (detail.gateway_token) {
const account = {
customer_id: customer.id,
gateway: detail.gateway
};
return this.addSource(account, detail.gateway_token, { transaction: options.transaction || null })
.then(source => {
source = source instanceof this.app.models['Source'].instance ? source.get({ plain: true }) : source;
delete detail.gateway_token;
detail.source = source;
return detail;
})
.catch(err => {
return detail;
});
}
else if (lodash_1.isNumber(detail.source)) {
return this.app.models['Source'].findById(detail.source)
.then(source => {
source = source instanceof this.app.models['Source'].instance ? source.get({ plain: true }) : source;
delete detail.gateway_token;
detail.source = source;
return detail;
})
.catch(err => {
return detail;
});
}
else {
return detail;
}
});
}
getDefaultSource(customer, options) {
options = options || {};
if (!customer) {
const err = new errors_1.ModelError('E_NOT_FOUND', 'Customer Not Provided');
return Promise.reject(err);
}
const Customer = this.app.models['Customer'];
let resCustomer;
return Customer.resolve(customer, { transaction: options.transaction || null, create: false })
.then(_customer => {
if (!_customer) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Customer Not Found');
}
resCustomer = _customer;
return resCustomer.getDefaultSource({ transaction: options.transaction || null });
});
}
updateAll(customer) {
}
update(account, updates, options) {
options = options || {};
const Account = this.app.models['Account'];
let resAccount;
return Account.resolve(account, options)
.then(_account => {
resAccount = _account;
let update = {
foreign_id: resAccount.foreign_id,
foreign_key: resAccount.foreign_key,
email: resAccount.email
};
update = lodash_1.merge(update, updates);
return this.app.services.PaymentGenericService.updateCustomer(update)
.then(updatedAccount => {
resAccount = lodash_1.extend(resAccount, updatedAccount);
return resAccount.save({ transaction: options.transaction || null });
});
})
.then(() => {
const event = {
object_id: resAccount.customer_id,
object: 'customer',
objects: [{
customer: resAccount.customer_id
}, {
account: resAccount.id
}],
type: 'customer.account.updated',
message: `Customer account ${resAccount.foreign_id} was updated on ${resAccount.gateway}`,
data: resAccount
};
return this.publish(event.type, event, {
save: true,
transaction: options.transaction || null
});
})
.then(event => {
return resAccount;
});
}
findAndCreate(account, options) {
options = options || {};
const Account = this.app.models['Account'];
const Source = this.app.models['Source'];
let resAccount, resSource;
return this.app.services.PaymentGenericService.findCustomer(account)
.then(serviceCustomer => {
const create = {
customer_id: account.customer_id,
email: account.email,
is_default: true,
gateway: serviceCustomer.gateway,
foreign_id: serviceCustomer.foreign_id,
foreign_key: serviceCustomer.foreign_key,
data: serviceCustomer.data
};
return Account.create(create, options)
.then(_account => {
resAccount = _account;
return this.app.services.PaymentGenericService.getCustomerSources(resAccount);
})
.then(accountWithSources => {
return Source.sequelize.Promise.mapSeries(accountWithSources.sources, (source, index) => {
source.customer_id = resAccount.customer_id;
source.is_default = index === 0 ? true : false;
return Source.create(source, { transaction: options.transaction || null })
.then(_source => {
if (!_source) {
throw new Error('Source was not created');
}
resSource = _source;
const event = {
object_id: resSource.customer_id,
object: 'customer',
objects: [{
customer: resAccount.customer_id
}, {
account: resAccount.id
}, {
source: resSource.id
}],
type: 'customer.source.created',
message: `Customer source ${resSource.foreign_id} was created on ${resSource.gateway}`,
data: resSource
};
return this.publish(event.type, event, {
save: true,
transaction: options.transaction || null
});
})
.then(event => {
return resSource;
});
});
})
.then(sources => {
const event = {
object_id: resAccount.customer_id,
object: 'customer',
objects: [{
customer: resAccount.customer_id
}, {
account: resAccount.id
}],
type: 'customer.account.created',
message: `Customer account ${account.foreign_id} was created on ${account.gateway}`,
data: resAccount
};
return this.publish(event.type, event, {
save: true,
transaction: options.transaction || null
});
})
.then(event => {
return resAccount;
});
});
}
addSource(account, gatewayToken, options) {
options = options || {};
const Account = this.app.models['Account'];
const Source = this.app.models['Source'];
let resAccount, resSource;
return Account.resolve(account, { transaction: options.transaction || null })
.then(_account => {
if (!_account) {
throw new Error('Account did not resolve');
}
resAccount = _account;
return this.app.services.PaymentGenericService.createCustomerSource({
account_foreign_id: resAccount.foreign_id,
gateway_token: gatewayToken
});
})
.then(serviceCustomerSource => {
serviceCustomerSource.gateway = resAccount.gateway;
serviceCustomerSource.account_id = resAccount.id;
serviceCustomerSource.customer_id = resAccount.customer_id;
serviceCustomerSource.is_default = true;
return Source.create(serviceCustomerSource, { transaction: options.transaction || null });
})
.then(source => {
if (!source) {
throw new Error('Source was not created');
}
resSource = source;
return Source.update({
is_default: false
}, {
where: {
account_id: resSource.account_id,
id: {
$ne: resSource.id
}
},
hooks: false,
individualHooks: false,
returning: false,
transaction: options.transaction || null
});
})
.then(() => {
const event = {
object_id: resSource.customer_id,
object: 'customer',
objects: [{
customer: resAccount.customer_id
}, {
account: resAccount.id
}, {
source: resSource.id
}],
type: 'customer.source.created',
message: `Customer source ${resSource.foreign_id} was created on ${resSource.gateway}`,
data: resSource
};
return this.publish(event.type, event, {
save: true,
transaction: options.transaction || null
});
})
.then(event => {
return resSource;
});
}
findSource(account, source, options) {
options = options || {};
const Account = this.app.models['Account'];
const Source = this.app.models['Source'];
let resAccount, resSource;
return Account.resolve(account, { transaction: options.transaction || null })
.then(_account => {
resAccount = _account;
return Source.resolve(source, { transaction: options.transaction || null });
})
.then(_source => {
resSource = _source;
const find = {
account_foreign_id: resAccount.foreign_id,
foreign_id: resSource.foreign_id
};
return this.app.services.PaymentGenericService.findCustomerSource(find);
})
.then(serviceCustomerSource => {
resSource = lodash_1.extend(resSource, serviceCustomerSource);
resSource.is_default = true;
return resSource.save({ transaction: options.transaction || null });
})
.then(() => {
return Source.update({
is_default: false
}, {
where: {
account_id: resSource.account_id,
id: {
$ne: resSource.id
}
},
hooks: false,
individualHooks: false,
returning: false,
transaction: options.transaction || null
});
})
.then(() => {
return resSource;
});
}
updateSource(account, source, updates, options) {
options = options || {};
const Account = this.app.models['Account'];
const Source = this.app.models['Source'];
let resAccount, resSource;
return Account.resolve(account, { transaction: options.transaction || null })
.then(_account => {
resAccount = _account;
return Source.resolve(source, { transaction: options.transaction || null });
})
.then(_source => {
resSource = _source;
let update = {
account_foreign_id: resAccount.foreign_id,
foreign_id: resSource.foreign_id
};
update = lodash_1.merge(update, updates);
return this.app.services.PaymentGenericService.updateCustomerSource(update);
})
.then(serviceCustomerSource => {
resSource = lodash_1.extend(resSource, serviceCustomerSource);
return resSource.save({ transaction: options.transaction || null });
})
.then(() => {
return Source.update({
is_default: false
}, {
where: {
account_id: resSource.account_id,
id: {
$ne: resSource.id
}
},
hooks: false,
individualHooks: false,
returning: false,
transaction: options.transaction || null
});
})
.then(() => {
const event = {
object_id: resAccount.customer_id,
object: 'customer',
objects: [{
customer: resAccount.customer_id
}, {
account: resAccount.id
}, {
source: resSource.id
}],
type: 'customer.source.updated',
message: `Customer source ${resSource.foreign_id} was updated on ${resSource.gateway}`,
data: resSource
};
return this.publish(event.type, event, {
save: true,
transaction: options.transaction || null
});
})
.then(event => {
return resSource;
});
}
removeSource(source, options) {
options = options || {};
const Source = this.app.models['Source'];
let resSource;
return Source.resolve(source, options)
.then(_source => {
resSource = _source;
return this.app.services.PaymentGenericService.removeCustomerSource(source);
})
.then(customerSource => {
return resSource.destroy({ transaction: options.transaction || null });
})
.then(() => {
return Source.findOne({
where: {
customer_id: resSource.customer_id,
account_id: resSource.account_id
}
})
.then(altSource => {
if (altSource) {
altSource.is_default = true;
return altSource.save({ transaction: options.transaction || null });
}
else {
return;
}
});
})
.then(() => {
const event = {
object_id: resSource.customer_id,
object: 'customer',
objects: [{
customer: resSource.customer_id
}, {
account: resSource.account_id
}, {
source: resSource.id
}],
type: 'customer.source.removed',
message: `Customer source ${source.foreign_id} was removed on ${source.gateway}`,
data: resSource
};
return this.publish(event.type, event, {
save: true,
transaction: options.transaction || null
});
})
.then(event => {
return resSource;
});
}
syncSources(account, options) {
options = options || {};
const Account = this.app.models['Account'];
const Source = this.app.models['Source'];
let resAccount;
return Account.resolve(account, { transaction: options.transaction || null })
.then(_account => {
resAccount = _account;
return this.app.services.PaymentGenericService.findCustomerSources(_account);
})
.then(serviceCustomerSources => {
return Source.sequelize.Promise.mapSeries(serviceCustomerSources, (source, index) => {
source.gateway = resAccount.gateway;
source.account_id = resAccount.id;
source.customer_id = resAccount.customer_id;
source.is_default = index === 0 ? true : false;
return Source.findOrCreate({
defaults: source,
where: {
customer_id: source.customer_id,
account_id: source.account_id,
foreign_id: source.foreign_id
},
transaction: options.transaction || null
});
});
});
}
sourceRetryTransactions(source, options) {
options = options || {};
const Source = this.app.models['Source'];
let resSource;
return Source.resolve(source, options)
.then(_source => {
if (!_source) {
throw new Error('Source could not be resolved');
}
resSource = _source;
return resSource.getTransactions({
where: {
status: [enums_1.TRANSACTION_STATUS.FAILURE, enums_1.TRANSACTION_STATUS.ERROR]
},
transaction: options.transaction || null
});
})
.then(transactions => {
return Source.sequelize.Promise.mapSeries(transactions, transaction => {
transaction.payment_details.source = resSource;
return this.app.services.TransactionService.retry(transaction, { transaction: options.transaction || null });
});
});
}
sourceExpired(source, options) {
options = options || {};
const Source = this.app.models['Source'];
let resSource;
return Source.resolve(source, { transaction: options.transaction || null })
.then(_source => {
if (!_source) {
throw new Error('Source did not resolve');
}
if (!(_source instanceof Source.instance)) {
throw new Error('Source did not resolve instance of Source');
}
resSource = _source;
return resSource.sendExpiredEmail({ transaction: options.transaction || null });
})
.then(notification => {
return resSource;
});
}
sourceWillExpire(source, options) {
options = options || {};
const Source = this.app.models['Source'];
let resSource;
return Source.resolve(source, { transaction: options.transaction || null })
.then(_source => {
if (!_source) {
throw new Error('Source did not resolve');
}
if (!(_source instanceof Source.instance)) {
throw new Error('Source did not resolve instance of Source');
}
resSource = _source;
return resSource.sendWillExpireEmail({ transaction: options.transaction || null });
})
.then(notification => {
return resSource;
});
}
afterSourceCreate(source, options) {
options = options || {};
return this.app.models['CustomerSource'].create({
source_id: source.id,
source: source.gateway,
account_id: source.account_id,
customer_id: source.customer_id
}, {
transaction: options.transaction || null
})
.then(customerSource => {
return source;
});
}
afterSourceDestroy(source, options) {
options = options || {};
return this.app.models['CustomerSource'].destroy({
where: {
source_id: source.id
},
transaction: options.transaction || null
})
.then(customerSource => {
return source;
});
}
sourcesExpiredThisMonth(options) {
options = options || {};
const start = moment()
.subtract(1, 'months');
const startMonth = start.clone().format('MM');
const startYear = start.clone().format('YYYY');
const Source = this.app.models['Source'];
const errors = [];
let sourcesTotal = 0;
this.app.log.debug('AccountService.sourcesExpiredThisMonth', startMonth, startYear);
return Source.batch({
where: {
$or: [{
payment_details: {
type: 'credit_card',
credit_card_exp_month: startMonth,
credit_card_exp_year: startYear
}
}, {
payment_details: {
type: 'debit_card',
credit_card_exp_month: startMonth,
credit_card_exp_year: startYear
}
}]
},
regressive: false,
transaction: options.transaction || null
}, (sources) => {
const Sequelize = Source.sequelize;
return Sequelize.Promise.mapSeries(sources, source => {
return this.sourceExpired(source, { transaction: options.transaction || null });
})
.then(results => {
sourcesTotal = sourcesTotal + results.length;
return;
})
.catch(err => {
this.app.log.error(err);
errors.push(err);
return;
});
})
.then(sources => {
const results = {
sources: sourcesTotal,
errors: errors
};
this.app.log.info(results);
this.app.services.EventsService.publish('account.sourcesExpiredThisMonth.complete', results);
return results;
})
.catch(err => {
this.app.log.error(err);
return;
});
}
sourcesWillExpireNextMonth(options) {
options = options || {};
const start = moment();
const startMonth = start.clone().format('MM');
const startYear = start.clone().format('YYYY');
const Source = this.app.models['Source'];
const errors = [];
let sourcesTotal = 0;
this.app.log.debug('account.sourcesWillExpireNextMonth', startMonth, startYear);
return Source.batch({
where: {
$or: [{
payment_details: {
type: 'credit_card',
credit_card_exp_month: startMonth,
credit_card_exp_year: startYear
}
}, {
payment_details: {
type: 'debit_card',
credit_card_exp_month: startMonth,
credit_card_exp_year: startYear
}
}]
},
regressive: false,
transaction: options.transaction || null
}, (sources) => {
const Sequelize = Source.sequelize;
return Sequelize.Promise.mapSeries(sources, source => {
return this.sourceWillExpire(source, { transaction: options.transaction || null });
})
.then(results => {
sourcesTotal = sourcesTotal + results.length;
return;
})
.catch(err => {
this.app.log.error(err);
errors.push(err);
return;
});
})
.then(sources => {
const results = {
sources: sourcesTotal,
errors: errors
};
this.app.log.info(results);
this.app.services.EventsService.publish('account.sourcesWillExpireNextMonth.complete', results);
return results;
})
.catch(err => {
this.app.log.error(err);
return;
});
}
}
exports.AccountService = AccountService;