#288 Billing with Stripe
- Download:
- source codeProject Files in Zip (101 KB)
- mp4Full Size H.264 Video (45.3 MB)
- m4vSmaller H.264 Video (21.1 MB)
- webmFull Size VP8 Video (22.9 MB)
- ogvFull Size Theora Video (52.7 MB)
Si alguna vez nos hace falta procesar pagos mediante tarjeta de crédito en nuestras aplicaciones Rails deberíamos echarle un vistazo a Stripe. Stripe es una pasarela de pago muy fácil de configurar y amistosa con el desarrollador. Sólo cobra comisiones por transacción y además son muy razonables. No hay tarifas fijas mensuales u otros costes ocultos (y con me llevo comisión por decirlo aquí).
Por el momento Stripe sólo está disponible en los Estados Unidos por lo que si queremos usarlo en nuestras aplicaciones necesitaremos una cuenta en un banco norteamericano aunque pronto debería estar disponible para cuentas internacionales. Esto no quiere decir que no podamos facturar a usuarios de todas partes del mundo, sólo quiere decir que el vendedor debe residir en los Estados Unidos.
Configuración de Stripe en una aplicación Rails
En este episodio vamos a ver cómo utilizar Stripe para configurar pagos recurrentes en una aplicación Rails. La aplicación que vamos a usar es un sitio que vende Besos de Llamas. ¿Por qué? Porque Ryan Bates creció en una granja de llamas y se perdió la oportunidad de vender besos de llamas, así que está tratando de recuperar el tiempo perdido.
Ya tenemos escrita la mayor parte de la aplicación. Hay cuatro planes diferentes, y los usuarios pueden suscribirse a cualquiera de ellos. Tan sólo hay un campo de correo en el formulario de alta, y cuando enviemos dicho campo se creará una nueva suscripción. Tendremos que añadir algunos campos más para poder guardar los detalles de la tarjeta de crédito.
Es muy fácil empezar a usar Stripe. Si visitamos stripe.com y hacemos clic en el enlace “Get Started with Stripe” iremos a una página donde podemos empezar a procesar transacciones de prueba, lo que también podremos hacer desde la línea de órdenes utilizando curl
. Si copiamos el código de ejemplo que aparece en la página de introducción a Stripe y lo ejecutamos en una ventana de terminal deberíamos ver devuelto cierto contenido en JSON.
$ curl https://api.stripe.com/v1/charges \ > -u zVyGPfdmfhSGpHAxhFiJT7kFnHeSi9ZN: \ > -d amount=100 \ > -d currency=usd \ > -d 'card[number]=4242424242424242' \ > -d 'card[exp_month]=5' \ > -d 'card[exp_year]=2012' \ > -d 'description=test charge IFnFXZgdZk' { "amount": 100, "created": 1318355241, "currency": "usd", "description": "test charge IFnFXZgdZk", "fee": 0, "id": "ch_oVfzHImv8sN5TV", "livemode": false, "object": "charge", "paid": true, "refunded": false, "card": { "country": "US", "exp_month": 5, "exp_year": 2012, "last4": "4242", "object": "card", "type": "Visa" } }
Si lanzamos las transacciones de prueba desde el sitio o desde la ventana de terminal, aparecen los pagos más recientes en el cuadro de mandos al fondo de la página.
Para crear una cuenta con Stripe tan sólo tenemos que introducir una dirección de correo y una clave en la página de gestión. Tras hacer esto podemos visitar la página de nuestra cuenta donde, entre otras cosas, podremos ver las claves de API mediante las que nos podemos comunicar con la API REST de Stripe. Stripe incluye una gema de Ruby para facilitar el uso de esta API desde una aplicación Rails. Instalemos a continuación esta gema, como viene siendo habitual lo haremos referenciándola desde el Gemfile
y luego ejecutando bundle
.
source 'http://rubygems.org' gem 'rails', '3.1.1' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', '~> 3.1.4' gem 'coffee-rails', '~> 3.1.1' gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' gem 'stripe'
Stripe requiere dos claves de API para reconocer con qué cuenta está trabajando. Guardaremos dichas claves en un fichero de inicialización stripe.rb
en el directorio /config/initializers
de nuestra aplicación. Las claves que debemos guardar aquí son la clave secreta que aparece en la página de nuestra cuenta y la clave pública de API. Como la gema Ruby no necesita la clave pública ya que ésta que sólo se usa en vistas y en JavaScript la guardaremos en una constante.
Stripe.api_key = "5J7dr36JmNpLrXXFwAXChdRzZZwLyCHV" STRIPE_PUBLIC_KEY = "pk_B1SaM15nXXruDU3g2D6uns2kJeu9m"
Como el fichero contiene información sensible es buena idea añadirlo en el fichero .gitignore
de la aplicación para que no se incluya con el repositorio. También utilizaremos claves diferentes en el sitio de producción, por lo que no queremos que este fichero se copie accidentalmente sobreescribiendo los datos de producción.
El JavaScript de Stripe y la clave de API
Cuando un usuario envia los datos de su tarjeta de crédito en el formulario, dicha información viaja directamente al servidor de Stripe y no pasa por nuestra aplicación. Con esto es mucho más fácil cumplir con la norma PCI. En el formulario de ejemplo de la página sobre la API de Stripe veremos que ninguno de los campos tiene el atributo name
, esto significa que cuando se envíe el formulario estos campos no van a nuestra aplicación, y en su lugar hay cierto código JavaScript que los lee al hacer clic en el botón del formulario.
Para utilizar Stripe con nuestra aplicación debemos incluir un fichero JavaScript que viene del servidor de Stripe. Luego definimos nuestra clave publica y el JavaScript que se activará cuando se envíe el formulario con los datos de la tarjeta. Este JavaScript generará un token basándose en los datos de la tarjeta introducidos en el formulario.
Añadamos el fichero de JavaScript de Stripe en el layout de nuestra aplicación. Tiene que ir antes del propio JavaScript de la aplicación. Podríamos añadir también algo de código que compruebe si estamos en la página de carrito de la compra y sólo cargue este código en ese caso pero por ahora no lo haremos. También tenemos que poner a disposición de este JavaScript la clave pública, lo que haremos igual que hace Rails con la clave CSRF, en una etiqueta meta
en la cabecera de la página.
<!DOCTYPE html> <html> <head> <title>Llama Kisses</title> <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "https://js.stripe.com/v1/", "application" %> <%= csrf_meta_tags %> <%= tag :meta, :name => "stripe-key", :content => STRIPE_PUBLIC_KEY %> </head> <body> <!-- Body omitted --> </body> </html>
Campos de tarjeta de crédito en la página de suscripción
A continuación añadiremos los campos de la tarjeta de crédito al formulario de suscripción. Dicho formulario ahora mismo contiene un campo oculto con el id
del plan escogido y un campo de texto para introducir una dirección de correo. Añadamos más campos para un número de tarjeta de crédito, un código de seguridad y una fecha de expiración.
<%= form_for @subscription do |f| %> <% if @subscription.errors.any? %> <div class="error_messages"> <h2><%= pluralize(@subscription.errors.count, "error") %> prohibited this subscription from being saved:</h2> <ul> <% @subscription.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <%= f.hidden_field :plan_id %> <div class="field"> <%= f.label :email %> <%= f.text_field :email %> </div> <div class="field"> <%= label_tag :card_number, "Credit Card Number " %> <%= text_field_tag :card_number, nil, name: nil %> </div> <div class="field"> <%= label_tag :card_code, "Security Code on Card (CVV)" %> <%= text_field_tag :card_code, nil, name: nil %> </div> <div class="field"> <%= label_tag :card_month, "Card Expiration" %> <%= select_month nil, {add_month_numbers_true: true}, {name: nil, id: "card_month"}%> <%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+15}, {name: nil, id: "card_year"}%> </div> <div class="actions"><%= f.submit "Subscribe" %></div> <% end %>
Hemos añadido dos campos de texto al formulario, uno para el número de tarjeta, y otro para el código de seguridad, así como dos desplegables para la fecha de expiración. Hay un par de cosas que cabe destacar. En el caso del número de tarjeta y el código de seguridad hemos utilizado label_tag
y text_field_tag
en lugar de usar el constructor de formulario porque estos atributos no son atributos de nuestro modelo Subscription
. No queremos que se envíen al servidor por lo que hemos puesto el nombre de cada campo a nil
. Igualmente, con los campos de expiración, estamos usando select_month
y select_year
y hemos especificado el id
manualmente por lo que no tenemos que preocuparnos por el que genera Rails.
Los nuevos campos aparecerán al recargar la página.
Lo siguiente que tenemos que hacer es escribir el JavaScript que se activa al enviar el formulario. Este script enviará los datos de la tarjeta a Stripe y enviará mediante el formulario el token recibido desde Stripe. Como nuestra aplicación está escrita en Rails 3.1 escribiremos este código en CoffeeScript, en el fichero subscriptions.js.coffee
.
jQuery -> Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content')) subscription.setupForm() subscription = setupForm: -> $('#new_subscription').submit -> $('input[type=submit]').attr('disabled', true) subscription.processCard() processCard: -> card = number: $('#card_number').val() cvc: $('#card_code').val() expMonth: $('#card_month').val() expYear: $('#card_year').val() Stripe.createToken(card, subscription.handleStripeResponse) handleStripeResponse: (status, response) -> if status == 200 alert(response.id) else alert(response.error.message)
El código sólo debería ejecutarse cuando el DOM de la página se haya cargado y por tanto se define con la función jQuery
. Lo primero que hace es configurar Stripe especificando la clave pública. El valor de la clave se lee del atributo meta
que pusimos en el layout de la aplicación. Lo podemos recuperar buscando una etiqueta meta
cuyo atributo name
tenga el valor stripe
y guardando el contenido de su atributo content
. El resto de la lógica se gestiona mediante el objeto subscription
, que dispone de una función llamada setupForm
que es la que hará todo el trabajo.
A continuación creamos el objeto subscription
y le damos la función setupForm
. En esta función recuperaremos el formulario de suscripción por su id
y añadiremos un callback que se activa cuando se dispara el evento de envío del formulario. Cuando se envíe el formulario desactivaremos el botón de envío (para evitar que se vuelva a pulsar accidentalmente) estableciendo su atributo disabled
a true
. Luego llamamos a la función processCard
y devolvemos false
de forma que se envíe el formulario.
La función processCard
crea un objeto que representa los valores de la tarjeta de crédito en el formato que Stripe espera, y lee los valores apropiados de cada campo del formulario con la función val()
. La siguiente línea es la más importante, llamamos a Stripe.createToken
con dos argumentos, el primero de los cuales es el objeto de la tarjeta que acabamos de crear (también podemos pasar aquí opcionalmente una cantidad) mientras que el segundo es una función para gestionar la respuesta de Stripe, que ocurre de forma asíncrona. Pasaremos aquí subscription.handleStripeResponse
.
La función handleStripeResponse
recibe dos argumentos; un status
y una response
. Si status
tiene el valor 200
esto quiere decir que la transacción ha tenido éxito y por tanto tendremos un token correcto en response.id
. Por ahora simplemente lanzaremos un alert
con el valor de status
para ver cuál es. Si el status
no es 200
entonces quiere decir que ha habido algún tipo de error y lanzaremos un alert
con el mensaje.
Ya podemos darle una pasada a nuestro nuevo formulario. Si lo recargamos e introducimos un número válido de tarjeta recibiremos un token de vuelta.
Si introducimos un número incorrecto de tarjeta veremos que en lugar del token aparece un mensaje de error.
Gestión de la respuesta
Ahora que ya sabemos que se procesan correctamente las llamadas a Stripe mejoraremos el código que gestiona los mensajes de error. En lugar de mostrar una alerta, mostraremos el mensaje de error en la página en un div
cuyo id sea stripe_error
y volviendo a activar el botón de envío para que el usuario pueda volver a introducir los datos.
handleStripeResponse: (status, response) -> if status == 200 alert(response.id) else $('#stripe_error').text(response.error.message) $('input[type=submit]').attr('disabled', false)
Una vez hecho esto, tenemos que añadir el div
a la vista.
<%= form_for @subscription do |f| %> <!-- Form fields omitted --> <div id="stripe_error"> <noscript>JavaScript is not enabled and is required for this form. First enable it in your web browser settings.</noscript> </div> <div class="actions"><%= f.submit "Subscribe" %></div> <% end %>
Hemos puesto también un elemento noscript
para que se muestre un error si alguien intenta usar el formulario en un navegador sin soporte de JavaScript. Si volvemos a cargar la página y enviamos el formulario con un número de tarjeta incorrecto veremos el mensaje de error embebido en su div
en lugar de una alerta.
A continuación cambiaremos el código que gestiona las transacciones correctas. En lugar de mostrar el token de respuesta en una alerta, lo pondremos en un campo oculto del formulario, que enviaremos a continuación al servidor de nuestra aplicación. Llamaremos a este campo stripe_card_token
.
<%= f.hidden_field :stripe_card_token %>
La tabla de la base de datos de suscripciones no dispone de un campo stripe_card_token
por lo que lo añadiremos como atributo virtual en nuestro modelo Subscription
.
class Subscription < ActiveRecord::Base belongs_to :plan validates_presence_of :plan_id validates_presence_of :email attr_accessible :stripe_card_token end
De vuelta en nuestro fichero CoffeeScript cambiaremos el alert
por código que asignará a nuestro nuevo campo oculto el valor recibido desde Stripe y luego enviará el formulario, llamando a submit
directamente sobre el objeto del formulario. Al hacer esto no se activa el evento onsubmit
y por tanto el formulario se envía a nuestro servidor normalmente.
handleStripeResponse: (status, response) -> if status == 200 $('#subscription_stripe_card_token').val(response.id) $('#new_subscription')[0].submit() else $('#stripe_error').text(response.error.message) $('input[type=submit]').attr('disabled', false)
El formulario lo recibe la acción create
de SubscriptionsController
y el token se pasará a una nueva Subscription
. Podríamos gestionar el pago mediante before_create
pero en su lugar crearemos un nuevo método en el modelo Subscription
llamado save_with_payment
, donde lo haremos. Antes de crear este método cambiaremos la llamada a save
por save_with_payment
.
def create @subscription = Subscription.new(params[:subscription]) if @subscription.save_with_payment redirect_to @subscription, :notice => "Thank you for subscribing!" else render :new end end
El método save_with_payment
utilizará la gema stripe
para gestionar el pago. Cabría la idea de utilizar el token de respuesta para realizar el cobro utilizando Stripe::Charge.create
pero no podemos hacerlo porque el token sólo es válido una vez y queremos que se trate de un pago recurrente. En su lugar utilizaremos el token para crear un nuevo Stripe::Customer
, tras lo cual le asignaremos un plan a dicho cliente con pagos recurrentes y Stripe gestionará automáticamente dichos pagos para nosotros. Todo esto se encuentra perfectamente cubierto en la excelente documentación de la API.
Antes de hacer cambios en el modelo Subscription
iremos a nuestra cuenta en Stirpe y configuraremos varios planes para que sepa cómo gestionar nuestros pagos recurrentes. Añadiremos cuatro planes con nombres e identificadores que coincidan con los que ya manejamos en nuestra aplicación Rails.
De vuelta a nuestro modelo Subscription
podemos escribir el método save_with_payment
.
class Subscription < ActiveRecord::Base belongs_to :plan validates_presence_of :plan_id validates_presence_of :email attr_accessor :stripe_card_token def save_with_payment if valid? customer = Stripe::Customer.create(description:email, ↵ plan: plan_id, card: stripe_card_token) self.stripe_customer_token = customer.id save! end end end
El método comprueba que el modelo sea válido en cuyo caso creará un nuevo Stripe::Customer
pasándole la dirección de correo como descripción, el plan_id
y el stripe_card_token
. Stripe creará un nuevo id
para el cliente que tendremos que guardar en la base de datos. Lo guardaremos en nuestro campo llamado stripe_customer_token
. Crearemos una migración para guardar este campo y luego ejecutaremos rake db:migrate
para añadirlo a la base de datos.
$ rails g migration add_stripe_to_subscriptions stripe_customer_token:string
Ya estamos listos para probar los pagos. Si vamos a la página de nueva suscripción, introducimos datos de una tarjeta de crédito correcta y luego hacemos clic en el botón de “Subscribe” se crearán el token y el cliente en segundo plano se enviará el formulario. Si luego visitamos la página de pagos en nuestra cuenta de Stripe veremos que este pago aparece en el listado con el precio del plan. Se trata de un pago recurrente que se ejecutará cada mes.
Control de errores
La llamada a Stripe::Customer.create
podría elevar una excepción si alguno de los datos que le pasamos no son válidos. Esto puede ocurrir, por ejemplo, si el usuario envía el formulario sin tener JavaScript activado por lo que no se le ha generado el stripe_card_token
. Es buena práctica utilizar rescue
para gestionar estos casos. Stripe elevará una excepción InvalidRequestError
en estos casos para que podamos capturarla, guardarla y mostrar el error correspondiente de validación en la página con el formulario de nuevo.
def save_with_payment if valid? customer = Stripe::Customer.create(description:email, ↵ plan: plan_id, card: stripe_card_token) self.stripe_customer_token = customer.id save! end rescue Stripe::InvalidRequestError => e logger.error "Stripe error while creating customer: #{e.message}" errors.add :base, "There was a problem with your credit card." end
También tenemos que gestionar mejor los errores que no tienen que ver con la tarjeta de crédito, como el campo de dirección de correo. Ahora mismo si intentamos enviar una suscripción sin dirección de correo pero con datos correctos de tarjeta de crédito recibiremos el token correcto desde Stripe pero en lugar de ser redirigidos a la página que muestra el alta correcta iremos de nuevo al formulario con un error de validación.
En estos casos deberíamos ocultar los datos de la tarjeta de crédito porque ya tenemos un token válido. Esto lo podemos hacer modificando la vista del formulario para que oculte los campos si ya existe un stripe_card_token
para la suscripción, en cuyo caso se mostrará un mensaje.
<div class="field"> <%= f.label :email %> <%= f.text_field :email %> </div> <% if @subscription.stripe_card_token %> Credit card has been provided <% else %> <div class="field"> <%= label_tag :card_number, "Credit Card Number " %> <%= text_field_tag :card_number, nil, name: nil %> </div> <div class="field"> <%= label_tag :card_code, "Security Code on Card (CVV)" %> <%= text_field_tag :card_code, nil, name: nil %> </div> <div class="field"> <%= label_tag :card_month, "Card Expiration" %> <%= select_month nil, {add_month_numbers_true: true}, ↵ {name: nil, id: "card_month"}%> <%= select_year nil, {start_year: Date.today.year, ↵ end_year: Date.today.year+15}, ↵ {name: nil, id: "card_year"} %> </div> </div> <% end %>
También tenemos que actualizar la función setupForm
de nuestro fichero CoffeeScript de forma que no intente procesar otra vez la tarjeta cuando el usuario envía el formulario en este caso. Podemos hacerlo comprobando si en el formulario ya aparece el número de la tarjeta de crédito, si no es así entonces está oculto porque dicha información ya ha sido procesada. En este caso no lo volveremos a procesar y devolveremos true
para que se envíe el formulario.
setupForm: -> $('#new_subscription').submit -> $('input[type=submit]').attr('disabled', true) if $('#card_number').length subscription.processCard() false else true
Si ahora enviamos el formulario con datos correctos de tarjeta pero sin una dirección de correo, veremos que se ocultan los campos de la tarjeta.
Si ahora rellenamos el campo del correo y volvemos a enviar el formulario se creará la suscripción y Strip procesará el pago.
WebHooks
Una de las funcionalidades de Stripe que no hemos visto son los Webhooks, que nos permiten definir una URL que Stripe invocará cuando ocurran determinados eventos. Por ejemplo si falla un pago recurrente queremos ser notificados para poder suspender la cuenta del usuario o para informarles del problema para que puedan actualizar los datos de su tarjeta de crédito. A propósito de esto, todavía no hay manera de que los usuarios puedan cambiar los datos de su tarjeta de crédito, cancelar las suscripciones, etc. en nuestra aplicación, pero no lo veremos aquí.
Stripe tiene muchas funcionalidades que hoy no hemos visto, pero esperamos haber dado información suficiente para empezar. Stripe es una forma muy cómoda de procesar pagos con tarjeta de crédito. Por desgracia, no está disponible fuera de los Estados Unidos por ahora, aunque pronto estará disponible en otros mercados.