为什么我收到 fetch is not defined 错误?

Why I am getting the fetch is not defined error?

我已尝试使用commercejs webhooks通过SendGrid创建自定义电子邮件收据模板,方法是this tutorial. I have uploaded this github repository this test Netlify site。主要代码是 /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 看起来像这样:

  "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"
  },

因此我很困惑为什么我会收到 fetch is not defined 错误。另外,我也对如何正确实现 headers 感到困惑,因为教程没有具体说明如何实现。所以我只是像这样添加了 headers,不知道这是不是这样做的方法:

let headers = {
    "Accept": "application/json",
    "Content-Type": "application/json",
};

let sgheaders = {
    Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
    "Content-Type": "application/json",
}; 

在当前上传到我的 netlify 站点的代码中,我必须更改 export default async function handler(req, res) {

exports.handler = async function(req, res) { 根据 Netlify 函数文档 Netlify 函数文档。 (因为 "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);
}

需要一些关于如何让这段代码实际工作的建议,教程似乎还没有完成,或者我可能误解了一些缩减的细节。

更新(下面的工作代码) 必须使用 axios 而不是 node.fetch(感谢@hotcakedev)wehn deplodey on netlify。还对代码进行了其他更改,以使其与 commerce.js 一起使用(请参阅 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);
}

此处为 Twilio SendGrid 开发人员布道师。

您已将node-fetch安装到项目中,但教程中没有要求将库添加到函数中才能使用。所以你需要 require node-fetch.

本教程也未能要求 SendGrid 库和设置 API 键。您应该在 Netlify 的环境中设置 SendGrid API 键,名称类似于 SENDGRID_API_KEY。然后将以下内容添加到函数的顶部:

const fetch = require("node-fetch");
const sgMailClient = require("@sendgrid/mail");
sgMailClient.setApiKey(process.env.SENDGRID_API_KEY);

至于您询问的 headers,它们是您的函数 return 响应传入 HTTP 请求的响应 headers。你 return 取决于你如何调用这个函数,但是你 不应该 return 你的 SendGrid API 键入 headers .

当您正在使用该功能时,我建议将 return headers 设置为空 object,直到您确定要设置的 Content-Type (响应 body 是“电子邮件已发送!”现在,这将是 text/plain 但这在前端并不是非常有用)以及您可能需要或可能不需要的其他 headers。

如果你想使用 Rest API 集成 sendgrid,我建议你使用 axios

所以在你的情况下,

    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);
    ...