UNPKG

@fabrix/spool-cart

Version:

Spool - eCommerce Spool for Fabrix

359 lines (358 loc) 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@fabrix/fabrix/dist/common"); const spool_sequelize_1 = require("@fabrix/spool-sequelize"); const errors_1 = require("@fabrix/spool-sequelize/dist/errors"); const lodash_1 = require("lodash"); const shortId = require("shortid"); const moment = require("moment"); const enums_1 = require("../../enums"); const enums_2 = require("../../enums"); const enums_3 = require("../../enums"); const enums_4 = require("../../enums"); class TransactionResolver extends spool_sequelize_1.SequelizeResolver { batch(options, batch) { const self = this; options.limit = options.limit || 100; options.offset = options.offset || 0; options.regressive = options.regressive || false; const recursiveQuery = function (options) { let count = 0; return self.findAndCountAll(options) .then(results => { count = results.count; return batch(results.rows); }) .then(batched => { if (count >= (options.regressive ? options.limit : options.offset + options.limit)) { options.offset = options.regressive ? 0 : options.offset + options.limit; return recursiveQuery(options); } else { return Promise.resolve(); } }); }; return recursiveQuery(options); } resolveByInstance(transaction, options = {}) { return Promise.resolve(transaction); } resolveById(transaction, options = {}) { return this.findById(transaction.id, options) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Transaction ${transaction.id} not found`); } return resUser; }); } resolveByToken(transaction, options = {}) { return this.findOne(this.app.services.SequelizeService.mergeOptionDefaults(options, { where: { token: transaction.token } })) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Transaction token ${transaction.token} not found`); } return resUser; }); } resolveByNumber(transaction, options = {}) { return this.findById(transaction, options) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Transaction ${transaction.token} not found`); } return resUser; }); } resolveByString(transaction, options = {}) { return this.findOne(this.app.services.SequelizeService.mergeOptionDefaults(options, { where: { code: transaction } })) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Transaction ${transaction} not found`); } return resUser; }); } resolve(transaction, options = {}) { const resolvers = { 'instance': transaction instanceof this.instance, 'id': !!(transaction && lodash_1.isObject(transaction) && transaction.id), 'token': !!(transaction && lodash_1.isObject(transaction) && transaction.token), 'number': !!(transaction && lodash_1.isNumber(transaction)), 'string': !!(transaction && lodash_1.isString(transaction)) }; const type = Object.keys(resolvers).find((key) => resolvers[key]); switch (type) { case 'instance': { return this.resolveByInstance(transaction, options); } case 'id': { return this.resolveById(transaction, options); } case 'token': { return this.resolveByToken(transaction, options); } case 'number': { return this.resolveByNumber(transaction, options); } case 'string': { return this.resolveByString(transaction, options); } default: { const err = new Error(`Unable to resolve Transaction ${transaction}`); return Promise.reject(err); } } } } exports.TransactionResolver = TransactionResolver; class Transaction extends common_1.FabrixModel { static get resolver() { return TransactionResolver; } static config(app, Sequelize) { return { options: { underscored: true, enums: { TRANSACTION_ERRORS: enums_1.TRANSACTION_ERRORS, TRANSACTION_STATUS: enums_2.TRANSACTION_STATUS, TRANSACTION_KIND: enums_3.TRANSACTION_KIND, TRANSACTION_DEFAULTS: enums_4.TRANSACTION_DEFAULTS }, description: 'A Transaction is a representation of a purchasing event.', scopes: { live: { where: { live_mode: true } }, authorized: { where: { kind: 'authorize', status: 'success' } }, captured: { where: { kind: ['capture', 'sale'], status: 'success' } }, voided: { where: { kind: 'void', status: 'success' } }, refunded: { where: { kind: 'refund', status: 'success' } } }, hooks: { beforeCreate: (transaction, options) => { if (!transaction.token) { transaction.token = `transaction_${shortId.generate()}`; } }, afterCreate: (transaction, options) => { return app.services.TransactionService.afterCreate(transaction, options) .catch(err => { return Promise.reject(err); }); }, afterUpdate: (transaction, options) => { return app.services.TransactionService.afterUpdate(transaction, options) .catch(err => { return Promise.reject(err); }); } } } }; } static schema(app, Sequelize) { return { token: { type: Sequelize.STRING, unique: true }, customer_id: { type: Sequelize.INTEGER, allowNull: true }, order_id: { type: Sequelize.INTEGER, allowNull: false }, source_id: { type: Sequelize.INTEGER, allowNull: true }, account_id: { type: Sequelize.INTEGER, allowNull: true }, foreign_key: { type: Sequelize.STRING }, foreign_id: { type: Sequelize.STRING }, amount: { type: Sequelize.INTEGER, defaultValue: 0 }, amount_refunded: { type: Sequelize.INTEGER, defaultValue: 0 }, authorization: { type: Sequelize.STRING }, authorization_exp: { type: Sequelize.DATE, defaultValue: moment() .subtract(app.config.get('cart.transactions.authorization_exp_days') || 0, 'days') .format('YYYY-MM-DD HH:mm:ss') }, device_id: { type: Sequelize.STRING }, gateway: { type: Sequelize.STRING }, source_name: { type: Sequelize.STRING, defaultValue: enums_4.TRANSACTION_DEFAULTS.SOURCE_NAME }, payment_details: { type: Sequelize.JSONB, defaultValue: {} }, kind: { type: Sequelize.ENUM, values: Object.values(enums_3.TRANSACTION_KIND), allowNull: false }, receipt: { type: Sequelize.JSONB, defaultValue: {} }, error_code: { type: Sequelize.ENUM, values: Object.values(enums_1.TRANSACTION_ERRORS) }, status: { type: Sequelize.ENUM, values: Object.values(enums_2.TRANSACTION_STATUS), defaultValue: enums_2.TRANSACTION_STATUS.PENDING }, currency: { type: Sequelize.STRING, defaultValue: app.config.get('cart.default_currency') || enums_4.TRANSACTION_DEFAULTS.CURRENCY }, description: { type: Sequelize.STRING }, retry_at: { type: Sequelize.DATE }, total_retry_attempts: { type: Sequelize.INTEGER, defaultValue: 0 }, shop_id: { type: Sequelize.INTEGER, }, cancelled_at: { type: Sequelize.DATE }, live_mode: { type: Sequelize.BOOLEAN, defaultValue: app.config.get('cart.live_mode') } }; } static associate(models) { models.Transaction.belongsTo(models.Order, {}); models.Transaction.belongsTo(models.Customer, {}); models.Transaction.belongsTo(models.Account, {}); models.Transaction.belongsTo(models.Source, {}); models.Transaction.belongsTo(models.Shop, { foreignKey: 'shop_id', }); } } exports.Transaction = Transaction; Transaction.prototype.retry = function () { this.retry_at = new Date(Date.now()); this.total_retry_attempts++; if (this.description && Boolean(this.description.match(/retry (\d+)/g))) { this.description = this.description.replace(/retry (\d+)/g, `retry ${this.total_retry_attempts}`); } else { this.description = `${this.description || 'transaction'} retry ${this.total_retry_attempts}`; } return this; }; Transaction.prototype.cancel = function () { this.cancelled_at = new Date(Date.now()); this.status = enums_2.TRANSACTION_STATUS.CANCELLED; if (this.description && !this.description.includes('cancelled')) { this.description = `${this.description || 'transaction'} cancelled`; } return this; }; Transaction.prototype.resolveOrder = function (options = {}) { const Order = this.app.models['Order']; if (this.Order && this.Order instanceof Order.instance && options.reload !== true) { return Promise.resolve(this); } else { return this.getOrder({ transaction: options.transaction || null }) .then(order => { order = order || null; this.Order = order; this.setDataValue('Order', order); this.set('Order', order); }); } }; Transaction.prototype.reconcileOrderFinancialStatus = function (options = {}) { const Order = this.app.models['Order']; if (!this.changed('status') && !this.changed('kind')) { return Promise.resolve(this); } let resOrder; return Order.findById(this.order_id, { transaction: options.transaction || null }) .then(foundOrder => { if (!foundOrder) { throw new Error('Order could not be resolved for transaction'); } resOrder = foundOrder; return resOrder.saveFinancialStatus({ transaction: options.transaction || null }); }) .then(() => { return resOrder.saveStatus({ transaction: options.transaction || null }); }) .then(() => { return this; }); };