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

Netlify functions example #11892

Merged
merged 26 commits into from Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fa7f196
without stripe
IslandRhythms Jun 3, 2022
200668b
not entirely sure how to redirect
IslandRhythms Jun 3, 2022
92787e8
Update checkout.js
IslandRhythms Jun 3, 2022
c9baa82
All thats left is checkout
IslandRhythms Jun 6, 2022
e865463
not done yet
IslandRhythms Jun 7, 2022
062ca30
added checkout test
IslandRhythms Jun 13, 2022
d983d33
created fixtures
IslandRhythms Jul 1, 2022
ad97863
fix addToCart test
IslandRhythms Jul 1, 2022
e852249
fix getCart test
IslandRhythms Jul 1, 2022
a85e5a5
fix getProducts test and remove test data on test completion
IslandRhythms Jul 1, 2022
9a8fad2
fix removeFromCart and removeFromCart test
IslandRhythms Jul 1, 2022
e89b2f9
almost done
IslandRhythms Jul 5, 2022
0d2d299
sinon not doing sinon things
IslandRhythms Jul 5, 2022
3c87f27
not getting sinon error messages
IslandRhythms Jul 5, 2022
6dec0c5
thanks val
IslandRhythms Jul 5, 2022
b693176
Update index.js
IslandRhythms Jul 5, 2022
dfb75b4
add
IslandRhythms Jul 6, 2022
115f9fa
need to fix tests
IslandRhythms Jul 6, 2022
41712f1
tests fixed
IslandRhythms Jul 6, 2022
28b5557
added dummy product script
IslandRhythms Jul 6, 2022
42f40d5
made tests work with live build
IslandRhythms Jul 7, 2022
118c97a
Merge branch 'master' into netlify-functions-example
vkarpov15 Jul 16, 2022
5301deb
fix: cleanup and various updates
vkarpov15 Jul 17, 2022
2e6b064
made requested changes
IslandRhythms Jul 19, 2022
eced2c7
Merge branch 'master' into netlify-functions-example
vkarpov15 Jul 20, 2022
2751883
fix tests
vkarpov15 Jul 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1 @@
{"imports":{"netlify:edge":"https://edge-bootstrap.netlify.app/v1/index.ts"}}
32 changes: 32 additions & 0 deletions examples/ecommerce-netlify-functions/functions/addToCart.js
@@ -0,0 +1,32 @@
'use strict';

const { Cart } = require('../models');

const handler = async(event) => {
try {
if (event.body.cartId) {
// get the document containing the specified cartId
const cart = await Cart.findOne({ _id: event.body.cartId }).setOptions({ sanitizeFilter: true });
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
for (const product of event.body.product) {
const exists = cart.items.find(item =>
item.productId.toString() === product.productId.toString()
);
if (!exists) {
cart.items.push(product);
} else {
exists.quantity += product.quantity;
}
}
await cart.save();
return { statusCode: 200, body: cart };
} else {
// If no cartId, create a new cart
const cart = await Cart.create({ items: event.body.product });
return { statusCode: 200, body: cart };
}
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};

module.exports = { handler };
59 changes: 59 additions & 0 deletions examples/ecommerce-netlify-functions/functions/checkout.js
@@ -0,0 +1,59 @@
'use strict';
const stripe = require('stripe')(process.env.STRIPE_KEY);

const { Cart, Order, Product } = require('../models');

const handler = async(event) => {
try {
const stripeProducts = { line_items: [] };
let total = 0;
for (let i = 0; i < event.body.product.length; i++) {
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
const product = await Product.findOne({ _id: event.body.product[i].productId });
stripeProducts.line_items.push({
price_data: {
currency: 'usd',
product_data: {
name: product.productName
},
unit_amount: product.productPrice
},
quantity: event.body.product[i].quantity
});
total = total + (product.productPrice * event.body.product[i].quantity);
}
const session = await stripe.checkout.sessions.create({
line_items: stripeProducts.line_items,
mode: 'payment',
success_url: 'insert a url here',
cancel_url: 'insert a url here'
});
const intent = await stripe.paymentIntents.retrieve(session.payment_intent);
if (intent.status !== 'succeeded') {
throw new Error(`Checkout failed because intent has status "${intent.status}"`);
}
const paymentMethod = await stripe.paymentMethods.retrieve(intent['payment_method']);
const orders = await Order.find();
const orderNumber = orders.length ? orders.length + 1 : 1;
const order = await Order.create({
items: event.body.product,
total: total,
orderNumber: orderNumber,
name: event.body.name,
email: event.body.email,
address1: event.body.address1,
city: event.body.city,
state: event.body.state,
zip: event.body.zip,
shipping: event.body.shipping,
paymentMethod: paymentMethod ? { id: paymentMethod.id, brand: paymentMethod.brand, last4: paymentMethod.last4 } : null
});
const cart = await Cart.findOne({ _id: event.body.cartId });
cart.orderId = order._id;
await cart.save();
return { statusCode: 200, body: { order: order, cart: cart }, headers: { Location: session.url } };
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};

module.exports = { handler };
15 changes: 15 additions & 0 deletions examples/ecommerce-netlify-functions/functions/getCart.js
@@ -0,0 +1,15 @@
'use strict';

const { Cart } = require('../models');

const handler = async(event) => {
try {
// get the document containing the specified cartId
const cart = await Cart.findOne({ _id: event.queryStringParameters.cartId }).setOptions({ sanitizeFilter: true });
return { statusCode: 200, body: cart };
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};

module.exports = { handler };
14 changes: 14 additions & 0 deletions examples/ecommerce-netlify-functions/functions/getProducts.js
@@ -0,0 +1,14 @@
'use strict';

const { Product } = require('../models');

const handler = async(event) => {
try {
const products = await Product.find();
return { statusCode: 200, body: products };
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};

module.exports = { handler };
27 changes: 27 additions & 0 deletions examples/ecommerce-netlify-functions/functions/removeFromCart.js
@@ -0,0 +1,27 @@
'use strict';

const { Cart } = require('../models');

const handler = async(event) => {
try {
const cart = await Cart.findOne({ _id: event.body.cartId });
const index = cart.items.findIndex((item) =>
item.productId == event.body.product.productId
);
if (index == -1) {
return { statusCode: 500, body: 'Product not found' };
}
if (event.body?.product?.quantity) {
cart.items[index].quantity -= event.body.product.quantity;
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
await cart.save();
} else {
cart.items.splice(index, 1);
await cart.save();
}
return { statusCode: 200, body: cart };
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};

module.exports = { handler };
83 changes: 83 additions & 0 deletions examples/ecommerce-netlify-functions/models.js
@@ -0,0 +1,83 @@
'use strict';
const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
productName: String,
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
productPrice: Number
});

const Product = mongoose.model('Product', productSchema);

module.exports.Product = Product;

const orderSchema = new mongoose.Schema({
items: [
{ productId: { type: mongoose.ObjectId, required: true, ref: 'Product' },
quantity: { type: Number, required: true, validate: v => v > 0 }
}
],
total: {
type: Number,
default: 0
},
status: {
type: String,
enum: ['PAID', 'IN_PROGRESS', 'SHIPPED', 'DELIVERED'],
default: 'PAID'
},
orderNumber: {
type: Number,
required: true
},
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
address1: {
type: String,
required: true
},
address2: {
type: String
},
city: {
type: String,
required: true
},
state: {
type: String,
required: true
},
zip: {
type: String,
required: true
},
shipping: {
type: String,
required: true,
enum: ['standard', '2day']
},
paymentMethod: {
id: String,
brand: String,
last4: String
}
}, { optimisticConcurrency: true });

const Order = mongoose.model('Order', orderSchema);

module.exports.Order = Order;

const cartSchema = new mongoose.Schema({
items: [{ productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, quantity: { type: Number, required: true } }],
orderId: { type: mongoose.ObjectId, ref: 'Order' }
}, { timestamps: true });

const Cart = mongoose.model('Cart', cartSchema);

module.exports.Cart = Cart;

12 changes: 12 additions & 0 deletions examples/ecommerce-netlify-functions/package.json
@@ -0,0 +1,12 @@
{
"dependencies": {
"mongoose": "^6.3.5",
"stripe": "^9.6.0"
},
"devDependencies": {
"mocha": "10.0.0"
},
"scripts": {
"test": "mocha --exit ./test/*.test.js"
}
}
35 changes: 35 additions & 0 deletions examples/ecommerce-netlify-functions/seed.js
@@ -0,0 +1,35 @@
'use strict';
const mongoose = require('mongoose');
const { Product } = require('./models');

async function run() {
await mongoose.connect('mongodb://localhost:27017/netlify');
await mongoose.connection.dropDatabase();

await Product.create({
productName: 'Netlify\'s First Flight',
productPrice: 2000,
quantity: 5
});

await Product.create({
productName: 'Netlify The Movie',
productPrice: 4000,
quantity: 5
});

await Product.create({
productName: 'Netlify vs The Internet',
productPrice: 6000,
quantity: 5
});

await Product.create({
productName: 'Netlify and The Wasp',
productPrice: 8000,
quantity: 5
});
console.log('done');
}

run();
52 changes: 52 additions & 0 deletions examples/ecommerce-netlify-functions/test/addToCart.test.js
@@ -0,0 +1,52 @@
'use strict';

const { describe, it, before, after } = require('mocha');
const assert = require('assert');
const { handler: addToCart } = require('../functions/addToCart');
const mongoose = require('mongoose');



describe('Add to Cart', function() {
before(async() => {
await mongoose.connect('mongodb://localhost:27017/netlify');
});

after(async() => {
await mongoose.disconnect();
});
it('Should create a cart and add a product to the cart.', async function() {
const params = {
body: {
cartId: null,
product: [
{ productId: '629e5f3686d82fc73b95b396', quantity: 2 },
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
{ productId: '629e5f3686d82fc73b95b398', quantity: 1 }
]
}
};
const result = await addToCart(params);
assert(result.body);
assert(result.body.items.length);
});
it('Should find the cart and add to it.', async function() {
const params = {
body: {
cartId: null,
product: [
{ productId: '629e5f3686d82fc73b95b396', quantity: 2 },
{ productId: '629e5f3686d82fc73b95b398', quantity: 1 }
]
}
};
const result = await addToCart(params);
assert(result.body);
assert(result.body.items.length);
params.body.cartId = result.body._id;
const findCart = await addToCart(params);
assert(findCart.body);
assert.deepStrictEqual(findCart.body._id, result.body._id);
assert.equal(findCart.body.items[0].quantity, 4);
assert.equal(findCart.body.items[1].quantity, 2);
});
});
37 changes: 37 additions & 0 deletions examples/ecommerce-netlify-functions/test/checkout.test.js
@@ -0,0 +1,37 @@
'use strict';

const { describe, it, before, after } = require('mocha');
const assert = require('assert');
const { handler: addToCart } = require('../functions/addToCart');
const { handler: checkout } = require('../functions/checkout');
const mongoose = require('mongoose');



describe('Checkout', function() {
before(async() => {
await mongoose.connect('mongodb://localhost:27017/netlify');
});

after(async() => {
await mongoose.disconnect();
});
it('Should do a successful checkout run', async function() {
const params = {
body: {
cartId: null,
product: [
{ productId: '629e5f3686d82fc73b95b396', quantity: 2 },
{ productId: '629e5f3686d82fc73b95b398', quantity: 1 }
]
}
};
const result = await addToCart(params);
assert(result.body);
assert(result.body.items.length);
params.body.cartId = result.body._id;
const finish = await checkout(params);
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
assert(finish.body.order);
assert(finish.body.cart);
});
});