@ecomplus/storefront-app
Version:
Vue.js ecommerce app with cart, checkout and account pages
417 lines (391 loc) • 11.7 kB
JavaScript
import {
name as getName,
price as getPrice
} from '@ecomplus/utils'
import ecomCart from '@ecomplus/shopping-cart'
import ecomClient from '@ecomplus/client'
import baseModulesRequestData from './../../lib/base-modules-request-data'
const fixMoneyValue = num => Math.round(num * 100) / 100
const fetchProduct = _id => {
return ecomClient.store({
url: `/products/${_id}.json`,
axiosConfig: {
timeout: 30000
}
})
}
const validateCartItem = (item, data) => new Promise((resolve, reject) => {
const { _id, quantity } = item
if (item.variation_id) {
let variation
if (data.variations) {
variation = data.variations.find(({ _id }) => _id === item.variation_id)
if (!variation) {
variation = data.variations.find(({ sku }) => sku === item.sku)
item.variation_id = variation._id
}
}
if (!variation) {
ecomCart.removeItem(_id)
} else {
const product = data
data = variation
for (const field in product) {
if (product[field] && !variation[field] && variation[field] !== 0) {
variation[field] = product[field]
}
}
}
}
const price = getPrice(data)
delete data.customizations
delete data.variations
delete data.body_html
delete data.body_text
delete data.inventory_records
delete data.price_change_records
if (item.picture && Object.keys(item.picture).length) {
delete data.pictures
}
Object.assign(item, data, {
_id,
price,
min_quantity: typeof data.min_quantity === 'number' ? data.min_quantity : 1,
max_quantity: data.quantity
})
item.quantity = data.available && data.quantity >= item.min_quantity
? Math.min(data.quantity, quantity)
: 0
if (item.quantity > 0 && item.kit_product) {
return validateCartItemKit(item)
.then(payload => {
data.quantity -= item.quantity
resolve(payload)
})
.catch(reject)
}
data.quantity -= item.quantity
resolve(item)
})
const validateCartItemKit = item => {
const kitProductId = item.kit_product._id
delete item.kit_product.price
return fetchProduct(kitProductId).then(({ data }) => {
if (data.available && data.kit_composition) {
let packQuantity = 0
let isFixedQuantity = true
let kitItem
data.kit_composition.forEach(currentKitItem => {
if (currentKitItem.quantity) {
packQuantity += currentKitItem.quantity
} else if (isFixedQuantity) {
isFixedQuantity = false
}
if (currentKitItem._id === item.product_id) {
kitItem = currentKitItem
}
})
if (!isFixedQuantity) {
packQuantity = data.min_quantity
}
if (kitItem && (kitItem.quantity === undefined || item.quantity % kitItem.quantity === 0)) {
let kitTotalQuantity = 0
ecomCart.data.items.forEach(item => {
if (item.kit_product && item.kit_product._id === kitProductId) {
kitTotalQuantity += item.quantity
}
})
const minPacks = kitItem.quantity
? item.quantity / kitItem.quantity
: 1
if (kitTotalQuantity && kitTotalQuantity % (minPacks * packQuantity) === 0) {
item.kit_product = {
...item.kit_product,
_id: data._id,
name: getName(data),
price: getPrice(data),
pack_quantity: packQuantity
}
if (data.slug) {
item.slug = data.slug
}
} else {
delete item.kit_product
}
}
}
return item
})
}
const prepareTransaction = ({ customer, transaction }) => {
const { name } = customer
let fullname
if (customer.registry_type === 'j') {
fullname = customer.corporate_name
}
if (!fullname) {
fullname = `${name.given_name} ` +
(name.middle_name ? `${name.middle_name} ${name.family_name}` : name.family_name)
}
const fillTransaction = transaction => {
const buyer = {
email: customer.main_email,
birth_date: {
day: 1,
month: 1,
year: 1950
},
fullname
}
;[
'inscription_number',
'inscription_type',
'doc_number',
'doc_country',
'registry_type',
'gender'
].forEach(prop => {
const value = customer[prop]
if (value) {
buyer[prop] = value
}
})
if (customer.phones.length) {
buyer.phone = customer.phones[0]
}
if (customer.birth_date) {
Object.assign(buyer.birth_date, customer.birth_date)
}
Object.assign(transaction, { buyer })
if (!transaction.billing_address) {
transaction.billing_address = customer.addresses.find(addr => addr.default)
}
}
if (Array.isArray(transaction)) {
transaction.forEach(fillTransaction)
} else {
fillTransaction(transaction)
}
return transaction
}
const { sessionStorage } = window
const couponStorageKey = 'st_discount_coupon'
const persistDiscountCoupon = couponCode => {
if (sessionStorage) {
sessionStorage.setItem(couponStorageKey, couponCode)
}
}
let checkoutPromise
const state = {
cart: ecomCart.data,
shippingService: {},
discountCoupon: (sessionStorage && sessionStorage.getItem(couponStorageKey)) || '',
discountRule: {},
paymentGateway: {},
notes: ''
}
const getters = {
amount: ({ cart, shippingService, discountRule, paymentGateway }) => {
const amount = {
subtotal: fixMoneyValue(cart.subtotal),
freight: shippingService.shipping_line
? fixMoneyValue(shippingService.shipping_line.total_price)
: 0,
discount: 0
}
amount.total = amount.subtotal + amount.freight
const addDiscount = discountValue => {
discountValue = fixMoneyValue(discountValue)
amount.discount += discountValue
amount.total -= discountValue
}
if (discountRule.extra_discount) {
addDiscount(discountRule.extra_discount.value)
}
if (paymentGateway.discount) {
const maxDiscount = amount[paymentGateway.discount.apply_at || 'total']
if (maxDiscount > 0) {
const { type, value } = paymentGateway.discount
let discountValue
if (type === 'percentage') {
discountValue = maxDiscount * value / 100
} else {
discountValue = value <= maxDiscount ? value : maxDiscount
}
addDiscount(discountValue)
}
}
amount.total = amount.total > 0 ? fixMoneyValue(amount.total) : 0
return amount
},
discountCoupon: ({ discountCoupon }) => discountCoupon,
discountRule: ({ discountRule }) => discountRule.extra_discount ? discountRule : undefined,
shippingZipCode: ({ shippingService }) => {
if (shippingService.shipping_line) {
return shippingService.shipping_line.to.zip
} else {
return ''
}
},
shippingService: ({ shippingService }) => shippingService.app_id ? shippingService : undefined,
paymentGateway: ({ paymentGateway }) => paymentGateway.app_id ? paymentGateway : undefined,
notes: ({ notes }) => notes
}
const mutations = {
selectShippingService (state, shippingService) {
state.shippingService = shippingService || {}
},
selectPaymentGateway (state, paymentGateway) {
state.paymentGateway = paymentGateway || {}
},
setDiscountCoupon (state, discountCoupon) {
if (discountCoupon && state.discountRule.extra_discount) {
persistDiscountCoupon(discountCoupon)
}
state.discountCoupon = discountCoupon || ''
},
setDiscountRule (state, discountRule) {
if (state.discountCoupon && discountRule.extra_discount) {
persistDiscountCoupon(state.discountCoupon)
}
state.discountRule = discountRule || {}
},
setNotes (state, notes) {
state.notes = notes
},
resetCart (state) {
if (sessionStorage) {
sessionStorage.removeItem(couponStorageKey)
}
ecomCart.reset()
state.cart = ecomCart.data
}
}
const actions = {
fetchCartItems ({ commit }, { removeOnError, items }) {
const promises = []
const itemsByProduct = (Array.isArray(items) && items.length ? items : ecomCart.data.items)
.reduce((itemsByProduct, item) => {
const group = itemsByProduct.find(({ _id }) => _id === item.product_id)
if (group) {
group.items.push(item)
} else {
itemsByProduct.push({
_id: item.product_id,
items: [item]
})
}
return itemsByProduct
}, [])
itemsByProduct.forEach(({ _id, items }) => {
const promise = new Promise(resolve => {
fetchProduct(_id)
.then(({ data }) => Promise.all(
items.map(item => {
return validateCartItem(item, data)
.then(item => {
if (item.quantity > 0) {
ecomCart.fixItem(item, false)
} else if (removeOnError) {
ecomCart.removeItem(item._id, false)
} else {
ecomCart.save()
}
})
.catch(err => {
console.error(err)
const status = err.response && err.response.status
if (removeOnError || (status >= 400 && status < 500)) {
ecomCart.removeItem(item._id, false)
}
})
})
))
.finally(resolve)
})
promises.push(promise)
})
return Promise.all(promises).then(() => {
ecomCart.save()
})
},
sendCheckout ({ getters }, payload) {
const customer = { ...payload.customer }
for (const prop in customer) {
if (
Object.prototype.hasOwnProperty.call(customer, prop) &&
(customer[prop] === null || customer[prop] === '')
) {
delete customer[prop]
}
}
const referral = sessionStorage.getItem('ecomReferral')
if (typeof referral === 'string' && /^[0-9a-f]{24}$/.test(referral)) {
customer.referral = referral
}
const checkoutBody = {
...baseModulesRequestData,
items: ecomCart.data.items.map(item => {
if (Array.isArray(item.flags)) {
const flags = []
item.flags.forEach(flag => {
if (!flags.includes(flag)) {
flags.push(flag)
}
})
item.flags = flags
}
return item
}),
shipping: {
...getters.shippingService,
to: customer.addresses.find(addr => addr.default)
},
transaction: prepareTransaction(payload),
customer,
notes: getters.notes
}
if (customer.referral) {
checkoutBody.affiliate_code = customer.referral
}
if (getters.discountRule) {
checkoutBody.discount = {
...getters.discountRule,
discount_coupon: getters.discountCoupon
}
}
if (!checkoutPromise) {
checkoutPromise = ecomClient.modules({
url: '/@checkout.json',
method: 'post',
data: checkoutBody
}).then(({ data }) => {
setTimeout(() => {
checkoutPromise = null
}, 2000)
const { order, transaction } = data
if (transaction.redirect_to_payment && transaction.payment_link) {
window.location = transaction.payment_link
} else {
order.transactions = order.transactions || []
order.transactions.push(transaction)
}
return {
status: 'open',
...order
}
}).catch(err => {
checkoutPromise = null
console.error(err)
throw err
})
}
return checkoutPromise
}
}
export default {
state,
getters,
mutations,
actions
}