trailpack-proxy-cart
Version:
eCommerce - Trailpack for Proxy Engine
408 lines (394 loc) • 13.8 kB
JavaScript
/* eslint new-cap: [0] */
/* eslint no-console: [0] */
'use strict'
const Model = require('trails/model')
const helpers = require('proxy-engine-helpers')
const Errors = require('proxy-engine-errors')
const _ = require('lodash')
const shortId = require('shortid')
/**
* @module Source
* @description Payment Source Model
*/
module.exports = class Source extends Model {
static config (app, Sequelize) {
return {
options: {
underscored: true,
// defaultScope: {
// where: {
// live_mode: app.config.proxyEngine.live_mode
// }
// },
scopes: {
live: {
where: {
live_mode: true
}
}
},
hooks: {
beforeCreate: (values, options) => {
// If not token was already created, create it
if (!values.token) {
values.token = `source_${shortId.generate()}`
}
},
afterCreate: (values, options) => {
return app.services.AccountService.afterSourceCreate(values)
.catch(err => {
return Promise.reject(err)
})
},
afterDestroy: (values, options) => {
return app.services.AccountService.afterSourceDestroy(values)
.catch(err => {
return Promise.reject(err)
})
},
},
classMethods: {
associate: (models) => {
models.Source.belongsTo(models.Account, {
// as: 'account',
// through: {
// model: models.CustomerSource,
// unique: false,
// foreignKey: 'account_id',
// }
// constraints: false
})
models.Source.belongsTo(models.Customer, {
// as: 'customer',
// through: {
// model: models.CustomerSource,
// unique: false,
// foreignKey: 'customer_id',
// }
// constraints: false
})
models.Source.hasMany(models.Transaction, {
as: 'transactions',
// constraints: false
})
},
/**
*
* @param options
* @param batch
* @returns Promise.<T>
*/
batch: function (options, batch) {
const self = this
options = options || {}
options.limit = options.limit || 10
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(() => {
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)
},
resolve: function(source, options){
const Source = this
if (source instanceof Source){
return Promise.resolve(source)
}
else if (source && _.isObject(source) && source.id) {
return Source.findById(source.id, options)
.then(resSource => {
if (!resSource) {
throw new Errors.FoundError(Error(`Source ${source.id} not found`))
}
return resSource
})
}
else if (source && _.isObject(source) && source.token) {
return Source.findOne(app.services.ProxyEngineService.mergeOptionDefaults(
options || {},
{
where: {
token: source.token
}
}
))
.then(resSource => {
if (!resSource) {
throw new Errors.FoundError(Error(`Source ${source.token} not found`))
}
return resSource
})
}
else if (source && _.isNumber(source)) {
return Source.findById(source, options)
.then(resSource => {
if (!resSource) {
throw new Errors.FoundError(Error(`Source ${source} not found`))
}
return resSource
})
}
else if (source && _.isString(source)) {
return Source.findOne(app.services.ProxyEngineService.mergeOptionDefaults(
options || {},
{
where: {
token: source
}
}
))
.then(resSource => {
if (!resSource) {
throw new Errors.FoundError(Error(`Source ${source} not found`))
}
return resSource
})
}
else {
// TODO create proper error
const err = new Error(`Unable to resolve Source ${source}`)
return Promise.reject(err)
}
}
},
instanceMethods: {
/**
* Get the Credit/Debit Card Brand Company Name
*/
getBrand: function() {
let brand = this.gateway
if (this.payment_details && this.payment_details.credit_card_company) {
brand = this.payment_details.credit_card_company
}
return brand
},
/**
* Get's the type of the
*/
getType: function() {
let type
switch (this.payment_details.type) {
case 'credit_card':
type = 'Credit Card'
break
case 'debit_card':
type = 'Debit Card'
break
default:
type = 'Payment Method'
}
return type
},
/**
* Get's the Last 4 Digits of a Payment Method, if Applicable
* @returns {string}
*/
getLast4: function () {
let last4 = '****'
if (this.payment_details && this.payment_details.credit_card_last4) {
last4 = this.payment_details.credit_card_last4
}
return last4
},
/**
* Get's the expiration date of the card if applicable.
* @returns {string}
*/
getExpiration: function () {
let expiration = 'MM/YYYY'
if (
this.payment_details
&& this.payment_details.credit_card_exp_year
&& this.payment_details.credit_card_exp_month
) {
expiration = `${this.payment_details.credit_card_exp_month}/${this.payment_details.credit_card_exp_year}`
}
return expiration
},
/**
*
* @param preNotification
* @param options
*/
notifyCustomer: function(preNotification, options) {
options = options || {}
if (this.customer_id) {
return this.resolveCustomer({
attributes: ['id','email','company','first_name','last_name','full_name'],
transaction: options.transaction || null,
reload: options.reload || null
})
.then(() => {
if (this.Customer && this.Customer instanceof app.orm['Customer']) {
return this.Customer.notifyUsers(preNotification, {transaction: options.transaction || null})
}
else {
return
}
})
.then(() => {
return this
})
}
else {
return Promise.resolve(this)
}
},
resolveCustomer: function(options) {
options = options || {}
if (
this.Customer
&& this.Customer instanceof app.orm['Customer']
&& options.reload !== true
) {
return Promise.resolve(this)
}
// A subscription always requires a customer, but just in case.
else if (!this.customer_id) {
return Promise.resolve(this)
}
else {
return this.getCustomer({transaction: options.transaction || null})
.then(_customer => {
_customer = _customer || null
this.Customer = _customer
this.setDataValue('Customer', _customer)
this.set('Customer', _customer)
return this
})
}
},
/**
*
* @param options
* @returns {Promise.<T>}
*/
sendExpiredEmail(options) {
options = options || {}
return app.emails.Source.expired(this, {
send_email: app.config.proxyCart.emails.sourceExpired
}, {
transaction: options.transaction || null
})
.then(email => {
return this.notifyCustomer(email, {transaction: options.transaction || null})
})
.catch(err => {
app.log.error(err)
return
})
},
/**
*
* @param options
* @returns {Promise.<T>}
*/
sendWillExpireEmail(options) {
options = options || {}
return app.emails.Source.willExpire(this, {
send_email: app.config.proxyCart.emails.sourceWillExpire
}, {
transaction: options.transaction || null
})
.then(email => {
return this.notifyCustomer(email, {transaction: options.transaction || null})
})
.catch(err => {
app.log.error(err)
return
})
}
}
}
}
}
static schema (app, Sequelize) {
return {
customer_id: {
type: Sequelize.INTEGER,
// references: {
// model: 'Customer',
// key: 'id'
// },
allowNull: false
},
account_id: {
type: Sequelize.INTEGER,
// references: {
// model: 'Customer',
// key: 'id'
// },
allowNull: true
},
// Unique identifier for a particular source.
token: {
type: Sequelize.STRING,
unique: true
},
// The gateway used to create this source
gateway: {
type: Sequelize.STRING,
defaultValue: 'payment_processor'
},
// The foreign key attribute on the 3rd party provider
account_foreign_key: {
type: Sequelize.STRING,
allowNull: false
},
// The foreign id on the 3rd party provider
account_foreign_id: {
type: Sequelize.STRING,
allowNull: false
},
// The foreign key attribute on the 3rd party provider
foreign_key: {
type: Sequelize.STRING,
allowNull: false
},
// The foreign id on the 3rd party provider
foreign_id: {
type: Sequelize.STRING,
allowNull: false
},
// If this is the default payment source for an account
is_default: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
// An object containing information about the credit card used for this transaction. Normally It has the following properties:
// type: The type of Source: credit_card, debit_card, prepaid_card, apple_pay, bitcoin
// gateway: the Gateway used
// avs_result_code: The Response code from AVS the address verification system. The code is a single letter; see this chart for the codes and their definitions.
// credit_card_iin: The issuer identification number (IIN), formerly known as bank identification number (BIN) ] of the customer's credit card. This is made up of the first few digits of the credit card number.
// credit_card_company: The name of the company who issued the customer's credit card.
// credit_card_number: The customer's credit card number, with most of the leading digits redacted with Xs.
// credit_card_last4: the last 4 of the customer's credit card number
// credit_card_exp_month: the 2 digit month
// credit_card_exp_year: the 2-4 digit year
// cvv_result_code: The Response code from the credit card company indicating whether the customer entered the card security code, a.k.a. card verification value, correctly. The code is a single letter or empty string; see this chart http://www.emsecommerce.net/avs_cvv2_response_codes.htm for the codes and their definitions.
// token: The card token from the Gateway
payment_details: helpers.JSONB('Source', app, Sequelize, 'payment_details', {
defaultValue: {}
}),
// Live Mode
live_mode: {
type: Sequelize.BOOLEAN,
defaultValue: app.config.proxyEngine.live_mode
}
}
}
}