authors are vetted experts in their fields and write on topics in which they have demonstrated experience. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Henrique Reinaldo Sarmento的头像

Henrique Reinaldo Sarmento

Henrique is a passionate full-stack developer and cloud computing enthusiast with experience in Ruby on Rails, Flask, Javascript and React.

Previously At

Universite de Lorraine
Share

这是全球速卖通(AliExpress)等大型电子商务公司的一个关键特征, Ebay, 而亚马逊是一种安全的支付方式, 哪些对他们的业务至关重要. 如果这个功能失败,后果将是毁灭性的. 这适用于行业领导者和 Ruby on Rails developers working on eCommerce apps.

网络安全对于防止攻击至关重要, and a way to make the transaction process more secure is asking a third-party service to handle it. 在应用程序中包含支付网关是实现这一目标的一种方法, 因为它们提供用户授权, data encryption, 还有一个仪表板,这样你就可以随时跟踪交易状态.

网络上有各种各样的支付网关服务, but in this article, 我将着重于积分 Stripe and PayPal to a Rails application. 再举几个例子:Amazon Payments、Square、secupay、WorldPay和Authorize.Net, 2Checkout.com、Braintree、Amazon或BlueSnap.

支付网关集成如何工作

涉及支付网关的交易的一般表示

In general, 在您的应用程序中将有一个表单/按钮,用户可以在其中登录/插入信用卡数据. PayPal和Stripe已经通过使用 iframe forms or popups which prevent your application from storing sensitive user credit card info as they will return a token representing this transaction. Some users also might already feel more confident to process payments by knowing that a third-party service is handling the transaction process, 所以这也可以成为你的应用程序的吸引力.

用户信息认证完成后, a payment gateway will confirm the payment by contacting a payment processor which communicates with banks in order to settle payments. 这确保了交易被恰当地借记/贷记.

Stripe使用信用卡表单询问信用卡号码、cvv和截止日期. 因此,用户必须在安全的Stripe输入中填写信用卡信息. After providing this information, your application back end processes this payment through a token.

与Stripe不同,PayPal将用户重定向到PayPal登录页面. 用户通过PayPal授权并选择支付方式, and again, 您的后端将处理令牌而不是用户敏感数据.

提到这一点很重要, 对于这两个支付网关, your back end should ask for proceeding transaction execution through Stripe or PayPal APIs which will give a OK/NOK response, 因此,应用程序应该相应地将用户重定向到成功或错误页面.

The intent of this article is to provide a quick guide for integrating these two payment gateways in a single application. For all tests, we will be using sandboxes and test accounts provided by Stripe and PayPal in order to simulate payments.

Setup

在整合支付网关之前, 我们将通过添加gems来初始化应用程序, database tables, and an index page. 这个项目是使用Rails版本5创建的.2.3 and Ruby 2.6.3.

Note: You can check out new 我们最近的文章介绍了Rails 6的特性.

Step 1: 初始化Rails应用程序.

方法运行项目初始化,从而初始化项目 rails 命令与您的应用程序名称:

rails new YOUR_APP_NAME

And cd in your application folder.

Step 2: Install gems.

除了Stripe和PayPal宝石,我们还添加了其他一些宝石:

  • devise:用于用户认证和授权
  • haml:呈现用户页面的模板工具
  • jquery-rails: for jquery in the front-end scripts
  • money-rails:用于显示格式化的货币值

Add to your Gemfile:

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

添加后,在命令行中运行:

bundle install

Step 3: Initialize gems.

除了安装它们之外,其中一些gem还需要初始化 bundle.

Installing devise:

rails g devise:install

Initializing money-rails:

money_rails:初始化器

Initialize jquery-rails 通过附加到的底部 应用程序/资产/ javascript /应用程序.js the following:

//= require jquery
//= require jquery_ujs

Step 4: Tables and migrations

本项目将使用三个表 Users, Products, and Orders.

  • Users:将通过设计产生
  • Products columns:
    • name
    • price_cents
    • Stripe_plan_name:表示在Stripe中创建的订阅计划的ID,以便用户可以订阅该计划. 仅分条计划关联的产品需要填写.
    • paypal_plan_name: The same as stripe_plan_name but for PayPal
  • Orders columns:
    • product_id
    • user_id
    • status:这将通知订单是否悬而未决,失败,或支付.
    • token: This is a token generated from the APIs (either Stripe or PayPal) in order to initialize a transaction.
    • price_cents:与产品类似,但用于使此值在订单记录中持久存在
    • payment_gateway:存储PayPal或Stripe订单所使用的支付网关
    • customer_id:这将用于Stripe,以便存储Stripe客户的订阅, 后面的部分将对此进行更详细的解释.

为了生成这些表,必须生成一些迁移:

For creating the Users table. Run:

rails g devise User

For creating the Products table. 执行以下命令生成迁移:

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

打开您创建的迁移文件,它应该位于 db/migrate/,并进行更改,使您的迁移看起来类似如下:

class CreateProducts < ActiveRecord::Migration[5.2]
  def change
    Create_table:product do |t|
      t.string :name
      t.string :stripe_plan_name
      t.string :paypal_plan_name
    end
    Add_money:产品,:价格,货币:{present: true}
  end
end

For creating the Orders table. 执行以下命令生成迁移:

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

再次打开您创建的迁移文件,该文件应该位于 db/migrate/ 并对该文件进行更改,使其看起来像这样:

class CreateOrders < ActiveRecord::Migration[5.2]
  def change
    Create_table:order do |t|
      t.integer :product_id
      t.integer :user_id
      t.整数:状态,默认值: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:订单,价格,货币:{present: false}
  end
end

通过执行以下命令运行数据库迁移:

rails db:migrate

Step 5: Create models.

用户模型已经从设计安装中创建,不需要对其进行任何更改. 除此之外,还将创建两个模型 Product and Order.

Product. Add a new file, app/models/product.rb, with:

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

Order. Add a new file, app/models/order.rb, with:

class Order < ApplicationRecord
  Enum状态:{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

Step 6: Populate the database.

将在控制台中创建一个用户和两个产品. 订单记录将根据付款测试创建.

  • Run rails s
  • In your browser, visit http://localhost:3000
  • 您将被重定向到注册页面.
  • 通过填写用户的电子邮件地址和密码来注册用户.
  • In your terminal, 将提示以下日志,显示在数据库中创建了一个用户:
User Create (0.INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?) …
  • 运行创建两个没有订阅的产品 rails c and adding:
    • Product.创建(name:“Awesome t恤”,price_cents: 3000)
    • Product.create(name:“Awesome Sneakers”,price_cents: 5000)

Step 7: Create an index page

该项目的主页面包括购买或订阅的产品选择. 此外,它也有一个支付方式选择部分(Stripe或PayPal)。. A submit button is also used for each payment gateway type as for PayPal we will add its own button design through its JavaScript library.

首先,为 index and submit in config/routes.rb.

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

Create and add actions index and submit in the orders controller app / controllers / orders_controller.rb. The orders#index Action存储了两个要在前端使用的变量: @products_purchase 哪个有一个没有计划的欧博体育app下载 @products_subscription 它的产品同时支持PayPal和Stripe.

class OrdersController < ApplicationController
  before_action: authenticate_user!
  def index
    products = Product.all
    @products_purchase =产品.在哪里(stripe_plan_name: nil, paypal_plan_name: nil)
    @products_subscription = products - @products_purchase
  end

  def submit
  end
end

Create a file in app/views/orders/index.html.haml. 该文件包含我们将通过submit方法发送到后端的所有输入, 以及支付网关和产品选择的交互. 以下是一些输入名称属性:

  • Orders[product_id] stores the product id.
  • Orders[payment_gateway] 包含支付网关与Stripe或PayPal值的另一个.
%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”:真}
          = radio_button_tag 'orders[product_id]', product.id, @products_purchase.first == product
          % {id:“radioButtonName #{产品.id}"} #{product.name}
          % {id:“radioButtonPrice #{产品.id}", :'data-price' => "#{product.#{humanized_money_with_symbol产品.price}
        %br
      %h4 Subscriptions
      - @products_subscription.each do |product|
        %div
          = radio_button_tag 'orders[product_id]', product.id, false
          % {id:“radioButtonName #{产品.id}"} #{product.name}
          % {id:“radioButtonPrice #{产品.id}", :'data-price' => "#{product.#{humanized_money_with_symbol产品.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',类:'paymentSelectionTab active'}
      %div{id:'card-element'}
      % div {id:“card-errors”角色:“警戒”}
      %br
      %br
      = submit_tag "Buy it!", id: "submit-stripe"
    % div {id:“tab-paypal”类:“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;
  }

运行应用程序时使用 rails s and visit your page in http://localhost:3000. 您应该能够看到页面如下所示:

原始索引页没有条纹和贝宝的整合

支付网关凭证存储

PayPal和Stripe密钥将存储在Git不跟踪的文件中. 该文件中为每个支付网关存储了两种类型的密钥, and for now, 我们将为它们使用一个虚拟值. 后面的部分将介绍创建这些键的其他说明.

Step 1: Add this in .gitignore.

/config/application.yml

Step 2: 创建一个包含您的凭据的文件 config/application.yml. 它应该包含所有你的PayPal和Stripe沙盒/测试键访问这些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

Step 3: 来存储文件中的变量 config/application.yml 当应用程序启动时,添加这些行 config/application.rb inside the Application 这样就可以在 ENV.

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

Stripe Configuration

我们将为使用Stripe API添加一个gem: stripe-rails. 还需要创建一个Stripe帐户,以便处理收费和订阅. 如果你需要,你可以参考的API方法的Stripe API official documentation.

Step 1: 将条纹轨道宝石添加到您的项目中.

The stripe-rails gem 将为这个项目中使用的所有API请求提供一个接口.

Add this in the Gemfile:

gem 'stripe-rails'

Run:

bundle install

Step 2: Generate your API keys.

为了有API密钥与Stripe通信, 你需要在Stripe中创建一个账户. To test the application, 可以使用测试模式, 所以在创建Stripe账户的过程中不需要填写真实的商业信息.

  • 在Stripe中创建一个账户(如果你还没有)。http://dashboard.stripe.com/).
  • 当仍然在条纹仪表板,登录后,切换 View Test Data on.
  • At http://dashboard.stripe.com/test/apikeys, replace YOUR_CREDENTIAL_HERE for the values STRIPE_PUBLISHABLE_KEY and STRIPE_SECRET_KEY in /config/application.yml with the content from Publishable Key and Secret key.

Step 3: Initialize Stripe module

除了更换钥匙, 我们仍然需要初始化Stripe模块, 这样它就会使用已经在 ENV.

Create a file in config/initializers/stripe.rb with:

Rails.application.configure do
  config.stripe.secret_key = ENV["STRIPE_SECRET_KEY"]
  config.stripe.publishhable_key = ENV[" stripe_publishhable_key "]
end

Step 4: 在前端集成Stripe.

We will be adding the Stripe JavaScript library and the logic for sending a token which represents the user credit card information and will be processed in our back end.

In the index.html.haml 文件,将其添加到文件的顶部. This will use the Stripe module (provided by the gem) to add the Stripe javascript library to the user’s page.

=  stripe_javascript_tag

Stripe使用通过API创建的安全输入字段. As they are created in an iframe created through this API, 您不必担心处理用户信用卡信息时可能存在的漏洞. Additionally, 您的后端将无法处理/存储任何用户敏感数据, 它将只接收表示此信息的令牌.

这些输入字段是通过调用 stripe.elements().create('card'). 之后只需要调用返回的对象 mount() 通过传递HTML元素id/class作为参数,这些输入应该装入其中. 更多信息可在 Stripe.

当用户点击提交按钮与Stripe支付方式, 在创建的Stripe card元素上执行另一个返回promise的API调用:

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

The result variable of this function, 如果没有分配属性错误, 是否有一个可以通过访问属性来检索的令牌 result.token.id. 这个令牌将被发送到后端.

为了进行这些更改,请替换注释的代码 //你的stripe和paypal代码将在这里 in index.html.haml with:

  (function setupStripe() {
    //使用publishable key初始化stripe
    var stripe = stripe ("#{ENV[' stripe_publishhable_key ']}");

    //创建Stripe信用卡元素.
    var elements = stripe.elements();
    var card = elements.create('card');

    //添加一个监听器来检查是否
    card.addEventListener('change', function(event) {
      // div card-errors包含错误细节
      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 = '';
      }
    });

    //在#card-element div中挂载条带卡元素.
    card.mount('#card-element');
    var form = document.getElementById(“订单细节”);
    //当用户点击#submit-stripe按钮时调用.
    form.addEventListener('submit', function(event) {
      $('#submit-stripe').prop('disabled', true);
      event.preventDefault();
      stripe.createToken(card).then(function(result) {
        if (result.error) {
          //提示出错.
          var errorElement = document.getElementById(“card-errors”);
          errorElement.textContent = result.error.message;
        } else {
        // Now we submit the form. 我们还添加了一个隐藏的输入存储 
    // the token. 所以我们的后端可以使用它.
          Var $form = $("#order-details");
          //添加一个隐藏的输入orders[token]
          $form.append($('').val(result.token.id));
          // Set order type
          $('#order-type').val('stripe');
          $form.submit();
        }
      });
      return false;
    });
  }());
  //你的paypal代码将在这里

If you visit your page, it should look like the following with the new Stripe secure input fields:

索引页集成了条纹安全输入字段.

Step 5: Test your application.

用测试卡填写信用卡表格(http://stripe.com/docs/testing) and submit the page. Check if the submit 使用所有参数(product_id, payment_gateway, and token) in your server output.

Stripe Charges

条纹收费代表一次性交易. 因此,在进行条纹收费交易后,您将直接从客户那里收到钱. 这对于销售与计划无关的产品非常理想. In a later section, 我将展示如何做与贝宝相同的交易类型, 但贝宝对这种交易的称呼是 Payment.

在本节中,我还将提供处理和提交订单的所有框架. We create an order in the submit 在提交Stripe表单时执行. 此订单最初将具有 pending 状态,因此如果在处理此订单时出现任何问题,该订单仍将处于状态 pending.

如果从Stripe API调用中出现任何错误,我们在a中设置顺序 failed 状态,如果充电成功完成,它将在 paid state. The user is also redirected according to the Stripe API response as shown in the following graph:

Stripe transactions.

另外,当执行条带收费时,会返回一个ID. We will be storing this ID so that you can later look for it in your Stripe dashboard if required. 如果订单需要退款,也可以使用此ID. 本文将不探讨这类问题.

Step 1: Create the Stripe service.

我们将使用一个单例类来表示使用Stripe API的Stripe操作. 为了创建一个电荷,方法 Stripe::Charge.create ,返回的对象ID属性将存储在订单记录中 charge_id. This create 函数通过传递源自前端的令牌来调用, the order price, and a description.

So, create a new folder app/services/orders, and add a Stripe service: app/services/orders/stripe.rb containing the Orders::Stripe 单例类,它在方法中有一个条目 execute.

class Orders::Stripe
  INVALID_STRIPE_OPERATION = '无效的条带操作'
  def self.execute(order:, user:)
    product = order.product
    检查订单是否为计划
    if product.stripe_plan_name.blank?
      charge = self.execute_charge (price_cents:产品.price_cents,
                                   description: product.name,
                                   card_token:  order.token)
    else
  	 #订阅将在这里处理
    end

    unless charge&.id.blank?
      #如果id有收费,设置订单支付.
      order.charge_id = charge.id
      order.set_paid
    end
  rescue Stripe::StripeError => e
    如果从API中抛出一个Stripe错误,
    # set status failed and 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

Step 2: 实现提交操作并调用Stripe服务.

In orders_controller.rb, add the following in the submit Action,它基本上会调用服务 Orders::Stripe.execute. 请注意,还添加了两个新的私有函数: prepare_new_order and order_params.

  def submit
    @order = nil
    #检查订单类型
    如果order_params[:payment_gateway] == "stripe"
      prepare_new_order
      Orders::Stripe.执行(order: @order, user: current_user)
    Elsif order_params[:payment_gateway] == "paypal"
      # paypal将在这里处理
    end
  ensure
    if @order&.save
      if @order.paid?
        当订单支付并保存时,显示成功
        返回渲染html: SUCCESS_MESSAGE
      elsif @order.failed? && !@order.error_message.blank?
        #仅在订单失败且有error_message时呈现错误
        return render html: @order.error_message
      end
    end
    渲染html: FAILURE_MESSAGE
  end

  private
  初始化一个新订单,并设置其用户、产品和价格.
  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

Step 3: Test your application.

Check if the submit action, 当使用有效的测试卡调用时, 对成功的消息执行重定向. 此外,检查你的 Stripe dashboard 如果顺序也显示了.

Stripe Subscriptions

可以为定期付款创建订阅或计划. With this type of product, the user is charged daily, weekly, 每月或每年自动根据 plan configuration. 在本节中,我们将使用product字段 stripe_plan_name in order to store the plan ID—actually, it is possible for us to choose the ID, and we will call it premium-plan-将用于创建关系 customer <-> subscription.

我们还将为users表创建一个名为 stripe_customer_id 这将是一个条带客户对象的id属性填充. 函数时创建条带客户 Stripe::Customer.create ,您还可以在()中查看创建并链接到您的帐户的客户。http://dashboard.stripe.com/test/customers). 创建客户 source parameter which, in our case, 令牌是在提交表单时发送的前端生成的吗.

从最后提到的Stripe API调用中获得的客户对象, 也用于创建订阅,这是通过调用 customer.subscriptions.create 并将计划ID作为参数传递.

Additionally, the stripe-rails gem provides the interface to retrieve and update a customer from Stripe, which is done by calling Stripe::Customer.retrieve and Stripe::Customer.update, respectively.

当一个用户记录已经有 stripe_customer_id,而不是使用 Stripe::Customer.create, we will call Stripe::Customer.retrieve passing the stripe_customer_id 作为参数,后面跟着a Stripe::Customer.update,在本例中,传递令牌一个参数.

Firstly we will be creating a plan using Stripe API so that we can create a new subscription product using the field stripe_plan_name. 之后,我们会在 orders_controller 和Stripe服务,以便处理Stripe订阅的创建和执行.

Step 1: 使用Stripe API创建一个计划.

使用命令打开控制台 rails c . 为您的Stripe账户创建订阅:

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

如果此步骤返回的结果为true, 这意味着计划创建成功, 你可以在你的 Stripe dasboard.

Step 2: 在数据库中创建一个产品 stripe_plan_name field set.

创建一个产品 stripe_plan_name set as premium-plan in the database:

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

Step 3: 生成添加列的迁移 stripe_customer_id in the users table.

在终端中执行如下命令:

addstripeccustomeridtouser stripe_customer_id:string

rails db:migrate

Step 4: 在Stripe服务类中实现订阅逻辑.

的私有方法中再添加两个函数 app/services/orders/stripe.rb: execute_subscription 谁负责在客户对象中创建订阅. The function find_or_create_customer 负责返回已创建的客户还是通过返回新创建的客户.

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 =条带::客户.检索({id: customer_id})
    if stripe_customer
      stripe_customer =条带::客户.update(stripe_customer.id, { source: card_token})
    end
  else
    stripe_customer =条带::客户.create({
      email: email,
      source: card_token
    })
  end
  stripe_customer
end

Finally, in the execute 函数(app/services/orders/stripe.rb), we will first call find_or_create_customer 然后通过调用来执行订阅 execute_subscription 通过传递之前检索/创建的客户. So, replace the comment #订阅将在这里处理 in the execute 方法,代码如下:

customer =  self.find_or_create_customer (card_token:秩序.token,
                               customer_id: user.stripe_customer_id,
                               email: user.email)
if customer
  user.更新(stripe_customer_id:客户.id)
  order.customer_id = customer.id
  charge = self.execute_subscription(计划:产品.stripe_plan_name,
                                     customer: customer)

Step 5: Test your application.

访问您的网站,选择订阅产品 Premium Plan,并填写有效的测试卡. 提交后,它应该将您重定向到一个成功的页面. 此外,检查你的 Stripe dashboard 如果订阅已成功创建.

PayPal Configuration

正如我们在Stripe所做的那样,我们也将为使用PayPal API添加一个gem: paypal-sdk-rest,还需要创建一个PayPal账户. 一个描述性的工作流程贝宝使用这个宝石可以在官方咨询 PayPal API documentation.

Step 1: Add the paypal-sdk-rest gem to your project.

Add this in the Gemfile:

gem 'paypal-sdk-rest'

Run:

bundle install

Step 2: Generate your API keys.

为了有API密钥与贝宝通信, 你需要创建一个PayPal账户. So:

  • 创建一个帐户(或使用您的PayPal帐户) http://developer.paypal.com/.
  • 还在登录你的账户,创建两个沙箱账户在 http://developer.paypal.com/developer/accounts/:
    • 个人(买方帐户)-这将在您的测试中用于付款和订阅.
    • 商业(商户帐户)-这将链接到应用程序, 哪些会有我们正在寻找的API密钥. 除此之外,所有的交易都可以在这个账户上跟踪.
  • Create an app at http://developer.paypal.com/developer/applications 使用之前的业务沙箱帐户.
  • 完成此步骤后,您将收到PayPal的两个密钥: Client ID and Secret.
  • In config/application.yml, replace YOUR_CREDENTIAL_HERE from PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET 用你刚收到的钥匙.

Step 3: 初始化PayPal模块.

类似于Stripe,除了替换密钥 application.yml,我们仍然需要初始化PayPal模块,以便它可以使用已经在我们的 ENV variable. 为此,在 config/initializers/paypal.rb with:

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

Step 4: 在前端集成PayPal.

In index.html.haml 把这个添加到文件的顶部:

%script(src="http://www.paypal.com/sdk/js?客户机id = # {ENV [' PAYPAL_CLIENT_ID ']}”)

Unlike Stripe, PayPal只使用一个按钮, when clicked, 打开一个安全的弹出窗口,用户可以登录并进行支付/订阅. 此按钮可以通过调用该方法来呈现 paypal.Button(PARAM1).render(PARAM2).

  • PARAM1 是一个具有环境配置和两个回调函数作为属性的对象: createOrder and onApprove.
  • PARAM2 表示PayPal按钮应该附加到的HTML元素标识符.

因此,仍然在同一个文件中,替换注释的代码 你的贝宝代码将在这里 with:

  (function setupPaypal() {
    function isPayment() {
      返回$('[data-charges-and-payments-section] input[name="orders[product_id]"]:checked').length
    }

    函数submitOrderPaypal(chargeID) {
      Var $form = $("#order-details");
      //添加一个隐藏的输入orders[charge_id]
      $form.append($('').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');
  }());

Step 5: Test your application.

Visit your page and check if the PayPal button is rendered when you select PayPal as the payment method.

PayPal Transactions

PayPal交易的逻辑, unlike Stripe, 从前端到后端涉及到更多的请求是否会更复杂一些. 这就是本节存在的原因. 我将或多或少地(不带任何代码)解释 createOrder and onApprove 方法将被实现,以及后端流程中所期望的内容.

Step 1: 当用户点击PayPal提交按钮时, 要求用户凭证的PayPal弹出窗口打开,但处于加载状态. The function callback createOrder is called.

PayPal弹出,加载状态

Step 2: In this function, 我们将执行一个请求到我们的后端,这将创建一个支付/订阅. 这是交易的开始, 目前还不会收费, 所以交易实际上在a中 pending state. 我们的后端应该返回一个令牌, 将使用PayPal模块(通过 paypal-rest-sdk gem).

Step 3: Still in createOrder callback, 我们返回后端生成的令牌, and If everything is ok, PayPal弹出将呈现以下内容, 请求用户凭据:

PayPal弹出,用户凭证

Step 4: 用户登录并选择付款方式后, 弹出窗口将改变其状态如下:

PayPal弹出,授权交易

Step 5: The onApprove 函数回调现在被调用. 我们将其定义如下: onApprove: function(data). The data 对象将具有支付信息,以便执行它. In this callback, another request to our back-end function will be performed this time passing the data object in order to execute the PayPal order.

Step 6: 我们的后端执行此事务并返回200(如果成功).

Step 7: 当我们的后端返回时,我们提交表单. 这是我们向后端发出的第三个请求.

注意,与Stripe不同的是,在这个过程中有三个请求发送到我们的后端. 我们将保持我们的订单记录状态同步相应:

  • createOrder callback: A transaction is created, and an order record is also created; therefore, it is in a pending state as default.
  • onApprove callback:交易被执行,我们的订单将被设置为 paypal_executed.
  • 提交订单页面:事务已经执行,因此没有任何更改. 订单记录将其状态更改为 paid.

整个过程如下图所示:

PayPal transactions

PayPal Payments

PayPal支付遵循与Stripe Charges相同的逻辑, 它们代表一次性交易, 但是正如前面提到的, 它们有不同的流逻辑. 以下是处理PayPal支付时需要进行的更改:

Step 1: 为PayPal创建新的路由并执行支付.

添加以下路由 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

这将为创建和执行支付创建两个新路由,这些支付将在 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.

该服务最初将有三个职责:

  • The create_payment 方法通过调用 PayPal::SDK::REST::Payment.new. A token 生成并返回到前端.
  • The execute_payment 方法通过查找前面创建的支付对象来执行支付 PayPal::SDK::REST::Payment.find(payment_id) which uses the payment_id 作为参数,它的值与 charge_id 存储在订单对象的前一步中. After that, we call execute 在以给定付款人为参数的支付对象中. This payer is given by the front end after the user has provided credentials and selected a payment method in the popup.
  • The finish 方法按特定的 charge_id 中查询最近创建的订单 paypal_executed state. 如果找到记录,则标记为已支付.
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(顺序:产品:)
    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
        },
        描述:“付款:#{产品.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.执行(payer_id: payer_id)
      order.set_paypal_executed
      return order.save
    end
  end

Step 3: 在提交操作中调用控制器中的PayPal服务.

Add a callback for prepare_new_order before the action paypal_create_payment (将在下一步中添加)通过在文件中添加以下内容来请求 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将在这里处理. with the following:

...
Elsif order_params[:payment_gateway] == "paypal"
  @order = Orders::Paypal.完成(order_params[:令牌)
end
...

Step 4: 创建处理请求的操作.

Still, in the app / controllers / orders_controller.rb 文件,创建两个新操作(应该是公共的),用于处理对 paypal_create_payment and paypal_execute_payment routes:

  • The paypal_create_payment method:将调用我们的服务方法 create_payment. 如果成功返回,它将返回订单 token created by Orders::Paypal.create_payment.
  • The paypal_execute_payment method:将调用我们的服务方法 execute_payment (执行我们的付款). 如果支付成功执行,则返回200.
...
  def paypal_create_payment
    result = Orders::Paypal.Create_payment(订单:@order,产品:@product)
    if result
      渲染json: {token: result}, status::ok
    else
      渲染json: {error: FAILURE_MESSAGE},状态::unprocessable_entity
    end
  end

  def paypal_execute_payment
    if Orders::Paypal.execute_payment(payment_id: params[:paymentID], payer_id: params[:payerID])
      渲染json:{},状态::ok
    else
      渲染json: {error: FAILURE_MESSAGE},状态::unprocessable_entity
    end
  end
...

Step 5: 实现的前端回调函数 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 $.邮报》(" # {paypal_create_payment_url} ", $(“#订单细节”).serialize()).then(function(data) {
            return data.token;
          });
        } else {
        }
      },
      onApprove: function(data) {
        if (isPayment()) {
          return $.邮报》(" # {paypal_execute_payment_url}, {
            paymentID: data.paymentID,
            payerID:   data.payerID
          }).then(function() {
            submitOrderPaypal(data.paymentID)
          });
        } else {
        }
      }
    }).render('#submit-paypal');

如前一节所述,我们调用 paypal_create_payment_url for the createOrder callback and paypal_execute_payment_url for the onApprove callback. 注意,如果最后一个请求返回成功, we submit the order, 哪个是向服务器发出的第三个请求.

In the createOrder 函数处理程序,我们返回一个令牌(从后端获得). In the onApprove 回调,我们有两个属性传递到后端 paymentID and payerID. 这些将用于执行付款.

最后,注意我们有两个空的 else 条款,因为我为下一节留下了空间,我们将添加PayPal订阅.

If you visit your page after integrating the front-end JavaScript section and select PayPal as the payment method, 它看起来应该如下所示:

与贝宝整合后的索引页

Step 6: Test your application.

  • Visit the index page.
  • 选择一个支付/收费产品,并选择PayPal作为支付方式.
  • 点击提交PayPal按钮.
  • In the PayPal popup:
    • 使用您创建的买方帐户的凭据.
    • 登录并确认您的订单.
    • The popup should close.
  • 检查您是否被重定向到成功页面.
  • Finally, check if the order was performed in the PayPal account by signing in with your business account at http://www.sandbox.paypal.com/signin and checking the dashboard http://www.sandbox.paypal.com/listing/transactions.

PayPal Subscriptions

PayPal计划/协议/订阅遵循与Stripe订阅相同的逻辑, 并且是为经常性付款而创建的. 使用这种类型的产品,用户每天都要收费, weekly, 每月或每年自动根据其 configuration.

我们将使用该领域的产品 paypal_plan_name,以便存储PayPal提供的计划ID. In this case, differently from Stripe, we don’t choose the ID, and PayPal returns this value to which will be used to update the last product created in our database.

对于创建订阅,不需要 customer 任何步骤都需要信息,如方法 onApprove 可能在其底层实现中处理此链接. 所以我们的表保持不变.

Step 1: 使用PayPal API创建一个计划.

使用命令打开控制台 rails c . 为您的PayPal账户创建一个订阅:

plan = PayPal::SDK::REST:: plan.new({
  name: '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:“继续”
  }
})
plan.create
plan_update = {
  op: 'replace',
  path: '/',
  value: {
    state: 'ACTIVE'
  }
}
plan.update(plan_update)

Step 2: 更新数据库中的最后一个产品 paypal_plan_name with the returned plan.id.

Run:

Product.last.更新(paypal_plan_name:计划.id) 

Step 3: 为PayPal订阅添加路由.

Add two new routes in config/routes.rb:

  post 'orders/paypal/create_subscription'  => 'orders#paypal_create_subscription', :: paypal_create_subscription
  post 'orders/paypal/execute_subscription'  => 'orders#paypal_execute_subscription', :: paypal_execute_subscription

Step 4: 处理PayPal服务中的创建和执行.

中添加两个用于创建和执行订阅的函数 Orders::Paypal of app/services/orders/paypal.rb:

  def self.create_subscription(顺序:产品:)
    协议= PayPal::SDK::REST::协议.new({
      name: product.name,
      描述:“订阅:#{产品.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(令牌)
    order = Order.recently_created.find_by(token: token)
    return false unless order
    协议= PayPal::SDK::REST::协议.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

In create_subscription,则通过调用该方法初始化协议 SDK贝宝::::::协议.new and passing the the product.paypal_plan_name as one of its attributes. 之后,我们创建了它,现在将为最后一个对象设置一个令牌. 我们还将令牌返回给前端.

In execute_subscription, we find the order 在上次通话中创建的记录. After that, we initialize a new agreement, we set the token of this previous object and execute it. 如果最后一步执行成功,则订单状态设置为 paypal_executed. 现在我们将协议ID返回到前端它也存储在 order.chager_id.

Step 5: 中添加用于创建和执行订阅的操作 orders_controller.

Change the app / controllers / orders_controller.rb. 在类的顶部,首先,然后更新回调 prepare_new_order to also be executed before paypal_create_subscription is called:

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

此外,在同一个文件中添加两个公共函数,以便它们调用 Orders::Paypal 与我们在PayPal支付中已经拥有的类似流程的服务:

...
  def paypal_create_subscription
    result = Orders::Paypal.Create_subscription(订单:@order,产品:@product)
    if result
      渲染json: {token: result}, status::ok
    else
      渲染json: {error: FAILURE_MESSAGE},状态::unprocessable_entity
    end
  end

  def paypal_execute_subscription
    result = Orders::Paypal.execute_subscription(令牌:params [: subscriptionToken])
    if result
      渲染json: {id: result}, status::ok
    else
      渲染json: {error: FAILURE_MESSAGE},状态::unprocessable_entity
    end
  end
 ...

Step 6: 添加订阅处理程序 createOrder and onApprove callbacks in the front end.

Finally, in index.html.haml, replace the paypal.Buttons 函数,这将填充两个空 else we had before:

paypal.Buttons({
  env:“# {env [' PAYPAL_ENV ']}”,
  createOrder: function() {
    $('#order-type').val("paypal");
    if (isPayment()) {
      return $.邮报》(" # {paypal_create_payment_url} ", $(“#订单细节”).serialize()).then(function(data) {
        return data.token;
      });
    } else {
      return $.邮报》(" # {paypal_create_subscription_url} ", $(“#订单细节”).serialize()).then(function(data) {
        return data.token;
      });
    }
  },
  onApprove: function(data) {
    if (isPayment()) {
      return $.邮报》(" # {paypal_execute_payment_url}, {
        paymentID: data.paymentID,
        payerID:   data.payerID
      }).then(function() {
        submitOrderPaypal(data.paymentID)
      });
    } else {
      return $.邮报》(" # {paypal_execute_subscription_url}, {
        subscriptionToken: data.orderID
      }).然后(函数(executeData) {
        submitOrderPaypal (executeData.id)
      });
    }
  }
}).render('#submit-paypal');

订阅的创建和执行具有与支付相似的逻辑. 一个不同之处在于,在执行支付时,回调函数中的数据 onApprove already has a paymentID representing the charge_id to submit the form through submitOrderPaypal(data.paymentID). 对于订阅,我们得到 charge_id 只有在通过请求 POST on paypal_execute_subscription_url, so we can call submitOrderPaypal (executeData.id).

Step 7: Test your application.

  • Visit the index page.
  • 选择一个订阅产品和PayPal作为支付方式.
  • 点击提交PayPal按钮.
  • In the PayPal popup:
    • 使用您创建的买方帐户的凭据.
    • 登录并确认您的订单.
    • The popup should close.
  • 检查您是否被重定向到成功页面.
  • Finally check if the order was performed in the PayPal account by signing in with your business account at http://www.sandbox.paypal.com/signin and checking the dashboard http://www.sandbox.paypal.com/listing/transactions.

Conclusion

After reading this article, you should be able to integrate payments/charges as well as subscriptions transactions for PayPal and Stripe in your Rails application. There are a lot of points that could be improved which I didn’t add in this article for the sake of brevity. 我根据难度的假设来安排一切:

我也推荐阅读关于Stripe Checkout元素, 这是将Stripe整合到前端的另一种方式吗. Unlike Stripe Elements, 我们在本教程中使用的是什么, Stripe Checkout opens a popup after clicking on a button (similar to PayPal) where the user fills credit card info OR choose to pay with Google Pay/Apple Pay http://stripe.com/docs/web.

第二个阅读建议是两个支付网关的安全页面.

最后,感谢您阅读本文! You can also check my GitHub项目用于本项目示例. There, I added rspec 在开发过程中进行测试.

Understanding the basics

  • 什么是Stripe ?它是如何运作的?

    Stripe is a company which develops software for individual or business to perform secured payments over the internet.

  • PayPal和Stripe有什么不同?

    他们提供涉及支付的不同应用程序, 他们对使用他们的服务收取不同的费用.

  • 什么是支付方式令牌?

    Payment tokenization is a process for handling sensitive data from users and transforming them to tokens, 因此没有敏感数据泄露.

  • PayPal是支付网关还是处理商?

    PayPal不是一个门户,而是一个完整的商业解决方案. 然而,它使用了一个叫做Payflow的支付网关.

  • Stripe是支付网关还是支付处理器?

    Stripe是一家处理客户银行卡的支付网关.

就这一主题咨询作者或专家.
Schedule a call
Henrique Reinaldo Sarmento的头像
Henrique Reinaldo Sarmento

Located in 库里蒂巴-巴拉州,巴西

Member since March 16, 2019

About the author

Henrique is a passionate full-stack developer and cloud computing enthusiast with experience in Ruby on Rails, Flask, Javascript and React.

Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

Universite de Lorraine

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.