Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/store #125

Merged
merged 29 commits into from
Sep 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
47f496e
initial commit for store
sanderpick Jul 4, 2015
4bd0556
add config
sanderpick Jul 8, 2015
deb3e12
initial commit for store
sanderpick Jul 12, 2015
27c571a
more WIP
sanderpick Jul 14, 2015
e2495b1
WIP: more store
sanderpick Jul 26, 2015
3a59dbb
add cart
sanderpick Jul 27, 2015
add1ffb
more wip
sanderpick Aug 1, 2015
e1e5785
choose shipping
sanderpick Aug 19, 2015
34c08fd
fix shipwire package
sanderpick Aug 19, 2015
8fe0ea3
more wip
sanderpick Aug 20, 2015
ffae218
more WIP
sanderpick Sep 1, 2015
6d782b8
create shipping order
sanderpick Sep 2, 2015
163bae1
error handling, buy now button
sanderpick Sep 4, 2015
1d55511
add shipping address to settings
sanderpick Sep 7, 2015
61d5549
style forms, front-end error handling, flow
sanderpick Sep 8, 2015
413b27b
add summary step
sanderpick Sep 8, 2015
5c2dba1
add processing step
sanderpick Sep 8, 2015
0f07a78
clean up
sanderpick Sep 10, 2015
cdc5af0
bring back films page, hide store page
sanderpick Sep 10, 2015
f440cd8
Increase timeout on member.js test
eyalcohen Sep 13, 2015
7e7abdf
review fixes
sanderpick Sep 16, 2015
41ac29c
add tests for shipping rate
sanderpick Sep 17, 2015
5c828a9
rename store test
sanderpick Sep 17, 2015
fca6183
store checkout tests
sanderpick Sep 18, 2015
6931e8a
test fixes
sanderpick Sep 19, 2015
b1199a3
show out of stock, blow up product images on click
sanderpick Sep 19, 2015
4f70b22
dont show not in use products
sanderpick Sep 19, 2015
c79d008
show shipping details on last screen
sanderpick Sep 20, 2015
d96777b
tweak confirmation message
sanderpick Sep 20, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@
"CARTODB_API_KEY": "883965c96f62fd219721f59f2e7c20f08db0123b",
"CARTODB_CRAGS_TABLE": "crags_dev",

"STRIPE_SECRET_KEY": "sk_test_jDlsyyvD8y77XMC7wC1w6cNB",
"STRIPE_PUBLISHABLE_KEY": "pk_test_hUrz7pk2qdjqgIU1BDuHraVv",

"SENDOWL_HOST": "www.sendowl.com",
"SENDOWL_KEY": "463c1877e8b8dbc",
"SENDOWL_SECRET": "424f38d72e99c6f54d89",

"SHIPWIRE_HOST": "api.beta.shipwire.com",
"SHIPWIRE_USER": "[email protected]",
"SHIPWIRE_PASS": "dc3996da781463ee133bbfb2d6355202",

"FACEBOOK_NAME": "The (new) Island (dev)",
"FACEBOOK_CLIENT_ID": 153015724883386,
"FACEBOOK_CLIENT_SECRET": "8cba32f72580806cca22306a879052bd",
Expand Down
4 changes: 2 additions & 2 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var CHANNELS = [
'watch'
];

// Handle Execution of fetch sample queues.
// Handle Execution of fetch queues.
function ExecutionQueue(maxInFlight) {
var inFlight = 0;
var queue = [];
Expand Down Expand Up @@ -182,4 +182,4 @@ Client.prototype.getUser = function(obj, cb) {
} else if (obj.target.indexOf('27crags') !== -1) {
lib27crags.searchUser(obj.userId, cb);
}
}
};
5 changes: 3 additions & 2 deletions lib/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ exports.resources = {
notification: require('./resources/notification'),
comment: require('./resources/comment'),
instagram: require('./resources/instagram'),
signup: require('./resources/signup')
}
signup: require('./resources/signup'),
store: require('./resources/store')
};
4 changes: 3 additions & 1 deletion lib/resources/member.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ var BLACKLIST = [
'photos',
'image',
'images',
'share'
'share',
'store'
];

var DEFUALT_CONFIG = {
Expand Down Expand Up @@ -1118,6 +1119,7 @@ exports.routes = function () {
delete doc.googleRefresh;
delete doc.instagramToken;
delete doc.instagramRefresh;
delete doc.address;
res.send(doc);
});
});
Expand Down
308 changes: 308 additions & 0 deletions lib/resources/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
/*
* store.js: Handles orders from the store.
*
*/

// Module Dependencies
var util = require('util');
var iutil = require('island-util');
var _ = require('underscore');
_.mixin(require('underscore.string'));
var profiles = require('island-collections').profiles;
var app = require('../../app');
var Step = require('step');
var store = require('../../store.json');

var MAX_PRODUCT_QUANTITY_PER_ORDER =
exports.MAX_PRODUCT_QUANTITY_PER_ORDER = 20;

exports.init = function () {
return this.routes();
};

exports.routes = function () {
var db = app.get('db');
var errorHandler = app.get('errorHandler');
var events = app.get('events');
var emailer = app.get('emailer');
var stripe = app.get('stripe');
var sendowl = app.get('sendowl');
var shipwire = app.get('shipwire');

app.post('/api/store/checkout', function (req, res) {
var token = req.body.token;
var cart = req.body.cart;
var shipping = req.body.shipping;
var description = req.body.description;

if (!token) {
return res.send(403, {error: {message: 'Token invalid'}});
}

if (!cart || _.isEmpty(cart)) {
return res.send(403, {error: {message: 'Cart invalid'}});
}

if (!shipping || !shipping.shipTo || !shipping.shipments) {
return res.send(403, {error: {message: 'Shipping invalid'}});
}

if (!description) {
return res.send(403, {error: {message: 'Description invalid'}});
}

var products = [];
var overMaxQuantityPerOrder = [];
var amount = shipping.shipments[0].cost.amount * 100;
_.each(cart, function (quantity, sku) {
if (!store[sku]) {
return;
}
var product = {
sku: sku,
quantity: quantity,
name: store[sku].name,
price: store[sku].price
};
products.push(product);
amount += (quantity * product.price);
if (product.quantity > MAX_PRODUCT_QUANTITY_PER_ORDER) {
overMaxQuantityPerOrder.push({
name: product.name,
requested: product.quantity,
allowed: MAX_PRODUCT_QUANTITY_PER_ORDER
});
}
});

if (products.length === 0) {
return res.send(403, {error: {message: 'Cart invalid'}});
}

if (overMaxQuantityPerOrder.length > 0) {
return res.send(403, {error: {
message: 'OVER_MAX_PRODUCT_QUANTITY_PER_ORDER',
data: overMaxQuantityPerOrder
}});
}

Step(
function () {
shipwire.stock.get(this);
},
function (err, data) {
if (err) {
return this(err);
}

var stock = {};
_.each(data.resource.items, function (i) {
stock[i.resource.sku] = i.resource;
});

// Check inventory.
var unknownProducts = [];
var insufficientStock = [];
_.each(products, function (product) {
var inventory = stock[product.sku];
if (!inventory) {
unknownProducts.push({
name: product.name
});
} else if (inventory.good < product.quantity) {
insufficientStock.push({
name: product.name,
requested: product.quantity,
good: inventory.good
});
}
});

if (unknownProducts.length > 0) {
return this({error: {code: 403, message: 'UNKNOWN_PRODUCT',
data: unknownProducts}});
}

if (insufficientStock.length > 0) {
return this({error: {code: 403, message: 'INSUFFICIENT_STOCK',
data: insufficientStock}});
}

stripe.charges.create({
amount: amount,
currency: 'usd',
source: token.id,
description: description,
metadata: {
email: token.email
},
statement_descriptor: _.prune(description, 22, '').toUpperCase()
}, this);
},
function (err, charge) {
if (err) {
return this(err);
}

if (!charge.paid || charge.status !== 'succeeded') {
return this({error: {code: 403, message: 'Payment failed'}});
}

var items = _.map(products, function (product) {
return {
sku: product.sku,
quantity: product.quantity,
commercialInvoiceValue: product.price / 100,
commercialInvoiceValueCurrency: charge.currency.toUpperCase()
};
});

var order = {
orderNo: charge.id,
items: items,
options: {
warehouseRegion: 'CHI',
warehouseId: 13,
currency: charge.currency.toUpperCase()
},
shipFrom: {
company: 'We Are Island, Inc.'
},
shipTo: {
email: token.email,
name: shipping.shipTo.name,
address1: shipping.shipTo.address,
city: shipping.shipTo.city,
state: shipping.shipTo.state,
postalCode: shipping.shipTo.zip,
country: shipping.shipTo.country,
isCommercial: 0,
isPoBox: 0
},
packingList: {
message1: {
body: '- David Graham, Daniel Woods, Nalle Hukkataival, ' +
'Jamie Emerson, Sander Pick, Eyal Cohen',
header: 'Thank you for supporting Island. Try hard out there.'
}
}
};

shipwire.orders.create(order, this);
},
function (err, data) {
if (errorHandler(err, req, res)) return;

var order = data.resource && data.resource.items &&
data.resource.items[0] ? data.resource.items[0].resource:
null;

if (data.status !== 200 || !order.orderNo || !order.id) {
return res.send(403, {error: {message: 'Order failed'}});
}

res.send({
message: 'Huzzah! Thank you for supporting Island. We\'ll send an ' +
'email from [email protected] to ' + token.email + ' when your ' +
'order has been received at our warehouse.',
orderNo: order.orderNo,
orderId: order.id
});
}
);
});

app.post('/api/store/shipping', function (req, res) {
var address = req.body.address;
var cart = req.body.cart;

if (!address || !address.name || !address.address || !address.city ||
!address.zip || !address.country) {
return res.send(403, {error: {message: 'Address invalid'}});
}

if (!cart || _.isEmpty(cart)) {
return res.send(403, {error: {message: 'Cart invalid'}});
}

var items = [];
var quantitiesValid = true;
_.each(cart, function (quantity, sku) {
if (!store[sku]) {
return;
}
items.push({sku: sku, quantity: quantity});
if (quantity > MAX_PRODUCT_QUANTITY_PER_ORDER) {
quantitiesValid = false;
}
});

if (items.length === 0) {
return res.send(403, {error: {message: 'Cart invalid'}});
}

if (!quantitiesValid) {
return res.send(403, {error: {
message: 'OVER_MAX_PRODUCT_QUANTITY_PER_ORDER'}});
}

var params = {
options: {
currency: 'USD',
groupBy: 'all',
canSplit: 0,
warehouseArea: 'US'
},
order: {
shipTo: {
address1: address.address,
city: address.city,
state: address.state,
postalCode: address.zip,
country: address.country,
isCommercial: 0,
isPoBox: 0
},
items: items
}
};

shipwire.rate.get(params, function (err, data) {
if (errorHandler(err, req, res)) return;
if (data.errors && data.errors.length > 0) {
return res.send(data.status, {
error: {message: data.errors[0].message}
});
}

var warnings = data.warnings;
if (warnings && warnings.length > 0) {
return res.send(400, {
error: {message: warnings[0].message}
});
}

var rates = data.resource.rates;
if (!rates || rates.length === 0) {
return res.send(400, {
error: {
message: 'No shipping options found for the specified address'
}
});
}

var options = data.resource.rates[0].serviceOptions;
if (!options || options.length === 0) {
return res.send(400, {
error: {
message: 'No shipping options found for the specified address'
}
});
}

res.send({shipTo: address, options: options});
});
});

return exports;
};
Loading