Integrando métodos de pagamento Stripe e PayPal em Ruby on Rails

Publicados: 2022-03-11

Um recurso importante para grandes empresas de comércio eletrônico, como AliExpress, Ebay e Amazon, é uma maneira segura de lidar com pagamentos, essencial para seus negócios. Se esse recurso falhar, as consequências seriam devastadoras. Isso se aplica a líderes do setor e desenvolvedores Ruby on Rails que trabalham em aplicativos de comércio eletrônico.

A cibersegurança é essencial para prevenir ataques, e uma forma de tornar o processo de transação mais seguro é pedir a um serviço de terceiros para lidar com isso. Incluir gateways de pagamento em seu aplicativo é uma forma de atingir esse objetivo, pois eles fornecem autorização do usuário, criptografia de dados e um painel para que você possa acompanhar o status da transação em tempo real.

Há uma variedade de serviços de gateway de pagamento na web, mas neste artigo, focarei na integração do Stripe e do PayPal a um aplicativo Rails. Para mencionar alguns outros: Amazon Payments, Square, SecurePay, WorldPay, Authorize.Net, 2Checkout.com, Braintree, Amazon ou BlueSnap.

Como funciona a integração do gateway de pagamento

Representação geral para transações envolvendo gateways de pagamento
Representação geral para transações envolvendo gateways de pagamento

Em geral, haverá um formulário/botão em seu aplicativo onde o usuário poderá fazer login/inserir dados de cartão de crédito. O PayPal e o Stripe já tornam essa primeira etapa mais segura usando formulários iframe ou pop- popups que impedem que seu aplicativo armazene informações confidenciais de cartão de crédito do usuário, pois eles retornarão um token representando essa transação. Alguns usuários também podem se sentir mais confiantes para processar pagamentos sabendo que um serviço de terceiros está cuidando do processo de transação, então isso também pode ser um atrativo para seu aplicativo.

Depois de autenticar as informações do usuário, um gateway de pagamento confirmará o pagamento entrando em contato com um processador de pagamentos que se comunica com os bancos para liquidar os pagamentos. Isso garante que a transação seja debitada/creditada corretamente.

O Stripe usa um formulário de cartão de crédito solicitando o número do cartão de crédito, cvv e data de validade. Portanto, o usuário deve preencher as informações do cartão de crédito nas entradas seguras do Stripe. Depois de fornecer essas informações, o back-end do seu aplicativo processa esse pagamento por meio de um token.

Ao contrário do Stripe, o PayPal redireciona o usuário para a página de login do PayPal. O usuário autoriza e seleciona o método de pagamento por meio do PayPal e, novamente, seu back-end lidará com tokens em vez de dados confidenciais do usuário.

É importante mencionar que, para esses dois gateways de pagamento, seu back-end deve solicitar a execução da transação por meio de APIs Stripe ou PayPal que darão uma resposta OK/NOK, portanto, seu aplicativo deve redirecionar o usuário para uma página de sucesso ou erro.

A intenção deste artigo é fornecer um guia rápido para integrar esses dois gateways de pagamento em um único aplicativo. Para todos os testes, usaremos sandboxes e contas de teste fornecidas pelo Stripe e pelo PayPal para simular pagamentos.

Configuração

Antes de integrar os gateways de pagamento, faremos uma configuração para inicializar o aplicativo adicionando gems, tabelas de banco de dados e uma página de índice. Este projeto foi criado usando Rails versão 5.2.3 e Ruby 2.6.3.

Nota: Você pode conferir os novos recursos do Rails 6 em nosso artigo recente.

Passo 1: Inicialize uma aplicação Rails.

Inicialize o projeto executando a inicialização do projeto com o comando rails com o nome do seu aplicativo:

 rails new YOUR_APP_NAME

E cd na pasta do seu aplicativo.

Passo 2: Instale as gemas.

Além das gemas Stripe e PayPal, algumas outras gemas foram adicionadas:

  • devise : usado para autenticação e autorização do usuário
  • haml : ferramenta de modelagem para renderizar páginas de usuário
  • jquery-rails : para jquery nos scripts de front-end
  • money-rails : para exibir valores monetários formatados

Adicione ao seu Gemfile :

 gem "devise", ">= 4.7.1" gem "haml" gem "jquery-rails" gem "money-rails"

Depois de adicioná-lo, execute em sua CLI:

 bundle install

Passo 3: Inicialize as gemas.

Algumas dessas gems exigirão inicialização além de instalá-las por meio de bundle .

Instalando o dispositivo:

 rails g devise:install

Inicializando money-rails :

 rails g money_rails:initializer

Inicialize jquery-rails anexando na parte inferior de app/assets/javascripts/application.js o seguinte:

 //= require jquery //= require jquery_ujs

Etapa 4: tabelas e migrações

Três tabelas serão usadas neste projeto Usuários , Produtos e Pedidos .

  • Users : Será gerado através do devise
  • Colunas de Products :
    • name
    • price_cents
    • Stripe_plan_name : Um ID que representa um plano de assinatura criado no Stripe, para que os usuários possam assiná-lo. Este campo é obrigatório apenas para produtos associados a um plano Stripe.
    • paypal_plan_name : O mesmo que stripe_plan_name , mas para PayPal
  • Colunas de Orders :
    • product_id
    • user_id
    • status : Informará se o pedido está pendente, falhou ou foi pago.
    • token : Este é um token gerado a partir das APIs (Stripe ou PayPal) para inicializar uma transação.
    • price_cents : Semelhante ao produto, mas usado para tornar esse valor persistente no registro do pedido
    • payment_gateway : Armazena qual gateway de pagamento está sendo usado para o pedido PayPal ou Stripe
    • customer_id : Isso será usado para o Stripe para armazenar o cliente do Stripe para uma assinatura e será explicado com mais detalhes em uma seção posterior.

Para gerar essas tabelas, algumas migrações devem ser geradas:

Para criar a tabela Usuários . Corre:

 rails g devise User

Para criar a tabela Produtos . Gere uma migração executando:

 rails generate migration CreateProducts name:string stripe_plan_name:string paypal_plan_name:string

Abra o arquivo de migração criado, que deve estar localizado em db/migrate/ , e faça as alterações para que sua migração fique semelhante a esta:

 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 criar a tabela Pedidos . Gere uma migração executando:

 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

Novamente, abra o arquivo de migração criado, que deve estar localizado em db/migrate/ , e faça alterações nesse arquivo para torná-lo semelhante a este:

 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

Execute migrações de banco de dados executando:

 rails db:migrate

Etapa 5: criar modelos.

O modelo de usuário já foi criado a partir da instalação do dispositivo e nenhuma alteração será necessária nele. Além disso, serão criados dois modelos para Produto e Pedido .

Produtos. Adicione um novo arquivo, app/models/product.rb , com:

 class Product < ActiveRecord::Base monetize :price_cents has_many :orders end

Ordem. Adicione um novo arquivo, app/models/order.rb , com:

 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

Etapa 6: preencher o banco de dados.

Um usuário e dois produtos serão criados no console. Os registros de pedidos serão criados de acordo com os testes de pagamento.

  • Executar rails s
  • No seu navegador, visite http://localhost:3000
  • Você será redirecionado para uma página de inscrição.
  • Inscreva um usuário preenchendo seu endereço de e-mail e senha.
  • Em seu terminal, os seguintes logs serão solicitados mostrando que um usuário foi criado em seu banco de dados:
 User Create (0.1ms) INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?) …
  • Crie dois produtos sem assinaturas executando rails c e adicionando:
    • Product.create(name: "Awesome T-Shirt", price_cents: 3000)
    • Product.create(name: "Awesome Sneakers", price_cents: 5000)

Etapa 7: criar uma página de índice

A página principal do projeto inclui a seleção de produtos para compras ou assinaturas. Além disso, também possui uma seção para seleção do método de pagamento (Stripe ou PayPal). Um botão de envio também é usado para cada tipo de gateway de pagamento, pois para o PayPal adicionaremos seu próprio design de botão por meio de sua biblioteca JavaScript.

Primeiro, crie as rotas para index e submit em config/routes.rb .

 Rails.application.routes.draw do devise_for :users get '/', to: 'orders#index' post '/orders/submit', to: 'orders#submit' end

Crie e adicione o index ações e submit no controlador de pedidos app/controllers/orders_controller.rb . A ação orders#index armazena duas variáveis ​​a serem consumidas no front-end: @products_purchase que possui uma lista de produtos sem planos e @products_subscription que possui produtos com planos PayPal e 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

Crie um arquivo em app/views/orders/index.html.haml . Este arquivo contém todas as entradas que enviaremos ao nosso back-end através do método submit e a interação para gateways de pagamento e seleção de produtos. Aqui estão alguns atributos de nome de entrada:

  • Orders[product_id] armazena o ID do produto.
  • Orders[payment_gateway] contém o gateway de pagamento com valores Stripe ou PayPal para o outro.
 %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; }

Se você executar seu aplicativo com rails s e visitar sua página em http://localhost:3000 . Você deve ser capaz de ver a página da seguinte forma:

Página de índice bruto sem integração com Stripe e PayPal
Página de índice bruto sem integração com Stripe e PayPal

Armazenamento de credenciais do gateway de pagamento

As chaves do PayPal e do Stripe serão armazenadas em um arquivo não rastreado pelo Git. Existem dois tipos de chaves armazenadas neste arquivo para cada gateway de pagamento e, por enquanto, usaremos um valor fictício para elas. Instruções adicionais para criar essas chaves são apresentadas em outras seções.

Etapa 1: adicione isso em .gitignore .

 /config/application.yml

Etapa 2: crie um arquivo com suas credenciais em config/application.yml . Ele deve conter todas as chaves de teste/sandbox do PayPal e Stripe para acessar essas APIs.

 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

Passo 3: Para armazenar as variáveis ​​do arquivo config/application.yml quando a aplicação iniciar, adicione estas linhas em config/application.rb dentro da classe Application para que fiquem disponíveis em ENV .

 config_file = Rails.application.config_for(:application) config_file.each do |key,value| ENV[key] = value end unless config_file.nil?

Configuração de faixa

Estaremos adicionando uma gem para usar a API Stripe: stripe-rails . A criação de uma conta Stripe também é necessária para que cobranças e assinaturas possam ser processadas. Se necessário, você pode consultar os métodos de API para a API Stripe na documentação oficial.

Etapa 1: adicione a gem stripe-rails ao seu projeto.

A gem stripe-rails fornecerá uma interface para todas as solicitações de API usadas neste projeto.

Adicione isso no Gemfile :

 gem 'stripe-rails'

Corre:

 bundle install

Etapa 2: gere suas chaves de API.

Para ter as chaves de API para comunicação com o Stripe, você precisará criar uma conta no Stripe. Para testar o aplicativo, é possível usar o modo de teste, portanto, nenhuma informação comercial real precisa ser preenchida no processo de criação da conta Stripe.

  • Crie uma conta no Stripe se você não tiver uma (https://dashboard.stripe.com/).
  • Ainda no painel do Stripe, após fazer login, ative Exibir dados de teste .
  • Em https://dashboard.stripe.com/test/apikeys, substitua YOUR_CREDENTIAL_HERE pelos valores STRIPE_PUBLISHABLE_KEY e STRIPE_SECRET_KEY em /config/application.yml pelo conteúdo de Publishable Key e Secret key .

Etapa 3: inicializar o módulo Stripe

Além de substituir as chaves, ainda precisamos inicializar o módulo Stripe, para que ele utilize as chaves já configuradas em nosso ENV .

Crie um arquivo em config/initializers/stripe.rb com:

 Rails.application.configure do config.stripe.secret_key = ENV["STRIPE_SECRET_KEY"] config.stripe.publishable_key = ENV["STRIPE_PUBLISHABLE_KEY"] end

Etapa 4: Integre o Stripe no front-end.

Adicionaremos a biblioteca JavaScript Stripe e a lógica para enviar um token que representa as informações do cartão de crédito do usuário e será processado em nosso back-end.

No arquivo index.html.haml , adicione-o ao topo do arquivo. Isso usará o módulo Stripe (fornecido pela gem) para adicionar a biblioteca javascript Stripe à página do usuário.

 = stripe_javascript_tag

O Stripe usa campos de entrada seguros que são criados por meio de sua API. Como eles são criados em um iframe criado por meio dessa API, você não precisa se preocupar com possíveis vulnerabilidades ao lidar com as informações do cartão de crédito do usuário. Além disso, seu back-end não poderá processar/armazenar dados confidenciais do usuário e receberá apenas um token representando essas informações.

Esses campos de entrada são criados chamando stripe.elements().create('card') . Depois disso, basta chamar o objeto retornado com mount() passando como argumento o id/class do elemento HTML onde essas entradas devem ser montadas. Mais informações podem ser encontradas em Stripe.

Quando o usuário clica no botão enviar com o método de pagamento Stripe, outra chamada de API retornando uma promessa é executada no elemento do cartão Stripe criado:

 stripe.createToken(card).then(function(result)

A variável result desta função, se não tiver um erro de propriedade atribuído, terá um token que pode ser recuperado acessando o atributo result.token.id . Este token será enviado para o back-end.

Para fazer essas alterações, substitua o código comentado // YOUR STRIPE AND PAYPAL CODE WILL BE HERE in index.html.haml por:

 (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

Se você visitar sua página, ela deve ter a seguinte aparência com os novos campos de entrada seguros do Stripe:

Página de índice integrada com campos de entrada seguros do Stripe.
Página de índice integrada com campos de entrada seguros do Stripe.

Etapa 5: teste seu aplicativo.

Preencha o formulário do cartão de crédito com um cartão de teste (https://stripe.com/docs/testing) e envie a página. Verifique se a ação de submit é chamada com todos os parâmetros ( product_id , payment_gateway e token ) na saída do servidor.

Taxas de distribuição

As cobranças de distribuição representam transações únicas. Portanto, após uma transação de cobrança do Stripe, você receberia dinheiro diretamente do cliente. Isso é ideal para vender produtos que não estão associados a planos. Em uma seção posterior, mostrarei como fazer o mesmo tipo de transação com o PayPal, mas o nome do PayPal para esse tipo de transação é Payment .

Nesta seção também fornecerei todo o esqueleto para manuseio e envio de um pedido. Criamos um pedido na ação submit quando o formulário Stripe é enviado. Este pedido terá inicialmente o status pendente , portanto, se algo der errado durante o processamento deste pedido, o pedido ainda estará pendente .

Se surgir algum erro das chamadas da API Stripe, definimos o pedido em um estado de falha e, se a cobrança for concluída com êxito, ele estará no estado pago . O usuário também é redirecionado de acordo com a resposta da API Stripe, conforme mostrado no gráfico a seguir:

Transações de faixa.
Transações de faixa.

Além disso, quando uma cobrança do Stripe é realizada, um ID é retornado. Estaremos armazenando esse ID para que você possa procurá-lo posteriormente no painel do Stripe, se necessário. Este ID também pode ser usado se o pedido tiver que ser reembolsado. Tal coisa não será explorada neste artigo.

Etapa 1: crie o serviço Stripe.

Usaremos uma classe singleton para representar as operações do Stripe usando a API do Stripe. Para criar uma cobrança, o método Stripe::Charge.create é chamado, e o atributo ID do objeto retornado será armazenado no registro do pedido charge_id . Essa função de create é chamada passando o token originado no front-end, o preço do pedido e uma descrição.

Então, crie uma nova pasta app/services/orders , e adicione um serviço Stripe: app/services/orders/stripe.rb contendo a classe singleton Orders::Stripe , que tem uma entrada no 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

Etapa 2: Implemente a ação de envio e chame o serviço Stripe.

Em orders_controller.rb , adicione o seguinte na ação submit , que basicamente chamará o serviço Orders::Stripe.execute . Observe que duas novas funções privadas também foram adicionadas: prepare_new_order e 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

Etapa 3: teste seu aplicativo.

Verifique se a ação submit, quando chamada com um cartão de teste válido, realiza um redirecionamento para uma mensagem de sucesso. Além disso, verifique no painel do Stripe se o pedido também é exibido.

Assinaturas do Stripe

Assinaturas ou planos podem ser criados para pagamentos recorrentes. Com este tipo de produto, o usuário é cobrado diariamente, semanalmente, mensalmente ou anualmente de forma automática de acordo com a configuração do plano. Nesta seção, usaremos o campo do produto stripe_plan_name para armazenar o ID do plano - na verdade, é possível escolher o ID e o chamaremos premium-plan que será usado para criar o relação customer <-> subscription .

Também criaremos uma nova coluna para a tabela users chamada stripe_customer_id que será preenchida com a propriedade id de um objeto cliente Stripe. Um cliente Stripe é criado quando a função Stripe::Customer.create é chamada, e você também pode verificar os clientes criados e vinculados à sua conta em (https://dashboard.stripe.com/test/customers). Os clientes são criados passando um parâmetro source que, no nosso caso, é o token gerado no front end que é enviado quando o formulário é enviado.

O objeto cliente obtido da última chamada da API Stripe mencionada também é usado para criar uma assinatura que é feita chamando customer.subscriptions.create e passando o ID do plano como parâmetro.

Além disso, a gem stripe-rails fornece a interface para recuperar e atualizar um cliente do Stripe, o que é feito chamando Stripe::Customer.retrieve e Stripe::Customer.update , respectivamente.

Assim, quando um registro de usuário já possui um stripe_customer_id , ao invés de criar um novo cliente usando Stripe::Customer.create , chamaremos Stripe::Customer.retrieve passando o stripe_customer_id como parâmetro, seguido por um Stripe::Customer.update , e neste caso, passando o token um parâmetro.

Primeiramente estaremos criando um plano usando a API Stripe para que possamos criar um novo produto de assinatura usando o campo stripe_plan_name . Em seguida, faremos modificações no serviço orders_controller e Stripe para que seja tratada a criação e execução de assinaturas do Stripe.

Etapa 1: crie um plano usando a API Stripe.

Abra seu console usando os rails c . Crie uma assinatura para sua conta Stripe com:

 Stripe::Plan.create({ amount: 10000, interval: 'month', product: { name: 'Premium plan', }, currency: 'usd', id: 'premium-plan', })

Se o resultado retornado nesta etapa for verdadeiro, significa que o plano foi criado com sucesso e você pode acessá-lo em seu painel do Stripe.

Etapa 2: crie um produto no banco de dados com o conjunto de campos stripe_plan_name .

Agora, crie um produto com o stripe_plan_name definido como premium-plan no banco de dados:

 Product.create(price_cents: 10000, name: 'Premium Plan', stripe_plan_name: 'premium-plan')

Etapa 3: gere uma migração para adicionar uma coluna stripe_customer_id na tabela de users .

Execute o seguinte no terminal:

 rails generate migration AddStripeCustomerIdToUser stripe_customer_id:string rails db:migrate

Etapa 4: Implemente a lógica de assinatura na classe de serviço Stripe.

Adicione mais duas funções nos métodos privados de app/services/orders/stripe.rb : execute_subscription é responsável por criar as assinaturas no objeto do cliente. A função find_or_create_customer é responsável por devolver o cliente já criado ou retornando um cliente recém-criado.

 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

Por fim, na função execute no mesmo arquivo ( app/services/orders/stripe.rb ), primeiro chamaremos find_or_create_customer e, em seguida, executaremos a assinatura chamando execute_subscription passando o cliente recuperado/criado anterior. Então, substitua o comentário #SUBSCRIPTIONS WILL BE HANDLED HERE no método execute pelo seguinte 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)

Etapa 5: teste seu aplicativo.

Visite seu site, selecione o produto de assinatura Premium Plan e preencha um cartão de teste válido. Após o envio, ele deve redirecioná-lo para uma página de sucesso. Além disso, verifique no painel do Stripe se a assinatura foi criada com sucesso.

Configuração do PayPal

Como fizemos no Stripe, também adicionaremos uma gem para usar a API do PayPal: paypal-sdk-rest , e também é necessário criar uma conta no PayPal. Um fluxo de trabalho descritivo para o PayPal usando esta gema pode ser consultado na documentação oficial da API do PayPal.

Etapa 1: adicione a gem paypal-sdk-rest ao seu projeto.

Adicione isso no Gemfile :

 gem 'paypal-sdk-rest'

Corre:

 bundle install

Etapa 2: gere suas chaves de API.

Para ter as chaves de API para comunicação com o PayPal, você precisará criar uma conta no PayPal. Assim:

  • Crie uma conta (ou use sua conta do PayPal) em https://developer.paypal.com/.
  • Ainda logado em sua conta, crie duas contas de sandbox em https://developer.paypal.com/developer/accounts/:
    • Pessoal (Conta do Comprador) – Isso será usado em seus testes para fazer pagamentos e assinaturas.
    • Business (Merchant Account) – Esta será vinculada ao aplicativo, que terá as chaves de API que estamos procurando. Além disso, todas as transações podem ser acompanhadas nesta conta.
  • Crie um aplicativo em https://developer.paypal.com/developer/applications usando a conta de sandbox empresarial anterior.
  • Após esta etapa, você receberá duas chaves para o PayPal: Client ID e Secret .
  • Em config/application.yml , substitua YOUR_CREDENTIAL_HERE de PAYPAL_CLIENT_ID e PAYPAL_CLIENT_SECRET pelas chaves que você acabou de receber.

Etapa 3: inicialize o módulo do PayPal.

Semelhante ao Stripe, além de substituir as chaves em application.yml , ainda precisamos inicializar o módulo do PayPal para que ele possa usar as chaves já definidas em nossa variável ENV . Para isso, crie um arquivo em config/initializers/paypal.rb com:

 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

Etapa 4: Integre o PayPal no front-end.

Em index.html.haml adicione isto ao topo do arquivo:

 %script(src="https://www.paypal.com/sdk/js?client-id=#{ENV['PAYPAL_CLIENT_ID']}")

Ao contrário do Stripe, o PayPal usa apenas um botão que, ao ser clicado, abre um popup seguro onde o usuário pode fazer o login e proceder ao pagamento/assinatura. Este botão pode ser renderizado chamando o método paypal.Button(PARAM1).render(PARAM2) .

  • PARAM1 é um objeto com a configuração do ambiente e duas funções de callback como propriedades: createOrder e onApprove .
  • PARAM2 indica o identificador do elemento HTML ao qual o botão do PayPal deve ser anexado.

Assim, ainda no mesmo arquivo, substitua o código comentado YOUR PAYPAL CODE WILL BE HERE por:

 (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'); }());

Etapa 5: teste seu aplicativo.

Visite sua página e verifique se o botão do PayPal é renderizado quando você seleciona o PayPal como forma de pagamento.

Transações do PayPal

A lógica das transações do PayPal, ao contrário do Stripe, é um pouco mais complexa, pois envolve mais solicitações originadas do front-end para o back-end. É por isso que esta seção existe. Estarei explicando mais ou menos (sem nenhum código) como as funções descritas nos métodos createOrder e onApprove serão implementadas e o que se espera nos processos de back-end também.

Etapa 1: quando o usuário clica no botão de envio do PayPal, um pop-up do PayPal solicitando as credenciais do usuário é aberto, mas em estado de carregamento. A função callback createOrder é chamada.

Popup do PayPal, estado de carregamento
Popup do PayPal, estado de carregamento

Passo 2: Nesta função, realizaremos uma solicitação ao nosso back-end que criará um pagamento/assinatura. Este é o início de uma transação e nenhuma cobrança será aplicada ainda, portanto, a transação está realmente em um estado pendente . Nosso back-end deve nos retornar um token, que será gerado usando o módulo PayPal (fornecido através da gem paypal-rest-sdk ).

Passo 3: Ainda no callback createOrder , retornamos este token gerado em nosso back-end, e se estiver tudo ok, o pop-up do PayPal irá renderizar o seguinte, solicitando as credenciais do usuário:

Pop-up do PayPal, credenciais do usuário
Pop-up do PayPal, credenciais do usuário

Etapa 4: Depois que o usuário fizer login e selecionar a forma de pagamento, o pop-up mudará seu estado para o seguinte:

Pop-up do PayPal, transação autorizada
Pop-up do PayPal, transação autorizada

Etapa 5: O retorno de chamada da função onApprove agora é chamado. Nós o definimos da seguinte forma: onApprove: function(data) . O objeto data terá as informações de pagamento para executá-lo. Neste callback, outra requisição para nossa função back-end será realizada desta vez passando o objeto de dados para executar o pedido do PayPal.

Etapa 6: nosso back-end executa essa transação e retorna 200 (se for bem-sucedido).

Passo 7: Quando nosso back-end retorna, enviamos o formulário. Este é o terceiro pedido que fazemos ao nosso back-end.

Observe que, diferentemente do Stripe, há três solicitações feitas ao nosso back-end nesse processo. E manteremos o status do registro do pedido sincronizado de acordo:

  • callback createOrder : Uma transação é criada e um registro de pedido também é criado; portanto, ele está em um estado pendente como padrão.
  • callback onApprove : A transação é executada e nosso pedido será definido como paypal_executed .
  • A página do pedido é enviada: A transação já foi executada, então nada muda. O registro do pedido mudará seu estado para pago .

Todo esse processo é descrito no gráfico a seguir:

Transações do PayPal
Transações do PayPal

Pagamentos do PayPal

Os pagamentos do PayPal seguem a mesma lógica dos Stripe Charges, portanto, representam transações únicas, mas, conforme mencionado na seção anterior, possuem uma lógica de fluxo diferente. Estas são as alterações que precisarão ser realizadas para lidar com pagamentos do PayPal:

Etapa 1: crie novas rotas para o PayPal e execute pagamentos.

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 calling PayPal::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 through PayPal::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 call execute 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. com o seguinte:

 ... 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 method create_payment . If that returns successfully, it will return the order token created by Orders::Paypal.create_payment .
  • The paypal_execute_payment method: Will call our service method execute_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:

Index page after integration with PayPal
Index page after integration with PayPal

Step 6: Test your application.

  • Visite a página de índice.
  • Selecione um produto de pagamento/cobrança e PayPal como método de pagamento.
  • Clique no botão enviar PayPal.
  • No pop-up do PayPal:
    • Use as credenciais da conta de comprador que você criou.
    • Faça login e confirme seu pedido.
    • O pop-up deve fechar.
  • Verifique se você foi redirecionado para uma página de sucesso.
  • Por fim, verifique se o pedido foi realizado na conta do PayPal acessando sua conta comercial em https://www.sandbox.paypal.com/signin e verificando o painel https://www.sandbox.paypal.com/listing /transações.

Assinaturas do PayPal

Os planos/acordos/assinaturas do PayPal seguem a mesma lógica das assinaturas do Stripe e são criados para pagamentos recorrentes. Com este tipo de produto o usuário é cobrado diariamente, semanalmente, mensalmente ou anualmente de forma automática de acordo com sua configuração.

Usaremos o campo para o produto paypal_plan_name , para armazenar o ID do plano fornecido pelo PayPal. Nesse caso, diferentemente do Stripe, não escolhemos o ID, e o PayPal devolve esse valor para o qual será utilizado para atualizar o último produto criado em nosso banco de dados.

Para criar uma assinatura, nenhuma informação do customer é necessária em nenhuma etapa, pois o método onApprove provavelmente lida com essa ligação em sua implementação subjacente. Portanto, nossas tabelas permanecerão as mesmas.

Etapa 1: crie um plano usando a API do PayPal.

Abra seu console usando os rails c . Crie uma assinatura para sua conta do PayPal com:

 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)

Etapa 2: atualize o último produto no banco de dados paypal_plan_name com o plan.id retornado.

Corre:

 Product.last.update(paypal_plan_name: plan.id)

Etapa 3: adicione rotas para assinatura do PayPal.

Adicione duas novas rotas em 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

Etapa 4: Gerencie a criação e a execução no serviço do PayPal.

Adicione mais duas funções para criar e executar assinaturas em 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

Em create_subscription , inicializamos um contrato chamando o método PayPal::SDK::REST::Agreement.new e passando o product.paypal_plan_name como um de seus atributos. Depois, nós o criamos, e agora um token será definido para este último objeto. Também devolvemos o token ao front-end.

Em execute_subscription , encontramos o registro do order criado na chamada anterior. Depois disso, inicializamos um novo contrato, definimos o token desse objeto anterior e o executamos. Se esta última etapa for realizada com sucesso, o status do pedido será definido como paypal_executed . E agora retornamos ao front-end o ID do contrato que também está armazenado em order.chager_id .

Etapa 5: adicione ações para criar e executar assinaturas em orders_controller .

Altere o app/controllers/orders_controller.rb . No topo da classe, primeiro, e depois atualize o callback prepare_new_order para também ser executado antes paypal_create_subscription seja chamado:

 class OrdersController < ApplicationController before_action :authenticate_user! before_action :prepare_new_order, only: [:paypal_create_payment, :paypal_create_subscription]

Além disso, no mesmo arquivo adicione as duas funções públicas para que chamem o serviço Orders::Paypal com um fluxo semelhante ao que já temos nos pagamentos do 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 ...

Etapa 6: adicionar manipuladores de assinatura para retornos de chamada createOrder e onApprove no front-end.

Finalmente, em index.html.haml , substitua a função paypal.Buttons pela seguinte, que preencherá os dois else vazios que tínhamos 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');

A criação e execução de assinaturas tem uma lógica semelhante à usada para pagamentos. Uma diferença é que ao executar pagamentos, os dados da função de callback onApprove já possuem um paymentID representando o charge_id para enviar o formulário por meio submitOrderPaypal(data.paymentID) . Para assinaturas, obtemos o charge_id somente após executá-lo solicitando um POST em paypal_execute_subscription_url , para que possamos chamar submitOrderPaypal(executeData.id) .

Etapa 7: teste seu aplicativo.

  • Visite a página de índice.
  • Selecione um produto de assinatura e o PayPal como método de pagamento.
  • Clique no botão enviar PayPal.
  • No pop-up do PayPal:
    • Use as credenciais da conta de comprador que você criou.
    • Faça login e confirme seu pedido.
    • O pop-up deve fechar.
  • Verifique se você foi redirecionado para uma página de sucesso.
  • Por fim, verifique se o pedido foi realizado na conta do PayPal acessando sua conta comercial em https://www.sandbox.paypal.com/signin e verificando o painel https://www.sandbox.paypal.com/listing/ transações.

Conclusão

Depois de ler este artigo, você poderá integrar pagamentos/cobranças, bem como transações de assinaturas para PayPal e Stripe em seu aplicativo Rails. Há muitos pontos que podem ser melhorados que não adicionei neste artigo por uma questão de brevidade. Organizei tudo com base em uma suposição de dificuldade:

  • Mais fácil:
    • Use Transport Layer Security (TLS) para que suas solicitações usem HTTPS.
    • Implemente configurações de ambiente de produção para PayPal e Stripe.
    • Adicione uma nova página para que os usuários possam acessar um histórico de pedidos anteriores.
  • Médio:
    • Reembolsar ou cancelar assinaturas.
    • Fornecer uma solução para pagamentos de usuários não registrados.
  • Mais difícil:
    • Forneça uma maneira de remover contas e reter seu token e customer_id se o usuário desejar voltar. Mas depois de alguns dias, remova esses dados para que seu aplicativo seja mais compatível com PCI.
    • Mude para a API do PayPal versão 2 no lado do servidor (https://developer.paypal.com/docs/api/payments/v2/) A gem que usamos neste tutorial paypal-sdk-rest , só tem uma versão beta para versão 2, para que possa ser usado com cautela (https://github.com/paypal/PayPal-Ruby-SDK/tree/2.0-beta).
    • Inclua solicitações idempotentes.
      • Faixa: https://stripe.com/docs/api/idempotent_requests
      • PayPal: https://developer.paypal.com/docs/api-basics/#api-idempotency

Também recomendo ler sobre o elemento Stripe Checkout, que é outra maneira de integrar o Stripe no front-end. Ao contrário do Stripe Elements, que usamos neste tutorial, o Stripe Checkout abre um pop-up após clicar em um botão (semelhante ao PayPal) onde o usuário preenche as informações do cartão de crédito OU opta por pagar com Google Pay/Apple Pay https://stripe.com /docs/web.

Uma segunda recomendação de leitura são as páginas de segurança para ambos os Gateways de Pagamento.

  • Para Faixa
  • Para PayPal

Finalmente, obrigado por ler este artigo! Você também pode verificar meu projeto GitHub usado para este exemplo de projeto. Lá, adicionei testes rspec também durante o desenvolvimento.