-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Netlify functions example #11892
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
fa7f196
without stripe
IslandRhythms 200668b
not entirely sure how to redirect
IslandRhythms 92787e8
Update checkout.js
IslandRhythms c9baa82
All thats left is checkout
IslandRhythms e865463
not done yet
IslandRhythms 062ca30
added checkout test
IslandRhythms d983d33
created fixtures
IslandRhythms ad97863
fix addToCart test
IslandRhythms e852249
fix getCart test
IslandRhythms a85e5a5
fix getProducts test and remove test data on test completion
IslandRhythms 9a8fad2
fix removeFromCart and removeFromCart test
IslandRhythms e89b2f9
almost done
IslandRhythms 0d2d299
sinon not doing sinon things
IslandRhythms 3c87f27
not getting sinon error messages
IslandRhythms 6dec0c5
thanks val
IslandRhythms b693176
Update index.js
IslandRhythms dfb75b4
add
IslandRhythms 115f9fa
need to fix tests
IslandRhythms 41712f1
tests fixed
IslandRhythms 28b5557
added dummy product script
IslandRhythms 42f40d5
made tests work with live build
IslandRhythms 118c97a
Merge branch 'master' into netlify-functions-example
vkarpov15 5301deb
fix: cleanup and various updates
vkarpov15 2e6b064
made requested changes
IslandRhythms eced2c7
Merge branch 'master' into netlify-functions-example
vkarpov15 2751883
fix tests
vkarpov15 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
'use strict'; | ||
|
||
module.exports = Object.freeze({ | ||
mongodbUri: 'mongodb://localhost:27017/ecommerce', | ||
stripeSecretKey: 'YOUR STRIPE KEY HERE', | ||
success_url: 'localhost:3000/success', | ||
cancel_url: 'localhost:3000/cancel' | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
'use strict'; | ||
|
||
if (process.env.NODE_ENV) { | ||
try { | ||
module.exports = require('./' + process.env.NODE_ENV); | ||
console.log('Using ' + process.env.NODE_ENV); | ||
} catch (err) { | ||
module.exports = require('./development'); | ||
} | ||
} else { | ||
console.log('using production'); | ||
module.exports = require('./production'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
'use strict'; | ||
|
||
module.exports = Object.freeze({ | ||
mongodbUri: 'mongodb://localhost:27017/ecommerce_test', | ||
stripeSecretKey: 'test', | ||
success_url: 'localhost:3000/success', | ||
cancel_url: 'localhost:3000/cancel' | ||
|
||
}); |
1 change: 1 addition & 0 deletions
1
examples/ecommerce-netlify-functions/.netlify/edge-functions-import-map.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"imports":{"netlify:edge":"https://edge-bootstrap.netlify.app/v1/index.ts"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# ecommerce-netlify-functions | ||
|
||
This sample demonstrates using Mongoose to build an eCommerce shopping cart using [Netlify Functions](https://www.netlify.com/products/functions/), which runs on [AWS Lambda](https://mongoosejs.com/docs/lambda.html). | ||
|
||
Other tools include: | ||
|
||
1. Stripe for payment processing | ||
2. [Mocha](https://masteringjs.io/mocha) and [Sinon](https://masteringjs.io/sinon) for testing | ||
|
||
## Running This Example | ||
|
||
1. Make sure you have a MongoDB instance running on `localhost:27017`, or update `mongodbUri` in `.config/development.js` to your MongoDB server's address. | ||
2. Run `npm install` | ||
3. Run `npm run seed` | ||
4. Run `npm start` | ||
5. Visit `http://localhost:8888/.netlify/functions/getProducts` to list all available products | ||
6. Run other endpoints using curl or postman | ||
|
||
## Testing | ||
|
||
Make sure you have a MongoDB instance running on `localhost:27017`, or update `mongodbUri` in `.config/test.js` to your MongoDB server's address. | ||
Then run `npm test`. | ||
|
||
``` | ||
$ npm test | ||
|
||
> test | ||
> env NODE_ENV=test mocha ./test/*.test.js | ||
|
||
Using test | ||
|
||
|
||
Add to Cart | ||
✔ Should create a cart and add a product to the cart | ||
✔ Should find the cart and add to the cart | ||
✔ Should find the cart and increase the quantity of the item(s) in the cart | ||
|
||
Checkout | ||
✔ Should do a successful checkout run | ||
|
||
Get the cart given an id | ||
✔ Should create a cart and then find the cart. | ||
|
||
Products | ||
✔ Should get all products. | ||
|
||
Remove From Cart | ||
✔ Should create a cart and then it should remove the entire item from it. | ||
✔ Should create a cart and then it should reduce the quantity of an item from it. | ||
|
||
|
||
8 passing (112ms) | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
'use strict'; | ||
|
||
const config = require('./.config'); | ||
const mongoose = require('mongoose'); | ||
|
||
let conn = null; | ||
|
||
module.exports = async function connect() { | ||
if (conn != null) { | ||
return conn; | ||
} | ||
conn = mongoose.connection; | ||
await mongoose.connect(config.mongodbUri); | ||
return conn; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
'use strict'; | ||
|
||
const config = require('../.config') | ||
|
||
module.exports = require('stripe')(config.stripeSecretKey); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
'use strict'; | ||
const mongoose = require('mongoose'); | ||
|
||
const productSchema = new mongoose.Schema({ | ||
name: String, | ||
price: Number, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't update tests to reflect this change |
||
image: String | ||
}); | ||
|
||
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; | ||
|
48 changes: 48 additions & 0 deletions
48
examples/ecommerce-netlify-functions/netlify/functions/addToCart.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
'use strict'; | ||
|
||
const { Cart, Product } = require('../../models'); | ||
const connect = require('../../connect'); | ||
|
||
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 }); | ||
|
||
if (cart == null) { | ||
return { statusCode: 404, body: JSON.stringify({ message: 'Cart not found' }) }; | ||
} | ||
if(!Array.isArray(event.body.items)) { | ||
return { statusCode: 500, body: JSON.stringify({ error: 'items is not an array' }) }; | ||
} | ||
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()); | ||
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
69
examples/ecommerce-netlify-functions/netlify/functions/checkout.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
'use strict'; | ||
|
||
const stripe = require('../../integrations/stripe') | ||
const config = require('../../.config'); | ||
const { Cart, Order, Product } = require('../../models'); | ||
const connect = require('../../connect'); | ||
|
||
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.name | ||
}, | ||
unit_amount: product.price | ||
}, | ||
quantity: cart.items[i].quantity | ||
}); | ||
total = total + (product.price * cart.items[i].quantity); | ||
} | ||
const session = await stripe.checkout.sessions.create({ | ||
line_items: stripeProducts.line_items, | ||
mode: 'payment', | ||
success_url: config.success_url, | ||
cancel_url: config.cancel_url | ||
}); | ||
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
19
examples/ecommerce-netlify-functions/netlify/functions/getCart.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
'use strict'; | ||
|
||
const { Cart } = require('../../models'); | ||
const connect = require('../../connect'); | ||
|
||
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 }; |
16 changes: 16 additions & 0 deletions
16
examples/ecommerce-netlify-functions/netlify/functions/getProducts.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
'use strict'; | ||
|
||
const { Product } = require('../../models'); | ||
const connect = require('../../connect'); | ||
|
||
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 }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👎 camelCase, no snake_case