如何在Rails中用Stripe发布广告以销售产品

297 阅读14分钟

Stripe被数以百万计的公司所使用,它为处理订阅和一次性购买的应用程序提供支付基础设施。Stripe Checkout允许我们通过托管的支付页面轻松地接受银行卡支付,这是为提高转换率而精心设计的。将其与webhooks相结合,允许开发人员销售产品和订阅,然后以数字方式交付。

虽然在技术上可以使用现有的供应商自己处理付款,但Stripe提供了许多好处。首先,它的速度更快。Stripe有超过4000人致力于使支付尽可能简单、安全和方便。

此外,让您的付款 "由Stripe提供 "是建立客户信任和提高转换率的一个简单方法。客户通常不愿意将他们的银行卡或银行信息提供给他们访问的每个网站,这也是理所当然的。

Stripe并不是在线支付领域的唯一供应商。Paddle是在线支付领域的另一个巨大参与者。Paddle提供了很多与Stripe类似的功能,但收费略高,而且据说开发者的体验更差。虽然其他几个竞争者是Stripe的替代品,但他们的产品在在线支付方面的纯粹普及使他们成为一个简单的选择。

对SaaS公司来说,处理经常性的订阅是很常见的,但许多产品会从数字产品的一次性销售中受益。使用Stripe的基础设施也同样容易,但他们的文档似乎对这个话题避而不谈。

设置

为了解释这个过程,我们将建立一个应用程序,允许用户付费在一个类似于Craigslist的论坛上发布广告。对于我们的具体例子,我们的应用程序将是一个为寻找室友的人提供的网站。用户可以免费浏览板子,但必须支付费用才能发布自己的广告。

创建一个Stripe账户

首先,你需要到Stripe的网站上创建一个账户。用你的信息或你的企业信息(如果适用)进行注册。

A screenshot of the Stripe sign-up page Stripe注册页面的截图

这篇文章不会告诉你创建账户的具体细节,但他们的文档可能会回答你的任何问题。我们对他们的支付和付款产品特别感兴趣,或者更具体地说,Stripe Checkout

创建一个基本的Rails应用程序

一旦你的Stripe账户设置完毕,就可以创建一个基本的Rails应用程序,我们将用它来进行整合。如果您正在集成一个现有的应用程序,或者只是对本教程中的Stripe部分感兴趣,请跳到 "Stripe基本集成"。

在这个例子中,我将使用以下版本:

  • Rails 6.1
  • Ruby 3.0.0

假设您已经安装了Ruby和Rails,继续运行以下程序:

rails new roommate-board

我为这个应用程序选择的名字是roommate-board ,但你可以自由选择其他名字。只要把你在示例代码中看到的其他地方的名字换掉就可以了。

用下面的方法切换到刚刚创建的目录中:

cd roommate-board

然后,用下面的方法运行该应用程序:

rails s

如果你在访问localhost:3000 时看到典型的Rails欢迎页面,那么恭喜你,我们已经准备好开始编码了!

首先,我们将添加Devise来为我们处理用户认证。只需在你的Gemfile中添加gem 'devise' ,然后运行以下程序。

bundle install

接下来,使用Devise生成器,通过运行以下程序来设置。

rails generate devise:install

接下来,通过运行以下程序将Devise连接到一个用户模型。

rails generate devise User

最后,用下面的程序运行迁移。

rails db:migrate

创建一个室友委员会

如果你正在读这篇文章,你可能对Stripe集成比前端设计更感兴趣,所以我们不会担心我们的例子的风格问题。

我们的下一步是创建发帖的模型我们将使用Rails的便捷生成器来创建模型、迁移、控制器和路由。只需运行以下程序。

rails generate scaffold Post address:string rent:integer content:text

这个模型并不包含我们整合Stripe所需要的一切。我们将在后面添加它,以防你在参考本教程时,没有时间从头开始的项目。运行迁移,创建数据库表,其内容如下:

rails db:migrate

接下来,为方便起见,我们将使帖子的索引视图成为我们应用程序的根。在config/routes.rb ,添加以下一行:

root 'posts#index'

现在它是相当空的,没有明确的方法来登录、退出或注册。功能已经有了,所以我们只需在标题中添加一些链接,让用户清楚地了解它。在app/views/layouts/application.html.erb ,我们将添加必要的链接。我的文件在做了修改后看起来像这样:

<!DOCTYPE html>
<html>
  <head>
    <title>RoommateBoard</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <header>
      <nav>
        <a href="/#" class="block">
          <h1>Roommate Board</h1>
        </a>
        <% if user_signed_in? %>
          <%= link_to "Manage Posts", manage_posts_url %>
          <%= link_to "Sign Out", destroy_user_session_url, :method => 'delete' %>
        <% else %>
          <%= link_to "New Post", new_user_registration_url %>
        <% end %>
      </nav>
    </header>
    <%= yield %>
  </body>
</html>

这给出了一个通往主页的链接,当用户点击 "新帖 "时,他们将被带到注册页面。如果他们已经登录,它会变成一个 "管理帖子 "按钮和一个 "退出 "按钮。在没有任何造型和没有登录的情况下,根页面现在看起来像这样。

A screenshot of the basic posts index page 基本帖子索引页的截图

接下来,我们要将每个帖子的范围扩大到一个独特的用户。本质上,我们将在Posting和User之间建立一个 belongs_to关联,以及一些相关的逻辑。我们将从Rails方便的迁移生成器开始;只需运行以下程序。

rails g migration AddUserIdToPosts

现在在db/migrate/ 中编辑迁移文件,使其看起来像这样:

class AddUserIdToPosts < ActiveRecord::Migration[6.1]
  def change
    add_column :posts, :user_id, :integer
  end
end

最后,用下面的命令运行迁移:

rails db migrate

接下来,我们将向模型本身添加关联。在app/models/post.rb 中,添加以下一行:

belongs_to :user

而在app/models/user.rb 中,添加以下内容:

has_many :posts

这个双向关联给了我们一些方便的Rails方法,比如我们即将在create和new方法中使用的方法。导航到app/controllers/posts_controller.rb (帖子控制器),看看方法。不要使用@post = Post.new ,而是使用@post = current_user.posts.build

创建方法中,我们将做类似的事情。用@post = current_user.posts.build(post_params) 替换@post = Post.new(post_params) 。新方法和创建方法应该看起来像这样:

# GET /posts/new
def new
  @post = current_user.posts.build
end

  # POST /posts or /posts.json
def create
  @post = current_user.posts.build(post_params)

  respond_to do |format|
    if @post.save
      format.html { redirect_to @post, notice: "Post was successfully created." }
      format.json { render :show, status: :created, location: @post }
    else
      format.html { render :new, status: :unprocessable_entity }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

这确保了用户的id属性被保存在每篇帖子中,但这并不能阻止其他用户编辑或删除不属于他们的帖子!在同一个PostsController中,让我们写一个方法,给我们一个布尔指标,显示用户是否拥有一个帖子。把这个方法添加到控制器的底部:

def user_owns_post?
  @post.user == current_user
end

编辑方法中,添加以下内容(这将是其中唯一的代码)。

unless user_owns_post?
  # Redirect them to an error page
  redirect_to posts_path, flash: { error: "You are not the owner of that post!" }
end

destroy方法中,把这个添加到开头:

unless user_owns_post?
  # Redirect them to an error page
  redirect_to posts_path, flash: { error: "You are not the owner of that post!" }
end

接下来,我们需要一些功能,让用户管理他们现有的帖子。我们的最终目标是让用户能够创建一个帖子,并在他们付款之前让它处于草稿状态。一旦他们付款,它就被设置为活动状态。在PostsController中,添加一个新方法:

def manage
  @posts = current_user.posts
end

接下来,添加一个路由,这样用户就可以到达那里了!在config/routes.rb ,添加以下内容:

get "/manage_posts/" =>'posts#manage'

最后,我们将创建帖子管理的视图!添加一个新文件,app/views/posts/manage.html.erb 。然后,将app/views/posts/index.html.erb 的全部内容复制到其中。只需将标题从 "帖子 "改为 "管理帖子"。这本质上是一个索引页,但由于我们的控制器逻辑,单个用户的帖子是唯一被索引的。从技术上讲,我们可以在个人的索引页上做这个,但这种分离使得以后添加其他功能更加容易。例如,我们现在要在这个管理页面上添加一个链接来创建一个新的帖子

在标题下,简单地添加以下内容:

<%= link_to "New Post", new_post_url%>

这就是我们需要的所有设置这是一个好主意,可以通过它来确保一切工作正常。你现在应该能够做以下事情:

  • 创建一个用户账户
  • 登录
  • 签出
  • 查看现有帖子的索引
  • 创建一个新的帖子

如果这一切都按你所期望的方式进行,那么是时候进行Stripe整合了!

Stripe集成

添加Stripe Ruby gem

首先,我们将添加Stripe Ruby gem。在您的Gemfile中,添加以下一行:

gem 'stripe'

在这后面加上运行:

bundle install

对于下一部分,您将需要您的Stripe API密钥。在Stripe控制台,点击侧边栏上的 "开发人员"。一个 "API密钥 "的选项将显示出来,所以继续点击它。记下您的 "秘密密钥",因为我们很快就会需要它。

但首先,在config/initializers 中创建一个初始化器。创建一个名为stripe.rb 。在这个文件中,添加一个看起来像这样的单行(但用你的秘密密钥代替)。

Stripe.api_key = <insert your key here as a string>

存储Stripe凭证

然而,这种存储凭证的方法是不安全的。我们不应该将生产秘密存储在明文中,因为它们会被git追踪,并且任何阅读代码库的人都可以看到。幸运的是,Rails提供了一种安全存储凭证的方法。

在你的shell中,运行EDITOR=vim bin/rails credentials:edit ,解密并在vim中打开凭证文件。按下键盘上的'i'键,切换到插入模式。添加一个新的部分,看起来像这样:

stripe:
  secret: your-secret-key
  public: your-public-key

接下来,保存该文件并退出vim。有几种方法可以做到这一点,但我最喜欢的是按下escape键(离开插入模式),然后输入:wq ,再按下回车键。如果你想了解更多关于Rails如何处理加密凭证的信息,这是一个很好的资源

现在凭证已经安全地储存起来了,用这个替换掉不安全的初始化代码。

Stripe.api_key = Rails.application.credentials[:stripe][:secret]

从产品链接到Stripe Checkout

我们有几种不同的方式可以利用Stripe进行支付,但一个很好的选择是Stripe Checkout。Stripe Checkout是一个托管的支付页面,这意味着你不需要为实际交易创建任何用户界面。用户会点击一个按钮,被重定向到Stripe,然后在支付完成后被重定向到你的应用程序。这使管理上下文成为一个挑战,但它消除了创建支付表单的负担,并让Stripe做大量的工作来最大化转换率。你可以轻松地接受卡以外的支付类型,包括ACH交易或许多国际支付方式。

首先,创建一个控制器来处理结账过程。创建app/controllers/checkout_controller.rb 。在其中,我们将写一个方法来处理结账会话的创建。

def create
  @session = Stripe::Checkout::Session.create({
    success_url: root_url,
    cancel_url: manage_posts_url,
    payment_method_types: ['card'],
    line_items: [{
        name: "Roommate Posting",
        amount: 2000,
        currency: "usd",
        quantity: 1
    }],
    mode: 'payment',
    metadata: {post_id: params[:post_id]},
    customer_email: current_user.email,
    success_url: manage_posts_url,
    cancel_url: manage_posts_url
  })

  respond_to do |format|
    format.js
  end
end

这将创建一个具有必要上下文的结账会话,以便Stripe发挥其魔力。我们传递一个单行项目,由于我们的应用程序只有一个产品,所以我们在这里硬编码了这个项目。但这将是一个很容易的地方,可以使用传递的参数来定制特定产品的结帐。Amount "变量是产品的成本 ,单位是美分 。值得一提的是,为了简单起见,我们还传入了当前用户的电子邮件。最后需要指出的是,我们把帖子的id作为元数据传递给了对方。这将使我们很容易在网络钩子部分实现我们用户的购买

接下来,我们显然需要一种方法来调用该方法。

config/routes.rb ,添加以下一行,创建一个通往控制器的路由:

post 'checkout/create' => 'checkout#create', as: "checkout_create"

接下来,我们将在管理帖子视图中添加一个按钮来提交任何帖子的付款。在app/views/manage.html.erb ,通过添加最后这句话在标题中添加一个额外的列。

<th>Payment</th>

同时,将<th colspan="3"></th> 切换到<th colspan="4"></th>

接下来,在表体中为付款按钮本身添加另一个项目。作为正文中的第四项,添加以下内容:

<td><%= button_to "Submit Payment", checkout_create_path, params: {:post_id => post.id }, remote: true %></td>

总而言之,管理帖子的视图现在看起来像这样:

<p id="notice"><%= notice %></p>

<h1>Manage Posts</h1>
<%= link_to "New Post", new_post_url%>

<table>
  <thead>
    <tr>
      <th>Address</th>
      <th>Rent</th>
      <th>Content</th>
      <th>Payment</th>
      <th colspan="4"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.address %></td>
        <td><%= post.rent %></td>
        <td><%= post.content %></td>
        <td><%= button_to "Submit Payment", checkout_create_path, params: {:post_id => post.id }, remote: true %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_post_path %>

JavaScript

不要忘记将Stripe的JavaScript包包括在这一行中添加到app/views/application.html.erb

<script src="https://js.stripe.com/v3/"></script>

接下来,你需要在app/views/checkout/create.js.erb 中添加一个新的文件(和目录!)。 在这个文件中,只需添加以下内容,允许在控制器的帮助下,在点击按钮时创建结账会话:

var stripe = Stripe("<%= Rails.application.credentials[:stripe][:public] %>")

stripe.redirectToCheckout({
    sessionId: '<%= @session.id %>'
}).then(function (result) {
    console.log(result.error_message)
});

设置Webhooks

现在,我们有了一个让用户为帖子付费的方法!然而,我们没有办法让应用程序知道一个帖子是否被支付或在支付后激活它。首先,在帖子模型中添加一个布尔值,以表明帖子是否已被支付。要使用Rails迁移生成器,请运行以下程序:

rails g migration AddPaymentDetailsToPost

打开这个迁移,并在其中添加以下一行:

add_column :posts, :is_paid, :boolean, :default => false

这将在post表/模型中添加一个属性/列,名为is_paid 。这个属性是一个布尔值,默认为false,这意味着每当创建一个帖子时,它都被标记为未被支付。当一个帖子被支付后,我们将手动翻转这个布尔值。但首先,运行你刚才写的迁移:

rails db:migrate

因为付款不是即时处理的,我们不能依靠Stripe的成功API响应来确定一个工作已经被支付。相反,我们可以在我们的应用程序上指出一个端点,让Stripe在付款完成处理后发出一个帖子请求。这个过程通常被称为webhooks,实际上比听起来要简单得多!

要开始,在app/controllers/ ,创建一个新的控制器,称为WebhooksController。在这个app/controllers/webhooks_controller.rb ,写下以下内容:

class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    payload = request.body.read
    sig_header = request.env['HTTP_STRIPE_SIGNATURE']
    event = nil

    begin
    event = Stripe::Webhook.construct_event(
      payload, sig_header, Rails.application.credentials[:stripe][:webhook]
    )
    rescue JSON::ParserError => e
      status 400
    return
    rescue Stripe::SignatureVerificationError => e
      # Invalid signature
      puts "Signature error"
      p e
      return
    end

    # Handle the event
    case event.type
    when 'checkout.session.completed'
      session = event.data.object
      post = Post.find_by(id: session.metadata.post_id)]
      post.is_paid = true
      post.save!
    end

    render json: { message: 'success' }
  end
end 

除了必要的模板外,这个方法将处理一个特定的事件类型,称为checkout.session.completed 。在Rails方面,剩下的就是将路由添加到config/routes.rb

resources :webhooks, only: [:create]

最后,你需要在Stripe仪表盘中把你的Stripe账户连接到这个端点。因为您将向Stripe仪表盘提供一个URL,所以这在本地是行不通的。您必须将其部署到互联网可访问的端点,以便webhooks能够正常工作。

返回到Stripe仪表板上的 "开发人员",但这次选择左侧面板上的 "Webhooks"。在 "端点 "部分,点击 "添加端点"。提供您的应用程序的URL后缀为/webhooks ,并将其指定为事件 "checkout.session.completed"。

这就是Stripe的集成!我们的模拟应用程序的最后一个实际步骤是让索引页只显示已付款的帖子。这可以很容易地在app/controllers/posts_controller.rb 的index方法中完成。 将该方法改为这样。

def index
  @posts = Post.where(:is_paid => true)
end

现在,用户可以创建一个帖子,用Stripe结账付款,并让它自动显示在应用程序的主页上!

切换到生产中

因为你的webhooks只有在部署到现场时才能工作,所以值得讨论一下部署时需要的变化。首先,您的通信必须是HTTPS,以便Stripe结账工作。如果您遇到任何问题,请检查JavaScript控制台,了解Stripe的一些提示。

很有可能在您的初始设置中,您使用了Stripe的测试密钥。如果您使用了,那么Stripe仪表盘的API密钥部分将看起来像这样。

A screenshot of Stripe dashboard API Keys 斯特赖普仪表盘API密钥的屏幕截图

只需点击 "查看测试数据 "旁边的切换按钮,就可以看到您的生产秘密和公共密钥。你需要像以前一样打开Rails凭证管理器,用它来替换测试密钥,这样你的应用程序才能处理实时数据。不过,测试环境对于用假的信用卡号码运行测试交易很有帮助。如果你有一个以上的环境或打算经常切换,那么在凭证管理器中创建另一个键/值对并在你的代码中动态地使用它是值得的。

总结

我们已经花了很多时间来创建我们的示例应用程序,并没有花太多时间来整合Stripe。多亏了Stripe结账,我们把很多责任,包括用户界面,都转嫁到了我们身上,节省了大量的代码。鉴于Stripe在网上市场的广泛采用,这为用户创造了一致的体验。Stripe的使命是提高互联网的GDP,他们需要像你这样的应用程序来实现这一目标。正因为如此,他们一直在寻找更容易的方式让你处理付款。虽然Stripe Checkout相当容易实现,但他们最近想到了一种更快速的方法。

Stripe Payment Links是Stripe的一个全新的产品,它有可能使我们的Checkout集成几乎没有必要。我们真正做的是把用户重定向到一个托管的结账页面,而一个简单的链接有可能使之变得更加容易。支付链接是一种无代码的解决方案,似乎是针对零售商的,但有可能适用于这种用例。不管怎么说,Stripe Checkout在市场上仍然拥有更大的灵活性和采用率,所以充分了解它是很重要的。

许多企业都在为处理税收问题而苦恼,尤其是在国际上。Stripe最近推出了一个新功能,为企业自动处理这个问题,但它仍处于测试阶段。一旦推出,它应该会大大减轻使用Stripe进行支付处理的人的会计负担

Stripe的文档和针对Ruby on Rails的现有文章都倾向于订阅而不是一次性购买。订阅是附加在用户和产品上的经常性费用。在 Stripe 这一端,其实现看起来很像一次性购买。然而,在应用程序方面,我们必须整合订阅管理,允许用户管理他们的订阅,并定期检查订阅状态。