-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11892 from Automattic/netlify-functions-example
Netlify functions example
- Loading branch information
Showing
26 changed files
with
728 additions
and
1 deletion.
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, | ||
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) { | ||
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.