featured image

A principios de junio publicamos la web de Altenwald para la venta de libros técnicos en castellano y aprovechando la salida del segundo volumen de Erlang/OTP. Esta web ha sido creada desde cero en Phoenix Framework hasta conseguir tener un sitio ecommerce completamente funcional, ¿quieres saber cómo se hizo?

En principio la venta de libros estaba centrada en erlang-otp.es. Esta web en principio usaba los botones de compra de PayPal y era HTML estático completamente con los siguientes complementos:

  • AddThis: para compartir la página en redes sociales.
  • Disqus: para los comentarios.
  • PayPal: para los botones de compra.
  • Mailchimp: para el boletín de noticias.
  • Google Analytics: para ver el flujo de visitas.
  • Sellfy: para gestionar las ventas de libros digitales.

Al no tener el control completo del proceso de compra no podía introducir ofertas, ni más flexibilidad a la hora de realizar las compras. Eso me llevó a realizar los primeros cambios a la web de erlang-otp.es en mayo para permitir comprar productos de otra forma diferente. En ese momento cambié el código estático por código dinámico.

El stack elegido...

El backend que elegí fue Phoenix Framework. Usa el lenguaje Elixir y la plataforma Erlang. Me gustó ver que Elixir cuenta a día de hoy con una gran cantidad de paquetes disponibles a través de Hex y es muy fácil crear nuevos paquetes y agregar dependencias a través del fichero mix.exs.

No espero tener un gran tráfico de primeras en la web pero está bien suponer que pueda haberlo en un futuro. Como base de datos tengo PostgreSQL configurado en multi-master gracias a Postgres-BDR (hablaré de ello en otro artículo).

Para frontend no me compliqué y decidí emplear la misma plantilla que estaba usando en la web del libro. Sé que tengo librerías desactualizadas y trabajaré en ello en futuras versiones.

Lo único novedoso entre la comunicación entre frontend y backend es la agregación de websockets. Si navegas un poco por la web, al agregar elementos al carro desde las páginas de los libros o desde la página principal, verás que la página no se recarga y la adición del elemento al carro se hace a través de una comunicación directa con el servidor a través de un websocket.

En un futuro ese mismo canal puede emplearse para chat y cambiar aún más la web sin necesidad de tener que hacer cargas completas de la misma.

¿Qué funcionalidad implementamos?

En principio el carro de la compra. Ese era el objetivo. Permitir realizar una orden donde agregar elementos. Cada elemento pueda agregarse una o varias veces (cantidad) y el sistema presente en una pantalla el contenido de la orden.

Por otro lado. Ofertas. Era algo que PayPal no permitía (o lo hacía muy difícil). Implementé un sistema de ofertas flexible basado en tres tipos de disparadores:

  • Combo: a través de una relación N:N con los formatos de los libros, si incluyes todos y cada uno de ellos se aplica la oferta.
  • Código: especificando un código válido.
  • Tiempo: hasta un tiempo limitado la oferta es válida y se aplica automáticamente.

Hay otras reglas que en caso de estar disponibles se aplican automáticamente también:

  • Aplica a Formatos: solo se aplica la oferta a los formatos de libros incluídos dentro de la oferta. Es otra relación N:N con esos formatos sobre los que se aplica la oferta.
  • Expiración: sería como combinar el tipo Tiempo con cualquiera de los otros dos.

También especificamos tres tipos de descuentos a ser aplicados:

  • Porcentaje: se descuenta un porcentaje específico de los formatos que se incluyan dentro de la oferta.
  • Dinero: se descuenta una cantidad de dinero fija de cada uno de los formatos incluídos en la oferta.
  • Transporte: se descuenta un porcentaje del transporte.

En la orden por lo tanto podemos agregar todos los elementos que queramos. Al agregar un elemento ya incluído este elemento sube en cantidad (no aparece dos veces).

En la página del carro podemos ver un cuadro para agregar códigos de descuento, además del formulario para proceder con el pago.

El pago se ha pensado para incluir tantos modos de pago como sea posible. Para no atar al sistema a PayPal. En principio para realizar la prueba implementamos el pago por PayPal y pago por transferencia. Obviamente el segundo pago no es posible a día de hoy de implementar de forma automatizada debido a la problemática que ofrecen los bancos españoles con respecto a la automatización. Pero este modelo abre las puertas para incluir en futuras versiones otros medios como Skrill o Stripe entre otros.

Por último. Una interfaz de administración que permita revisar el estado de las órdenes, cambiar información de los libros, las ofertas, etc. Debido a los cambios en las versiones de Phoenix solo encontré un sistema que permitía realizar esto. No da la suficiente automatización pero es eficaz: Torch.

Las integraciones

La idea del sistema es que automatice lo máximo posible. Si un libro se vende y es versión digital, no tenga la necesidad de realizar ningún proceso manual. El comprador debe poder acceder a su compra de forma inmediata.

Las integraciones que hice fueron a través de Tesla, una librería de Elixir que permite crear clientes de API en muy, pero muy, muy pocas líneas de código:

defmodule Altenwaldbooks.Api.Mailchimp do
  use Tesla

  plug Tesla.Middleware.BaseUrl, "https://#{cfg(:dc)}.api.mailchimp.com"
  plug Tesla.Middleware.BasicAuth, username: "anystring",
                                   password: cfg(:api_key)
  plug Tesla.Middleware.Headers, [{"content-type", "application/json"}]
  plug Tesla.Middleware.JSON

  defp cfg(:api_key) do
    Application.get_env(:altenwaldbooks, :mailchimp_api_key, "")
  end
  defp cfg(:dc) do
    cfg(:api_key)
    |> String.split("-")
    |> Enum.at(1)
  end

  def list_lists(query \\ []) do
    get("/3.0/lists", query: query)
  end

  def list_members(list_id, query \\ []) do
    get("/3.0/lists/#{list_id}/members", query: query)
  end

  def add_member(list_id, data) do
    post("/3.0/lists/#{list_id}/members", data)
  end

end

Este código presenta la implementación de tres funciones de API: listar las listas, listar miembros y agregar miembros. Las funciones conectadas (plug) hacen todo el trabajo de agregar la autenticación y otros procesos como traducir las estructuras hacia y desde JSON.

Por lo tanto cree ficheros como estos para gestionar:

  • PayPal: para integrar los pagos directamente. Además me aseguro que en PayPal solo se pueda pagar. No se permiten cambios de dirección, por ejemplo.
  • Mailchimp: para agregar a los compradores a las listas de erratas o preventa. Según el caso.
  • FacturaDirecta: para crear automáticamente una factura simplificada de la compra y agregar la referencia del pago de PayPal o dejar la factura en borrador a espera de recibir la transferencia.
  • FreeGeoIP: para obtener información del país del usuario y así ayudarle a seleccionar en el combo de países el suyo. Teniendo en cuenta la cantidad de países que hay es una gran ayuda que se auto-seleccione.
  • Slack: para notificar cada pago y semanalmente enviar una estadística de ventas.

Con esto, en cada compra se realiza una cantidad importante de acciones y requiere de menos supervisión por mi parte.

Otras dependencias interesantes...

Otro de los problemas que tuve es la necesidad de escribir los textos legales. No me gusta escribir directamente en HTML. Busqué un poco y encontré una librería que me permitía escribirlos directamente en Markdown.

Entonces, tenemos instaladas...

  • Tesla: como dije antes para crear los clientes de API.
  • Money: para gestionar la presentación y cálculo de precios de forma simplificada y precisa.
  • HashIds: para emplear IDs más uniformes (uso entre 6 y 8 caracteres) para los códigos de descarga.
  • Countries: para rellenar el combo de países.
  • Cronex: para disparar eventos de tiempo y ejecutar tareas como el envío de estadísticas a Slack o limpiar los pedidos no realizados.
  • Phoenix Markdown: para escribir páginas en Markdown en lugar de HTML.
  • Torch: para generar las páginas de administración.
  • Basic Auth: para la autenticación de la sección de administración.
  • Sitemap: para generar el fichero sitemap.xml.
  • Wallaby: para generar tests funcionales. Esto hablaré en otro artículo, me resultó muy interesante y es bastante largo de contar.
  • Coverex: para generar los resultados de la covertura de los tests.

Además de las dependencias típicas de Phoenix y PostgreSQL.

¿Qué más?

Esta es una primera versión de la web de altenwald books. Espero poder seguir trabajando en ella y abordar temas como los usuarios, agregar funcionalidades de compras cruzadas, sugerencias, cambiar de disqus a un sistema de comentarios propio integrado en el sitio, hacer muchos más tests funcionales y mucho más.

Esto han sido solo 3 semanas de desarrollo y no a tiempo completo.

¿Qué te ha parecido? ¿Te atreverías a probar Phoenix Framework? ¿Qué funcionalidades de ecommerce consideras más necesarias? ¡Déjanos tu comentario!