Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ผสานรวม One-Time Purchase ใน Rails ด้วย Stripe Checkout อย่างราบรื่น

Stripe ถูกใช้โดยล้านบริษัท ให้ Payment Infrastructure จัดการ Subscriptions และ One-time Purchases Stripe Checkout ช่วยรับชำระผ่านบัตรผ่าน Hosted Payments Page ที่ออกแบบเพื่อเพิ่ม Conversions รวมกับ Webhooks ให้นักพัฒนาขาย Products/Subscriptions และ Deliver ดิจิทัลได้

แม้จะ Handle Payments เองด้วย Provider อื่นได้ แต่ Stripe มี Benefits: เร็วกว่า (4,000+ คนทำงานเพื่อ Payments ให้ Simple, Secure, Convenient) Build Trust "Powered by Stripe" เพิ่ม Conversion Customers หวั่นให้ Card/Bank Info ทุกเว็บ ไม่ผิด

Stripe ไม่ใช่ Player คนเดียว Paddle ใหญ่เท่า Stripe Features คล้ายแต่ Fee สูงกว่า Developer Experience แย่กว่า (Anecdotally) Competitors อื่นๆ มีแต่ Popularity ของ Stripe ทำให้เลือกง่าย

SaaS มัก Recurring Subscriptions แต่มาก Products ได้ประโยชน์จาก One-time Sales Digital Products ง่ายเท่าเดิมกับ Stripe แต่ Documentation ดูเฉยๆ เรื่องนี้

Setup (การตั้งค่า)

สร้าง App ให้ Users จ่ายเงินโพสต์โฆษณา Craigslist-style Example: Website หา Roommate Browse ฟรี จ่าย Fee เพื่อโพสต์เอง

Create a Stripe Account

สมัคร Stripe Account ที่เว็บไซต์ ใช้ Info Particular หรือ Business เราสนใจ Payments and Payouts Product โดยเฉพาะ Stripe Checkout

ผสานรวม One-Time Purchase ใน Rails ด้วย Stripe Checkout อย่างราบรื่น Screenshot Stripe Sign-up Page

Create a Basic Rails App

Stripe Account Ready -> สร้าง Rails App Basic สำหรับ Integration Existing App หรือสนใจแค่ Stripe ข้ามไป "Basic Stripe Integration"

Versions: Rails 6.1, Ruby 3.0.0 Assuming Ruby/Rails Installed:

rails new roommate-board
cd roommate-board
rails s

เห็น Rails Welcome Page ที่ localhost:3000 = Ready!

Add Devise Authentication: gem 'devise' -> bundle install -> rails generate devise:install -> rails generate devise User -> rails db:migrate

Create a Roommate Board

ไม่ Focus Front-end Design สร้าง Model Posting: rails generate scaffold Post address:string rent:integer content:text -> rails db:migrate

Root Route = Posts Index ใน config/routes.rb: root 'posts#index'

Header Links Sign In/Out/Up ใน 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>

ไม่ Signed In -> Root Page:

ผสานรวม One-Time Purchase ใน Rails ด้วย Stripe Checkout อย่างราบรื่น Screenshot Basic Posts Index

Scope Posting ต่อ User: belongs_to :user (Post) + has_many :posts (User) Migration: rails g migration AddUserIdToPosts Edit Migration: add_column :posts, :user_id, :integer -> rails db:migrate

Models Association: belongs_to :user (Post), has_many :posts (User) Controllers: @post = current_user.posts.build (new), @post = current_user.posts.build(post_params) (create)

Ownership Check Method ใน PostsController:

def user_owns_post?
  @post.user == current_user
end

Edit/Destroy Methods: Redirect ถ้าไม่ใช่ Owner

Manage Posts Feature: def manage; @posts = current_user.posts; end Route: get "/manage_posts/" => 'posts#manage' View app/views/posts/manage.html.erb Copy จาก index.html.erb เปลี่ยน Header "Manage Posts" Add Link "New Post"

Test ทุกอย่าง: Create User, Sign In/Out, View Index, Create Posting -> Ready for Stripe!

Stripe Integration

Adding the Stripe Ruby Gem

gem 'stripe'
bundle install

Stripe API Key: Stripe Console -> Developers -> API Keys -> Secret Key สร้าง Initializer config/initializers/stripe.rb: Stripe.api_key = "your-secret-key"

Storing Stripe Credentials (เก็บ Credentials อย่างปลอดภัย)

วิธีนี้ Insecure (Plaintext, Git Tracked) Rails มี Encrypted Credentials: EDITOR=vim bin/rails credentials:edit ใส่:

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

Save Exit Vim (Esc -> :wq + Enter) Update Initializer: Stripe.api_key = Rails.application.credentials[:stripe][:secret]

Linking to Stripe Checkout From the Product

Stripe Checkout = Hosted Payment Page ไม่ต้องสร้าง UI Transaction เอง User Click Button -> Redirect Stripe -> Redirect Back App หลังจ่าย สะดวกแต่ Manage Context ยาก Stripe จัด Payment Form ให้ Maximize Conversions รองรับ Payment Types หลายอย่าง (ACH, International)

สร้าง Controller Checkout: 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

สร้าง Checkout Session พร้อม Context Line Item Hardcoded (Single Product) Amount = Cents ใส่ User Email Metadata = Post ID สำหรับ Fulfillment ใน Webhooks

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

Button Submit Payment ใน Manage Posts View (app/views/manage.html.erb): Add Column "Payment" ใน Header และ Body:

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

JavaScript

Add Stripe JS ใน app/views/application.html.erb: <script src="https://js.stripe.com/v3/"></script>

File 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)
});

Setting Up Webhooks

Users จ่ายได้แล้วแต่ App ไม่รู้ว่า Post จ่ายแล้วหรือยัง Activate ยังไง Add Boolean is_paid Migration: rails g migration AddPaymentDetailsToPost -> add_column :posts, :is_paid, :boolean, default: false -> rails db:migrate

Payments ไม่ Instant Process ไม่เชื่อ API Response ได้ ใช้ Webhooks: Stripe POST Request เมื่อ Payment เสร็จ สร้าง WebhooksController:

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
      puts "Signature error"; p e; return
    end
    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

Route: resources :webhooks, only: [:create]

Connect Stripe Dashboard: Developers -> Webhooks -> Add Endpoint URL + /webhooks Event: checkout.session.completed ต้อง Deploy Live Site Webhooks จึงทำงาน (Local ไม่ได้)

Index Page แสดงแค่ Paid Posts: def index; @posts = Post.where(is_paid: true); end

Switching to Production

Webhooks ต้อง Deploy Live HTTPS Required สำหรับ Stripe Checkout Check JS Console หาก Issues

Test Keys -> Production Keys: Stripe Dashboard Toggle "Viewing Test Data" แล้ว Update Rails Credentials ด้วย Production Keys Test Env ช่วย Test Transactions Fake Cards Multiple Environments -> เพิ่ม Key/Value Pair ใน Credentials Dynamically Use

Conclusion (สรุป)

ใช้เวลาสร้าง Example App มาก Stripe Integration ไม่นาน Stripe Checkout โยน Responsibility (UI) ให้ Stripe ประหยัด Code ให้ Consistent Experience (Stripe Popular) Mission: Increase GDP of Internet ต้องการ Apps เหล่านี้ จึงหา Ways ให้ Process Payments Easier

Stripe Payment Links = New Product ทำให้ Checkout Integration เกือบ Unnecessary Redirect User -> Hosted Page Simple Link ทำให้ง่ายกว่า No-code Solution สำห Retailers แต่ Potential สำหรับ Use Case นี้ Stripe Checkout ยัง Flexible/Adopted มากกว่า Important To Understand

Tax Handling (International) = Struggle Stripe New Feature Auto-handle (Beta) Available -> Reduce Accounting Burden

Stripe Docs/Articles เลานไป Subscriptions มากกว่า One-time Subscriptions = Recurring Charges Implementation คล้าย One-time แต่ App Side: Subscription Management, User Manage, Periodic Status Check บทความ Focus One-time แต่ Community Resources เยอะสำหรับ Subscriptions