Integración de los métodos de pago de Stripe y PayPal en Ruby on Rails
Publicado: 2022-03-11Una característica clave para las grandes empresas de comercio electrónico como AliExpress, Ebay y Amazon es una forma segura de manejar los pagos, lo cual es esencial para su negocio. Si esta característica falla, las consecuencias serían devastadoras. Esto se aplica a los líderes de la industria y a los desarrolladores de Ruby on Rails que trabajan en aplicaciones de comercio electrónico.
La ciberseguridad es esencial para prevenir ataques, y una forma de hacer que el proceso de transacción sea más seguro es pedirle a un servicio de terceros que lo maneje. Incluir pasarelas de pago en su aplicación es una forma de lograr este objetivo, ya que brindan autorización de usuario, encriptación de datos y un panel para que pueda seguir el estado de la transacción sobre la marcha.
Hay una variedad de servicios de pasarela de pago en la web, pero en este artículo me centraré en la integración de Stripe y PayPal en una aplicación de Rails. Por mencionar algunos otros: Amazon Payments, Square, SecurePay, WorldPay, Authorize.Net, 2Checkout.com, Braintree, Amazon o BlueSnap.
Cómo funciona la integración de la pasarela de pago

En general, habrá un formulario/botón en su aplicación donde el usuario puede iniciar sesión/insertar los datos de la tarjeta de crédito. PayPal y Stripe ya hacen que este primer paso sea más seguro mediante el uso de formularios iframe
o popups
que evitan que su aplicación almacene información confidencial de la tarjeta de crédito del usuario, ya que devolverán un token que representa esta transacción. Es posible que algunos usuarios ya se sientan más seguros para procesar pagos al saber que un servicio de terceros está manejando el proceso de transacción, por lo que esto también puede ser una atracción para su aplicación.
Después de autenticar la información del usuario, una pasarela de pago confirmará el pago poniéndose en contacto con un procesador de pagos que se comunica con los bancos para liquidar los pagos. Esto asegura que la transacción se carga/abona correctamente.
Stripe utiliza un formulario de tarjeta de crédito que solicita el número de tarjeta de crédito, cvv y fecha de vencimiento. Entonces, el usuario debe completar la información de la tarjeta de crédito en las entradas seguras de Stripe. Después de proporcionar esta información, el back-end de su aplicación procesa este pago a través de un token.
A diferencia de Stripe, PayPal redirige al usuario a la página de inicio de sesión de PayPal. El usuario autoriza y selecciona el método de pago a través de PayPal y, nuevamente, su back-end manejará tokens en lugar de datos confidenciales del usuario.
Es importante mencionar que, para estas dos pasarelas de pago, su back-end debe solicitar la ejecución de la transacción en curso a través de las API de Stripe o PayPal, lo que dará una respuesta OK/NOK, por lo que su aplicación debería redirigir al usuario a una página correcta o de error en consecuencia.
La intención de este artículo es proporcionar una guía rápida para integrar estas dos pasarelas de pago en una sola aplicación. Para todas las pruebas, utilizaremos sandboxes y cuentas de prueba proporcionadas por Stripe y PayPal para simular pagos.
Configuración
Antes de integrar las pasarelas de pago, realizaremos una configuración para inicializar la aplicación agregando gemas, tablas de base de datos y una página de índice. Este proyecto fue creado usando Rails versión 5.2.3 y Ruby 2.6.3.
Nota: Puede consultar las nuevas características de Rails 6 en nuestro artículo reciente.
Paso 1: inicialice una aplicación Rails.
Inicialice el proyecto ejecutando la inicialización del proyecto con el comando rails
con el nombre de su aplicación:
rails new YOUR_APP_NAME
Y cd
en la carpeta de tu aplicación.
Paso 2: Instala gemas.
Además de las gemas de Stripe y PayPal, se agregaron algunas otras gemas:
-
devise
: utilizado para la autenticación y autorización del usuario -
haml
: herramienta de plantillas para renderizar páginas de usuario -
jquery-rails
: parajquery
en los scripts front-end -
money-rails
: para mostrar valores de dinero formateados
Agregue a su Gemfile
:
gem "devise", ">= 4.7.1" gem "haml" gem "jquery-rails" gem "money-rails"
Después de agregarlo, ejecute en su CLI:
bundle install
Paso 3: inicializa las gemas.
Algunas de estas gemas requerirán inicialización además de instalarlas a través de bundle
.
Dispositivo de instalación:
rails g devise:install
Inicializando money-rails
:
rails g money_rails:initializer
Inicialice jquery-rails
agregando al final de app/assets/javascripts/application.js
lo siguiente:
//= require jquery //= require jquery_ujs
Paso 4: Tablas y migraciones
En este proyecto se utilizarán tres tablas Usuarios , Productos y Pedidos .
-
Users
: se generará a través del dispositivo - Columnas de
Products
:-
name
-
price_cents
-
Stripe_plan_name
: ID que representa un plan de suscripción creado en Stripe, para que los usuarios puedan suscribirse. Este campo solo es obligatorio para productos asociados a un plan Stripe. -
paypal_plan_name
: Lo mismo questripe_plan_name
pero para PayPal
-
- Columnas
Orders
:-
product_id
-
user_id
-
status
: Esto informará si el pedido está pendiente, fallido o pagado. -
token
: este es un token generado a partir de las API (ya sea Stripe o PayPal) para inicializar una transacción. -
price_cents
: Similar al producto, pero se usa para que este valor sea persistente en el registro del pedido. -
payment_gateway
: Almacena qué pasarela de pago se está utilizando para el pedido PayPal o Stripe -
customer_id
: Esto se usará para Stripe para almacenar el cliente de Stripe para una suscripción, y se explicará con más detalle en una sección posterior.
-
Para generar estas tablas, se deben generar algunas migraciones:
Para crear la tabla de Usuarios . Correr:
rails g devise User
Para crear la tabla Productos . Genere una migración ejecutando:
rails generate migration CreateProducts name:string stripe_plan_name:string paypal_plan_name:string
Abra su archivo de migración creado, que debe estar ubicado en db/migrate/
, y realice cambios para que su migración se vea similar a esto:
class CreateProducts < ActiveRecord::Migration[5.2] def change create_table :products do |t| t.string :name t.string :stripe_plan_name t.string :paypal_plan_name end add_money :products, :price, currency: { present: true } end end
Para crear la tabla Pedidos . Genere una migración ejecutando:
rails generate migration CreateOrders product_id:integer user_id:integer status:integer token:string charge_id:string error_message:string customer_id:string payment_gateway:integer
Nuevamente, abra su archivo de migración creado que debe estar ubicado en db/migrate/
y realice cambios en ese archivo para que se vea similar a esto:
class CreateOrders < ActiveRecord::Migration[5.2] def change create_table :orders do |t| t.integer :product_id t.integer :user_id t.integer :status, default: 0 t.string :token t.string :charge_id t.string :error_message t.string :customer_id t.integer :payment_gateway t.timestamps end add_money :orders, :price, currency: { present: false } end end
Ejecute migraciones de bases de datos ejecutando:
rails db:migrate
Paso 5: Crear modelos.
El modelo de usuario ya se creó a partir de la instalación del dispositivo y no se requerirán cambios en él. Además de eso, se crearán dos modelos para Producto y Pedido .
Producto. Agrega un nuevo archivo, app/models/product.rb
, con:
class Product < ActiveRecord::Base monetize :price_cents has_many :orders end
Pedido. Agrega un nuevo archivo, app/models/order.rb
, con:
class Order < ApplicationRecord enum status: { pending: 0, failed: 1, paid: 2, paypal_executed: 3} enum payment_gateway: { stripe: 0, paypal: 1 } belongs_to :product belongs_to :user scope :recently_created, -> { where(created_at: 1.minutes.ago..DateTime.now) } def set_paid self.status = Order.statuses[:paid] end def set_failed self.status = Order.statuses[:failed] end def set_paypal_executed self.status = Order.statuses[:paypal_executed] end end
Paso 6: Complete la base de datos.
Se creará un usuario y dos productos en la consola. Los registros de pedidos se crearán de acuerdo con las pruebas de pago.
- Ejecutar
rails s
- En su navegador, visite
http://localhost:3000
- Se le redirigirá a una página de registro.
- Registre un usuario completando su dirección de correo electrónico y contraseña.
- En su terminal, aparecerán los siguientes registros que muestran que se creó un usuario en su base de datos:
User Create (0.1ms) INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?) …
- Cree dos productos sin suscripciones ejecutando
rails c
y agregando:-
Product.create(name: "Awesome T-Shirt", price_cents: 3000)
-
Product.create(name: "Awesome Sneakers", price_cents: 5000)
-
Paso 7: crea una página de índice
La página principal del proyecto incluye la selección de productos para compras o suscripciones. Adicionalmente, también cuenta con un apartado para la selección del método de pago (Stripe o PayPal). También se utiliza un botón de envío para cada tipo de pasarela de pago, ya que para PayPal agregaremos su propio diseño de botón a través de su biblioteca de JavaScript.
Primero, cree las rutas para el index
y submit
en config/routes.rb
.
Rails.application.routes.draw do devise_for :users get '/', to: 'orders#index' post '/orders/submit', to: 'orders#submit' end
Cree y agregue un index
acciones y submit
en el controlador de pedidos app/controllers/orders_controller.rb
. La acción orders#index
almacena dos variables para ser consumidas en el front-end: @products_purchase
que tiene una lista de productos sin planes y @products_subscription
que tiene productos con planes de PayPal y Stripe.
class OrdersController < ApplicationController before_action :authenticate_user! def index products = Product.all @products_purchase = products.where(stripe_plan_name:nil, paypal_plan_name:nil) @products_subscription = products - @products_purchase end def submit end end
Cree un archivo en app/views/orders/index.html.haml
. Este archivo contiene todas las entradas que enviaremos a nuestro back-end a través del método de envío y la interacción para las pasarelas de pago y la selección de productos. Aquí hay algunos atributos de nombre de entrada:
-
Orders[product_id]
almacena la identificación del producto. -
Orders[payment_gateway]
contiene la pasarela de pago con valores de Stripe o PayPal para la otra.
%div %h1 List of products = form_tag({:controller => "orders", :action => "submit" }, {:id => 'order-details'}) do %input{id:'order-type', :type=>"hidden", :value=>"stripe", :name=>'orders[payment_gateway]'} .form_row %h4 Charges/Payments - @products_purchase.each do |product| %div{'data-charges-and-payments-section': true} = radio_button_tag 'orders[product_id]', product.id, @products_purchase.first == product %span{id: "radioButtonName#{product.id}"} #{product.name} %span{id: "radioButtonPrice#{product.id}", :'data-price' => "#{product.price_cents}"} #{humanized_money_with_symbol product.price} %br %h4 Subscriptions - @products_subscription.each do |product| %div = radio_button_tag 'orders[product_id]', product.id, false %span{id: "radioButtonName#{product.id}"} #{product.name} %span{id: "radioButtonPrice#{product.id}", :'data-price' => "#{product.price_cents}"} #{humanized_money_with_symbol product.price} %br %hr %h1 Payment Method .form_row %div = radio_button_tag 'payment-selection', 'stripe', true, onclick: "changeTab();" %span Stripe %br %div = radio_button_tag 'payment-selection', 'paypal', false, onclick: "changeTab();" %span Paypal %br %br %div{id:'tab-stripe', class:'paymentSelectionTab active'} %div{id:'card-element'} %div{id:'card-errors', role:"alert"} %br %br = submit_tag "Buy it!", id: "submit-stripe" %div{id:'tab-paypal', class:'paymentSelectionTab'} %div{id: "submit-paypal"} %br %br %hr :javascript function changeTab() { var newActiveTabID = $('input[name="payment-selection"]:checked').val(); $('.paymentSelectionTab').removeClass('active'); $('#tab-' + newActiveTabID).addClass('active'); } :css #card-element { width:500px; } .paymentSelectionTab { display: none; } .paymentSelectionTab.active { display: block !important; }
Si ejecuta su aplicación con rails s
y visita su página en http://localhost:3000
. Debería poder ver la página de la siguiente manera:

Almacenamiento de credenciales de pasarela de pago
Las claves de PayPal y Stripe se almacenarán en un archivo que Git no rastreará. Hay dos tipos de claves almacenadas en este archivo para cada pasarela de pago y, por ahora, usaremos un valor ficticio para ellas. En secciones posteriores se presentan instrucciones adicionales para crear estas claves.
Paso 1: Agrega esto en .gitignore
.
/config/application.yml
Paso 2: Cree un archivo con sus credenciales en config/application.yml
. Debe contener todas sus claves de prueba/sandbox de PayPal y Stripe para acceder a estas API.
test: &default PAYPAL_ENV: sandbox PAYPAL_CLIENT_ID: YOUR_CREDENTIAL_HERE PAYPAL_CLIENT_SECRET: YOUR_CREDENTIAL_HERE STRIPE_PUBLISHABLE_KEY: YOUR_CREDENTIAL_HERE STRIPE_SECRET_KEY: YOUR_CREDENTIAL_HERE development: <<: *default
Paso 3: para almacenar las variables del archivo config/application.yml
cuando se inicia la aplicación, agregue estas líneas en config/application.rb
dentro de la clase de Application
para que estén disponibles en ENV
.
config_file = Rails.application.config_for(:application) config_file.each do |key,value| ENV[key] = value end unless config_file.nil?
Configuración de bandas
Agregaremos una joya para usar la API de Stripe: stripe-rails
. También es necesario crear una cuenta de Stripe para que se puedan procesar los cargos y las suscripciones. Si es necesario, puede consultar los métodos API para Stripe API en la documentación oficial.
Paso 1: agregue la gema de rieles de rayas a su proyecto.
La gema stripe-rails proporcionará una interfaz para todas las solicitudes de API utilizadas en este proyecto.
Agregue esto en el Gemfile
:
gem 'stripe-rails'
Correr:
bundle install
Paso 2: Genere sus claves API.
Para tener las claves API para comunicarse con Stripe, deberá crear una cuenta en Stripe. Para probar la aplicación, es posible usar el modo de prueba, por lo que no es necesario completar información comercial real en el proceso de creación de la cuenta de Stripe.
- Crea una cuenta en Stripe si no tienes una (https://dashboard.stripe.com/).
- Mientras aún está en el panel de control de Stripe, después de iniciar sesión, active Ver datos de prueba .
- En https://dashboard.stripe.com/test/apikeys, reemplace
YOUR_CREDENTIAL_HERE
por los valoresSTRIPE_PUBLISHABLE_KEY
ySTRIPE_SECRET_KEY
en/config/application.yml
con el contenido dePublishable Key
ySecret key
.
Paso 3: Inicializa el módulo Stripe
Además de reemplazar las claves, todavía necesitamos inicializar el módulo Stripe, para que use las claves ya configuradas en nuestro ENV
.
Cree un archivo en config/initializers/stripe.rb
con:
Rails.application.configure do config.stripe.secret_key = ENV["STRIPE_SECRET_KEY"] config.stripe.publishable_key = ENV["STRIPE_PUBLISHABLE_KEY"] end
Paso 4: Integra Stripe en el front-end.
Agregaremos la biblioteca Stripe JavaScript y la lógica para enviar un token que represente la información de la tarjeta de crédito del usuario y se procesará en nuestro back-end.
En el archivo index.html.haml
, agregue esto en la parte superior de su archivo. Esto usará el módulo Stripe (proporcionado por la gema) para agregar la biblioteca de javascript de Stripe a la página del usuario.
= stripe_javascript_tag
Stripe utiliza campos de entrada seguros que se crean a través de su API. Como se crean en un iframe
creado a través de esta API, no tendrá que preocuparse por posibles vulnerabilidades en el manejo de la información de la tarjeta de crédito del usuario. Además, su back-end no podrá procesar/almacenar ningún dato confidencial del usuario y solo recibirá un token que represente esta información.
Estos campos de entrada se crean llamando a stripe.elements().create('card')
. Después de eso, solo se requiere llamar al objeto devuelto con mount()
pasando como argumento el id/clase del elemento HTML en el que se deben montar estas entradas. Puede encontrar más información en Stripe.
Cuando el usuario presiona el botón Enviar con el método de pago Stripe, se realiza otra llamada a la API que devuelve una promesa en el elemento de la tarjeta Stripe creado:
stripe.createToken(card).then(function(result)
La variable de result
de esta función, si no tiene asignado un error de propiedad, tendrá un token que se puede recuperar accediendo al atributo result.token.id
. Este token se enviará al back-end.
Para realizar estos cambios, reemplace el código comentado // YOUR STRIPE AND PAYPAL CODE WILL BE HERE
en index.html.haml
con:
(function setupStripe() { //Initialize stripe with publishable key var stripe = Stripe("#{ENV['STRIPE_PUBLISHABLE_KEY']}"); //Create Stripe credit card elements. var elements = stripe.elements(); var card = elements.create('card'); //Add a listener in order to check if card.addEventListener('change', function(event) { //the div card-errors contains error details if any var displayError = document.getElementById('card-errors'); document.getElementById('submit-stripe').disabled = false; if (event.error) { // Display error displayError.textContent = event.error.message; } else { // Clear error displayError.textContent = ''; } }); // Mount Stripe card element in the #card-element div. card.mount('#card-element'); var form = document.getElementById('order-details'); // This will be called when the #submit-stripe button is clicked by the user. form.addEventListener('submit', function(event) { $('#submit-stripe').prop('disabled', true); event.preventDefault(); stripe.createToken(card).then(function(result) { if (result.error) { // Inform that there was an error. var errorElement = document.getElementById('card-errors'); errorElement.textContent = result.error.message; } else { // Now we submit the form. We also add a hidden input storing // the token. So our back-end can consume it. var $form = $("#order-details"); // Add a hidden input orders[token] $form.append($('<input type="hidden" name="orders[token]"/>').val(result.token.id)); // Set order type $('#order-type').val('stripe'); $form.submit(); } }); return false; }); }()); //YOUR PAYPAL CODE WILL BE HERE
Si visita su página, debería tener el siguiente aspecto con los nuevos campos de entrada segura de Stripe:

Paso 5: Pruebe su aplicación.
Complete el formulario de tarjeta de crédito con una tarjeta de prueba (https://stripe.com/docs/testing) y envíe la página. Compruebe si la acción de submit
se llama con todos los parámetros ( product_id , payment_gateway y token ) en la salida de su servidor.
Cargos de rayas
Los cargos de Stripe representan transacciones únicas. Por lo tanto, después de una transacción de cargo de Stripe, recibiría dinero directamente del cliente. Esto es ideal para vender productos que no están asociados a planes. En una sección posterior, mostraré cómo realizar el mismo tipo de transacción con PayPal, pero el nombre de PayPal para este tipo de transacción es Pago .
En esta sección también proporcionaré todo el esqueleto para manejar y enviar una orden. Creamos un pedido en la acción de submit
cuando se envía el formulario de Stripe. Este pedido inicialmente tendrá el estado pendiente , por lo que si algo sale mal mientras se procesa este pedido, seguirá estando pendiente .
Si surge algún error de las llamadas a la API de Stripe, configuramos el pedido en un estado fallido y, si el cargo se completa con éxito, estará en el estado pagado . El usuario también es redirigido de acuerdo con la respuesta de la API de Stripe, como se muestra en el siguiente gráfico:

Además, cuando se realiza un cargo de Stripe, se devuelve una identificación. Estaremos almacenando esta identificación para que luego pueda buscarla en su panel de control de Stripe si es necesario. Esta identificación también se puede utilizar si el pedido debe ser reembolsado. Tal cosa no será explorada en este artículo.
Paso 1: Crea el servicio Stripe.
Usaremos una clase singleton para representar las operaciones de Stripe usando la API de Stripe. Para crear un cargo, se llama al método Stripe::Charge.create
y el atributo ID del objeto devuelto se almacenará en el registro de pedido charge_id
. Esta función de create
se llama pasando el token originado en el front-end, el precio del pedido y una descripción.
Por lo tanto, cree una nueva carpeta app/services/orders
y agregue un servicio Stripe: app/services/orders/stripe.rb
que contenga la clase Orders::Stripe
singleton, que tiene una entrada en el método execute
.
class Orders::Stripe INVALID_STRIPE_OPERATION = 'Invalid Stripe Operation' def self.execute(order:, user:) product = order.product # Check if the order is a plan if product.stripe_plan_name.blank? charge = self.execute_charge(price_cents: product.price_cents, description: product.name, card_token: order.token) else #SUBSCRIPTIONS WILL BE HANDLED HERE end unless charge&.id.blank? # If there is a charge with id, set order paid. order.charge_id = charge.id order.set_paid end rescue Stripe::StripeError => e # If a Stripe error is raised from the API, # set status failed and an error message order.error_message = INVALID_STRIPE_OPERATION order.set_failed end private def self.execute_charge(price_cents:, description:, card_token:) Stripe::Charge.create({ amount: price_cents.to_s, currency: "usd", description: description, source: card_token }) end end
Paso 2: implemente la acción de envío y llame al servicio Stripe.
En orders_controller.rb
, agregue lo siguiente en la acción de submit
, que básicamente llamará al servicio Orders::Stripe.execute
. Tenga en cuenta que también se agregaron dos nuevas funciones privadas: prepare_new_order
y order_params
.
def submit @order = nil #Check which type of order it is if order_params[:payment_gateway] == "stripe" prepare_new_order Orders::Stripe.execute(order: @order, user: current_user) elsif order_params[:payment_gateway] == "paypal" #PAYPAL WILL BE HANDLED HERE end ensure if @order&.save if @order.paid? # Success is rendered when order is paid and saved return render html: SUCCESS_MESSAGE elsif @order.failed? && [email protected]_message.blank? # Render error only if order failed and there is an error_message return render html: @order.error_message end end render html: FAILURE_MESSAGE end private # Initialize a new order and and set its user, product and price. def prepare_new_order @order = Order.new(order_params) @order.user_id = current_user.id @product = Product.find(@order.product_id) @order.price_cents = @product.price_cents end def order_params params.require(:orders).permit(:product_id, :token, :payment_gateway, :charge_id) end
Paso 3: Pruebe su aplicación.
Compruebe si la acción de envío, cuando se llama con una tarjeta de prueba válida, realiza una redirección a un mensaje correcto. Además, verifique en su panel de control de Stripe si también se muestra el pedido.
Suscripciones de banda
Se pueden crear suscripciones o planes para pagos recurrentes. Con este tipo de productos se cobra al usuario de forma diaria, semanal, mensual o anual de forma automática según la configuración del plan. En esta sección, usaremos el campo para el producto stripe_plan_name
para almacenar la ID del plan; en realidad, podemos elegir la ID, y lo llamaremos premium-plan
que se usará para crear el relación customer <-> subscription
.
También crearemos una nueva columna para la tabla de usuarios llamada stripe_customer_id
que se completará con la propiedad id de un objeto de cliente de Stripe. Un cliente de Stripe se crea cuando se llama a la función Stripe::Customer.create
, y también puede consultar los clientes creados y vinculados a su cuenta en (https://dashboard.stripe.com/test/customers). Los clientes se crean pasando un parámetro de source
que, en nuestro caso, es el token generado en el front-end que se envía cuando se envía el formulario.
El objeto de cliente obtenido de la última llamada API de Stripe mencionada también se usa para crear una suscripción que se realiza llamando a customer.subscriptions.create
y pasando la ID del plan como parámetro.
Además, la gema stripe-rails
proporciona la interfaz para recuperar y actualizar un cliente desde Stripe, lo que se hace llamando a Stripe::Customer.retrieve
y Stripe::Customer.update
, respectivamente.
Entonces, cuando un registro de usuario ya tiene un stripe_customer_id
, en lugar de crear un nuevo cliente usando Stripe::Customer.create
, llamaremos a Stripe::Customer.retrieve
pasando stripe_customer_id
como parámetro, seguido de Stripe::Customer.update
, y en este caso, pasando al token un parámetro.

En primer lugar, crearemos un plan con la API de Stripe para que podamos crear un nuevo producto de suscripción con el campo stripe_plan_name
. Posteriormente, haremos modificaciones en orders_controller
y en el servicio de Stripe para que se maneje la creación y ejecución de suscripciones de Stripe.
Paso 1: crea un plan con la API de Stripe.
Abra su consola usando los rails c
. Crea una suscripción para tu cuenta de Stripe con:
Stripe::Plan.create({ amount: 10000, interval: 'month', product: { name: 'Premium plan', }, currency: 'usd', id: 'premium-plan', })
Si el resultado devuelto en este paso es verdadero, significa que el plan se creó correctamente y puede acceder a él en su panel de Stripe.
Paso 2: Cree un producto en la base de datos con el conjunto de campos stripe_plan_name
.
Ahora, cree un producto con stripe_plan_name
establecido como premium-plan
en la base de datos:
Product.create(price_cents: 10000, name: 'Premium Plan', stripe_plan_name: 'premium-plan')
Paso 3: Genere una migración para agregar una columna stripe_customer_id
en la tabla de users
.
Ejecuta lo siguiente en la terminal:
rails generate migration AddStripeCustomerIdToUser stripe_customer_id:string rails db:migrate
Paso 4: implementar la lógica de suscripción en la clase de servicio Stripe.
Agregue dos funciones más en los métodos privados de app/services/orders/stripe.rb
: execute_subscription
es responsable de crear las suscripciones en el objeto del cliente. La función find_or_create_customer
se encarga de devolver el cliente ya creado o devolver un cliente recién creado.
def self.execute_subscription(plan:, token:, customer:) customer.subscriptions.create({ plan: plan }) end def self.find_or_create_customer(card_token:, customer_id:, email:) if customer_id stripe_customer = Stripe::Customer.retrieve({ id: customer_id }) if stripe_customer stripe_customer = Stripe::Customer.update(stripe_customer.id, { source: card_token}) end else stripe_customer = Stripe::Customer.create({ email: email, source: card_token }) end stripe_customer end
Finalmente, en la función de execute
en el mismo archivo ( app/services/orders/stripe.rb
), primero llamaremos a find_or_create_customer
y luego ejecutaremos la suscripción llamando a execute_subscription
pasando el cliente recuperado/creado anterior. Por lo tanto, reemplace el comentario #SUBSCRIPTIONS WILL BE HANDLED HERE
en el método de execute
con el siguiente código:
customer = self.find_or_create_customer(card_token: order.token, customer_id: user.stripe_customer_id, email: user.email) if customer user.update(stripe_customer_id: customer.id) order.customer_id = customer.id charge = self.execute_subscription(plan: product.stripe_plan_name, customer: customer)
Paso 5: Pruebe su aplicación.
Visite su sitio web, seleccione el Premium Plan
del producto de suscripción y complete una tarjeta de prueba válida. Después de enviar, debería redirigirte a una página exitosa. Además, verifique en su panel de Stripe si la suscripción se creó con éxito.
Configuración de PayPal
Como hicimos en Stripe, también agregaremos una joya para usar la API de PayPal: paypal-sdk-rest
, y también se requiere crear una cuenta de PayPal. Se puede consultar un flujo de trabajo descriptivo para PayPal usando esta gema en la documentación oficial de la API de PayPal.
Paso 1: agregue la gema paypal-sdk-rest
a su proyecto.
Agregue esto en el Gemfile
:
gem 'paypal-sdk-rest'
Correr:
bundle install
Paso 2: Genere sus claves API.
Para tener las claves API para comunicarse con PayPal, deberá crear una cuenta de PayPal. Entonces:
- Cree una cuenta (o use su cuenta de PayPal) en https://developer.paypal.com/.
- Aún conectado a su cuenta, cree dos cuentas de sandbox en https://developer.paypal.com/developer/accounts/:
- Personal (cuenta de comprador): se utilizará en sus pruebas para realizar pagos y suscripciones.
- Business (Cuenta de comerciante): se vinculará a la aplicación, que tendrá las claves API que estamos buscando. Además de eso, todas las transacciones se pueden seguir en esta cuenta.
- Cree una aplicación en https://developer.paypal.com/developer/applications usando la cuenta de sandbox comercial anterior.
- Después de este paso, recibirá dos claves para PayPal:
Client ID
ySecret
. - En
config/application.yml
, reemplaceYOUR_CREDENTIAL_HERE
dePAYPAL_CLIENT_ID
yPAYPAL_CLIENT_SECRET
con las claves que acaba de recibir.
Paso 3: Inicialice el módulo de PayPal.
Similar a Stripe, además de reemplazar las claves en application.yml
, aún necesitamos inicializar el módulo de PayPal para que pueda usar las claves ya configuradas en nuestra variable ENV
. Para ello, cree un archivo en config/initializers/paypal.rb
con:
PayPal::SDK.configure( mode: ENV['PAYPAL_ENV'], client_id: ENV['PAYPAL_CLIENT_ID'], client_secret: ENV['PAYPAL_CLIENT_SECRET'], ) PayPal::SDK.logger.level = Logger::INFO
Paso 4: integre PayPal en el front-end.
En index.html.haml
agregue esto en la parte superior del archivo:
%script(src="https://www.paypal.com/sdk/js?client-id=#{ENV['PAYPAL_CLIENT_ID']}")
A diferencia de Stripe, PayPal usa solo un botón que, cuando se hace clic, abre una ventana emergente segura donde el usuario puede iniciar sesión y proceder al pago/suscripción. Este botón se puede representar llamando al método paypal.Button(PARAM1).render(PARAM2)
.
-
PARAM1
es un objeto con la configuración del entorno y dos funciones de devolución de llamada como propiedades:createOrder
yonApprove
. -
PARAM2
indica el identificador del elemento HTML al que debe adjuntarse el botón de PayPal.
Entonces, aún en el mismo archivo, reemplace el código comentado YOUR PAYPAL CODE WILL BE HERE
con:
(function setupPaypal() { function isPayment() { return $('[data-charges-and-payments-section] input[name="orders[product_id]"]:checked').length } function submitOrderPaypal(chargeID) { var $form = $("#order-details"); // Add a hidden input orders[charge_id] $form.append($('<input type="hidden" name="orders[charge_id]"/>').val(chargeID)); // Set order type $('#order-type').val('paypal'); $form.submit(); } paypal.Buttons({ env: "#{ENV['PAYPAL_ENV']}", createOrder: function() { }, onApprove: function(data) { } }).render('#submit-paypal'); }());
Paso 5: Pruebe su aplicación.
Visite su página y verifique si el botón de PayPal aparece cuando selecciona PayPal como método de pago.
Transacciones de PayPal
La lógica de las transacciones de PayPal, a diferencia de Stripe, es un poco más compleja al involucrar más solicitudes que se originan desde el front-end hasta el back-end. Por eso existe esta sección. Explicaré más o menos (sin ningún código) cómo se implementarán las funciones descritas en los métodos createOrder
y onApprove
y qué se espera en los procesos de back-end también.
Paso 1: cuando el usuario hace clic en el botón Enviar de PayPal, se abre una ventana emergente de PayPal que solicita las credenciales del usuario, pero se está cargando. Se llama a la función callback createOrder
.

Paso 2: en esta función, realizaremos una solicitud a nuestro back-end que creará un pago/suscripción. Este es el comienzo de una transacción y aún no se aplicarán cargos, por lo que la transacción se encuentra en estado pendiente . Nuestro back-end debería devolvernos un token, que se generará utilizando el módulo de PayPal (proporcionado a través de la gema paypal-rest-sdk
).
Paso 3: aún en la devolución de llamada de createOrder
, devolvemos este token generado en nuestro back-end, y si todo está bien, la ventana emergente de PayPal mostrará lo siguiente, solicitando las credenciales de usuario:

Paso 4: después de que el usuario haya iniciado sesión y seleccionado el método de pago, la ventana emergente cambiará su estado a lo siguiente:

Paso 5: Ahora se llama a la devolución de llamada de la función onApprove
. Lo hemos definido como lo siguiente: onApprove: function(data)
. El objeto data
tendrá la información de pago para poder ejecutarlo. En esta devolución de llamada, se realizará otra solicitud a nuestra función de back-end, esta vez pasando el objeto de datos para ejecutar la orden de PayPal.
Paso 6: Nuestro back-end ejecuta esta transacción y devuelve 200 (si tiene éxito).
Paso 7: cuando nuestro back-end regresa, enviamos el formulario. Esta es la tercera solicitud que hacemos a nuestro back-end.
Tenga en cuenta que, a diferencia de Stripe, se realizan tres solicitudes a nuestro back-end en este proceso. Y mantendremos el estado de nuestro registro de pedidos sincronizado en consecuencia:
- devolución de llamada
createOrder
: se crea una transacción y también se crea un registro de pedido; por lo tanto, está en estado pendiente por defecto. -
onApprove
callback: la transacción se ejecuta y nuestro pedido se establecerá como paypal_executed . - Se envía la página del pedido: la transacción ya se ejecutó, por lo que no cambia nada. El registro del pedido cambiará su estado a pagado .
Todo este proceso se describe en el siguiente gráfico:

Pagos de PayPal
Los pagos de PayPal siguen la misma lógica que los Cargos de Stripe, por lo que representan transacciones únicas, pero como se mencionó en la sección anterior, tienen una lógica de flujo diferente. Estos son los cambios que deberán realizarse para manejar los pagos de PayPal:
Paso 1: Cree nuevas rutas para PayPal y ejecute los pagos.
Add the following routes in config/routes.rb
:
post 'orders/paypal/create_payment' => 'orders#paypal_create_payment', as: :paypal_create_payment post 'orders/paypal/execute_payment' => 'orders#paypal_execute_payment', as: :paypal_execute_payment
This will create two new routes for creating and executing payments which will be handled in the paypal_create_payment
and paypal_execute_payment
orders controller methods.
Step 2: Create the PayPal service.
Add the singleton class Orders::Paypal
at: app/services/orders/paypal.rb
.
This service will initially have three responsibilities:
- The
create_payment
method creates a payment by callingPayPal::SDK::REST::Payment.new
. A token is generated and returned to the front-end. - The
execute_payment
method executes the payment by first finding the previous created payment object throughPayPal::SDK::REST::Payment.find(payment_id)
which uses the payment_id as an argument which has the same value as the charge_id stored in the previous step in the order object. After that, we callexecute
in the payment object with a given payer as the parameter. This payer is given by the front end after the user has provided credentials and selected a payment method in the popup. - The
finish
method finds an order by a specific charge_id querying for recently created orders in the paypal_executed state. If a record is found, it is marked as paid.
class Orders::Paypal def self.finish(charge_id) order = Order.paypal_executed.recently_created.find_by(charge_id: charge_id) return nil if order.nil? order.set_paid order end def self.create_payment(order:, product:) payment_price = (product.price_cents/100.0).to_s currency = "USD" payment = PayPal::SDK::REST::Payment.new({ intent: "sale", payer: { payment_method: "paypal" }, redirect_urls: { return_url: "/", cancel_url: "/" }, transactions: [{ item_list: { items: [{ name: product.name, sku: product.name, price: payment_price, currency: currency, quantity: 1 } ] }, amount: { total: payment_price, currency: currency }, description: "Payment for: #{product.name}" }] }) if payment.create order.token = payment.token order.charge_id = payment.id return payment.token if order.save end end def self.execute_payment(payment_id:, payer_id:) order = Order.recently_created.find_by(charge_id: payment_id) return false unless order payment = PayPal::SDK::REST::Payment.find(payment_id) if payment.execute( payer_id: payer_id ) order.set_paypal_executed return order.save end end
Step 3: Call the PayPal service in the controller in the submit action.
Add a callback for prepare_new_order
before the action paypal_create_payment
(which will be added in the next step) is requested by adding the following in the file app/controllers/orders_controller.rb
:
class OrdersController < ApplicationController before_action :authenticate_user! before_action :prepare_new_order, only: [:paypal_create_payment] ...
Again, in the same file, call PayPal service in the submit action by replacing the commented code #PAYPAL WILL BE HANDLED HERE.
con lo siguiente:
... elsif order_params[:payment_gateway] == "paypal" @order = Orders::Paypal.finish(order_params[:token]) end ...
Step 4: Create the actions for handling requests.
Still, in the app/controllers/orders_controller.rb
file, create two new actions (which should be public) for handling requests to paypal_create_payment
and paypal_execute_payment
routes:
- The
paypal_create_payment
method: Will call our service methodcreate_payment
. If that returns successfully, it will return the order token created byOrders::Paypal.create_payment
. - The
paypal_execute_payment
method: Will call our service methodexecute_payment
(which executes our payments). If the payment is performed successfully, it returns 200.
... def paypal_create_payment result = Orders::Paypal.create_payment(order: @order, product: @product) if result render json: { token: result }, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end end def paypal_execute_payment if Orders::Paypal.execute_payment(payment_id: params[:paymentID], payer_id: params[:payerID]) render json: {}, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end end ...
Step 5: Implement the front-end callback functions for createOrder
and onApprove
.
Make your paypal.Button.render
call look like this:
paypal.Buttons({ env: "#{ENV['PAYPAL_ENV']}", createOrder: function() { $('#order-type').val("paypal"); if (isPayment()) { return $.post("#{paypal_create_payment_url}", $('#order-details').serialize()).then(function(data) { return data.token; }); } else { } }, onApprove: function(data) { if (isPayment()) { return $.post("#{paypal_execute_payment_url}", { paymentID: data.paymentID, payerID: data.payerID }).then(function() { submitOrderPaypal(data.paymentID) }); } else { } } }).render('#submit-paypal');
As mentioned in the previous section, we call paypal_create_payment_url
for the createOrder
callback and paypal_execute_payment_url
for the onApprove
callback. Notice that if the last request returns success, we submit the order, which is the third request made to the server.
In the createOrder
function handler, we return a token (obtained from the back end). In the onApprove
callback, we have two properties passed down to our back-end paymentID
and payerID
. These will be used in order to execute the payment.
Finally, notice that we have two empty else
clauses as I'm leaving room for the next section where we will be adding PayPal subscriptions.
If you visit your page after integrating the front-end JavaScript section and select PayPal as the payment method, it should look like the following:

Step 6: Test your application.
- Visite la página de índice.
- Seleccione un producto de pago/cobro y PayPal como método de pago.
- Haga clic en el botón enviar PayPal.
- En la ventana emergente de PayPal:
- Utilice las credenciales de la cuenta de comprador que creó.
- Inicie sesión y confirme su pedido.
- La ventana emergente debería cerrarse.
- Compruebe si se le redirige a una página de éxito.
- Finalmente, verifique si el pedido se realizó en la cuenta de PayPal iniciando sesión con su cuenta comercial en https://www.sandbox.paypal.com/signin y revisando el tablero https://www.sandbox.paypal.com/listing /actas.
Suscripciones de PayPal
Los planes/acuerdos/suscripciones de PayPal siguen la misma lógica que las suscripciones de Stripe y se crean para pagos recurrentes. Con este tipo de productos se cobra al usuario de forma diaria, semanal, mensual o anual de forma automática según su configuración.
Usaremos el campo del producto paypal_plan_name
para almacenar el ID del plan proporcionado por PayPal. En este caso, a diferencia de Stripe, no elegimos el ID, y PayPal devuelve este valor al que se utilizará para actualizar el último producto creado en nuestra base de datos.
Para crear una suscripción, no se requiere información del customer
en ningún paso, ya que el método onApprove
probablemente maneja este vínculo en su implementación subyacente. Así que nuestras mesas seguirán siendo las mismas.
Paso 1: Cree un plan utilizando la API de PayPal.
Abra su consola usando los rails c
. Crea una suscripción para tu cuenta PayPal con:
plan = PayPal::SDK::REST::Plan.new({ name: 'Premium Plan', description: 'Premium Plan', type: 'fixed', payment_definitions: [{ name: 'Premium Plan', type: 'REGULAR', frequency_interval: '1', frequency: 'MONTH', cycles: '12', amount: { currency: 'USD', value: '100.00' } }], merchant_preferences: { cancel_url: 'http://localhost:3000/', return_url: 'http://localhost:3000/', max_fail_attempts: '0', auto_bill_amount: 'YES', initial_fail_amount_action: 'CONTINUE' } }) plan.create plan_update = { op: 'replace', path: '/', value: { state: 'ACTIVE' } } plan.update(plan_update)
Paso 2: actualice el último producto en la base de datos paypal_plan_name
con el plan.id
devuelto.
Correr:
Product.last.update(paypal_plan_name: plan.id)
Paso 3: agregue rutas para la suscripción de PayPal.
Agregue dos nuevas rutas en config/routes.rb
:
post 'orders/paypal/create_subscription' => 'orders#paypal_create_subscription', as: :paypal_create_subscription post 'orders/paypal/execute_subscription' => 'orders#paypal_execute_subscription', as: :paypal_execute_subscription
Paso 4: Manejar la creación y ejecución en el servicio de PayPal.
Agregue dos funciones más para crear y ejecutar suscripciones en Orders::Paypal
de app/services/orders/paypal.rb
:
def self.create_subscription(order:, product:) agreement = PayPal::SDK::REST::Agreement.new({ name: product.name, description: "Subscription for: #{product.name}", start_date: (Time.now.utc + 1.minute).iso8601, payer: { payment_method: "paypal" }, plan: { id: product.paypal_plan_name } }) if agreement.create order.token = agreement.token return agreement.token if order.save end end def self.execute_subscription(token:) order = Order.recently_created.find_by(token: token) return false unless order agreement = PayPal::SDK::REST::Agreement.new agreement.token = token if agreement.execute order.charge_id = agreement.id order.set_paypal_executed return order.charge_id if order.save end end
En create_subscription
, inicializamos un acuerdo llamando al método PayPal::SDK::REST::Agreement.new
y pasando product.paypal_plan_name
como uno de sus atributos. Luego lo creamos, y ahora se establecerá un token para este último objeto. También devolvemos el token al front-end.
En execute_subscription
encontramos el registro de order
creado en la llamada anterior. Después de eso, inicializamos un nuevo acuerdo, configuramos el token de este objeto anterior y lo ejecutamos. Si este último paso se realiza con éxito, el estado del pedido se establece en paypal_ejecutado . Y ahora devolvemos al front-end el ID del acuerdo que también se almacena en order.chager_id
.
Paso 5: agregue acciones para crear y ejecutar suscripciones en orders_controller
.
Cambie app/controllers/orders_controller.rb
. En la parte superior de la Clase, en primer lugar, y luego actualice la devolución de llamada prepare_new_order
para que también se ejecute antes de llamar a paypal_create_subscription
:
class OrdersController < ApplicationController before_action :authenticate_user! before_action :prepare_new_order, only: [:paypal_create_payment, :paypal_create_subscription]
Además, en el mismo archivo agregue las dos funciones públicas para que llamen al servicio Orders::Paypal
con un flujo similar al que ya tenemos en los pagos de PayPal:
... def paypal_create_subscription result = Orders::Paypal.create_subscription(order: @order, product: @product) if result render json: { token: result }, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end end def paypal_execute_subscription result = Orders::Paypal.execute_subscription(token: params[:subscriptionToken]) if result render json: { id: result}, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end end ...
Paso 6: Adición de controladores de suscripción para las devoluciones de llamadas createOrder
y onApprove
en el front-end.
Finalmente, en index.html.haml
, reemplace la función paypal.Buttons
con lo siguiente, que llenará los dos vacíos else
teníamos antes:
paypal.Buttons({ env: "#{ENV['PAYPAL_ENV']}", createOrder: function() { $('#order-type').val("paypal"); if (isPayment()) { return $.post("#{paypal_create_payment_url}", $('#order-details').serialize()).then(function(data) { return data.token; }); } else { return $.post("#{paypal_create_subscription_url}", $('#order-details').serialize()).then(function(data) { return data.token; }); } }, onApprove: function(data) { if (isPayment()) { return $.post("#{paypal_execute_payment_url}", { paymentID: data.paymentID, payerID: data.payerID }).then(function() { submitOrderPaypal(data.paymentID) }); } else { return $.post("#{paypal_execute_subscription_url}", { subscriptionToken: data.orderID }).then(function(executeData) { submitOrderPaypal(executeData.id) }); } } }).render('#submit-paypal');
La creación y ejecución de suscripciones tiene una lógica similar a la utilizada para los pagos. Una diferencia es que, al ejecutar pagos, los datos de la función de devolución de llamada onApprove
ya tienen un paymentID
. de pago que representa el charge_id
de cargo para enviar el formulario a través submitOrderPaypal(data.paymentID)
. Para suscripciones, obtenemos charge_id
solo después de ejecutarlo solicitando un POST
en paypal_execute_subscription_url
, por lo que podemos llamar a submitOrderPaypal(executeData.id)
.
Paso 7: Pruebe su aplicación.
- Visite la página de índice.
- Seleccione un producto de suscripción y PayPal como método de pago.
- Haga clic en el botón enviar PayPal.
- En la ventana emergente de PayPal:
- Utilice las credenciales de la cuenta de comprador que creó.
- Inicie sesión y confirme su pedido.
- La ventana emergente debería cerrarse.
- Compruebe si se le redirige a una página de éxito.
- Finalmente, verifique si el pedido se realizó en la cuenta de PayPal iniciando sesión con su cuenta comercial en https://www.sandbox.paypal.com/signin y revisando el tablero https://www.sandbox.paypal.com/listing/ actas.
Conclusión
Después de leer este artículo, debería poder integrar pagos/cargos, así como transacciones de suscripciones para PayPal y Stripe en su aplicación Rails. Hay muchos puntos que podrían mejorarse y que no agregué en este artículo en aras de la brevedad. Organicé todo en base a una suposición de dificultad:
- Más fácil:
- Use Transport Layer Security (TLS) para que sus solicitudes usen HTTPS.
- Implemente configuraciones de entorno de producción para PayPal y Stripe.
- Agregue una nueva página para que los usuarios puedan acceder a un historial de pedidos anteriores.
- Medio:
- Reembolsar o cancelar suscripciones.
- Proporcionar una solución para los pagos de usuarios no registrados.
- Más difícil:
- Proporcione una forma de eliminar cuentas y conservar su token y customer_id si el usuario desea volver. Pero después de una cierta cantidad de días, elimine estos datos para que su aplicación sea más compatible con PCI.
- Mover a la API de la versión 2 de PayPal en el lado del servidor (https://developer.paypal.com/docs/api/payments/v2/) La gema que usamos en este tutorial paypal-sdk-rest , solo tiene una versión beta para la versión 2, por lo que podría usarse con precaución (https://github.com/paypal/PayPal-Ruby-SDK/tree/2.0-beta).
- Incluir solicitudes idempotentes.
- Banda: https://stripe.com/docs/api/idempotent_requests
- PayPal: https://developer.paypal.com/docs/api-basics/#api-idempotency
También recomiendo leer sobre el elemento Stripe Checkout, que es otra forma de integrar Stripe en el front-end. A diferencia de Stripe Elements, que usamos en este tutorial, Stripe Checkout abre una ventana emergente después de hacer clic en un botón (similar a PayPal) donde el usuario completa la información de la tarjeta de crédito O elige pagar con Google Pay/Apple Pay https://stripe.com /docs/web.
Una recomendación de segunda lectura son las páginas de seguridad de ambas pasarelas de pago.
- Para rayas
- para paypal
Finalmente, ¡gracias por leer este artículo! También puede consultar mi proyecto de GitHub utilizado para este ejemplo de proyecto. Allí, también agregué pruebas de rspec durante el desarrollo.