Netlify函数运行Commerce.js+SendGrid邮件功能报fetch is not defined错误求助
Commerce.js+SendGrid自定义邮件收据Netlify部署故障修复
问题描述
我尝试使用Commerce.js webhook搭配SendGrid制作自定义邮件收据模板,代码部署到Netlify测试站点,核心代码路径为/functions/email.js。确认.env变量配置正确,但始终未收到邮件收据,查看Netlify函数日志得到如下报错:
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
现有配置确认
- 已在
package.json的dependencies中安装node-fetch:
"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" }
- 已按照Netlify函数文档要求,将原本的
export default async function handler(req, res) {改为exports.handler = async function(req, res) {,解决了导出语法错误。 - 自行配置的请求头如下,不确定是否正确:
let headers = { "Accept": "application/json", "Content-Type": "application/json", }; let sgheaders = { Authorization: `Bearer ${process.env.SENDGRID_SECRET}`, "Content-Type": "application/json", };
- 原始代码如下:
// 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 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); }
故障原因
- 核心报错fetch is not defined:Netlify函数运行的Node.js版本未内置fetch API,安装的
node-fetch@3是ES Module版本,不符合Netlify函数默认的CommonJS规范,且代码中没有手动引入fetch依赖,直接调用全局fetch自然报错。 - 其他隐藏问题:
- 请求商户信息的
X-Authorization头拼写错误,混入了特殊字符 - fetch请求未加
await,直接将Promise对象赋值给merchant,无法正确读取商户邮箱 - 使用SendGrid官方SDK时不需要自行配置请求头,SDK已经内置了鉴权逻辑
- 部分返回值格式不符合Netlify函数的要求
- 未引入
crypto、SendGrid SDK等必要依赖,也没有对SendGrid SDK做初始化配置
修复后可运行代码
改用CommonJS兼容的axios替代node-fetch,同时适配Commerce.js的返回数据结构,代码如下:
const axios = require('axios'); const sgMailClient = require("@sendgrid/mail"); sgMailClient.setApiKey(process.env.SENDGRID_API_KEY); // 引入crypto模块 const crypto = require('crypto'); exports.handler = async function(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); const { signature } = data; delete data.signature; const webHookSigningKey = 'KEJlxz6cIlrWIpsX5jypcMeGl2uh7jJg'; // 校验签名 const expectedSignature = crypto.createHmac('sha256', webHookSigningKey) .update(JSON.stringify(data)) .digest('hex'); if (expectedSignature !== signature) { console.error('Signature mismatched, skipping.') } // 校验请求时效,避免伪造请求 if (new Date(data.created * 1000) < new Date() - 5 * 60 * 1000) { console.error('Webhook was sent too long ago, could potentially be fake, ignoring'); } // 处理订单商品列表 const orderLineItems = data.payload.order.line_items.map((lineItem) => ({ text: lineItem.product_name, price: lineItem.line_total.formatted_with_symbol, })); // 组装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}`, 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, }, template_id: 'd-xxx' // 替换为自己的SendGrid模板ID }; let response = {}; try { 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); } }
内容的提问来源于stack exchange,提问作者Fjott
相关产品推荐
相关产品推荐

