I have tried to create a custom email receipt template with SendGrid using commercejs webhooks, by following this tutorial. I have uploaded this github repository to
this test Netlify site. The main code is /functions/email.js
I am pretty sure my .env
variables are correct, still no receipt emails are received and probably send. When checking the Netlify function email log says:
5:55:03 PM: f7c003e5 ERROR Invoke Error {"errorType":"ReferenceError","errorMessage":"fetch is not defined","stack":["ReferenceError: fetch is not defined"," at Runtime.exports.handler (/var/task/email.js:30:22)"," at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"]}
5:55:03 PM: f7c003e5 Duration: 2.24 ms Memory Usage: 52 MB
However my package.json
dependencies
look like this:
"dependencies": {
"@chec/commerce.js": "2.2.0",
"@hookform/error-message": "0.0.5",
"@sendgrid/mail": "^7.4.7",
"@stripe/react-stripe-js": "1.1.2",
"@stripe/stripe-js": "1.11.0",
"autoprefixer": "10.0.4",
"classcat": "4.1.0",
"framer-motion": "2.9.4",
"next": "10.0.2",
"next-google-fonts": "1.2.1",
"node-fetch": "^3.0.0",
"pdf-lib": "^1.16.0",
"postcss": "8.1.14",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-hook-form": "6.11.5",
"react-toastify": "6.1.0",
"use-debounce": "^7.0.0"
},
Therefore it's confusing why I am getting the fetch is not defined error. Also, I am also confused about how to implement the headers correctly, because the tutorial did not specify how. So I just added the headers like this, without knowing if this is the way to do it:
let headers = {
"Accept": "application/json",
"Content-Type": "application/json",
};
let sgheaders = {
Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
"Content-Type": "application/json",
};
In the code currently uploaded to my netlify site, I had to change the export default async function handler(req, res) {
to
exports.handler = async function(req, res) {
according to the Netlify functions documentation the Netlify functions documentation. (because of "errorMessage": "SyntaxError: Unexpected token 'export'")
// Create the API endpoint function with a req and res parameter
exports.handler = async function(req, res) {
const checSecretAPIKey = process.env.CHEC_SECRET_KEY;
let headers = {
"Accept": "application/json",
"Content-Type": "application/json",
};
//export default async function handler(req, res) {
if (!req.body || req.httpMethod !== 'POST') {
return {
status: 405,
headers,
body: JSON.stringify({
status: 'Invalid HTTP method',
}),
}
}
const { data } = JSON.parse(req.body);
// Request for your merchant information so that you can use your email
// to include as the 'from' property to send to the SendGrid API
const merchant = fetch(`${process.env.CHEC_API_URL}/v1/merchants`, {
headers: {
'X-Authoriza†ion': process.env.CHEC_SECRET_KEY,
},
}).then((response) => response.json);
// Extract the signature from the registered `orders.create` webhook
const { signature } = data;
delete data.signature;
// Your Chec webhook signing key, from the Chec Dashboard webhooks view
const webHookSigningKey = 'KEJlxz6cIlrWIpsX5jypcMeGl2uh7jJg';
// Verify the signature
const expectedSignature = crypto.createHmac('sha256', webHookSigningKey)
.update(JSON.stringify(data))
.digest('hex');
if (expectedSignature !== signature) {
console.error('Signature mismatched, skipping.')
}
// Verify the age of the request to make sure it isn't more than 5 minutes old.
if (new Date(data.created * 1000) < new Date() - 5 * 60 * 1000) {
console.error('Webhook was sent too long ago, could potentially be fake, ignoring');
}
// Because you will need to list out the order line items, map through the returned line items
// and structure out the data you need to display in the email receipt for your customer
// Note that we are keeping the data structure minimal here
const orderLineItems = data.payload.order.line_items.map((lineItem) => ({
text: lineItem.product_name,
price: lineItem.line_total.formatted_with_symbol,
}));
// Signature is verified, continue to send data to SendGrid
// Create the email object payload to fire off to SendGrid
const emailPayload = {
to: data.payload.customer.email,
from: merchant.support_email,
subject: `Thank you for your order ${data.payload.customer.firstname}`,
text: `Your order number is ${data.payload.customer_reference}`,
// SendGrid expects a JSON blog containing the dynamic order data your template will use
// More information below in 'What's next?' on how to configure your dynamic template in SendGrid
// The property key names will depend on what dynamic template you create in SendGrid
dynamic_template_data: {
total: data.payload.order.subtotal.formatted_with_symbol,
items: orderLineItems,
receipt: true,
name: data.payload.shipping.name,
address01: data.payload.shipping.street,
city: data.payload.shipping.town_city,
state: data.payload.shipping.county_state,
zip : data.payload.shipping.postal_zip_code,
orderId : data.payload.id,
},
// In addition to specifying the dynamic template data, you need to specify the template ID. This comes from your SendGrid dashboard when you create you dynamic template
// https://mc.sendgrid.com/dynamic-templates
template_id: 'd-xxx'
}
let sgheaders = {
Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
"Content-Type": "application/json",
};
let response = {};
try {
// Call the SendGrid send mail endpoint
response = await sgMailClient.send(emailPayload);
return {
statusCode: 200,
headers: sgheaders,
body: 'Email sent!'
}
} catch (err) {
console.error('Error', err)
}
// Return the response from the request
return res.status(200).json(response);
}
Need some advice on how to get this code to actually work, the tutorial seems to be uncompleted or I might have misunderstood some curtail details.
UPDATE (Working code below) Had to use axios instead of node.fetch (thanks @hotcakedev) wehn deplodey on netlify. Also other changes to the code in order to make it work with commerce.js (see working code for detalis)
const axios = require('axios');
const sgMailClient = require("@sendgrid/mail");
sgMailClient.setApiKey(process.env.SENDGRID_API_KEY);
// Includes crypto module
const crypto = require('crypto');
// Create the API endpoint function with a req and res parameter
exports.handler = async function(req, res) {
//export default async function handler(req, res) {
if (!req.body || req.httpMethod !== 'POST') {
return {
status: 405,
headers: {},
body: JSON.stringify({
status: 'Invalid HTTP method',
}),
}
}
const data = JSON.parse(req.body);
// Request for your merchant information so that you can use your email
// to include as the 'from' property to send to the SendGrid API
const merchant = axios.get(`${process.env.CHEC_API_URL}/v1/merchants`, {
headers: {
'X-Authorization': process.env.CHEC_SECRET_KEY,
"Accept": "application/json",
"Content-Type": "application/json",
},
}).then((response) => response.json);
//console.log(data);
// Extract the signature from the registered `orders.create` webhook
const { signature } = data;
delete data.signature;
// Your Chec webhook signing key, from the Chec Dashboard webhooks view
const webHookSigningKey = 'KEJlxz6cIlrWIpsX5jypcMeGl2uh7jJg';
// Verify the signature
const expectedSignature = crypto.createHmac('sha256', webHookSigningKey)
.update(JSON.stringify(data))
.digest('hex');
if (expectedSignature !== signature) {
console.error('Signature mismatched, skipping.')
}
// Verify the age of the request to make sure it isn't more than 5 minutes old.
if (new Date(data.created * 1000) < new Date() - 5 * 60 * 1000) {
console.error('Webhook was sent too long ago, could potentially be fake, ignoring');
}
// Because you will need to list out the order line items, map through the returned line items
// and structure out the data you need to display in the email receipt for your customer
// Note that we are keeping the data structure minimal here
const orderLineItems = data.payload.order.line_items.map((lineItem) => ({
text: lineItem.product_name,
price: lineItem.line_total.formatted_with_symbol,
}));
// Signature is verified, continue to send data to SendGrid
// Create the email object payload to fire off to SendGrid
const emailPayload = {
to: data.payload.customer.email,
from: data.payload.merchant.support_email,
subject: `Thank you for your order ${data.payload.customer.firstname}`,
text: `Your order number is ${data.payload.customer_reference}`,
// SendGrid expects a JSON blog containing the dynamic order data your template will use
// More information below in 'What's next?' on how to configure your dynamic template in SendGrid
// The property key names will depend on what dynamic template you create in SendGrid
dynamic_template_data: {
total: data.payload.order.subtotal.formatted_with_symbol,
items: orderLineItems,
receipt: true,
name: data.payload.billing.name,
address01: data.payload.billing.street,
city: data.payload.billing.town_city,
state: data.payload.billing.county_state,
zip : data.payload.billing.postal_zip_code,
orderId : data.payload.id,
},
// In addition to specifying the dynamic template data, you need to specify the template ID. This comes from your SendGrid dashboard when you create you dynamic template
// https://mc.sendgrid.com/dynamic-templates
template_id: 'd-xxx'
};
/*let sgheaders = {
Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
"Content-Type": "application/json",
};*/
let response = {};
try {
// Call the SendGrid send mail endpoint
response = await sgMailClient.send(emailPayload);
return {
statusCode: 200,
headers: {},
body: JSON.stringify({
status: 'Email sent!',
}),
}
} catch (err) {
console.error('Error from function: ', err)
console.error(err.response.body);
console.log("Payload content: ", emailPayload );
}
// Return the response from the request
//return res.status(200).json(response);
}
If you want to integrate sendgrid using Rest API, I suggest you to use axios.
So in your case,
import axios from 'axios';
...
const merchant = axios.get(`${process.env.CHEC_API_URL}/v1/merchants`, {
headers: {
'X-Authoriza†ion': process.env.CHEC_SECRET_KEY,
},
}).then((response) => response.json);
...