Processing Stripe Payments with a Background Worker in Rails

Giới thiệu

Kỹ thuật được sữ dụng trong bài viết này có thể được sữ dụng để xữ lý bất kỳ công việc dài hạn nào trong background. Ví dụ dưới đây sẽ không thực sự xử lý việc thanh toán mà background job sẽ nhận dữ liệu để xử lý.

Xử lý thanh toán trong Background job

Tạo một class background job để xử lý thanh toán:

rails g job stripe_payment

Lúc này file app/jobs/stripe_payment_job.rb sẽ như sau:

class StripePaymentJob < ApplicationJob
  queue_as :default

  def perform(*args)
    # Xử lý thanh toán ở đây
  end
end

Thêm gem rescue vào Gemfile và chạy bundle:

gem 'resque'

# Chạy bundle
bundle install

Trong file application.rb định nghĩa queue_adapter:

config.active_job.queue_adapter = :resque

Tạo một controller để xử lý thanh toán.

rails g controller payments create

Nó sẽ nhận một token và payment id trong action create và sẽ xử lý thanh toán trong background:

class PaymentsController < ApplicationController
  def create
    StripePaymentJob.perform_later('process payment later')
  end
end

Nó sẽ chỉ gửi một chuỗi tham số, bạn có thể gửi toklen và payment id dưới dạng hash cho phương thức perform_later. Hãy khởi động server và vào địa chỉ http://localhost:3000/payments/create. Bạn sẽ thấy:

Started GET "/payments/create" for ::1 at 2016-06-07 19:39:45 -0700
Processing by PaymentsController#create as HTML
[ActiveJob] Enqueued StripePaymentJob (Job ID: 3b3ab29c-5978-4f70-912f-bf5ec0e8a98e) to Resque(default) with arguments: "process payment later"

Server sẽ lấy thông tin mỗi 5s. AJAX sẽ gọi payments/show để kiểm tra trạng thái của thanh toán bằng function poll trong tệp tin log:

(function poll(){
   setTimeout(function(){
      $.ajax({ url: "show", success: function(data){
        //Setup the next poll recursively
        poll();
      }, dataType: "json"});
  }, 5000);
})();

Thêm phương thức show vào PaymentsController:

def show
  render json: { result: 'success'}
end

Bây giờ chúng ta có thể tự quản lý trạng thái của thanh toán (success, failure, pending) trong file payment.js:

(function poll(){
   setTimeout(function(){
      $.ajax({ url: "show", success: function(data){
        console.log(data.result);
        if(data.result === 'success') {
            alert('It was success');
        } else if (data.result === 'failure') {
            alert('It failed');
        } else if (data.result === 'pending') {
            //Setup the next poll recursively
            poll();         
        }
      }, dataType: "json"});
  }, 5000);
})();

Chúng ta tiếp tục check cho trường hợp thanh toán đang ở trạng thái pending bằng cách mô phỏng trường hợp fail trong action show:

def show
  render json: { result: 'failure'}
end

Chúng ta có thể thêm trạng thái của thanh toán để báo cho người dùng biết trạng thái hiện tại của payment.

(function poll(){
   setTimeout(function(){
      $.ajax({ url: "show", 
        beforeSend: function() {
           $('#loader').show();
        },      
       success: function(data){
        console.log(data.result);
        if(data.result === 'success') {
            $('#loader').hide();
            $('#status').html('Payment processed successfully.')
        } else if (data.result === 'failure') {
            $('#loader').hide();
            $('#status').html('Payment processing failed.')
        } else if (data.result === 'pending') {
            //Setup the next poll recursively
            poll();         
        }
      }, dataType: "json"});
  }, 5000);
})();

Làm cách nào để truyền payment id từ Rails qua Javascripts?

Thêm đoạn code sau vào PaymentsController:

def create
  @payment = OpenStruct.new(id: 200)
  StripePaymentJob.perform_later('process payment later')
end

Chúng ta có thể sử dụng javascript window object để thiếp lập paymentID trong create.html.erb:

<h1>Payments#create</h1>
<%= javascript_tag do %>
  window.paymentID = '<%= @payment.id %>';
<% end %>
<div id='loader'>
    <img src="http://preloaders.net/preloaders/712/Floating%20rays.gif"/>
</div>
<div id='status'>Payment is being processed. Please wait...</div>

Sau đó, chuyển action show sang fail:

def show
  logger.info params
  render json: { result: 'failure'}
end

Lúc này, chúng ta có thể truyền paymentID từ create.html.erb sang payment.js

(function poll(){
   setTimeout(function(){
      $.ajax({ url: "show/" + paymentID,
        beforeSend: function() {
           $('#loader').show();
           console.log(paymentID);
        },      
       success: function(data){
        console.log(data.result);
        if(data.result === 'success') {
            $('#loader').hide();
            $('#status').html('Payment processed successfully.')
        } else if (data.result === 'failure') {
            $('#loader').hide();
            $('#status').html('Payment processing failed.')
        } else if (data.result === 'pending') {
            //Setup the next poll recursively
            poll();         
        }
      }, dataType: "json"});
  }, 5000);
})();

Nếu bạn sẽ gặp phải lỗi sau:

GET http://localhost:3000/payments/show/200 404 (Not Found)

thì hãy định nghĩa routes:

get 'payments/show/:id', to: 'payments#show'

Bây giờ chúng ta có thể thấy được payment id đã được truyền lên server

Started GET "/payments/show/200" for ::1 at 2016-06-07 15:52:20 -0700
Processing by PaymentsController#show as JSON
Parameters: {"id"=>"200"}

Chúng ta có thể sử dụng payment id để kiểm tra trạng thái của xử lý thanh toán bằng cách đẩy vào trong database. Hãy hiển thị receipt id trong trường hợp thanh toán thành công:

$('#status').html('Payment processed successfully. Your receipt ID is : ' + data.receipt_id);

Thêm đoạn code sau vào PaymentsController:

def show
  logger.info params
  render json: { result: 'success', receipt_id: 'CXM4873'}
end

Thay thế URL trong javascript

Sử dụng các thuộc tính dữ liệu của HTML 5 để loại bỏ các window object mà chúng ta đã thêm vào ở phần trên:

<div id='loader' data-url="<%= payments_show_url(@payment.id) %>">
    <img src="http://preloaders.net/preloaders/712/Floating%20rays.gif"/>
</div>

Thêm vào thư mục routes sau để hiển thị:

get 'payments/show/:id', to: 'payments#show', as: :payments_show

Xóa phần truyền paymentID từ rails qua javascript đã thêm ở trên:

<h1>Payments#create</h1>
<div id='loader' data-url="<%= payments_show_url(@payment.id) %>">
    <img src="http://preloaders.net/preloaders/712/Floating%20rays.gif"/>
</div>
<div id='status'>Payment is being processed. Please wait...</div>

Cuối cùng file payment.js sẽ như sau:

(function poll(){
   setTimeout(function(){
       $.ajax({ url: $('#loader').data('url'), 
        beforeSend: function() {
           $('#loader').show();
        },      
       success: function(data){
        console.log(data.result);
        if(data.result === 'success') {
            $('#loader').hide();
            $('#status').html('Payment processed successfully. Your receipt ID is : ' + data.receipt_id);
        } else if (data.result === 'failure') {
            $('#loader').hide();
            $('#status').html('Payment processing failed.')
        } else if (data.result === 'pending') {
            //Setup the next poll recursively
            poll();         
        }
      }, dataType: "json"});
  }, 5000);
})();

Tham khảo

https://rubyplus.com/articles/4111-Processing-Stripe-Payments-with-a-Background-Worker-in-Rails-5