在Ruby on Rails中集成Stripe和PayPal付款方式

本文概述

  • 付款网关集成如何工作
  • 设定
  • 支付网关凭证存储
  • Stripe配置
  • Stripe收费
  • Stripe订阅
  • PayPal配置
  • 总结
对于诸如AliExpress, Ebay和Amazon之类的大型电子商务公司而言, 一项关键功能是一种安全的付款方式, 这对他们的业务至关重要。如果此功能失败, 后果将是灾难性的。这适用于从事电子商务应用程序的行业领导者和Ruby on Rails开发人员。
网络安全对于防止攻击至关重要, 而使交易过程更加安全的一种方法是要求第三方服务来处理它。在应用程序中包括支付网关是实现此目标的一种方法, 因为它们提供了用户授权, 数据加密和仪表板, 因此你可以即时跟踪交易状态。
Web上有多种支付网关服务, 但是在本文中, 我将重点介绍将Stripe和PayPal集成到Rails应用程序中。再说几个:Amazon Payments, Square, SecurePay, WorldPay, Authorize.Net, 2Checkout.com, Braintree, Amazon或BlueSnap。
付款网关集成如何工作
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
涉及支付网关的交易的一般表示
通常, 你的应用程序中将有一个表单/按钮, 用户可以在其中登录/插入信用卡数据。 PayPal和Stripe已经通过使用iframe表单或弹出窗口使第一步更加安全, 这些表单或弹出窗口可防止你的应用存储敏感的用户信用卡信息, 因为它们将返回代表此交易的令牌。有些用户可能已经知道第三方服务正在处理交易过程, 因此已经对处理付款更加有信心, 因此这也可能对你的应用程序有吸引力。
验证用户信息后, 支付网关将通过联系与银行进行通信的支付处理器来确认付款, 以确认付款。这确保了交易被适当地借记/贷记。
Stripe使用信用卡表格询问信用卡号, 简历和有效期。因此, 用户必须在安全的Stripe输入中填写信用卡信息。提供此信息后, 你的应用程序后端将通过令牌处理此付款。
与Stripe不同, PayPal将用户重定向到PayPal登录页面。用户通过PayPal授权并选择付款方式, 同样, 你的后端将处理令牌而不是用户敏感数据。
值得一提的是, 对于这两个支付网关, 你的后端应要求通过Stripe或PayPal API进行交易执行, 这将给出OK / NOK响应, 因此你的应用程序应将用户相应地重定向到成功页面或错误页面。
本文的目的是提供一个快速指南, 以将这两个支付网关集成在单个应用程序中。对于所有测试, 我们将使用Stripe和PayPal提供的沙盒和测试帐户来模拟付款。
设定 在集成支付网关之前, 我们将通过添加gem, 数据库表和索引页面进行设置以初始化应用程序。该项目是使用Rails版本5.2.3和Ruby 2.6.3创建的。
注意:你可以在我们最近的文章中查看Rails 6的新功能。
步骤1:初始化Rails应用程序。
通过使用带有你的应用程序名称的rails命令运行项目初始化来初始化项目:
rails new YOUR_APP_NAME

并在你的应用程序文件夹中cd。
步骤2:安装gem。
除了Stripe和PayPal宝石外, 还添加了其他一些宝石:
  • 设计:用于用户身份验证和授权
  • haml:用于呈现用户页面的模板工具
  • jquery-rails:用于前端脚本中的jquery
  • money-rails:用于显示格式化的货币值
添加到你的Gemfile:
gem "devise", "> = 4.7.1" gem "haml" gem "jquery-rails" gem "money-rails"

添加后, 在你的CLI中运行:
bundle install

步骤3:初始化gem。
除了通过捆绑包安装这些宝石外, 其中一些宝石还需要初始化。
安装装置:
rails g devise:install

初始化钱轨:
rails g money_rails:initializer

通过将以下内容附加到app / assets / javascripts / application.js的底部来初始化jquery-rails:
//= require jquery //= require jquery_ujs

步骤4:表格和迁移
此项目的” 用户” , “ 产品” 和” 订单” 中将使用三个表。
  • 用户:将通过设计生成
  • 产品栏:
    • 名称
    • price_cents
    • Stripe_plan_name:一个ID, 表示在Stripe中创建的订阅计划, 因此用户可以订阅它。仅与Stripe计划关联的产品才需要此字段。
    • paypal_plan_name:与stripe_plan_name相同, 但适用于PayPal
  • 订单栏:
    • product_id
    • 用户身份
    • 状态:这将通知订单是待处理, 失败还是已付款。
    • 令牌:这是从API(Stripe或PayPal)生成的令牌, 用于初始化交易。
    • price_cents:与产品类似, 但用于使此值在订单记录中保持不变
    • Payment_gateway:存储用于PayPal或Stripe订单的支付网关
    • customer_id:这将用于Stripe, 以便存储Stripe客户以进行订阅, 这将在后面的部分中更详细地说明。
为了生成这些表, 必须生成一些迁移:
用于创建用户表。跑:
rails g devise User

用于创建产品表。通过运行以下命令来生成迁移:
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 :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

用于创建订单表。通过运行以下命令来生成迁移:
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 :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

通过执行以下命令来运行数据库迁移:
rails db:migrate

步骤5:创建模型。
用户模型已经通过devise安装创建, 并且不需要对其进行任何更改。除此之外, 将为产品和订单创建两个模型。
产品。添加一个新文件app / models / product.rb, 其中包含:
class Product < ActiveRecord::Base monetize :price_cents has_many :orders end

订购。添加一个新文件, app / models / order.rb, 其中包含:
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 :userscope :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

步骤6:填充数据库。
一个用户和两个产品将在控制台中创建。订单记录将根据付款测试创建。
  • 滑轨
  • 在浏览器中, 访问http:// localhost:3000
  • 你将被重定向到注册页面。
  • 通过填写用户的电子邮件地址和密码来注册用户。
  • 在你的终端中, 将提示以下日志, 显示你在数据库中创建了一个用户:
User Create (0.1ms)INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?) …

  • 通过运行rails c并添加以下内容来创建两个没有订阅的产品:
    • Product.create(名称:” Awesome T-Shirt” , price_cents:3000)
    • Product.create(名称:” Awesome Sneakers” , price_cents:5000)
步骤7:建立索引页面
该项目的主页包括用于购买或订阅的产品选择。此外, 它还有一个用于选择付款方式的部分(“ Stripe” 或” PayPal” )。对于每种付款网关类型, 还使用一个提交按钮, 因为对于PayPal, 我们将通过其JavaScript库添加其自己的按钮设计。
首先, 为索引创建路由, 然后在config / routes.rb中提交。
Rails.application.routes.draw do devise_for :users get '/', to: 'orders#index' post '/orders/submit', to: 'orders#submit' end

创建并添加操作索引, 然后在订单控制器app / controllers / orders_controller.rb中提交。 orders#index操作存储两个要在前端使用的变量:@products_purchase(具有不带计划的产品列表)和@products_subscription(具有带PayPal和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 enddef submit end end

在app / views / orders / index.html.haml中创建一个文件。该文件包含我们将通过Submit方法发送到后端的所有输入, 以及支付网关和产品选择的交互。以下是一些输入名称属性:
  • Orders [product_id]存储产品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=http://www.srcmini.com/>"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; }

如果你使用rails运行应用程序, 并访问http:// localhost:3000中的页面。你应该能够看到如下页面:
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
不带Stripe和PayPal集成的原始索引页
支付网关凭证存储 PayPal和Stripe密钥将存储在Git无法跟踪的文件中。每个付款网关在此文件中存储的密钥有两种, 现在, 我们将为其使用伪值。有关创建这些密钥的其他说明, 请参见其他章节。
步骤1:在.gitignore中添加它。
/config/application.yml

步骤2:使用config / application.yml中的凭据创建一个文件。它应包含所有用于访问这些API的PayPal和Stripe沙箱/测试键。
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

步骤3:为了在应用程序启动时存储文件config / application.yml中的变量, 请将这些行添加到Application类内的config / application.rb中, 以便它们在ENV中可用。
config_file = Rails.application.config_for(:application) config_file.each do |key, value| ENV[key] = value end unless config_file.nil?

Stripe配置 我们将添加一个使用Stripe API的工具:stripe-rails。还需要创建一个Stripe帐户, 以便可以处理费用和订阅。如果需要, 可以在官方文档中查阅Stripe API的API方法。
步骤1:将stripe-rails gem添加到你的项目中。
stripe-rails gem将为该项目中使用的所有API请求提供一个接口。
将此添加到Gemfile中:
gem 'stripe-rails'

运行:
bundle install

第2步:生成你的API密钥。
为了拥有用于与Stripe通信的API密钥, 你将需要在Stripe中创建一个帐户。要测试应用程序, 可以使用测试模式, 因此在Stripe帐户创建过程中无需填写真实的业务信息。
  • 如果你没有, 请在Stripe中创建一个帐户(https://dashboard.stripe.com/)。
  • 仍在Stripe仪表板中时, 登录后, 打开” 查看测试数据” 。
  • 在https://dashboard.stripe.com/test/apikeys上, 将/config/application.yml中值STRIPE_PUBLISHABLE_KEY和STRIPE_SECRET_KEY的YOUR_CREDENTIAL_HERE替换为可发布密钥和秘密密钥中的内容。
步骤3:初始化Stripe模块
除了替换密钥外, 我们仍然需要初始化Stripe模块, 以便它使用已在ENV中设置的密钥。
使用以下命令在config / initializers / stripe.rb中创建一个文件:
Rails.application.configure do config.stripe.secret_key = ENV["STRIPE_SECRET_KEY"] config.stripe.publishable_key = ENV["STRIPE_PUBLISHABLE_KEY"] end

步骤4:在前端集成Stripe。
我们将添加Stripe JavaScript库和发送令牌的逻辑, 该令牌代表用户信用卡信息, 并将在后端进行处理。
在index.html.haml文件中, 将此文件添加到文件顶部。这将使用Stripe模块(由gem提供)将Stripe javascript库添加到用户页面。
=stripe_javascript_tag

Stripe使用通过其API创建的安全输入字段。由于它们是通过此API在iframe中创建的, 因此你无需担心可能会存在处理用户信用卡信息的漏洞。此外, 你的后端将无法处理/存储任何用户敏感数据, 并且只会收到代表此信息的令牌。
这些输入字段是通过调用stripe.elements()。create(‘ card’ )创建的。之后, 只需要通过将输入要安装到的HTML元素id / class作为参数传递, 就可以用mount()调用返回的对象。可以在Stripe中找到更多信息。
当用户使用Stripe付款方法点击Submit按钮时, 将在创建的Stripe卡元素上执行另一个返回承诺的API调用:
stripe.createToken(card).then(function(result)

如果未分配属性错误, 则此函数的结果变量将具有一个令牌, 可以通过访问属性result.token.id来检索该令牌。该令牌将发送到后端。
为了进行这些更改, 请在index.html.haml中将注释的代码// //替换为你的Stripe和PayPal代码:
(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

如果你访问页面, 则其外观应如下所示, 带有新的Stripe安全输入字段:
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
与Stripe安全输入字段集成的索引页。
步骤5:测试你的应用程序。
用测试卡(https://stripe.com/docs/testing)填写信用卡表格, 然后提交页面。检查是否在服务器输出中使用所有参数(product_id, payment_gateway和令牌)调用了commit操作。
Stripe收费 Stripe收费代表一次性交易。因此, 在进行Stripe收费交易后, 你将直接从客户那里收到钱。这是销售与计划无关的产品的理想选择。在下一节中, 我将展示如何使用PayPal进行相同的交易类型, 但是PayPal的这种交易类型名称为Payment。
在本节中, 我还将提供用于处理和提交订单的所有框架。提交Stripe表单时, 我们在Submit动作中创建一个订单。该订单最初将处于待处理状态, 因此, 如果在处理该订单时出现任何问题, 该订单仍将待处理。
如果Stripe API调用出现任何错误, 我们会将订单设置为失败状态, 并且如果成功完成收费, 则它将处于已付款状态。还根据Stripe API响应重定向用户, 如下图所示:
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
Stripe交易。
此外, 执行Stripe充电时, 将返回ID。我们将存储此ID, 以便你以后可以根据需要在Stripe仪表板中查找它。如果必须退还订单, 也可以使用此ID。本文不会探讨这种事情。
步骤1:创建Stripe服务。
我们将使用Singleton类通过Stripe API表示Stripe操作。为了创建费用, 调用Stripe :: Charge.create方法, 并将返回的对象ID属性存储在订单记录charge_id中。通过传递起源于前端的令牌, 订单价格和描述来调用此create函数。
因此, 创建一个新的文件夹app / services / orders, 并添加一个Stripe服务:app / services / orders / stripe.rb, 其中包含Orders :: Stripe单例类, 该类在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 endunless 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

步骤2:实施Submit操作并调用Stripe服务。
在orders_controller.rb中, 在submit操作中添加以下内容, 该操作基本上将调用服务Orders :: Stripe.execute。注意, 还添加了两个新的私有函数:prepare_new_order和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 endprivate # 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 enddef order_params params.require(:orders).permit(:product_id, :token, :payment_gateway, :charge_id) end

步骤3:测试你的应用程序。
使用有效的测试卡调用提交操作时, 请检查是否将重定向到成功的消息。此外, 如果同时显示订单, 请在Stripe仪表板中检查。
Stripe订阅 可以创建用于定期付款的订阅或计划。使用这种类型的产品, 将根据计划配置自动向用户每天, 每周, 每月或每年收费。在本部分中, 我们将使用产品stripe_plan_name的字段来存储计划ID(实际上, 我们可以选择ID, 我们将其称为premium-plan), 该字段将用于创建计划ID。关系客户< -> 订阅。
我们还将为用户表创建一个名为stripe_customer_id的新列, 其中将填充Stripe客户对象的id属性。调用功能Stripe :: Customer.create函数时会创建一个Stripe客户, 你还可以在(https://dashboard.stripe.com/test/customers)中检查创建并链接到你帐户的客户。通过传递源参数来创建客户, 在本例中, 该源参数是在前端提交时提交的表单中发送的令牌。
从最后提到的Stripe API调用获得的客户对象也用于创建预订, 该预订通过调用customer.subscriptions.create并将计划ID作为参数传递来完成。
此外, stripe-rails gem提供了从Stripe检索和更新客户的接口, 这可以通过分别调用Stripe :: Customer.retrieve和Stripe :: Customer.update来完成。
因此, 当用户记录已经具有stripe_customer_id时, 我们将使用Stripe_customer_id作为参数, 然后通过Stripe :: Customer.update调用Stripe :: Customer.retrieve, 而不是使用Stripe :: Customer.create创建新客户。 , 在这种情况下, 将令牌传递给参数。
首先, 我们将使用Stripe API创建计划, 以便可以使用stripe_plan_name字段创建新的订阅产品。之后, 我们将在orders_controller和Stripe服务中进行修改, 以便处理Stripe订阅的创建和执行。
步骤1:使用Stripe API创建计划。
使用命令栏c打开控制台。使用以下步骤为你的Stripe帐户创建订阅:
Stripe::Plan.create({ amount: 10000, interval: 'month', product: { name: 'Premium plan', }, currency: 'usd', id: 'premium-plan', })

如果在此步骤中返回的结果为true, 则表示该计划已成功创建, 你可以在Stripe仪表板上访问它。
步骤2:在带有stripe_plan_name字段集的数据库中创建产品。
现在, 在数据库中创建带有stripe_plan_name设置为premium-plan的产品:
Product.create(price_cents: 10000, name: 'Premium Plan', stripe_plan_name: 'premium-plan')

步骤3:生成迁移, 以在用户表中添加列stripe_customer_id。
在终端中运行以下命令:
rails generate migration AddStripeCustomerIdToUser stripe_customer_id:stringrails db:migrate

步骤4:在Stripe服务类中实现订阅逻辑。
在app / services / orders / stripe.rb的私有方法中添加另外两个功能:execute_subscription负责在客户的对象中创建订阅。函数find_or_create_customer负责返回已创建的客户或返回新创建的客户。
def self.execute_subscription(plan:, token:, customer:) customer.subscriptions.create({ plan: plan }) enddef 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

最后, 在同一文件(app / services / orders / stripe.rb)中的execute函数中, 我们将首先调用find_or_create_customer, 然后通过传递先前检索/创建的客户来调用execute_subscription来执行订阅。因此, 用以下代码替换execute方法中将在此处处理的#SUBSCRIPTIONS注释:
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)

步骤5:测试你的应用程序。
访问你的网站, 选择订阅产品高级计划, 然后填写有效的测试卡。提交后, 它应将你重定向到成功的页面。此外, 请在你的Stripe仪表板中检查是否已成功创建订阅。
PayPal配置 与在Stripe中所做的一样, 我们还将添加一个使用PayPal API的工具:paypal-sdk-rest, 并且还需要创建一个PayPal帐户。可以在官方的PayPal API文档中查阅使用此gem的PayPal描述性工作流。
步骤1:将paypal-sdk-rest gem添加到你的项目中。
将此添加到Gemfile中:
gem 'paypal-sdk-rest'

运行:
bundle install

第2步:生成你的API密钥。
为了拥有用于与PayPal通信的API密钥, 你将需要创建一个PayPal帐户。所以:
  • 在https://developer.paypal.com/上创建一个帐户(或使用你的PayPal帐户)。
  • 仍然登录到你的帐户, 在https://developer.paypal.com/developer/accounts/创建两个沙箱帐户:
    • 个人(买方帐户)–将在你的测试中使用该帐户进行付款和订阅。
    • 商业(商家帐户)–这将链接到应用程序, 该应用程序将具有我们要查找的API密钥。除此之外, 所有交易都可以在该帐户中进行。
  • 使用以前的业务沙箱帐户在https://developer.paypal.com/developer/applications上创建一个应用程序。
  • 完成此步骤后, 你将收到PayPal的两个密钥:客户端ID和密钥。
  • 在config / application.yml中, 将PAYPAL_CLIENT_ID和PAYPAL_CLIENT_SECRET中的YOUR_CREDENTIAL_HERE替换为你刚收到的密钥。
步骤3:初始化PayPal模块。
与Stripe相似, 除了替换application.yml中的密钥外, 我们仍然需要初始化PayPal模块, 以便它可以使用已在ENV变量中设置的密钥。为此, 请使用以下命令在config / initializers / paypal.rb中创建一个文件:
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

步骤4:在前端集成PayPal。
在index.html.haml中将其添加到文件顶部:
%script(src="https://www.paypal.com/sdk/js?client-id=#{ENV['PAYPAL_CLIENT_ID']}")

与Stripe不同, PayPal仅使用一个按钮, 单击该按钮会打开一个安全弹出窗口, 用户可以在其中登录并继续进行付款/订阅。可以通过调用方法paypal.Button(PARAM1).render(PARAM2)来呈现此按钮。
  • PARAM1是具有环境配置和两个回调函数作为属性的对象:createOrder和onApprove。
  • PARAM2指示应将PayPal按钮附加到的HTML元素标识符。
因此, 仍在同一个文件中, 将注释代码替换为:
(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'); }());

步骤5:测试你的应用程序。
当你选择PayPal作为付款方式时, 请访问你的页面并检查是否显示了PayPal按钮。
PayPal交易
与Stripe不同, PayPal(PayPal)交易的逻辑要复杂一些, 因为它涉及从前端到后端发起的更多请求。这就是为什么存在此部分的原因。我将或多或少地(没有任何代码)解释createOrder和onApprove方法中描述的功能将如何实现, 以及后端过程中的期望。
步骤1:当用户单击PayPal提交按钮时, 要求用户凭据的PayPal弹出窗口将打开, 但处于加载状态。函数回调createOrder被调用。
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
PayPal弹出窗口, 加载状态
步骤2:在此功能中, 我们将向后端执行请求, 这将创建一个付款/订阅。这是交易的开始, 尚不收取费用, 因此交易实际上处于待处理状态。我们的后端应返回一个令牌, 该令牌将使用PayPal模块(通过paypal-rest-sdk gem提供)生成。
步骤3:仍在createOrder回调中, 我们返回在后端生成的此令牌, 如果一切正常, 则PayPal弹出窗口将呈现以下内容, 要求用户提供凭据:
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
PayPal弹出窗口, 用户凭证
步骤4:在用户登录并选择付款方式后, 弹出窗口会将其状态更改为以下内容:
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
PayPal弹出窗口, 授权交易
步骤5:现在调用onApprove函数回调。我们将其定义如下:onApprove:函数(数据)。数据对象将具有付款信息以便执行。在此回调中, 这次将传递数据对象以执行PayPal订单, 这是对后端功能的另一个请求。
步骤6:我们的后端执行此事务并返回200(如果成功)。
步骤7:当后端返回时, 我们提交表单。这是我们对后端的第三个请求。
请注意, 与Stripe不同, 在此过程中, 我们向后端提出了三个请求。我们将相应地使订单记录状态保持同步:
  • createOrder回调:创建交易, 并创建订单记录;因此, 默认情况下它处于挂起状态。
  • onApprove回调:交易已执行, 我们的订单将设置为paypal_exected。
  • 提交订单页面:事务已经执行, 因此没有任何变化。订单记录会将其状态更改为已付款。
下图描述了整个过程:
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
PayPal交易
PayPal付款
PayPal支付遵循与Stripe Charges相同的逻辑, 因此它们表示一次性交易, 但是如上一节所述, 它们具有不同的流逻辑。这些是处理PayPal付款所需的更改:
步骤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和paypal_execute_payment订单控制器方法中进行处理。
步骤2:创建PayPal服务。
在以下位置添加单例类Orders :: Paypal:app / services / orders / paypal.rb。
该服务最初将承担三项职责:
  • create_payment方法通过调用PayPal :: SDK :: REST :: Payment.new创建付款。令牌已生成并返回到前端。
  • execute_payment方法通过首先通过PayPal :: SDK :: REST :: Payment.find(payment_id)查找先前创建的付款对象来执行付款, 该对象使用payment_id作为参数, 其值与上一步中存储的charge_id相同在订单对象中。之后, 我们在给定付款人作为参数的付款对象中调用execute。在用户提供凭据并在弹出窗口中选择付款方式后, 前端会给此付款人。
  • finish方法通过特定的charge_id查找订单, 以查询最近创建的处于paypal_exected状态的订单。如果找到记录, 则将其标记为已付款。
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 enddef 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 enddef 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

步骤3:在Submit操作中调用控制器中的PayPal服务。
通过在文件app / controllers / orders_controller.rb中添加以下内容, 在请求paypal_create_payment操作(将在下一步中添加)之前添加prepare_new_order的回调:
class OrdersController < ApplicationController before_action :authenticate_user! before_action :prepare_new_order, only: [:paypal_create_payment] ...

同样, 在同一文件中, 通过替换注释的代码#PAYPAL将在此处处理, 在Submit操作中调用PayPal服务。具有以下内容:
... elsif order_params[:payment_gateway] == "paypal" @order = Orders::Paypal.finish(order_params[:token]) end ...

步骤4:创建用于处理请求的操作。
仍然在app / controllers / orders_controller.rb文件中, 创建两个新操作(应该是公共的)来处理对paypal_create_payment和paypal_execute_payment路由的请求:
  • paypal_create_payment方法:将调用我们的服务方法create_payment。如果成功返回, 它将返回由Orders :: Paypal.create_payment创建的订单令牌。
  • paypal_execute_payment方法:将调用我们的服务方法execute_payment(执行我们的付款)。如果付款成功完成, 则返回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 enddef 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 ...

步骤5:为createOrder和onApprove实现前端回调函数。
使你的paypal.Button.render调用看起来像这样:
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');

如上一节所述, 我们为createOrder回调调用paypal_create_payment_url, 为onApprove回调调用paypal_execute_payment_url。请注意, 如果最后一个请求返回成功, 我们将提交订单, 这是对服务器的第三个请求。
在createOrder函数处理程序中, 我们返回一个令牌(从后端获取)。在onApprove回调中, 我们有两个属性传递给后端的PaymentID和payerID。这些将用于执行付款。
最后, 请注意, 我们有两个空白的else子句, 因为我在下一节中将要添加PayPal订阅的地方留出了空间。
如果你在集成了前端JavaScript部分之后访问了你的页面, 并选择PayPal作为付款方式, 则其外观应如下所示:
在Ruby on Rails中集成Stripe和PayPal付款方式

文章图片
与PayPal集成后的索引页
步骤6:测试你的应用程序。
  • 访问索引页面。
  • 选择一种付款/收费产品, 然后选择PayPal作为付款方式。
  • 单击提交PayPal按钮。
  • 在PayPal弹出窗口中:
    • 使用你创建的买方帐户的凭据。
    • 登录并确认你的订单。
    • 弹出窗口应该关闭。
  • 检查是否将你重定向到成功页面。
  • 最后, 通过在https://www.sandbox.paypal.com/signin上登录你的企业帐户并检查仪表板https://www.sandbox.paypal.com/listing, 检查是否在PayPal帐户中执行了订单/交易。
PayPal订阅
PayPal计划/协议/订阅遵循与Stripe订阅相同的逻辑, 并创建用于定期付款。使用这种类型的产品, 用户会根据其配置自动每天, 每周, 每月或每年向其收费。
我们将使用产品paypal_plan_name的字段, 以存储PayPal提供的计划ID。在这种情况下, 与Stripe不同, 我们没有选择ID, 而PayPal会将此值返回到该值, 该值将用于更新数据库中最后创建的产品。
对于创建订阅, 任何步骤都不需要客户信息, 因为o??nApprove方法可能会在其基础实现中处理此链接。因此, 我们的表将保持不变。
第1步:使用PayPal API创建计划。
使用命令栏c打开控制台。使用以下方法为你的PayPal帐户创建订阅:
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)

步骤2:使用返回的plan.id更新数据库paypal_plan_name中的最后一个产品。
运行:
Product.last.update(paypal_plan_name: plan.id)

步骤3:为PayPal订阅添加路由。
在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

步骤4:在PayPal服务中处理创建和执行。
在Orders :: App / services / orders / paypal.rb的Pays中添加两个用于创建和执行订阅的功能:
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 enddef 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

在create_subscription中, 我们通过调用方法PayPal :: SDK :: REST :: Agreement.new并传递product.paypal_plan_name作为其属性之一来初始化协议。之后, 我们创建它, 现在将为此最后一个对象设置一个令牌。我们还将令牌返回到前端。
在execute_subscription中, 我们找到在上一个调用中创建的订单记录。之后, 我们初始化一个新协议, 设置这个先前对象的令牌并执行它。如果最后一步成功执行, 则订单状态将设置为paypal_exected。现在我们返回到前端的协议ID, 该ID也存储在order.chager_id中。
步骤5:在orders_controller中添加用于创建和执行订阅的操作。
更改app / controllers / orders_controller.rb。首先, 在类的顶部, 然后在调用paypal_create_subscription之前更新回调prepare_new_order也要执行:
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: @order, product: @product) if result render json: { token: result }, status: :ok else render json: {error: FAILURE_MESSAGE}, status: :unprocessable_entity end enddef 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 ...

步骤6:在前端为createOrder和onApprove回调添加订阅处理程序。
最后, 在index.html.haml中, 将paypal.Buttons函数替换为以下内容, 这将填补我们之前遇到的两个空白:
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');

订阅的创建和执行具有与付款类似的逻辑。一个区别是, 执行付款时, 来自回调函数onApprove的数据已经具有一个payloadID, 它表示负责通过submitOrderPaypal(data.paymentID)提交表单的charge_id。对于订阅, 仅在执行后通过在paypal_execute_subscription_url上请求POST才能获得charge_id, 因此我们可以调用submitOrderPaypal(executeData.id)。
步骤7:测试你的应用程序。
  • 访问索引页面。
  • 选择订阅产品, 然后选择PayPal作为付款方式。
  • 单击提交PayPal按钮。
  • 在PayPal弹出窗口中:
    • 使用你创建的买方帐户的凭据。
    • 登录并确认你的订单。
    • 弹出窗口应该关闭。
  • 检查是否将你重定向到成功页面。
  • 最后, 通过在https://www.sandbox.paypal.com/signin上使用你的企业帐户登录并检查仪表板https://www.sandbox.paypal.com/listing/, 来检查订单是否在PayPal帐户中执行了交易。
总结 阅读本文之后, 你应该能够在Rails应用程序中集成PayPal和Stripe的付款/收费以及订阅交易。为了简洁起见, 我在本文中未添加很多要改进的地方。我根据困难的假设组织了一切:
  • 更轻松:
    • 使用传输层安全性(TLS), 以便你的请求使用HTTPS。
    • 为PayPal和Stripe实施生产环境配置。
    • 添加一个新页面, 以便用户可以访问以前的订单的历史记录。
  • 介质:
    • 退款或取消订阅。
    • 提供非注册用户付款的解决方案。
  • 更难:
    • 如果用户希望回来, 提供一种删除帐户并保留其令牌和customer_id的方法。但是几天后, 请删除此数据, 以使你的应用程序更兼容PCI。
    • 移至服务器端的PayPal版本2 API(https://developer.paypal.com/docs/api/payments/v2/)我们在本教程中使用的gem paypal-sdk-rest, 仅具有beta版本2, 因此可以谨慎使用(https://github.com/paypal/PayPal-Ruby-SDK/tree/2.0-beta)。
    • 包括幂等请求。
      • Stripe:https://stripe.com/docs/api/idempotent_requests
      • PayPal:https://developer.paypal.com/docs/api-basics/#api-idempotency
我还建议阅读有关Stripe Checkout元素的信息, 这是将Stripe集成到前端的另一种方法。与本教程中使用的Stripe Elements不同, Stripe Checkout在单击按钮(类似于PayPal)后打开弹出窗口, 用户可在其中填写信用卡信息或选择使用Google Pay / Apple Pay https://stripe.com进行支付/ docs / web。
第二读建议是两个支付网关的安全页面。
  • 对于Stripe
  • 对于PayPal
【在Ruby on Rails中集成Stripe和PayPal付款方式】最后, 感谢你阅读本文!你还可以检查用于该项目示例的我的GitHub项目。在那里, 我在开发时也添加了rspec测试。

    推荐阅读