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 22 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
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -50,6 +50,9 @@ docs/typescript/*.html
docs/api/*.html
index.html

# Local Netlify folder
.netlify

# yarn package-lock
yarn.lock

Expand Down
@@ -0,0 +1 @@
{"imports":{"netlify:edge":"https://edge-bootstrap.netlify.app/v1/index.ts"}}
20 changes: 20 additions & 0 deletions examples/ecommerce-netlify-functions/dummyProducts.js
@@ -0,0 +1,20 @@
const { Product } = require('./models');
const mongoose = require('mongoose');

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

await Product.create({
productName: 'Dummy Product',
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
productPrice: 500
});

await Product.create({
productName: 'Another Dummy Product',
productPrice: 600
});

console.log('done');
}

createProducts();
20 changes: 20 additions & 0 deletions examples/ecommerce-netlify-functions/index.js
@@ -0,0 +1,20 @@
'use strict';

const mongoose = require('mongoose');


mongoose.connection.on('connected', () => {
console.log('connected')
});

let conn = null;

module.exports = async function connect() {
if (conn != null) {
return conn;
}
conn = mongoose.connection;
await mongoose.connect('mongodb://localhost:27017/netlify');
return conn;

}
5 changes: 5 additions & 0 deletions examples/ecommerce-netlify-functions/integrations/stripe.js
@@ -0,0 +1,5 @@
'use strict';

const config = require('../.config')

module.exports = require('stripe')(config.stripeSecretKey);
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;

@@ -0,0 +1,41 @@
'use strict';

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

const handler = async(event) => {
try {
event.body = JSON.parse(event.body || {});
await connect();
const products = await Product.find();
if (event.body.cartId) {
// get the document containing the specified cartId
const cart = await Cart.findOne({ _id: event.body.cartId }).setOptions({ sanitizeFilter: true });
for (const product of event.body.items) {
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
const exists = cart.items.find(item => item.productId.toString() === product.productId.toString());
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
if (!exists && products.find(p => product.productId.toString() === p._id.toString())) {
cart.items.push(product);
await cart.save();
} else {
exists.quantity += product.quantity;
await cart.save();
}
}

if (!cart.items.length) {
return { statusCode: 200, body: JSON.stringify({ cart: null }) };
}

await cart.save();
return { statusCode: 200, body: JSON.stringify(cart) };
} else {
// If no cartId, create a new cart
const cart = await Cart.create({ items: event.body.items });
return { statusCode: 200, body: JSON.stringify(cart) };
}
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};

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

const stripe = require('../../integrations/stripe')

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

const handler = async(event) => {
try {
event.body = JSON.parse(event.body || {});
await connect();
const cart = await Cart.findOne({ _id: event.body.cartId });

const stripeProducts = { line_items: [] };
let total = 0;
for (let i = 0; i < cart.items.length; i++) {
const product = await Product.findOne({ _id: cart.items[i].productId });
stripeProducts.line_items.push({
price_data: {
currency: 'usd',
product_data: {
name: product.productName
},
unit_amount: product.productPrice
},
quantity: cart.items[i].quantity
});
total = total + (product.productPrice * cart.items[i].quantity);
}
const session = await stripe.checkout.sessions.create({
line_items: stripeProducts.line_items,
mode: 'payment',
success_url: 'insert a url here',
IslandRhythms marked this conversation as resolved.
Show resolved Hide resolved
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
});

cart.orderId = order._id;
await cart.save();
return {
statusCode: 200,
body: JSON.stringify({ order: order, cart: cart }),
headers: { Location: session.url }
};
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};

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

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

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

module.exports = { handler };
@@ -0,0 +1,16 @@
'use strict';

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

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

module.exports = { handler };
@@ -0,0 +1,34 @@
'use strict';

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

const handler = async(event) => {
try {
event.body = JSON.parse(event.body || {});
await connect();
const cart = await Cart.findOne({ _id: event.body.cartId });
const index = cart.items.findIndex((item) =>
item.productId.toString() == event.body.item.productId.toString()
);
if (index == -1) {
return { statusCode: 200, body: cart };
}
if (event.body?.item?.quantity) {
cart.items[index].quantity -= event.body.item.quantity;
if (cart.items[index].quantity <= 0) {
cart.items.splice(index, 1);
}
await cart.save();
} else {
cart.items.splice(index, 1);
await cart.save();
}
return { statusCode: 200, body: JSON.stringify(cart) };
} catch (error) {
console.log(error);
return { statusCode: 500, body: error.toString() };
}
};

module.exports = { handler };
14 changes: 14 additions & 0 deletions examples/ecommerce-netlify-functions/package.json
@@ -0,0 +1,14 @@
{
"dependencies": {
"mongoose": "^6.3.5",
"sinon": "^14.0.0",
"stripe": "^9.6.0"
},
"devDependencies": {
"mocha": "10.0.0",
"netlify-cli": "^10.7.1"
},
"scripts": {
"test": "mocha --exit ./test/*.test.js"
}
}