Improving checkout experience Fixes #103

master
Mark Moffat 2019-12-21 13:16:48 +10:30
parent 588fdddb75
commit b48b2237ee
21 changed files with 602 additions and 292 deletions

View File

@ -67,15 +67,6 @@ const convertBool = (value) => {
return false;
};
const showCartCloseBtn = (page) => {
let showCartCloseButton = true;
if(page === 'checkout' || page === 'pay'){
showCartCloseButton = false;
}
return showCartCloseButton;
};
// adds products to sitemap.xml
const addSitemapProducts = (req, res, cb) => {
const db = req.app.db;
@ -587,7 +578,6 @@ module.exports = {
safeParseInt,
checkboxBool,
convertBool,
showCartCloseBtn,
addSitemapProducts,
clearSessionValue,
updateTotalCartAmount,

View File

@ -0,0 +1,51 @@
{
"$id": "saveCustomer",
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "emailAddress"
},
"firstName": {
"type": "string",
"isNotEmpty": true
},
"lastName": {
"type": "string",
"isNotEmpty": true
},
"address1": {
"type": "string",
"isNotEmpty": true
},
"address2": {
"type": "string"
},
"country": {
"type": "string",
"isNotEmpty": true
},
"state": {
"type": "string",
"isNotEmpty": true
},
"postcode": {
"type": "string",
"isNotEmpty": true
},
"phone": {
"type": "string",
"isNotEmpty": true
}
},
"required": [
"email",
"firstName",
"lastName",
"address1",
"country",
"state",
"postcode",
"phone"
]
}

View File

@ -166,5 +166,9 @@
"Payment ID": "Payment ID",
"Payment Message": "Payment Message",
"Password": "Password",
"Cart Email": "Cart Email"
"Cart Email": "Cart Email",
"Information": "Information",
"Shipping options": "Shipping options",
"Proceed to payment": "Proceed to payment",
"Return to information": "Return to information"
}

View File

@ -58,8 +58,9 @@ $(document).ready(function (){
}
});
$('.menu-btn').on('click', function(e){
$(document).on('click', '.menu-btn', function(e){
e.preventDefault();
$('body').addClass('pushy-open-right');
});
// add the table class to all tables
@ -75,9 +76,11 @@ $(document).ready(function (){
$(this).toggleClass('hover');
});
$('.product-title').dotdotdot({
ellipsis: '...'
});
if($('.product-title').length){
$('.product-title').dotdotdot({
ellipsis: '...'
});
}
$(document).on('click', '.btn-qty-minus', function(e){
var qtyElement = $(e.target).parent().parent().find('.cart-product-quantity');
@ -161,12 +164,21 @@ $(document).ready(function (){
}
});
$('#createCustomerAccount').validator().on('click', function(e){
$(document).on('click', '#createAccountCheckbox', function(e){
$('#newCustomerPassword').prop('required', $('#createAccountCheckbox').prop('checked'));
});
$('#checkoutInformation').validator().on('click', function(e){
e.preventDefault();
if($('#shipping-form').validator('validate').has('.has-error').length === 0){
// Change route if customer to be saved for later
var route = '/customer/save';
if($('#createAccountCheckbox').prop('checked')){
route = '/customer/create';
}
$.ajax({
method: 'POST',
url: '/customer/create',
url: route,
data: {
email: $('#shipEmail').val(),
firstName: $('#shipFirstname').val(),
@ -177,11 +189,12 @@ $(document).ready(function (){
state: $('#shipState').val(),
postcode: $('#shipPostcode').val(),
phone: $('#shipPhoneNumber').val(),
password: $('#newCustomerPassword').val()
password: $('#newCustomerPassword').val(),
orderComment: $('#orderComment').val()
}
})
.done(function(){
showNotification('Customer created successfully', 'success', true);
window.location = '/checkout/shipping';
})
.fail(function(msg){
showNotification(msg.responseJSON.message, 'danger');

File diff suppressed because one or more lines are too long

View File

@ -51,10 +51,24 @@ router.post('/customer/create', async (req, res) => {
const newCustomer = await db.customers.insertOne(customerObj);
indexCustomers(req.app)
.then(() => {
// Customer creation successful
// Return the new customer
const customerReturn = newCustomer.ops[0];
delete customerReturn.password;
req.session.customer = customerReturn;
// Set the customer into the session
req.session.customerPresent = true;
req.session.customerEmail = customerReturn.email;
req.session.customerFirstname = customerReturn.firstName;
req.session.customerLastname = customerReturn.lastName;
req.session.customerAddress1 = customerReturn.address1;
req.session.customerAddress2 = customerReturn.address2;
req.session.customerCountry = customerReturn.country;
req.session.customerState = customerReturn.state;
req.session.customerPostcode = customerReturn.postcode;
req.session.customerPhone = customerReturn.phone;
req.session.orderComment = req.body.orderComment;
// Return customer oject
res.status(200).json(customerReturn);
});
}catch(ex){
@ -65,6 +79,41 @@ router.post('/customer/create', async (req, res) => {
}
});
router.post('/customer/save', async (req, res) => {
const customerObj = {
email: req.body.email,
firstName: req.body.firstName,
lastName: req.body.lastName,
address1: req.body.address1,
address2: req.body.address2,
country: req.body.country,
state: req.body.state,
postcode: req.body.postcode,
phone: req.body.phone
};
const schemaResult = validateJson('saveCustomer', customerObj);
if(!schemaResult.result){
res.status(400).json(schemaResult.errors);
return;
}
// Set the customer into the session
req.session.customerPresent = true;
req.session.customerEmail = customerObj.email;
req.session.customerFirstname = customerObj.firstName;
req.session.customerLastname = customerObj.lastName;
req.session.customerAddress1 = customerObj.address1;
req.session.customerAddress2 = customerObj.address2;
req.session.customerCountry = customerObj.country;
req.session.customerState = customerObj.state;
req.session.customerPostcode = customerObj.postcode;
req.session.customerPhone = customerObj.phone;
req.session.orderComment = req.body.orderComment;
res.status(200).json(customerObj);
});
// Update a customer
router.post('/admin/customer/update', restrict, async (req, res) => {
const db = req.app.db;
@ -259,7 +308,17 @@ router.post('/customer/login_action', async (req, res) => {
}
// Customer login successful
req.session.customer = customer;
req.session.customerPresent = true;
req.session.customerEmail = customer.email;
req.session.customerFirstname = customer.firstName;
req.session.customerLastname = customer.lastName;
req.session.customerAddress1 = customer.address1;
req.session.customerAddress2 = customer.address2;
req.session.customerCountry = customer.country;
req.session.customerState = customer.state;
req.session.customerPostcode = customer.postcode;
req.session.customerPhone = customer.phone;
res.status(200).json({
message: 'Successfully logged in',
customer: customer
@ -379,7 +438,7 @@ router.post('/customer/reset/:token', async (req, res) => {
common.sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body);
req.session.message = 'Password successfully updated';
req.session.message_type = 'success';
return res.redirect('/pay');
return res.redirect('/checkout/payment');
}catch(ex){
console.log('Unable to reset password', ex);
req.session.message = 'Unable to reset password';
@ -390,7 +449,19 @@ router.post('/customer/reset/:token', async (req, res) => {
// logout the customer
router.post('/customer/logout', (req, res) => {
req.session.customer = null;
// Clear our session
req.session.customerPresent = null;
req.session.customerEmail = null;
req.session.customerFirstname = null;
req.session.customerLastname = null;
req.session.customerAddress1 = null;
req.session.customerAddress2 = null;
req.session.customerCountry = null;
req.session.customerState = null;
req.session.customerPostcode = null;
req.session.customerPhone = null;
req.session.orderComment = null;
res.status(200).json({});
});

View File

@ -6,7 +6,6 @@ const _ = require('lodash');
const {
getId,
hooker,
showCartCloseBtn,
clearSessionValue,
sortMenu,
getMenu,
@ -61,7 +60,6 @@ router.get('/payment/:orderId', async (req, res, next) => {
title: 'Payment complete',
config: req.app.config,
session: req.session,
pageCloseBtn: showCartCloseBtn('payment'),
result: order,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
@ -75,7 +73,32 @@ router.get('/emptycart', async (req, res, next) => {
emptyCart(req, res, '');
});
router.get('/checkout', async (req, res, next) => {
// router.get('/checkout', async (req, res, next) => {
// const config = req.app.config;
// // if there is no items in the cart then render a failure
// if(!req.session.cart){
// req.session.message = 'The are no items in your cart. Please add some items before checking out';
// req.session.messageType = 'danger';
// res.redirect('/');
// return;
// }
// // render the checkout
// res.render(`${config.themeViews}checkout`, {
// title: 'Checkout',
// config: req.app.config,
// session: req.session,
// checkout: 'hidden',
// page: 'checkout',
// message: clearSessionValue(req.session, 'message'),
// messageType: clearSessionValue(req.session, 'messageType'),
// helpers: req.handlebars.helpers,
// showFooter: 'showFooter'
// });
// });
router.get('/checkout/information', async (req, res, next) => {
const config = req.app.config;
// if there is no items in the cart then render a failure
@ -86,47 +109,20 @@ router.get('/checkout', async (req, res, next) => {
return;
}
// render the checkout
res.render(`${config.themeViews}checkout`, {
title: 'Checkout',
config: req.app.config,
session: req.session,
pageCloseBtn: showCartCloseBtn('checkout'),
checkout: 'hidden',
page: 'checkout',
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers,
showFooter: 'showFooter'
});
});
router.get('/pay', async (req, res, next) => {
const config = req.app.config;
// if there is no items in the cart then render a failure
if(!req.session.cart){
req.session.message = 'The are no items in your cart. Please add some items before checking out';
req.session.messageType = 'danger';
res.redirect('/checkout');
return;
}
let paymentType = '';
if(req.session.cartSubscription){
paymentType = '_subscription';
}
// render the payment page
res.render(`${config.themeViews}pay`, {
title: 'Pay',
res.render(`${config.themeViews}checkout-information`, {
title: 'Checkout',
config: req.app.config,
paymentConfig: getPaymentConfig(),
pageCloseBtn: showCartCloseBtn('pay'),
session: req.session,
paymentPage: true,
paymentType,
page: 'pay',
cartSize: 'part',
cartClose: false,
page: 'checkout-information',
countryList,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
@ -135,16 +131,86 @@ router.get('/pay', async (req, res, next) => {
});
});
router.get('/cartPartial', (req, res) => {
router.get('/checkout/shipping', async (req, res, next) => {
const config = req.app.config;
res.render(`${config.themeViews}cart`, {
pageCloseBtn: showCartCloseBtn(req.query.path),
page: req.query.path,
layout: false,
helpers: req.handlebars.helpers,
// if there is no items in the cart then render a failure
if(!req.session.cart){
req.session.message = 'The are no items in your cart. Please add some items before checking out';
req.session.messageType = 'danger';
res.redirect('/');
return;
}
if(!req.session.customerEmail){
req.session.message = 'Cannot proceed to shipping without customer information';
req.session.messageType = 'danger';
res.redirect('/checkout/information');
return;
}
// render the payment page
res.render(`${config.themeViews}checkout-shipping`, {
title: 'Checkout',
config: req.app.config,
session: req.session
session: req.session,
cartSize: 'part',
cartClose: false,
page: 'checkout-shipping',
countryList,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers,
showFooter: 'showFooter'
});
});
router.get('/checkout/cart', (req, res) => {
const config = req.app.config;
res.render(`${config.themeViews}checkout-cart`, {
page: req.query.path,
cartSize: 'full',
config: req.app.config,
session: req.session,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers,
showFooter: 'showFooter'
});
});
router.get('/checkout/payment', (req, res) => {
const config = req.app.config;
// if there is no items in the cart then render a failure
if(!req.session.cart){
req.session.message = 'The are no items in your cart. Please add some items before checking out';
req.session.messageType = 'danger';
res.redirect('/');
return;
}
let paymentType = '';
if(req.session.cartSubscription){
paymentType = '_subscription';
}
res.render(`${config.themeViews}checkout-payment`, {
title: 'Checkout',
config: req.app.config,
paymentConfig: getPaymentConfig(),
session: req.session,
paymentPage: true,
paymentType,
cartSize: 'part',
cartClose: true,
page: 'checkout-information',
countryList,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers,
showFooter: 'showFooter'
});
});
@ -180,7 +246,6 @@ router.get('/product/:id', async (req, res) => {
images: images,
productDescription: product.productDescription,
metaDescription: config.cartTitle + ' - ' + product.productTitle,
pageCloseBtn: showCartCloseBtn('product'),
config: config,
session: req.session,
pageUrl: config.baseUrl + req.originalUrl,
@ -210,6 +275,12 @@ router.post('/product/updatecart', (req, res, next) => {
let hasError = false;
let stockError = false;
// Check cart exists
if(!req.session.cart){
emptyCart(req, res, 'json', 'There are no items if your cart or your cart is expired');
return;
}
async.eachSeries(cartItems, async (cartItem, callback) => {
// Find index in cart
const cartIndex = _.findIndex(req.session.cart, { productId: cartItem.productId });
@ -308,11 +379,12 @@ router.post('/product/emptycart', async (req, res, next) => {
emptyCart(req, res, 'json');
});
const emptyCart = async (req, res, type) => {
const emptyCart = async (req, res, type, customMessage) => {
const db = req.app.db;
// Remove from session
delete req.session.cart;
delete req.session.shippingAmount;
delete req.session.orderId;
// Remove cart from DB
@ -324,13 +396,19 @@ const emptyCart = async (req, res, type) => {
// Update checking cart for subscription
updateSubscriptionCheck(req, res);
// Set returned message
let message = 'Cart successfully emptied';
if(customMessage){
message = customMessage;
}
// If POST, return JSON else redirect nome
if(type === 'json'){
res.status(200).json({ message: 'Cart successfully emptied', totalCartItems: 0 });
res.status(200).json({ message: message, totalCartItems: 0 });
return;
}
req.session.message = 'Cart successfully emptied.';
req.session.message = message;
req.session.messageType = 'success';
res.redirect('/');
};
@ -513,7 +591,6 @@ router.get('/search/:searchTerm/:pageNum?', (req, res) => {
session: req.session,
metaDescription: req.app.config.cartTitle + ' - Search term: ' + searchTerm,
searchTerm: searchTerm,
pageCloseBtn: showCartCloseBtn('search'),
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
productsPerPage: numberProducts,
@ -569,7 +646,6 @@ router.get('/category/:cat/:pageNum?', (req, res) => {
session: req.session,
searchTerm: searchTerm,
metaDescription: `${req.app.config.cartTitle} - Category: ${searchTerm}`,
pageCloseBtn: showCartCloseBtn('category'),
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
productsPerPage: numberProducts,
@ -650,7 +726,6 @@ router.get('/page/:pageNum', (req, res, next) => {
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
metaDescription: req.app.config.cartTitle + ' - Products page: ' + req.params.pageNum,
pageCloseBtn: showCartCloseBtn('page'),
config: req.app.config,
productsPerPage: numberProducts,
totalProductCount: results.totalProducts,
@ -692,7 +767,6 @@ router.get('/:page?', async (req, res, next) => {
session: req.session,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
pageCloseBtn: showCartCloseBtn('page'),
config,
productsPerPage: numberProducts,
totalProductCount: results.totalProducts,
@ -722,7 +796,6 @@ router.get('/:page?', async (req, res, next) => {
session: req.session,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
pageCloseBtn: showCartCloseBtn('page'),
config: req.app.config,
metaDescription: req.app.config.cartTitle + ' - ' + page,
helpers: req.handlebars.helpers,

View File

@ -77,16 +77,16 @@ router.post('/checkout_action', async (req, res, next) => {
orderPaymentGateway: 'Adyen',
orderPaymentMessage: response.refusalReason,
orderTotal: req.session.totalCartAmount,
orderEmail: req.body.shipEmail,
orderFirstname: req.body.shipFirstname,
orderLastname: req.body.shipLastname,
orderAddr1: req.body.shipAddr1,
orderAddr2: req.body.shipAddr2,
orderCountry: req.body.shipCountry,
orderState: req.body.shipState,
orderPostcode: req.body.shipPostcode,
orderPhoneNumber: req.body.shipPhoneNumber,
orderComment: req.body.orderComment,
orderEmail: req.session.customerEmail,
orderFirstname: req.session.customerFirstname,
orderLastname: req.session.customerLastname,
orderAddr1: req.session.customerAddress1,
orderAddr2: req.session.customerAddress2,
orderCountry: req.session.customerCountry,
orderState: req.session.customerState,
orderPostcode: req.session.customerPostcode,
orderPhoneNumber: req.session.customerPhone,
orderComment: req.session.orderComment,
orderStatus: paymentStatus,
orderDate: new Date(),
orderProducts: req.session.cart,

View File

@ -60,16 +60,16 @@ router.post('/checkout_action', (req, res, next) => {
orderPaymentGateway: 'AuthorizeNet',
orderPaymentMessage: 'Your payment was successfully completed',
orderTotal: req.session.totalCartAmount,
orderEmail: req.body.shipEmail,
orderFirstname: req.body.shipFirstname,
orderLastname: req.body.shipLastname,
orderAddr1: req.body.shipAddr1,
orderAddr2: req.body.shipAddr2,
orderCountry: req.body.shipCountry,
orderState: req.body.shipState,
orderPostcode: req.body.shipPostcode,
orderPhoneNumber: req.body.shipPhoneNumber,
orderComment: req.body.orderComment,
orderEmail: req.session.customerEmail,
orderFirstname: req.session.customerFirstname,
orderLastname: req.session.customerLastname,
orderAddr1: req.session.customerAddress1,
orderAddr2: req.session.customerAddress2,
orderCountry: req.session.customerCountry,
orderState: req.session.customerState,
orderPostcode: req.session.customerPostcode,
orderPhoneNumber: req.session.customerPhone,
orderComment: req.session.orderComment,
orderStatus: orderStatus,
orderDate: new Date(),
orderProducts: req.session.cart

View File

@ -138,7 +138,7 @@ router.post('/checkout_action', (req, res, next) => {
if(error){
req.session.message = 'There was an error processing your payment. You have not been changed and can try again.';
req.session.messageType = 'danger';
res.redirect('/pay');
res.redirect('/checkout/payment');
return;
}
if(payment.payer.payment_method === 'paypal'){
@ -164,16 +164,16 @@ router.post('/checkout_action', (req, res, next) => {
orderPaymentId: payment.id,
orderPaymentGateway: 'Paypal',
orderTotal: req.session.totalCartAmount,
orderEmail: req.body.shipEmail,
orderFirstname: req.body.shipFirstname,
orderLastname: req.body.shipLastname,
orderAddr1: req.body.shipAddr1,
orderAddr2: req.body.shipAddr2,
orderCountry: req.body.shipCountry,
orderState: req.body.shipState,
orderPostcode: req.body.shipPostcode,
orderPhoneNumber: req.body.shipPhoneNumber,
orderComment: req.body.orderComment,
orderEmail: req.session.customerEmail,
orderFirstname: req.session.customerFirstname,
orderLastname: req.session.customerLastname,
orderAddr1: req.session.customerAddress1,
orderAddr2: req.session.customerAddress2,
orderCountry: req.session.customerCountry,
orderState: req.session.customerState,
orderPostcode: req.session.customerPostcode,
orderPhoneNumber: req.session.customerPhone,
orderComment: req.session.orderComment,
orderStatus: payment.state,
orderDate: new Date(),
orderProducts: req.session.cart

View File

@ -18,13 +18,13 @@ router.post('/checkout_action', (req, res, next) => {
source: req.body.stripeToken,
description: stripeConfig.stripeDescription,
shipping: {
name: `${req.body.shipFirstname} ${req.body.shipLastname}`,
name: `${req.session.customerFirstname} ${req.session.customerFirstname}`,
address: {
line1: req.body.shipAddr1,
line2: req.body.shipAddr2,
postal_code: req.body.shipPostcode,
state: req.body.shipState,
country: req.body.shipCountry
line1: req.session.customerAddress1,
line2: req.session.customerAddress2,
postal_code: req.session.customerPostcode,
state: req.session.customerState,
country: req.session.customerCountry
}
}
};
@ -37,7 +37,7 @@ router.post('/checkout_action', (req, res, next) => {
req.session.message = 'Your payment has declined. Please try again';
req.session.paymentApproved = false;
req.session.paymentDetails = '';
res.redirect('/pay');
res.redirect('/checkout/payment');
return;
}
@ -53,16 +53,16 @@ router.post('/checkout_action', (req, res, next) => {
orderPaymentGateway: 'Stripe',
orderPaymentMessage: charge.outcome.seller_message,
orderTotal: req.session.totalCartAmount,
orderEmail: req.body.shipEmail,
orderFirstname: req.body.shipFirstname,
orderLastname: req.body.shipLastname,
orderAddr1: req.body.shipAddr1,
orderAddr2: req.body.shipAddr2,
orderCountry: req.body.shipCountry,
orderState: req.body.shipState,
orderPostcode: req.body.shipPostcode,
orderPhoneNumber: req.body.shipPhoneNumber,
orderComment: req.body.orderComment,
orderEmail: req.session.customerEmail,
orderFirstname: req.session.customerFirstname,
orderLastname: req.session.customerLastname,
orderAddr1: req.session.customerAddress1,
orderAddr2: req.session.customerAddress2,
orderCountry: req.session.customerCountry,
orderState: req.session.customerState,
orderPostcode: req.session.customerPostcode,
orderPhoneNumber: req.session.customerPhone,
orderComment: req.session.orderComment,
orderStatus: paymentStatus,
orderDate: new Date(),
orderProducts: req.session.cart,
@ -183,13 +183,13 @@ router.post('/checkout_action_subscription', async (req, res, next) => {
if(!plan){
req.session.messageType = 'danger';
req.session.message = 'The plan connected to this product doesn\'t exist';
res.redirect('/pay/');
res.redirect('/checkout/payment');
return;
}
}catch(ex){
req.session.messageType = 'danger';
req.session.message = 'The plan connected to this product doesn\'t exist';
res.redirect('/pay/');
res.redirect('/checkout/payment');
return;
}
@ -207,7 +207,7 @@ router.post('/checkout_action_subscription', async (req, res, next) => {
req.session.message = 'Your subscripton has declined. Please try again';
req.session.paymentApproved = false;
req.session.paymentDetails = '';
res.redirect('/pay');
res.redirect('/checkout/payment');
return;
}
@ -217,7 +217,7 @@ router.post('/checkout_action_subscription', async (req, res, next) => {
req.session.message = 'Your subscripton has declined. Please try again';
req.session.paymentApproved = false;
req.session.paymentDetails = '';
res.redirect('/pay');
res.redirect('/checkout/payment');
return;
}
@ -229,16 +229,16 @@ router.post('/checkout_action_subscription', async (req, res, next) => {
orderPaymentGateway: 'Stripe',
orderPaymentMessage: subscription.collection_method,
orderTotal: req.session.totalCartAmount,
orderEmail: req.body.shipEmail,
orderFirstname: req.body.shipFirstname,
orderLastname: req.body.shipLastname,
orderAddr1: req.body.shipAddr1,
orderAddr2: req.body.shipAddr2,
orderCountry: req.body.shipCountry,
orderState: req.body.shipState,
orderPostcode: req.body.shipPostcode,
orderPhoneNumber: req.body.shipPhoneNumber,
orderComment: req.body.orderComment,
orderEmail: req.session.customerEmail,
orderFirstname: req.session.customerFirstname,
orderLastname: req.session.customerLastname,
orderAddr1: req.session.customerAddress1,
orderAddr2: req.session.customerAddress2,
orderCountry: req.session.customerCountry,
orderState: req.session.customerState,
orderPostcode: req.session.customerPostcode,
orderPhoneNumber: req.session.customerPhone,
orderComment: req.session.orderComment,
orderStatus: 'Pending',
orderDate: new Date(),
orderProducts: req.session.cart,

View File

@ -121,19 +121,17 @@
</li>
{{/ifCond}}
{{#unless admin}}
{{#ifCond page '!=' 'checkout'}}
{{#ifCond page '!=' 'pay'}}
{{#if @root.session.cart}}
<li><a href="/cart" class="menu-btn"><i class="fa fa-shopping-cart" aria-hidden="true"></i> {{ @root.__ "Cart" }} <span class="badge" id="cart-count">{{@root.session.cartTotalItems}}</span></a></li>
<li><a href="/checkout/cart" class="menu-btn"><i class="fa fa-shopping-cart" aria-hidden="true"></i> {{ @root.__ "Cart" }} <span class="badge" id="cart-count">{{@root.session.cartTotalItems}}</span></a></li>
{{else}}
<li><a href="/cart" class="menu-btn"><i class="fa fa-shopping-cart" aria-hidden="true"></i> {{ @root.__ "Cart" }} <span class="badge" id="cart-count">0</span></a></li>
<li><a href="/checkout/cart" class="menu-btn"><i class="fa fa-shopping-cart" aria-hidden="true"></i> {{ @root.__ "Cart" }} <span class="badge" id="cart-count">0</span></a></li>
{{/if}}
{{/ifCond}}
{{/ifCond}}
{{/unless}}
{{#if admin}}
{{#if @root.session.user}}
<li><a href="/admin/logout"><i class="fa fa-sign-out" aria-hidden="true"> </i>Logout</a></li>
{{/if}}
{{/if}}
</ul>
</div>
</div>
@ -142,8 +140,25 @@
{{#unless admin}}
{{#ifCond page '!=' 'checkout'}}
{{#ifCond page '!=' 'pay'}}
<div id="cart" class="col-md-12 pushy pushy-right">
{{> (getTheme 'cart')}}
<div id="cart" class="col-md-12 pad-left-12 pushy pushy-right">
<div class="row {{checkout}}">
<div class="col-lg-12 col l12 text-right">
<button class="pushy-link btn btn-primary" type="button">X</button>
</div>
</div>
<div class="row">
<div id="cart" class="col-md-12">
{{> (getTheme 'cart')}}
<div class="row">
{{#if @root.session.cart}}
<div class="col-xs-12 col s12">
<button class="btn btn-danger pull-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
<a href="/checkout/information" class="btn btn-primary pull-right">Checkout</a>
</div>
{{/if}}
</div>
</div>
</div>
</div>
{{/ifCond}}
{{/ifCond}}

View File

@ -52,7 +52,7 @@
data-clientKey="{{@root.paymentConfig.clientKey}}"
data-acceptUIFormBtnTxt="Submit"
data-acceptUIFormHeaderTxt="Card Information"
data-responseHandler="responseHandler">Process payment
data-responseHandler="responseHandler">Pay Now
</button>
</form>
</div>

View File

@ -11,14 +11,14 @@
data-name="{{@root.config.cartTitle}}"
data-description="{{@root.config.cartTitle}} Payment"
data-image="{{@root.paymentConfig.stripeLogoURL}}"
data-email="{{@root.session.customer.email}}"
data-email="{{@root.session.customerEmail}}"
{{#if @root.session.cartSubscription}}
data-subscription="{{@root.session.cartSubscription}}"
{{/if}}
data-locale="auto"
data-zip-code="false"
data-currency="{{@root.paymentConfig.stripeCurrency}}">
<i class="fa fa-cc-stripe fa-lg" aria-hidden="true"></i> Process payment <i class="fa fa-cc-stripe fa-lg" aria-hidden="true"></i>
<i class="fa fa-cc-stripe fa-lg" aria-hidden="true"></i> Pay Now <i class="fa fa-cc-stripe fa-lg" aria-hidden="true"></i>
</button>
<script src="https://checkout.stripe.com/v2/checkout.js"></script>
</form>

View File

@ -1,108 +1,83 @@
<div class="row">
<div class="col-xs-12 col s12">
{{#if pageCloseBtn}}
<div class="row {{checkout}}">
<div class="col-lg-12 col l12 text-right">
<button class="pushy-link btn btn-primary" type="button">X</button>
</div>
</div>
{{/if}}
<div class="panel panel-default" style="margin-top: 30px;">
<div class="panel-heading">{{ @root.__ "Cart contents" }}</div>
<div class="panel-body cart-body">
<div class="container-fluid">
{{#each @root.session.cart}}
<div class="row cart-row">
<div class="col-xs-4 col-md-2 col s4 m2">
{{#if productImage}}
<img class="img-responsive" src="{{this.productImage}}" alt="{{this.title}} product image"> {{else}}
<img class="img-responsive" src="/uploads/placeholder.png" alt="{{this.title}} product image"> {{/if}}
</div>
<div class="cart-item-row text-left col-xs-12 col-md-7 col s12 m7">
<p>
<a class="cart-link" href="/product/{{this.link}}">{{this.title}}</a>
</p>
<p>
&nbsp;
{{#each this.options}}
{{#if @last}}
{{@key}}: {{this}}
{{else}}
{{@key}}: {{this}} /
{{/if}}
{{/each}}
</p>
<p>
<div class="form-group">
<div class="col-lg-5 col l5 no-pad-left">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-sm btn-primary btn-qty-minus" type="button">-</button>
</span>
<input type="number" class="form-control cart-product-quantity input-sm text-center" data-id="{{this.productId}}" id="{{@key}}"
maxlength="2" value="{{this.quantity}}">
<span class="input-group-btn">
<button class="btn btn-sm btn-primary btn-qty-add" type="button">+</button>
</span>
</div>
</div>
<span class="pull-right">
<button class="btn btn-sm btn-danger btn-delete-from-cart" data-id="{{this.productId}}" type="button"><i class="fa fa-trash" data-id="{{this.productId}}" aria-hidden="true"></i></button>
<div class="panel panel-default" style="margin-top: 30px;">
<div class="panel-heading">{{ @root.__ "Cart contents" }}</div>
<div class="panel-body cart-body">
<div class="container-fluid">
{{#each @root.session.cart}}
<div class="row cart-row">
<div class="col-xs-4 col-md-2 col s4 m2">
{{#if productImage}}
<img class="img-responsive" src="{{this.productImage}}" alt="{{this.title}} product image"> {{else}}
<img class="img-responsive" src="/uploads/placeholder.png" alt="{{this.title}} product image"> {{/if}}
</div>
<div class="cart-item-row text-left col-xs-12 col-md-7 col s12 m7">
<p>
<a class="cart-link" href="/product/{{this.link}}">{{this.title}}</a>
</p>
<p>
&nbsp;
{{#each this.options}}
{{#if @last}}
{{@key}}: {{this}}
{{else}}
{{@key}}: {{this}} /
{{/if}}
{{/each}}
</p>
<p>
<div class="form-group">
<div class="col-lg-5 col l5 no-pad-left">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-sm btn-primary btn-qty-minus" type="button">-</button>
</span>
<input type="number" class="form-control cart-product-quantity input-sm text-center" data-id="{{this.productId}}" data-index="{{@key}}"
maxlength="2" value="{{this.quantity}}">
<span class="input-group-btn">
<button class="btn btn-sm btn-primary btn-qty-add" type="button">+</button>
</span>
</div>
</p>
</div>
<span class="pull-right">
<button class="btn btn-sm btn-danger btn-delete-from-cart" data-id="{{this.productId}}" type="button"><i class="fa fa-trash" data-id="{{this.productId}}" aria-hidden="true"></i></button>
</span>
</div>
<div class="col-xs-12 col-lg-4 col s12 l4 cart-item-row text-right no-pad-right">
<strong>{{currencySymbol ../config.currencySymbol}}{{formatAmount this.totalItemPrice}}</strong>
</div>
</div>
{{/each}}
</p>
</div>
<div class="container-fluid">
{{#if @root.session.cart}}
<div class="row">
<div class="cart-contents-shipping col-md-12 col m12 no-pad-right">
{{#ifCond @root.session.shippingCostApplied '===' true}}
<div class="text-right">
{{ @root.__ "Shipping" }}
<strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.config.flatShipping}}</strong>
</div>
{{else}}
<div class="text-right">
{{ @root.__ "Shipping" }}
<strong>FREE</strong>
</div>
{{/ifCond}}
<div class="text-right">
Total:
<strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartAmount}}</strong>
</div>
</div>
<div class="col-xs-12 col-lg-4 col s12 l4 cart-item-row text-right no-pad-right">
<strong>{{currencySymbol ../config.currencySymbol}}{{formatAmount this.totalItemPrice}}</strong>
</div>
</div>
{{/each}}
</div>
<div class="container-fluid">
{{#if @root.session.cart}}
<div class="row">
<div class="cart-contents-shipping col-md-12 col m12 no-pad-right">
{{#ifCond @root.session.shippingCostApplied '===' true}}
<div class="text-right">
{{ @root.__ "Shipping" }}
<strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.config.flatShipping}}</strong>
</div>
{{else}}
<div class="row">
<div class="cart-contents-shipping col-md-12 no-pad-right">
Cart empty
</div>
<div class="text-right">
{{ @root.__ "Shipping" }}
<strong>FREE</strong>
</div>
{{/ifCond}}
<div class="text-right">
Total:
<strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartAmount}}</strong>
</div>
{{/if}}
</div>
</div>
</div>
<div class="row">
{{#if @root.session.cart}}
<div class="col-xs-6 col s6 text-left align-right">
<button class="btn btn-danger" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
{{else}}
<div class="row">
<div class="cart-contents-shipping col-md-12 no-pad-right">
Cart empty
</div>
</div>
{{#ifCond page '!=' 'pay'}}
<div class="text-right align-right col-xs-6 col s6">
{{#ifCond @root.page '==' 'checkout'}}
<a href="/pay" class="btn btn-default">{{ @root.__ "Pay now" }}</a>
{{else}}
<a href="/checkout" class="btn btn-default">Checkout</a>
{{/ifCond}}
</div>
{{/ifCond}} {{/if}}
{{/if}}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
<div class="col-xl-8 col-xl-offset-2 col-xs-12">
<div class="row">
<div id="cart" class="col-md-12">
{{> (getTheme 'cart')}}
<div class="row">
{{#if @root.session.cart}}
<div class="col-xs-12 col s12">
<button class="btn btn-danger pull-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
<a href="/checkout/information" class="btn btn-danger pull-right">Checkout</a>
</div>
{{/if}}
</div>
</div>
</div>
</div>

View File

@ -2,8 +2,14 @@
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item active" aria-current="page"><a href="/checkout">Checkout</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ @root.__ "Pay now" }}</li>
<li class="breadcrumb-item active" aria-current="page"><a href="/checkout/information">Information</a></li>
{{#if @root.session.customerPresent}}
<li class="breadcrumb-item" aria-current="page"><a href="/checkout/shipping">Shipping</a></li>
<li class="breadcrumb-item" aria-current="page"><a href="/checkout/payment">Payment</a></li>
{{else}}
<li class="breadcrumb-item text-muted" aria-current="page">Shipping</li>
<li class="breadcrumb-item text-muted" aria-current="page">Payment</li>
{{/if}}
</ol>
</nav>
<div class="row">
@ -13,7 +19,7 @@
<div class="col-md-5">
<div class="panel panel-default" style="margin-top: 30px;">
<div class="panel-heading">{{ @root.__ "Customer details" }}</div>
{{#unless session.customer}}
{{#unless @root.session.customerPresent}}
<div class="panel-body customer-details-login">
<p>{{ @root.__ "Existing customer" }}</p>
<div class="form-group">
@ -26,49 +32,52 @@
<a href="/customer/forgotten" class="btn btn-default pull-left">{{ @root.__ "Forgotten" }}</a>
</div>
<div class="form-group">
<button id="customerLogin" class="btn btn-success pull-right" type="submit">Login</button>
<button id="customerLogin" class="btn btn-primary pull-right" type="submit">Login</button>
</div>
</div>
{{/unless}}
<div class="panel-body customer-details">
{{#if session.customer}}
{{#if @root.session.customerPresent}}
<div class="col-xs-12 col-md-12">
<button id="customerLogout" class="btn btn-sm btn-success pull-right">{{ @root.__ "Change customer" }}</button>
<button id="customerLogout" class="btn btn-sm btn-primary pull-right">{{ @root.__ "Change customer" }}</button>
</div>
{{/if}}
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action{{@root.paymentType}}" method="post" role="form" data-toggle="validator" novalidate="false">
<form id="shipping-form" class="shipping-form" role="form" data-toggle="validator" novalidate="false">
{{> themes/Cloth/shipping-form}}
{{#if session.customer}}
{{#ifCond config.paymentGateway '==' 'paypal'}}
{{> partials/payments/paypal}}
{{/ifCond}}
{{/if}}
{{#unless session.customer}}
{{#unless @root.session.customerPresent}}
<div class="col-xs-12 col-md-12">
<p class="text-muted">{{ @root.__ "Enter a password to create an account for next time" }}</p>
</div>
<div class="col-xs-6 col-md-6">
<div class="form-group">
<input type="password" class="form-control customerDetails" id="newCustomerPassword" name="newCustomerPassword" placeholder="Password" required>
<input type="password" class="form-control customerDetails" id="newCustomerPassword" name="newCustomerPassword" placeholder="Password">
</div>
</div>
<div class="col-xs-6 col-md-6">
<div class="checkbox">
<label>
<input type="checkbox" id="createAccountCheckbox"> Create an account
</label>
</div>
<a id="createCustomerAccount" class="btn btn-success pull-right">{{ @root.__ "Create account" }}</a>
</div>
{{/unless}}
<div class="col-xs-12 col-md-12">
<a href="/checkout/cart" class="btn btn-primary pull-left">Return to cart</a>
<a href="/checkout/shipping" id="checkoutInformation" class="btn btn-primary pull-right">Continue to shipping</a>
</div>
</form>
{{#if session.customer}}
{{#ifCond config.paymentGateway '==' 'stripe'}}
{{> partials/payments/stripe}}
{{/ifCond}}
{{#ifCond config.paymentGateway '==' 'authorizenet'}}
{{> partials/payments/authorizenet}}
{{/ifCond}}
{{#ifCond config.paymentGateway '==' 'adyen'}}
{{> partials/payments/adyen}}
{{/ifCond}}
{{/if}}
</div>
</div>
</div>
<div id="cart" class="col-md-7">
{{> (getTheme 'cart')}}
<div class="row">
{{#if @root.session.cart}}
<div class="col-xs-12 col s12">
<button class="btn btn-danger pull-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
</div>
{{/if}}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,65 @@
<div class="col-xl-8 col-xl-offset-2 col-xs-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item" aria-current="page"><a href="/checkout/information">Information</a></li>
<li class="breadcrumb-item" aria-current="page"><a href="/checkout/shipping">Shipping</a></li>
<li class="breadcrumb-item active" aria-current="page"><a href="/checkout/payment">Payment</a></li>
</ol>
</nav>
<div class="row">
{{#if paymentMessage}}
<p class="text-danger text-center">{{paymentMessage}}</p>
{{/if}}
<div class="col-md-5">
<div class="panel panel-default" style="margin-top: 30px;">
<div class="panel-heading">{{ @root.__ "Customer details" }}</div>
<div class="panel-body">
<ul class="list-group">
<li class="list-group-item">
{{@root.session.customerFirstname}} {{@root.session.customerLastname}} -
{{@root.session.customerEmail}}
<span class="pull-right"><a href="/checkout/information">Change</a></span>
</li>
</ul>
<ul class="list-group">
{{#if @root.session.shippingCostApplied}}
<li class="list-group-item">
<div class="row">
<div class="col-md-6">
Standard shipping
</div>
<div class="col-md-6">
<span><strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.config.flatShipping}}</strong></span>
<span class="pull-right"><a href="/checkout/shipping">Change</a></span>
</div>
</div>
</li>
{{else}}
<li class="list-group-item">FREE shipping <span class="pull-right"><a href="/checkout/shipping">Change</a></span></li>
{{/if}}
</ul>
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action{{@root.paymentType}}" method="post" role="form" data-toggle="validator" novalidate="false">
{{#if session.customerPresent}}
{{#ifCond config.paymentGateway '==' 'paypal'}}
{{> partials/payments/paypal}}
{{/ifCond}}
{{#ifCond config.paymentGateway '==' 'stripe'}}
{{> partials/payments/stripe}}
{{/ifCond}}
{{#ifCond config.paymentGateway '==' 'authorizenet'}}
{{> partials/payments/authorizenet}}
{{/ifCond}}
{{#ifCond config.paymentGateway '==' 'adyen'}}
{{> partials/payments/adyen}}
{{/ifCond}}
{{/if}}
</form>
</div>
</div>
</div>
<div id="cart" class="col-md-7">
{{> (getTheme 'cart')}}
</div>
</div>
</div>

View File

@ -0,0 +1,40 @@
<div class="col-xl-8 col-xl-offset-2 col-xs-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item" aria-current="page"><a href="/checkout/information">Information</a></li>
<li class="breadcrumb-item active" aria-current="page"><a href="/checkout/shipping">Shipping</a></li>
<li class="breadcrumb-item" aria-current="page"><a href="/checkout/payment">Payment</a></li>
</ol>
</nav>
<div class="row">
<div class="col-md-5">
<div class="panel panel-default" style="margin-top: 30px;">
<div class="panel-heading">{{ @root.__ "Shipping options" }}</div>
<div class="panel-body cart-body">
{{#unless @root.session.shippingOptions}}
<ul class="list-group">
{{#if @root.session.shippingCostApplied}}
<li class="list-group-item">Standard shipping <span class="pull-right"><strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.config.flatShipping}}</strong></span></li>
{{else}}
<li class="list-group-item">FREE shipping <span class="pull-right"></li>
{{/if}}
</ul>
{{/unless}}
</div>
</div>
<a href="/checkout/information" class="btn btn-primary pull-left">{{ @root.__ "Return to information" }}</a>
<a href="/checkout/payment" class="btn btn-primary pull-right">{{ @root.__ "Proceed to payment" }}</a>
</div>
<div id="cart" class="col-md-7">
{{> (getTheme 'cart')}}
<div class="row">
{{#if @root.session.cart}}
<div class="col-xs-12 col s12">
<button class="btn btn-danger pull-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
</div>
{{/if}}
</div>
</div>
</div>
</div>

View File

@ -1,11 +0,0 @@
<div class="col-xl-8 col-xl-offset-2 col-xs-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Checkout</li>
</ol>
</nav>
<div id="cart">
{{> (getTheme 'cart')}}
</div>
</div>

View File

@ -1,26 +1,26 @@
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="email" class="form-control customerDetails" id="shipEmail" name="shipEmail" minlength="5" placeholder="Email address" value="{{session.customer.email}}" required>
<input type="email" class="form-control customerDetails" id="shipEmail" name="shipEmail" minlength="5" placeholder="Email address" value="{{@root.session.customerEmail}}" required>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipFirstname" name="shipFirstname" placeholder="First name" value="{{session.customer.firstName}}" required>
<input type="text" class="form-control customerDetails" id="shipFirstname" name="shipFirstname" placeholder="First name" value="{{@root.session.customerFirstname}}" required>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipLastname" name="shipLastname" placeholder="Last name" value="{{@root.session.customer.lastName}}" required>
<input type="text" class="form-control customerDetails" id="shipLastname" name="shipLastname" placeholder="Last name" value="{{@root.session.customerLastname}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipAddr1" name="shipAddr1" placeholder="Address 1" value="{{session.customer.address1}}" required>
<input type="text" class="form-control customerDetails" id="shipAddr1" name="shipAddr1" placeholder="Address 1" value="{{@root.session.customerAddress1}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipAddr2" name="shipAddr2" placeholder="Address 2 (optional)" value="{{session.customer.address2}}">
<input type="text" class="form-control customerDetails" id="shipAddr2" name="shipAddr2" placeholder="Address 2 (optional)" value="{{@root.session.customerAddress2}}">
</div>
</div>
<div class="col-xs-12">
@ -28,28 +28,28 @@
<select class="form-control" id="shipCountry" name="shipCountry" required>
<option value="" disabled selected>Select Country</option>
{{#each countryList}}
<option {{selectState this @root.session.customer.country}} value="{{this}}">{{this}}</option>
<option {{selectState this @root.session.customerCountry}} value="{{this}}">{{this}}</option>
{{/each}}
</select>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipState" name="shipState" placeholder="State" value="{{session.customer.state}}" required>
<input type="text" class="form-control customerDetails" id="shipState" name="shipState" placeholder="State" value="{{@root.session.customerState}}" required>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipPostcode" name="shipPostcode" placeholder="Post code" value="{{session.customer.postcode}}" required>
<input type="text" class="form-control customerDetails" id="shipPostcode" name="shipPostcode" placeholder="Post code" value="{{@root.session.customerPostcode}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="number" class="form-control customerDetails" id="shipPhoneNumber" name="shipPhoneNumber" placeholder="Phone number" value="{{session.customer.phone}}" required>
<input type="number" class="form-control customerDetails" id="shipPhoneNumber" name="shipPhoneNumber" placeholder="Phone number" value="{{@root.session.customerPhone}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<textarea class="form-control customerDetails" placeholder="Order comment" id="orderComment" name="orderComment"></textarea>
<textarea class="form-control customerDetails" placeholder="Order comment" id="orderComment" name="orderComment">{{@root.session.orderComment}}</textarea>
</div>
</div>