Construyamos una tienda de comercio electrónico JAMstack con funciones de Netlify | Programar Plus

Mucha gente está confundida acerca de qué es JAMstack. El acrónimo significa JavaScript, API y Markup, pero en verdad, JAMstack no tiene que incluir los tres. Lo que define a JAMstack es que se sirve sin servidores web. Si considera la historia de la informática, este tipo de abstracción no es poco natural; más bien es la progresión inevitable hacia la que se ha estado moviendo esta industria.

Entonces, si JAMstack tiende a ser estático por definición, no puede tener una funcionalidad dinámica, eventos del lado del servidor o usar un marco de JavaScript, ¿verdad? Afortunadamente, no es así. En este tutorial, configuraremos una aplicación de comercio electrónico JAMstack y agregaremos algunas funciones sin servidor con Netlify Functions (que resumen AWS Lambda y, en mi opinión, son geniales).

Mostraré más directamente cómo se configuró la parte de Nuxt/Vue en una publicación de seguimiento, pero por ahora nos centraremos en la función sin servidor de Stripe. Le mostraré cómo configuro este, e incluso hablaremos sobre cómo conectarse a otros generadores de sitios estáticos como Gatsby. Divulgación completa, trabajo para Netlify y estoy usando sus herramientas para esto, es posible conectarse a Stripe con otros servicios. Elegí trabajar para Netlify en parte porque disfruto de algunas de las agradables abstracciones que ofrecen sus servicios.

Este sitio y el repositorio deberían ayudarlo a comenzar si desea configurar algo como esto usted mismo:

Sitio de demostración

Repositorio de GitHub

Scaffold nuestra aplicación

El primer paso es configurar nuestra aplicación. Este está construido con Nuxt para crear una aplicación Vue, pero puede reemplazar estos comandos con la pila de tecnología que elija:

yarn create nuxt-app

hub create

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

git push -u origin master

Estoy usando yarn, hub (que me permite crear repositorios desde la línea de comandos) y Nuxt. Es posible que deba instalar estas herramientas local o globalmente antes de continuar.

Con estos pocos comandos, siguiendo las indicaciones, podemos configurar un proyecto Nuxt completamente nuevo, así como el repositorio.

Si iniciamos sesión en Netlify y nos autenticamos, nos pedirá que elijamos un repositorio:

elegir un repositorio en netlify

voy a usar yarn generate para crear el proyecto. Con eso, puedo agregar la configuración del sitio para Nuxt en el dist directorio y presiona feploy! ¡Eso es todo lo que se necesita para configurar CI/CD e implementar el sitio! Ahora, cada vez que empujo hacia el master rama, no solo implementaré, sino que también se me dará un enlace único para esa implementación en particular. Tan genial.

Una función básica sin servidor con Netlify

Así que aquí está la parte emocionante, ¡porque la configuración de este tipo de funcionalidad es muy rápida! Si no está familiarizado con Serverless, puede considerarlo como las mismas funciones de JavaScript que conoce y ama, pero ejecutadas en el servidor. Las funciones sin servidor son una lógica basada en eventos y su precio es extremadamente bajo (no solo en Netlify, sino en toda la industria) y escala con su uso. Y sí, tenemos que agregar el calificador aquí: serverless todavía usa servidores, pero cuidarlos ya no es su trabajo. Empecemos.

Nuestra función muy básica se ve así. Almacené el mío en una carpeta llamada funciones, y simplemente lo llamé index.js. Realmente puede llamar a la carpeta y hacer lo que quiera.

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

También tendremos que crear un netlify.toml archivo en la raíz del proyecto y hágale saber en qué directorio encontrar la función, que en este caso, es “funciones”.

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

Si empujamos a master y vaya al tablero, ¡puede ver cómo selecciona la función!

función netlify en el tablero

Si observa el punto final mencionado anteriormente, está almacenado aquí:
https://ecommerce-netlify.netlify.com/.netlify/functions/index

Realmente, para cualquier sitio que le des, la URL seguirá este patrón:
https:/<yoursiteurlhere>/.netlify/functions/<functionname>

Cuando llegamos a ese punto final, nos proporciona el mensaje que pasamos, así como también todos los datos de eventos que registramos:

el evento de función en el navegador

¡Me encanta lo pocos pasos que son! Este pequeño fragmento de código nos brinda poder y capacidades infinitos para una funcionalidad rica y dinámica en nuestros sitios.

conectar la raya

El emparejamiento con Stripe es extremadamente divertido porque es fácil de usar, sofisticado, tiene excelentes documentos y funciona bien con funciones sin servidor. Tengo otros tutoriales en los que usé Stripe porque disfruto mucho usando su servicio.

Aquí hay una vista de pájaro de la aplicación que construiremos:

Primero iremos al tablero de Stripe y obtendremos nuestras claves. Para cualquiera que esté totalmente escandalizado en este momento, está bien, estas son claves de prueba. También puede usarlos, pero aprenderá más si los configura por su cuenta. (Son dos clics y prometo que no es difícil seguirlo desde aquí).

prueba de teclas en el tablero de instrumentos de la raya

Instalaremos un paquete llamado dotenv que nos ayudará a almacenar nuestra clave y probarla localmente. Luego, almacenaremos nuestra clave secreta de Stripe en una variable de Stripe. (Puedes llamarlo como quieras, pero aquí lo he llamado STRIPE_SECRET_KEY, y eso es más o menos estándar de la industria).

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

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

En el tablero de Netlify, iremos a “Crear e implementar”, luego a “Entorno” para agregar las variables de entorno, donde el STRIPE_SECRET_KEY es clave, y el valor será la clave que comienza con sk.

También agregaremos algunos encabezados para que no nos encontremos con problemas de CORS. Usaremos estos encabezados a lo largo de la función que vamos a construir.

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

Entonces, ahora crearemos la funcionalidad para hablar con Stripe. Nos aseguraremos de manejar los casos en los que no es el método HTTP lo que esperamos, y también de que estamos obteniendo la información que esperamos.

Ya puede ver aquí qué datos necesitaremos enviar a Stripe según lo que verifiquemos. Necesitaremos el token, la cantidad total y un clave de idempotencia.

Si no está familiarizado con las claves de idempotencia, son valores únicos generados por un cliente y enviados a una API junto con una solicitud en caso de que se interrumpa la conexión. Si el servidor recibe una llamada que se da cuenta de que es un duplicado, ignora la solicitud y responde con un código de estado exitoso. Ah, y es una palabra imposible de pronunciar.

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

¡Ahora, iniciaremos el procesamiento de pagos de Stripe! Crearemos un cliente de Stripe usando el correo electrónico y el token, haremos un pequeño registro y luego crearemos el cargo de Stripe. Especificaremos la moneda, el monto, el correo electrónico, la identificación del cliente y le daremos una descripción mientras estamos en eso. Finalmente, proporcionaremos la clave de idempotencia (pronunciada eye-dem-po-ten-see), y registre que fue exitoso.

(Si bien no se muestra aquí, también manejaremos algunos errores).

// 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}`)
        })
    })

Conéctelo a Nuxt

Si miramos hacia atrás en nuestra aplicación, puede ver que tenemos páginas y componentes que viven dentro de las páginas. La tienda Vuex es como el cerebro de nuestra aplicación. Mantendrá el estado de la aplicación, y eso es lo que se comunicará con Stripe. Sin embargo, aún necesitamos comunicarnos con nuestro usuario a través del cliente. Recopilaremos los datos de la tarjeta de crédito en un componente llamado AppCard.vue que vivirá en la página del carrito.

Primero, instalaremos un paquete llamado vue-stripe-elements-plus, que nos brinda algunos elementos de formulario de Stripe que nos permiten recopilar datos de tarjetas de crédito e incluso nos configura un método de pago que nos permite crear tokens para stripe. procesando pago. También agregaremos una biblioteca llamada uuid que nos permitirá generar claves únicas, que usaremos para la clave de idempotencia.

yarn add vue-stripe-elements-plus uuid

La configuración predeterminada que nos dan para trabajar con vue-stripe-elements-plus se ve así:

<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>

Así que esto es lo que vamos a hacer. Actualizaremos el formulario para almacenar el correo electrónico del cliente y actualizaremos el método de pago para enviarlo y el token o la clave de error a la tienda Vuex. Enviaremos una acción para hacerlo.

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

Esa acción postStripeFunction que enviamos se ve así:

// 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)
            …

Vamos a actualizar el estado de la interfaz de usuario para que se cargue mientras procesamos. Luego, usaremos axios para publicar en el punto final de nuestra función (la URL que vio anteriormente en la publicación cuando configuramos nuestra función). Enviaremos el correo electrónico, el amt, el token y la clave única que esperamos que haya creado la función.

Luego, si tuvo éxito, actualizaremos el estado de la interfaz de usuario para reflejarlo.

Una última nota que daré es que almaceno el estado de la interfaz de usuario en una cadena, en lugar de un booleano. Por lo general, comienzo con algo como “inactivo” y, en este caso, también tendré “cargando”, “éxito” y “fracaso”. No uso estados booleanos porque rara vez me he encontrado con una situación en la que el estado de la interfaz de usuario solo tenga dos estados. Cuando trabaja con valores booleanos para este propósito, tiende a necesitar dividirlo en más y más estados, y verificarlos todos puede volverse cada vez más complicado.

Tal como está, puedo reflejar los cambios en la interfaz de usuario en la página del carrito con condicionales legibles, como este:

<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="https://css-tricks.com/">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>

¡Y ahí lo tienes! Estamos listos y funcionando para aceptar pagos con stripe en un sitio de Nuxt, Vue con una función de Netlify, ¡y ni siquiera fue tan complicado de configurar!

Aplicaciones Gatsby

Usamos Nuxt en este caso, pero si desea configurar el mismo tipo de funcionalidad con algo que usa React como Gatsby, hay un complemento para eso. (Todo es complemento en Gatsby. ☺️)

Lo instalarías con este comando:

yarn add gatsby-plugin-netlify-functions

… y agregue el complemento a su aplicación de esta manera:

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

La función sin servidor utilizada en esta demostración es JavaScript, por lo que también es portátil para las aplicaciones React. Hay un complemento para agregar el script de Stripe a su aplicación Gatsby (nuevamente, todo es un complemento). Advertencia justa: esto agrega el script a cada página del sitio. Para recopilar la información de la tarjeta de crédito del cliente, usaría React Stripe Elements, que es similar al Vue que usamos anteriormente.

Solo asegúrese de recopilar del cliente y pasar toda la información que espera la función:

  • El correo electrónico del usuario
  • La cantidad total, en centavos
  • la ficha
  • La clave de la idempotencia

Sitio de demostración

Repositorio de GitHub

Con una barrera de entrada tan baja, puede ver cómo puede crear experiencias realmente dinámicas con las aplicaciones JAMstack. Es increíble todo lo que puede lograr sin los costos de mantenimiento de los servidores. ¡Las funciones de Stripe y Netlify hacen que configurar el procesamiento de pagos en una aplicación estática sea una experiencia de desarrollador tan fluida!

(Visited 2 times, 1 visits today)