Viele Leute sind verwirrt darüber, was JAMstack ist. Das Akronym steht für JavaScript, APIs und Markup, aber JAMstack muss nicht alle drei beinhalten. Was JAMstack definiert, ist, dass es ohne Webserver ausgeliefert wird. Wenn man die Geschichte der Computertechnik betrachtet, ist diese Art von Abstraktion nicht unnatürlich; vielmehr ist es die unvermeidliche Entwicklung, auf die sich die Branche zubewegt.
Also, wenn JAMstack per Definition eher statisch ist, kann es keine dynamische Funktionalität, serverseitige Ereignisse haben oder ein JavaScript-Framework verwenden, richtig? Zum Glück nicht. In diesem Tutorial richten wir eine JAMstack-E-Commerce-App ein und fügen einige serverlose Funktionalitäten mit Netlify Functions hinzu (die AWS Lambda abstrahieren und meiner Meinung nach super dope sind).
Ich werde im Detail zeigen, wie der Nuxt/Vue-Teil eingerichtet wurde, in einem Folgebeitrag, aber für jetzt konzentrieren wir uns auf die Stripe-serverlose Funktion. Ich werde zeigen, wie ich diese eingerichtet habe, und wir werden sogar darüber sprechen, wie man sich mit anderen statischen Website-Generatoren wie Gatsby verbindet. Vollständige Offenlegung, ich arbeite für Netlify und nutze deren Tools dafür, es ist möglich, sich mit Stripe über andere Dienste zu verbinden. Ich habe mich zum Teil für Netlify entschieden, weil ich einige der schönen Abstraktionen genieße, die ihre Dienste bieten.
Diese Seite und das Repository sollten Ihnen den Einstieg erleichtern, wenn Sie etwas Ähnliches selbst einrichten möchten.
App-Gerüst erstellen
Der allererste Schritt ist die Einrichtung unserer App. Diese hier ist mit Nuxt erstellt, um eine Vue-App zu erstellen, aber Sie können diese Befehle durch Ihren bevorzugten Tech-Stack ersetzen.
yarn create nuxt-app
hub create
git add -A
git commit -m “initial commit”
git push -u origin master
Ich verwende yarn, hub (was mir erlaubt, Repos von der Kommandozeile aus zu erstellen) und Nuxt. Möglicherweise müssen Sie diese Tools lokal oder global installieren, bevor Sie fortfahren.
Mit diesen wenigen Befehlen und der Befolgung der Anweisungen können wir ein brandneues Nuxt-Projekt sowie das Repository einrichten.
Wenn wir uns bei Netlify anmelden und authentifizieren, werden wir aufgefordert, ein Repository auszuwählen.

Ich werde yarn generate verwenden, um das Projekt zu erstellen. Damit kann ich die Website-Einstellungen für Nuxt im dist-Verzeichnis hinzufügen und auf "deploy" klicken! Das ist alles, was nötig ist, um CI/CD einzurichten und die Website zu deployen! Jetzt wird bei jedem Push auf den master-Branch nicht nur deployed, sondern ich erhalte auch einen eindeutigen Link für diesen speziellen Deploy. So toll.
Eine grundlegende serverlose Funktion mit Netlify
Hier ist der spannende Teil, denn die Einrichtung für diese Art von Funktionalität ist so schnell! Wenn Sie mit Serverless nicht vertraut sind, können Sie es sich wie dieselben JavaScript-Funktionen vorstellen, die Sie kennen und lieben, aber auf dem Server ausgeführt werden. Serverlose Funktionen sind ereignisgesteuerte Logik und ihre Preise sind extrem niedrig (nicht nur bei Netlify, sondern branchenweit) und skalieren mit Ihrer Nutzung. Und ja, wir müssen hier die Einschränkung hinzufügen: Serverless verwendet immer noch Server, aber das "Babysitten" dieser ist nicht mehr Ihre Aufgabe. Lassen Sie uns beginnen.
Unsere sehr grundlegende Funktion sieht so aus. Ich habe meine in einem Ordner namens `functions` gespeichert und ihn einfach index.js genannt. Sie können den Ordner und die Funktion wirklich nennen, wie Sie möchten.
// functions/index.js
exports.handler = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({
message: "Hi there Tacos",
event
})
}
}
Wir müssen auch eine Datei namens netlify.toml im Stammverzeichnis des Projekts erstellen und sie wissen lassen, in welchem Verzeichnis sie die Funktion finden soll, nämlich "functions".
// netlify.toml
[build]
functions = "functions"
Wenn wir nach master pushen und in das Dashboard gehen, können Sie sehen, wie es die Funktion erkennt!

Wenn Sie sich den oben genannten Endpunkt ansehen, ist er hier gespeichert
https://ecommerce-netlify.netlify.com/.netlify/functions/index
Wirklich, für jede Website, die Sie ihm geben, folgt die URL diesem Muster
https:/<IhreSiteURLHier>/.netlify/functions/<FunktionsName>
Wenn wir diesen Endpunkt aufrufen, erhalten wir die von uns übergebene Nachricht sowie alle Protokollierungsdaten des Ereignisses.

Ich liebe, wie wenige Schritte das sind! Dieses kleine Stück Code gibt uns unendliche Macht und Möglichkeiten für reichhaltige, dynamische Funktionalität auf unseren Websites.
Stripe einbinden
Die Anbindung an Stripe macht extrem viel Spaß, weil es einfach zu bedienen ist, anspruchsvoll, tolle Dokumentation hat und gut mit serverlosen Funktionen funktioniert. Ich habe andere Tutorials, in denen ich Stripe verwendet habe, weil ich seinen Service so sehr genieße.
Hier ist ein Überblick über die App, die wir bauen werden.

Zuerst gehen wir zum Stripe-Dashboard und holen unsere Schlüssel. Für alle, die jetzt total schockiert sind, es ist in Ordnung, das sind Testschlüssel. Sie können sie auch verwenden, aber Sie werden mehr lernen, wenn Sie sie selbst einrichten. (Es sind zwei Klicks und ich verspreche, es ist nicht schwer, von hier aus zu folgen.)

Wir werden ein Paket namens dotenv installieren, das uns hilft, unseren Schlüssel zu speichern und ihn lokal zu testen. Dann speichern wir unseren Stripe-Geheimschlüssel in einer Stripe-Variable. (Sie können ihn beliebig nennen, aber hier habe ich ihn STRIPE_SECRET_KEY genannt, und das ist ziemlich branchenüblich.)
yarn add dotenv
require("dotenv").config()
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY)
Im Netlify-Dashboard gehen wir zu "Build & deploy" und dann zu "Environment", um Umgebungsvariablen hinzuzufügen, wobei STRIPE_SECRET_KEY der Schlüssel ist und der Wert der Schlüssel ist, der mit sk beginnt.

Wir werden auch einige Header hinzufügen, damit wir keine CORS-Probleme bekommen. Diese Header werden wir in der Funktion verwenden, die wir erstellen werden.
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type"
}
Nun erstellen wir die Funktionalität für die Kommunikation mit Stripe. Wir stellen sicher, dass wir die Fälle behandeln, in denen es sich nicht um die erwartete HTTP-Methode handelt, und dass wir auch die erwarteten Informationen erhalten.
Man kann hier schon sehen, welche Daten wir an Stripe senden müssen, anhand dessen, wonach wir suchen. Wir benötigen den Token, den Gesamtbetrag und einen **Idempotenzschlüssel**.
Wenn Sie mit Idempotenzschlüsseln nicht vertraut sind, handelt es sich um eindeutige Werte, die von einem Client generiert und zusammen mit einer Anfrage an eine API gesendet werden, falls die Verbindung unterbrochen wird. Wenn der Server einen Aufruf erhält, der als Duplikat erkannt wird, ignoriert er die Anfrage und antwortet mit einem erfolgreichen Statuscode. Oh, und es ist ein unmögliches Wort zum Aussprechen.
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"
})
}
}
Jetzt starten wir die Stripe-Zahlungsabwicklung! Wir erstellen einen Stripe-Kunden mit der E-Mail und dem Token, protokollieren ein wenig und erstellen dann die Stripe-Zahlung. Wir geben die Währung, den Betrag, die E-Mail, die Kunden-ID an und geben eine Beschreibung hinzu. Schließlich stellen wir den Idempotenzschlüssel (ausgesprochen eye-dem-po-ten-see) zur Verfügung und protokollieren, dass er erfolgreich war.
(Obwohl hier nicht gezeigt, werden wir auch einige Fehlerbehandlungen durchführen.)
// 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}`)
})
})
Mit Nuxt verbinden

Wenn wir uns unsere Anwendung ansehen, sehen wir, dass wir Seiten und Komponenten haben, die sich innerhalb der `pages` befinden. Der Vuex Store ist wie das Gehirn unserer Anwendung. Er speichert den Zustand der App und kommuniziert mit Stripe. Wir müssen jedoch weiterhin über den Client mit unserem Benutzer kommunizieren. Wir sammeln die Kreditkartendaten in einer Komponente namens AppCard.vue, die sich auf der Warenkorbseite befindet.
Zuerst installieren wir ein Paket namens vue-stripe-elements-plus, das uns einige Stripe-Formularelemente zur Verfügung stellt, mit denen wir Kreditkartendaten erfassen können, und uns sogar mit einer pay-Methode einrichtet, die uns die Erstellung von Tokens für die Stripe-Zahlungsabwicklung ermöglicht. Wir fügen auch eine Bibliothek namens uuid hinzu, die uns die Generierung eindeutiger Schlüssel ermöglicht, die wir für den Idempotenzschlüssel verwenden werden.
yarn add vue-stripe-elements-plus uuid
Die Standardeinrichtung, die sie uns zur Verfügung stellen, um mit vue-stripe-elements-plus zu arbeiten, sieht so aus.
<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>
Hier ist also, was wir tun werden. Wir werden das Formular aktualisieren, um die Kunden-E-Mail zu speichern, und die pay-Methode aktualisieren, um diese und den Token oder den Fehlercode an den Vuex Store zu senden. Wir werden eine Aktion versenden, um dies zu tun.
data() {
return {
...
stripeEmail: ""
};
},
methods: {
pay() {
createToken().then(data => {
const stripeData = { data, stripeEmail: this.stripeEmail };
this.$store.dispatch("postStripeFunction", stripeData);
});
},
...
Diese versendete `postStripeFunction`-Aktion sieht so aus.
// 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)
…
Wir werden den UI-Status auf "loading" aktualisieren, während wir verarbeiten. Dann werden wir axios verwenden, um an unseren Funktionsendpunkt zu posten (die URL, die Sie weiter oben im Beitrag gesehen haben, als wir unsere Funktion eingerichtet haben). Wir werden die E-Mail, den Betrag, den Token und den eindeutigen Schlüssel senden, den die Funktion erwartet.
Wenn es erfolgreich war, werden wir den UI-Status entsprechend aktualisieren.
Ein letzter Hinweis, den ich geben kann, ist, dass ich den UI-Status in einem String speichere, anstatt in einem Boolean. Ich beginne ihn normalerweise mit etwas wie "idle" und in diesem Fall werde ich auch "loading", "success" und "failure" haben. Ich verwende keine Booleschen Zustände, weil ich selten eine Situation angetroffen habe, in der der UI-Status nur zwei Zustände hat. Wenn Sie Boolesche Werte für diesen Zweck verwenden, neigen Sie dazu, sie in immer mehr Zustände aufzuteilen, und die Überprüfung aller davon kann immer komplizierter werden.
Wie es jetzt steht, kann ich Änderungen in der Benutzeroberfläche auf der Warenkorbseite mit lesbaren Bedingungen wie folgt widerspiegeln.
<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>
Und da haben Sie es! Wir sind bereit, Zahlungen mit Stripe auf einer Nuxt, Vue-Website mit einer Netlify-Funktion zu akzeptieren, und es war nicht einmal kompliziert einzurichten!
Gatsby-Anwendungen
Wir haben in diesem Fall Nuxt verwendet, aber wenn Sie die gleiche Art von Funktionalität mit etwas wie React wie Gatsby einrichten möchten, gibt es ein Plugin dafür. (Bei Gatsby ist alles ein Plugin. ☺️)
Sie würden es mit diesem Befehl installieren.
yarn add gatsby-plugin-netlify-functions
…und das Plugin wie folgt zu Ihrer Anwendung hinzufügen.
plugins: [
{
resolve: `gatsby-plugin-netlify-functions`,
options: {
functionsSrc: `${__dirname}/src/functions`,
functionsOutput: `${__dirname}/functions`,
},
},
]
Die in dieser Demo verwendete serverlose Funktion ist reines JavaScript, daher ist sie auch auf React-Anwendungen portierbar. Es gibt ein Plugin zum Hinzufügen des Stripe-Skripts zu Ihrer Gatsby-App (nochmal, alles ist ein Plugin). Zur Warnung: Dies fügt das Skript zu jeder Seite der Website hinzu. Um die Kreditkarteninformationen auf dem Client zu erfassen, würden Sie React Stripe Elements verwenden, was dem von uns oben verwendeten Vue-Element ähnelt.
Stellen Sie nur sicher, dass Sie vom Client erfassen und alle Informationen übergeben, die die Funktion erwartet.
- Die E-Mail des Benutzers.
- Der Gesamtbetrag in Cent.
- Der Token.
- Der Idempotenzschlüssel.
Mit solch einer geringen Einstiegshürde können Sie sehen, wie Sie wirklich dynamische Erlebnisse mit JAMstack-Anwendungen schaffen können. Es ist erstaunlich, wie viel Sie ohne Wartungskosten für Server erreichen können. Stripe und Netlify Functions machen die Einrichtung der Zahlungsabwicklung in einer statischen Anwendung zu einer reibungslosen Entwicklererfahrung!
Ich liebe Artikel wie diese, Netlify ist wirklich erstaunlich!
Eine kleine Sache: Ich denke, es wäre fair, irgendwo anzugeben, dass Sarah Drasner (der ich sehr gerne hier und auf Twitter folge!) für Netlify arbeitet? Es soll nichts von dem Artikel nehmen, nur ein Vorschlag, transparenter zu sein :)
Kleiner Tippfehler: Sie sagen, Sie sollen etwas in eine "Stripe-Variable" statt in eine "Netlify-Variable" speichern.
"Ohne einen Webserver" ist falsch. Sie geben dies erst zur Hälfte zu. Wenn eine Seite über HTTP(S) ausgeliefert wird, dann gibt es einen Webserver. In diesem Fall ist der Server eine JS-App anstelle von nginx oder Apache. Und JA, es ist immer noch Ihre Aufgabe, ihn zu "babysitten".
Was "serverless" angeht, ist es normalerweise nur ein schicker Ausdruck dafür, dass die Daten Ihrer Benutzer auf Drittanbieterdiensten gespeichert werden - was erhebliche Datenschutzimplikationen hat, über die Entwickler gründlich nachdenken sollten (ähnlich wie CDS Datenschutz- und Sicherheitsrisiken bergen).
Ihr Ton ist dort ziemlich aggressiv, falls Sie das nicht selbst bemerken.
Verwandt: https://css-tricks.de/psa-yes-serverless-still-involves-servers/
Nun, das hat mich inspiriert, einen schnellen E-Commerce-Storefront auf Netlify zu erstellen, der von Shopifys Backend unterstützt wird.
Ich arbeite bereits an einem komplexeren Projekt in diesem Bereich, aber für diejenigen mit einem knappen Budget – dies ist definitiv ein günstiger Einstieg in einen gut funktionierenden Shop.
Guter Punkt! Das mache ich normalerweise und habe es hier vergessen, definitiv nicht meine Absicht. Ich werde heute einige Änderungen vornehmen, danke!
Toller Artikel! Dein Vortrag zu diesem Thema auf der JAMstack Conf in London war auch sehr interessant!
Haben Sie sich schon überlegt, wie Sie sicherstellen können, dass Preise während des Checkout-Prozesses nicht geändert werden können? Ich denke, Stripe ist super sicher, aber durch das reine Übergeben eines Store-Wertes an die Netlify-Funktion könnte dieser doch geändert werden, oder?
Ja, alternativ können Sie IDs und Mengen an die Netlify-Funktion senden und den Preis für jeden Artikel abfragen, dann die Summe berechnen und diese zu dem Wert in "data.stripeAmt" hinzufügen.
Danke Sarah, danke für diesen tollen Artikel!
Ich mache mir Sorgen um die Sicherheit bei diesem Ansatz. Ich habe diese Zeile ("this.$nuxt.$store.state.cart[0].price = 1") in meine Chrome-Entwicklerkonsole eingegeben und bemerkt, dass die Daten mit dem gefälschten Preis an die Funktion gesendet wurden. Gibt es eine Möglichkeit, dies von der Client-Seite aus zu vermeiden?
Meine Lösung war, nur Produkt-IDs an die Funktion zu senden und dann die Gesamtsumme von der Funktion (serverseitig) zu ermitteln.
Hallo Sarah, toller Artikel – danke! Darf ich fragen, warum Sie Nuxt statt Gridsome gewählt haben?
Ich habe darüber nachgedacht, einen Online-Shop auf Basis von Gridsome aufzubauen, und würde mich sehr freuen, Ihre Gedanken dazu zu hören.