很多人对 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
我正在使用 yarn、hub(允许我从命令行创建存储库)和 Nuxt。在继续之前,您可能需要在本地或全局安装这些工具。
通过这几个命令,按照提示操作,我们可以设置一个全新的 Nuxt 项目以及存储库。
如果我们登录 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
并进入仪表板,您会看到它拾取了该函数!

如果您查看上面列出的端点,它存储在这里
https://ecommerce-netlify.netlify.com/.netlify/functions/index
实际上,对于您提供的任何站点,URL 都将遵循此模式
https:/<yoursiteurlhere>/.netlify/functions/<functionname>
当我们访问该端点时,它会提供我们传入的消息,以及我们记录的所有事件数据。

我喜欢它只有这么少的步骤!这少量代码为我们的站点提供了无限的强大功能和丰富、动态的功能。
连接 Stripe
与 Stripe 配对非常有趣,因为它易于使用、功能强大、文档齐全,并且可以很好地与无服务器函数配合使用。我有 其他教程 使用了 Stripe,因为我非常喜欢使用他们的服务。
以下是我们将构建的应用程序的概览。

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

我们将安装一个名为 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 函数 使在静态应用程序中设置支付处理变得非常流畅!
喜欢这样的文章,Netlify 确实很棒!
只有一点:我认为在某个地方披露 Sarah Drasner(我真的很喜欢在这里和 Twitter 上关注她!)在 Netlify 工作可能比较公平?这并不是要贬低文章,只是一个建议,希望更透明一点 :)
一个小小的错别字:您说将某个内容存储到“Stripe 变量”中,而不是“Netlify 变量”。
“无需 Web 服务器”是不正确的。您只在中途部分承认了这一点。如果页面通过 HTTP(S) 提供服务,那么就存在 Web 服务器。在这种情况下,服务器是 JS 应用程序,而不是 nginx 或 Apache。并且,是的,您仍然需要“照看”它。
就“无服务器”而言,它通常只是一种花哨的说法,表示您的用户数据存储在第三方服务上——这对开发者来说有巨大的隐私影响,应该仔细考虑(就像 CDS 带来隐私和安全风险一样)。
您的语气有点咄咄逼人,以防您自己没有注意到。
相关:https://css-tricks.cn/psa-yes-serverless-still-involves-servers/
好吧,这启发我在 Netlify 上快速构建了一个电子商务店面,由 Shopify 的后端支持。
已经在处理这个领域更复杂的项目,但对于预算有限的人来说——这绝对是一个启动高性能商店的廉价方法。
说得对!我通常会这样做,但在这里忘记了,绝对不是我的本意。我会今天进行一些编辑,谢谢!
很棒的文章!您在伦敦的 JAMstack 大会上关于此主题的演讲也非常有趣!
您是否考虑过如何确保在结账过程中无法更改价格?我认为 Stripe 非常安全,但仍然可以通过将商店值传递给 Netlify 函数来更改它,对吗?
是的,或者您可以将 ID 和数量发送到 Netlify 函数,并查询每个商品的价格,然后计算总价,并将其附加到“data.stripeAmt”中的该值。
谢谢 Sarah,感谢您撰写这篇精彩的文章!
我担心这种方法的安全性,我在 Chrome 开发者控制台中写了这行代码(“this.$nuxt.$store.state.cart[0].price = 1”),我注意到数据是用伪造的价格发送到函数的。有没有办法从客户端避免这种情况?
我的解决方案是只将产品 ID 发送到函数,然后从函数(服务器端)计算总价。
嘿 Sarah,很棒的文章——谢谢!请问您为什么选择 Nuxt 而不是 Gridsome?
我正在考虑在 Gridsome 的基础上构建一个在线商店,非常乐意听听您对此的看法。