@fabrix/spool-cart
Version:
Spool - eCommerce Spool for Fabrix
621 lines (620 loc) • 25.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@fabrix/fabrix/dist/common");
const _ = require("lodash");
const shortid = require("shortid");
const errors_1 = require("@fabrix/spool-sequelize/dist/errors");
const enums_1 = require("../../enums");
const enums_2 = require("../../enums");
const enums_3 = require("../../enums");
class CartService 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();
}
create(cart, options = {}) {
const Cart = this.app.models['Cart'];
if (!cart.line_items) {
cart.line_items = [];
}
const items = cart.line_items;
delete cart.line_items;
if (cart.shipping_address && !cart.billing_address) {
cart.billing_address = cart.shipping_address;
}
if (cart.billing_address && !cart.shipping_address) {
cart.shipping_address = cart.billing_address;
}
let resCart;
return Cart.create({
email: cart.email,
shop_id: cart.shop_id,
customer_id: cart.customer_id,
currency: cart.currency,
notes: cart.notes,
owners: cart.owners,
ip: cart.ip,
client_details: cart.client_details,
user_id: cart.user_id,
status: cart.status || enums_2.CART_STATUS.OPEN
}, {
include: [
{
model: this.app.models['Address'].instance,
as: 'shipping_address'
},
{
model: this.app.models['Address'].instance,
as: 'billing_address'
}
]
})
.then(_cart => {
if (!_cart) {
throw new Error('Cart was not created');
}
resCart = _cart;
if (cart.shipping_address && !_.isEmpty(cart.shipping_address)) {
return resCart.updateShippingAddress(cart.shipping_address, { transaction: options.transaction || null });
}
return;
})
.then(() => {
if (cart.billing_address) {
return resCart.updateBillingAddress(cart.billing_address, { transaction: options.transaction || null });
}
return;
})
.then(() => {
if (resCart.customer_id && !cart.shipping_address) {
return resCart.resolveCustomer({ transaction: options.transaction || null })
.then(() => {
if (resCart.Customer && resCart.Customer.shipping_address_id) {
return resCart.setShipping_address(resCart.Customer.shipping_address_id, { transaction: options.transaction || null });
}
return;
});
}
return;
})
.then(() => {
if (resCart.customer_id && !cart.billing_address) {
return resCart.resolveCustomer({ transaction: options.transaction || null })
.then(() => {
if (resCart.Customer && resCart.Customer.billing_address_id) {
return resCart.setBilling_address(resCart.Customer.billing_address_id, { transaction: options.transaction || null });
}
return;
});
}
return;
})
.then(() => {
return Cart.sequelize.Promise.mapSeries(items, item => {
return this.app.services.ProductService.resolveItem(item, { transaction: options.transaction || null });
});
})
.then(resolvedItems => {
return Cart.sequelize.Promise.mapSeries(resolvedItems, (item, index) => {
return resCart.addLine(item, items[index].quantity, items[index].properties, items[index].shop);
});
})
.then(() => {
return resCart.save({ transaction: options.transaction || null });
});
}
update(identifier, cart, options = {}) {
const Cart = this.app.models['Cart'];
let resCart;
const update = _.pick(cart, ['customer_id', 'host', 'ip', 'update_ip', 'client_details']);
return Cart.resolve(identifier, { transaction: options.transaction || null })
.then(_cart => {
if (!_cart) {
throw new Error('Could not resolve Cart');
}
resCart = _.extend(_cart, update);
if (cart.shipping_address) {
return resCart.updateShippingAddress(cart.shipping_address, { transaction: options.transaction || null });
}
return;
})
.then(() => {
if (cart.billing_address) {
return resCart.updateBillingAddress(cart.billing_address, { transaction: options.transaction || null });
}
return;
})
.then(() => {
return resCart.save({ transaction: options.transaction || null });
})
.then(() => {
return resCart.resolveCustomer({ transaction: options.transaction || null });
})
.then(() => {
return resCart.resolveShippingAddress({ transaction: options.transaction || null });
})
.then(() => {
return resCart.resolveBillingAddress({ transaction: options.transaction || null });
});
}
checkout(req, options = {}) {
if (!req.body.cart) {
const err = new errors_1.ModelError('E_NOT_FOUND', 'Cart is missing in request');
return Promise.reject(err);
}
let resOrder;
return this.app.models['Cart'].datastore.transaction(t => {
options.transaction = t;
return this.prepareForOrder(req, { transaction: options.transaction || null })
.then(newOrder => {
return this.app.services.OrderService.create(newOrder, { transaction: options.transaction || null });
})
.then(order => {
if (!order) {
throw new Error('Unexpected error during checkout');
}
resOrder = order;
return this.afterOrder(req, resOrder, { transaction: options.transaction || null });
})
.then(() => {
if (resOrder.customer_id) {
const event = {
object_id: resOrder.customer_id,
object: 'customer',
objects: [{
customer: resOrder.customer_id
}, {
order: resOrder.id
}],
type: 'customer.cart.checkout',
message: `Customer Cart ${resOrder.cart_token} checked out and created Order ${resOrder.name}`,
data: resOrder
};
return this.publish(event.type, event, {
save: true,
transaction: options.transaction || null
});
}
else {
return;
}
})
.then(() => {
if (resOrder.financial_status === enums_3.ORDER_FINANCIAL.PARTIALLY_PAID) {
return resOrder.sendPartiallyPaidEmail({ transaction: options.transaction || null });
}
else if (resOrder.financial_status === enums_3.ORDER_FINANCIAL.PAID) {
return resOrder.sendPaidEmail({ transaction: options.transaction || null });
}
else {
return resOrder.sendCreatedEmail({ transaction: options.transaction || null });
}
})
.then(email => {
return this.createAndSwitch(req, { transaction: options.transaction || null });
})
.then(newCart => {
const results = {
cart: newCart,
order: resOrder,
};
if (resOrder.Customer) {
results.customer = resOrder.Customer;
}
return results;
});
});
}
prepareForOrder(req, options = {}) {
const AccountService = this.app.services.AccountService;
const Cart = this.app.models['Cart'];
const Customer = this.app.models['Customer'];
let resCart, userID;
if (req.user && req.user.id) {
userID = req.user.id;
}
return Cart.resolve(req.body.cart, { transaction: options.transaction || null })
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
if (req.body.email) {
resCart.email = req.body.email;
}
if (req.body.customer && req.body.customer.id) {
return resCart.setCustomer(req.body.customer.id, { transaction: options.transaction || null });
}
else if (req.body.customer_id) {
resCart.customer_id = req.body.customer_id;
return resCart.setCustomer(req.body.customer_id, { transaction: options.transaction || null });
}
else {
return;
}
})
.then(() => {
if (req.body.shipping_address && !_.isEmpty(req.body.shipping_address)) {
return resCart.updateShippingAddress(req.body.shipping_address, { transaction: options.transaction || null });
}
return;
})
.then(() => {
return resCart.resolveShippingAddress({ transaction: options.transaction || null });
})
.then(() => {
if (req.body.billing_address && !_.isEmpty(req.body.billing_address)) {
return resCart.updateBillingAddress(req.body.billing_address, { transaction: options.transaction || null });
}
return;
})
.then(() => {
return resCart.resolveBillingAddress({ transaction: options.transaction || null });
})
.then(() => {
if (resCart.email && !resCart.customer_id) {
return Customer.resolve({
email: req.body.email,
first_name: req.body.first_name,
last_name: req.body.last_name,
shipping_address: resCart.shipping_address,
billing_address: resCart.billing_address,
cart: resCart
}, {
transaction: options.transaction || null,
create: true
})
.then(customer => {
return resCart.setCustomer(customer.id, { transaction: options.transaction || null });
});
}
else {
return;
}
})
.then(() => {
return resCart.resolveCustomer({ transaction: options.transaction || null });
})
.then(() => {
if (!resCart.email && resCart.Customer) {
resCart.email = resCart.Customer.email;
}
if (!resCart.email && !resCart.Customer) {
throw new Error('Order Missing Identifier (customer and email), please provide an email address');
}
resCart.close(enums_2.CART_STATUS.CLOSED);
return resCart.recalculate({ transaction: options.transaction || null });
})
.then(cart => {
if (resCart.Customer && (req.body.payment_details && req.body.payment_details.length > 0)) {
return AccountService.resolvePaymentDetailsToSources(resCart.Customer, req.body.payment_details, { transaction: options.transaction || null })
.then(paymentDetails => {
return paymentDetails;
});
}
else if (resCart.Customer && (req.body.payment_details && req.body.payment_details.length === 0)) {
return resCart.Customer.getDefaultSource({ transaction: options.transaction || null })
.then(source => {
if (!source) {
return [];
}
return [{
gateway: source.gateway,
source: source,
}];
});
}
else {
return req.body.payment_details;
}
})
.then(paymentDetails => {
return resCart.buildOrder({
client_details: req.body.client_details,
ip: req.body.ip,
payment_details: paymentDetails,
payment_kind: req.body.payment_kind,
transaction_kind: req.body.transaction_kind,
fulfillment_kind: req.body.fulfillment_kind,
processing_method: enums_1.PAYMENT_PROCESSING_METHOD.CHECKOUT,
shipping_address: req.body.shipping_address,
billing_address: req.body.billing_address,
customer_id: resCart.customer_id,
email: resCart.email || null,
user_id: userID || null,
});
});
}
afterOrder(req, order, options = {}) {
const Cart = this.app.models['Cart'];
return Cart.resolve(req.body.cart, { transaction: options.transaction || null })
.then(cart => {
cart.ordered(order);
return cart.save({ transaction: options.transaction || null });
});
}
pricingOverrides(overrides, id, admin, options = {}) {
const Cart = this.app.models['Cart'];
if (_.isObject(overrides) && overrides.pricing_overrides) {
overrides = overrides.pricing_overrides;
}
overrides = overrides.map(override => {
override.admin_id = override.admin_id ? override.admin_id : admin.id;
override.price = this.app.services.ProxyCartService.normalizeCurrency(parseInt(override.price, 10));
return override;
});
let resCart;
return Cart.resolve(id, { transaction: options.transaction || null })
.then(_cart => {
if (!_cart) {
throw new Error('Cart could not be resolved');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
resCart.pricing_overrides = overrides;
resCart.pricing_override_id = admin.id;
return resCart.save({ transaction: options.transaction || null });
});
}
addDiscountToCart(cart, options) {
return Promise.resolve(cart);
}
removeDiscountFromCart(cart, options) {
return Promise.resolve(cart);
}
addCouponToCart(cart, options) {
return Promise.resolve(cart);
}
removeCouponFromCart(cart, options) {
return Promise.resolve(cart);
}
addGiftCardToCart(cart, options) {
return Promise.resolve(cart);
}
removeGiftCardFromCart(cart, options) {
return Promise.resolve(cart);
}
addItemsToCart(items, cart, options = {}) {
const Cart = this.app.models['Cart'];
if (items.line_items) {
items = items.line_items;
}
let resCart;
return Cart.resolve(cart, { transaction: options.transaction || null })
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
return Cart.sequelize.Promise.mapSeries(items, item => {
return this.app.services.ProductService.resolveItem(item, { transaction: options.transaction || null });
});
})
.then(resolvedItems => {
return Cart.sequelize.Promise.mapSeries(resolvedItems, (item, index) => {
return resCart.addLine(item, items[index].quantity, items[index].properties, items[index].shop, { transaction: options.transaction || null });
});
})
.then(resolvedItems => {
return resCart.save({ transaction: options.transaction || null });
});
}
removeItemsFromCart(items, cart, options = {}) {
const Cart = this.app.models['Cart'];
if (items.line_items) {
items = items.line_items;
}
let resCart;
return Cart.resolve(cart, { transaction: options.transaction || null })
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
return Cart.sequelize.Promise.mapSeries(items, item => {
return this.app.services.ProductService.resolveItem(item, { transaction: options.transaction || null });
});
})
.then(resolvedItems => {
return Cart.sequelize.Promise.mapSeries(resolvedItems, (item, index) => {
return resCart.removeLine(item, items[index].quantity, { transaction: options.transaction || null });
});
})
.then(resolvedItems => {
return resCart.save({ transaction: options.transaction || null });
});
}
clearCart(cart, options = {}) {
const Cart = this.app.models['Cart'];
let resCart;
return Cart.resolve(cart, { transaction: options.transaction || null })
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
resCart.clear();
return resCart.save({ transaction: options.transaction || null });
});
}
createAndSwitch(req, options = {}) {
const User = this.app.models['User'];
const cart = {};
const owners = [];
let customerId;
if (req.user) {
owners.push(req.user);
customerId = req.user.current_customer_id;
cart.customer_id = customerId;
}
if (!customerId && req.customer) {
cart.customer_id = req.customer.id;
}
let resCart, resUser;
return this.create(cart, { transaction: options.transaction || null })
.then(createdCart => {
if (!createdCart) {
throw new Error('New Cart was not able to be created');
}
resCart = createdCart;
if (req.user) {
return User.resolve(req.user, { transaction: options.transaction || null })
.then(_user => {
if (!_user) {
throw new Error('User could not be resolved');
}
resUser = _user;
resUser.current_cart_id = resCart.id;
return resUser.save({ transaction: options.transaction || null });
});
}
else {
return;
}
})
.then(() => {
return new Promise((resolve, reject) => {
req.loginCart(resCart, (err) => {
if (err) {
return reject(err);
}
return resolve(resCart);
});
});
});
}
addShipping(cart, shipping, options = {}) {
if (!shipping) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Shipping is not defined');
}
let resCart;
const Cart = this.app.models['Cart'];
return Cart.resolve(cart, options)
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
return resCart.addShipping(shipping, { transaction: options.transaction || null });
});
}
removeShipping(cart, shipping, options = {}) {
if (!shipping) {
throw new errors_1.ModelError('E_BAD_REQUEST', 'Shipping is not defined');
}
let resCart;
const Cart = this.app.models['Cart'];
return Cart.resolve(cart, options)
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
return resCart.removeShipping(shipping, { transaction: options.transaction || null });
});
}
addTaxes(cart, taxes, options = {}) {
if (!taxes) {
throw new errors_1.ModelError('E_BAD_REQUEST', 'Taxes is not defined');
}
let resCart;
const Cart = this.app.models['Cart'];
return Cart.resolve(cart, options)
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
return resCart.addTaxes(taxes, { transaction: options.transaction || null });
});
}
removeTaxes(cart, taxes, options = {}) {
if (!taxes) {
throw new errors_1.ModelError('E_BAD_REQUEST', 'Taxes is not defined');
}
let resCart;
const Cart = this.app.models['Cart'];
return Cart.resolve(cart, options)
.then(_cart => {
if (!_cart) {
throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found');
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) {
throw new Error(`Cart is already ${_cart.status}`);
}
resCart = _cart;
return resCart.removeTaxes(taxes, { transaction: options.transaction || null });
});
}
retarget(options) {
}
beforeCreate(cart, options = {}) {
if (cart.ip) {
cart.create_ip = cart.ip;
}
if (!cart.token) {
cart.token = `cart_${shortid.generate()}`;
}
return this.app.models['Shop'].resolve(cart.shop_id, {
transaction: options.transaction || null,
default: true
})
.then(shop => {
cart.shop_id = shop.id;
return cart.recalculate({ transaction: options.transaction || null });
})
.catch(err => {
return cart.recalculate({ transaction: options.transaction || null });
});
}
beforeUpdate(cart, options = {}) {
if (cart.ip) {
cart.update_ip = cart.ip;
}
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(cart.status) > -1) {
return cart.recalculate({ transaction: options.transaction || null });
}
else {
return Promise.resolve(cart);
}
}
beforeSave(cart, options = {}) {
if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(cart.status) > -1) {
return cart.recalculate({ transaction: options.transaction || null });
}
else {
return Promise.resolve(cart);
}
}
}
exports.CartService = CartService;