使用 Netlify 函数构建 JAMstack 电子商务商店

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

很多人对 JAMstack 感到困惑。该首字母缩写词代表 JavaScript、API 和标记,但实际上,JAMstack 不必包含所有这三者。定义 JAMstack 的是它无需 Web 服务器即可提供服务。如果您考虑计算的历史,这种类型的抽象并不违反自然规律;相反,这是该行业一直在朝着的方向发展的必然趋势。

因此,如果 JAMstack 从定义上来说往往是静态的,那么它就不能具有动态功能、服务器端事件或使用 JavaScript 框架,对吧?谢天谢地,并非如此。在本教程中,我们将设置一个 JAMstack 电子商务应用程序,并使用 Netlify 函数(它对 AWS Lambda 进行了抽象,在我看来非常棒)添加一些无服务器功能。

我将在后续文章中更直接地展示 Nuxt/Vue 部分的设置方式,但现在我们将重点关注 Stripe 无服务器函数。我将向您展示我如何设置此函数,我们甚至会讨论如何将其连接到其他静态站点生成器,例如 Gatsby。完全披露,我在 Netlify 工作,并且正在使用他们的工具来完成此操作,可以使用其他服务连接到 Stripe。我选择在 Netlify 工作的部分原因是我喜欢他们服务提供的一些不错的抽象。

如果您想自己设置类似的东西,此站点和存储库可以帮助您入门。

搭建我们的应用程序

第一步是设置我们的应用程序。此应用程序使用 Nuxt 构建 Vue 应用程序,但您可以使用您选择的技术栈替换这些命令。

yarn create nuxt-app

hub create

git add -A
git commit -m “initial commit”

git push -u origin master

我正在使用 yarnhub(允许我从命令行创建存储库)和 Nuxt。在继续之前,您可能需要在本地或全局安装这些工具。

通过这几个命令,按照提示操作,我们可以设置一个全新的 Nuxt 项目以及存储库。

如果我们登录 Netlify 并进行身份验证,它会要求我们选择一个存储库。

choose a repo in netlify

我将使用 yarn generate 创建项目。有了它,我可以在 dist 目录中添加 Nuxt 的站点设置并点击部署!设置 CI/CD 并部署站点就这么简单!现在,每次我推送到 master 分支时,不仅会部署,还会为该特定部署提供唯一的链接。太棒了。

使用 Netlify 的基本无服务器函数

所以,这是激动人心的部分,因为这种功能的设置非常快!如果您不熟悉 无服务器,您可以将其视为与您所知和喜爱的 JavaScript 函数相同,但在服务器上执行。无服务器函数是事件驱动的逻辑,其定价非常低(不仅在 Netlify 上,而且在整个行业范围内)并且会随着您的使用情况而扩展。是的,我们必须在这里添加限定词:无服务器仍然使用服务器,但您不再需要负责维护它们。让我们开始吧。

我们的非常基本的函数如下所示。我将其存储在一个名为 functions 的文件夹中,并将其命名为 index.js。您实际上可以根据需要命名文件夹和函数。

// functions/index.js
exports.handler = async (event, context) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Hi there Tacos",
      event
    })
  }
}

我们还需要在项目的根目录下创建一个 netlify.toml 文件,并让它知道在哪里找到该函数,在本例中为“functions”。

// netlify.toml
[build]
  functions = "functions"

如果我们推送到 master 并进入仪表板,您会看到它拾取了该函数!

netlify function in the dashboard

如果您查看上面列出的端点,它存储在这里
https://ecommerce-netlify.netlify.com/.netlify/functions/index

实际上,对于您提供的任何站点,URL 都将遵循此模式
https:/<yoursiteurlhere>/.netlify/functions/<functionname>

当我们访问该端点时,它会提供我们传入的消息,以及我们记录的所有事件数据。

the function event in the browser

我喜欢它只有这么少的步骤!这少量代码为我们的站点提供了无限的强大功能和丰富、动态的功能。

连接 Stripe

Stripe 配对非常有趣,因为它易于使用、功能强大、文档齐全,并且可以很好地与无服务器函数配合使用。我有 其他教程 使用了 Stripe,因为我非常喜欢使用他们的服务。

以下是我们将构建的应用程序的概览。

首先,我们将转到 Stripe 仪表板并获取我们的密钥。对于现在完全感到震惊的任何人,没关系,这些是测试密钥。您也可以使用它们,但如果您自己设置它们,将会学到更多。(只需点击两次,我保证从这里开始很容易理解。)

testing keys in the stripe dashboard

我们将安装一个名为 dotenv 的包,它将帮助我们存储密钥并在本地对其进行测试。然后,我们将 Stripe 密钥存储到 Stripe 变量中。(您可以将其命名为任何内容,但这里我将其命名为 STRIPE_SECRET_KEY,这几乎是行业标准。)

yarn add dotenv
require("dotenv").config()

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY)

在 Netlify 仪表板中,我们将转到“构建和部署”,然后转到“环境”以添加环境变量,其中 STRIPE_SECRET_KEY 是密钥,值将是以 sk 开头的密钥。

我们还将添加一些标头,以便我们不会遇到 CORS 问题。我们将在将要构建的函数中使用这些标头。

const headers = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "Content-Type"
}

因此,现在我们将创建与 Stripe 通信的功能。我们将确保处理我们不期望的 HTTP 方法的情况,以及我们获得我们期望的信息的情况。

您已经可以在此处看到,我们需要通过检查哪些内容来发送给 Stripe 的数据。我们将需要令牌、总金额和**幂等密钥**。

如果您不熟悉幂等密钥,它们是由客户端生成的唯一值,并在连接中断时与请求一起发送到 API。如果服务器收到一个它认为是重复调用的请求,它会忽略该请求并响应成功的状态码。哦,它是一个难以发音的词。

exports.handler = async (event, context) => {
  if (!event.body || event.httpMethod !== "POST") {
    return {
      statusCode: 400,
      headers,
      body: JSON.stringify({
        status: "invalid http method"
      })
    }
  }

  const data = JSON.parse(event.body)

  if (!data.stripeToken || !data.stripeAmt || !data.stripeIdempotency) {
    console.error("Required information is missing.")

    return {
      statusCode: 400,
      headers,
      body: JSON.stringify({
        status: "missing information"
      })
    }
  }

现在,我们将启动 Stripe 支付处理!我们将使用电子邮件和令牌创建 Stripe 客户,进行一些记录,然后创建 Stripe 收费。我们将指定货币、金额、电子邮件、客户 ID,并在处理过程中提供描述。最后,我们将提供幂等密钥(发音为eye-dem-po-ten-see),并记录它已成功。

(虽然此处未显示,但我们还将进行一些错误处理。)

// stripe payment processing begins here
try {
  await stripe.customers
    .create({
      email: data.stripeEmail,
      source: data.stripeToken
    })
    .then(customer => {
      console.log(
        `starting the charges, amt: ${data.stripeAmt}, email: ${data.stripeEmail}`
      )
      return stripe.charges
        .create(
          {
            currency: "usd",
            amount: data.stripeAmt,
            receipt_email: data.stripeEmail,
            customer: customer.id,
            description: "Sample Charge"
          },
          {
            idempotency_key: data.stripeIdempotency
          }
        )
        .then(result => {
          console.log(`Charge created: ${result}`)
        })
    })

将其连接到 Nuxt

如果我们回顾我们的应用程序,您会看到我们在页面内有页面和组件。 Vuex 存储 就像我们应用程序的大脑。它将保存应用程序的状态,这就是与 Stripe 通信的内容。但是,我们仍然需要通过客户端与用户进行通信。我们将在名为 AppCard.vue 的组件中收集信用卡数据,该组件将位于购物车页面上。

首先,我们将安装一个名为 vue-stripe-elements-plus 的包,它为我们提供了一些 Stripe 表单元素,允许我们收集信用卡数据,甚至为我们设置了一种支付方式,允许我们为 Stripe 支付处理创建令牌。我们还将添加一个名为 uuid 的库,它将允许我们生成唯一密钥,我们将将其用于幂等密钥。

yarn add vue-stripe-elements-plus uuid

他们为我们提供的与 vue-stripe-elements-plus 配合使用的默认设置如下所示

<template>
  <div id='app'>
    <h1>Please give us your payment details:</h1>
    <card class='stripe-card'
      :class='{ complete }'
      stripe='pk_test_XXXXXXXXXXXXXXXXXXXXXXXX'
      :options='stripeOptions'
      @change='complete = $event.complete'
    />
    <button class='pay-with-stripe' @click='pay' :disabled='!complete'>Pay with credit card</button>
  </div>
</template>
<script>
import { stripeKey, stripeOptions } from './stripeConfig.json'
import { Card, createToken } from 'vue-stripe-elements-plus'

export default {
  data () {
    return {
      complete: false,
      stripeOptions: {
        // see https://stripe.com/docs/stripe.js#element-options for details
      }
    }
  },

  components: { Card },

  methods: {
    pay () {
      // createToken returns a Promise which resolves in a result object with
      // either a token or an error key.
      // See https://stripe.com/docs/api#tokens for the token object.
      // See https://stripe.com/docs/api#errors for the error object.
      // More general https://stripe.com/docs/stripe.js#stripe-create-token.
      createToken().then(data => console.log(data.token))
    }
  }
}
</script>

所以我们将要这样做。我们将更新表单以存储客户电子邮件,并更新支付方式以将该电子邮件和令牌或错误密钥发送到 Vuex 存储。我们将分派一个操作来执行此操作。

data() {
    return {
      ...
      stripeEmail: ""
    };
  },
  methods: {
    pay() {
      createToken().then(data => {
        const stripeData = { data, stripeEmail: this.stripeEmail };
        this.$store.dispatch("postStripeFunction", stripeData);
      });
    },
 ...

我们分派的那个 postStripeFunction 操作如下所示

// Vuex store
export const actions = {
  async postStripeFunction({ getters, commit }, payload) {
    commit("updateCartUI", "loading")

    try {
      await axios
        .post(
          "https://ecommerce-netlify.netlify.com/.netlify/functions/index",
          {
            stripeEmail: payload.stripeEmail,
            stripeAmt: Math.floor(getters.cartTotal * 100), //it expects the price in cents                        
            stripeToken: "tok_visa", //testing token, later we would use payload.data.token
            stripeIdempotency: uuidv1() //we use this library to create a unique id
          },
          {
            headers: {
              "Content-Type": "application/json"
            }
          }
        )
        .then(res => {
          if (res.status === 200) {
            commit("updateCartUI", "success")
            setTimeout(() => commit("clearCart"), 3000)
            …

在处理过程中,我们将更新 UI 状态为加载中。然后,我们将使用 axios 发布到我们的函数端点(我们在设置函数时在文章前面看到的 URL)。我们将发送电子邮件、金额、令牌以及我们构建函数以期望的唯一密钥。

然后,如果成功,我们将更新 UI 状态以反映这一点。

我要说明的最后一点是,我将 UI 状态存储在字符串中,而不是布尔值中。我通常以“idle”之类的内容开头,在本例中,我还将有“loading”、“success”和“failure”。我不使用布尔状态,因为我很少遇到 UI 状态只有两种状态的情况。当您为此目的使用布尔值时,您往往需要将其分解成越来越多的状态,并且检查所有这些状态会变得越来越复杂。

照此看来,我可以使用清晰易懂的条件语句(如下所示)在购物车页面上反映 UI 中的更改

<section v-if="cartUIStatus === 'idle'">
  <app-cart-display />
</section>

<section v-else-if="cartUIStatus === 'loading'" class="loader">
  <app-loader />
</section>

<section v-else-if="cartUIStatus === 'success'" class="success">
  <h2>Success!</h2>
  <p>Thank you for your purchase. You'll be receiving your items in 4 business days.</p>
  <p>Forgot something?</p>

  <button class="pay-with-stripe">
    <nuxt-link exact to="/">Back to Home</nuxt-link>
  </button>
</section>

<section v-else-if="cartUIStatus === 'failure'">
  <p>Oops, something went wrong. Redirecting you to your cart to try again.</p>
</section>

就是这样!我们已经设置好并运行,可以使用 Stripe 在 Nuxt、Vue 网站上接受付款,并使用一个Netlify 函数,设置起来甚至不复杂!

Gatsby 应用

我们在此示例中使用了 Nuxt,但如果您想使用 React(如 Gatsby)设置相同的功能,有一个插件可以实现。(在 Gatsby 中,一切都是插件。☺️)

您可以使用以下命令安装它

yarn add gatsby-plugin-netlify-functions

…并将插件添加到您的应用程序,如下所示

plugins: [
  {
    resolve: `gatsby-plugin-netlify-functions`,
    options: {
      functionsSrc: `${__dirname}/src/functions`,
      functionsOutput: `${__dirname}/functions`,
    },
  },
]

此演示中使用的无服务器函数是纯 JavaScript,因此它也可以移植到 React 应用程序中。有一个插件可以将 Stripe 脚本添加到您的 Gatsby 应用程序中(同样,一切都是插件)。友情提示:这会将脚本添加到网站上的每个页面。要收集客户端的信用卡信息,您将使用React Stripe Elements,它类似于我们上面使用的 Vue 插件。

只需确保您正在从客户端收集信息,并将函数期望的所有信息都传递过去即可

  • 用户邮箱
  • 总金额(以美分计)
  • 令牌
  • 幂等键

由于入门门槛很低,您可以看到如何使用 JAMstack 应用程序创建真正动态的体验。令人惊奇的是,在没有服务器维护成本的情况下,您可以完成多少工作。Stripe 和Netlify 函数 使在静态应用程序中设置支付处理变得非常流畅!