Adding Plaid to Your Rails Application

duartemartins

Duarte Martins

Posted on May 19, 2024

Adding Plaid to Your Rails Application

This is a repost of the orignal article at: https://popadex.com/2024/05/19/adding-plaid-to-rails-app

Integrating Plaid into your Rails application allows you to link bank accounts and retrieve financial data, providing a seamless way to access and manage financial information. This guide will walk you through the process of adding Plaid to your Rails app, assuming you have TailwindCSS already set up for styling. We will cover the necessary steps to configure the backend, set up routes, and create views for linking bank accounts and displaying account details.

Step 1: Add the Plaid Gem

First, you need to add the Plaid gem to your Gemfile. This gem provides the necessary methods to interact with the Plaid API.

Gemfile:

gem 'plaid'
Enter fullscreen mode Exit fullscreen mode

Then, run:

bundle install
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Plaid Configuration

Next, you need to initialize Plaid in your application. Add the following code to your initializers:

config/initializers/plaid.rb:

# Initialize Plaid configuration
plaid_config = Plaid::Configuration.new
plaid_config.server_index = Plaid::Configuration::Environment["sandbox"]  # or another environment as needed
plaid_config.api_key["PLAID-CLIENT-ID"] = Rails.application.credentials.dig(:development, :plaid, :client_id)
plaid_config.api_key["PLAID-SECRET"] = Rails.application.credentials.dig(:development, :plaid, :secret)

# Create an API client instance
api_client = Plaid::ApiClient.new(plaid_config)

# Create a Plaid API instance to use across the application
PlaidClient = Plaid::PlaidApi.new(api_client)

api_client.create_connection do |builder|
  builder.use Faraday::Response::Logger
end
Enter fullscreen mode Exit fullscreen mode

Step 3: Set Up Routes

Add the necessary routes to your config/routes.rb file:

Rails.application.routes.draw do
  root 'plaid#index'
  post 'plaid/create_link_token', to: 'plaid#create_link_token'
  post 'plaid/exchange_public_token', to: 'plaid#exchange_public_token'
  get 'plaid/accounts', to: 'plaid#accounts'
end
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Plaid Controller

Create a controller to handle Plaid operations:

app/controllers/plaid_controller.rb:

class PlaidController < ApplicationController
  before_action :authenticate_user!
  protect_from_forgery with: :null_session # to handle CSRF protection for API requests

  def index
  end

  def create_link_token
    user = current_user
    link_token_create_request = Plaid::LinkTokenCreateRequest.new({
      user: { client_user_id: user.id.to_s },
      client_name: 'Your App Name',
      products: %w[auth transactions],
      country_codes: ['US'],
      language: 'en'
    })

    begin
      link_token_response = PlaidClient.link_token_create(link_token_create_request)
      render json: { link_token: link_token_response.link_token }
    rescue Plaid::ApiError => e
      Rails.logger.error("Plaid API error: #{e.response_body}")
      render json: { error: e.response_body }, status: :internal_server_error
    end
  end

  def exchange_public_token
    Rails.logger.debug("Received public_token: #{params[:public_token]}")

    if params[:public_token].blank?
      Rails.logger.error('No public_token received')
      return render json: { error: 'No public_token received' }, status: :bad_request
    end

    begin
      exchange_token(params[:public_token])
      render json: { message: 'Bank account linked successfully.' }, status: :ok
    rescue Plaid::ApiError => e
      Rails.logger.error("Plaid API error: #{e.response_body}")
      render json: { error: e.response_body }, status: :internal_server_error
    end
  end

  def exchange_token(public_token)
    request = Plaid::ItemPublicTokenExchangeRequest.new({ public_token: public_token })

    response = PlaidClient.item_public_token_exchange(request)
    access_token = response.access_token
    item_id = response.item_id

    Rails.logger.debug("Access token: #{access_token}")
    Rails.logger.debug("Item ID: #{item_id}")

    if current_user.update(plaid_access_token: access_token, plaid_item_id: item_id)
      Rails.logger.debug('Access token and item ID saved successfully.')
      Rails.logger.debug("Current user after save: #{current_user.inspect}")
    else
      Rails.logger.error("Failed to save access token and item ID. Errors: #{current_user.errors.full_messages.join(', ')}")
    end
  end

  def accounts
    access_token = current_user.plaid_access_token

    if access_token.blank?
      flash[:error] = 'Access token is missing. Please link your account again.'
      return redirect_to root_path
    end

    begin
      accounts_request = Plaid::AccountsGetRequest.new({ access_token: access_token })
      accounts_response = PlaidClient.accounts_get(accounts_request)
      @accounts = accounts_response.accounts
    rescue Plaid::ApiError => e
      Rails.logger.error("Plaid API error: #{e.response_body}")
      flash[:error] = "Plaid API error: #{e.response_body}"
      return redirect_to root_path
    rescue StandardError => e
      Rails.logger.error("Internal server error: #{e.message}")
      flash[:error] = 'Internal server error'
      return redirect_to root_path
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Step 5: Create the View

Create a view to display the link button and account details.

app/views/plaid/index.html.erb:

<!DOCTYPE html>
<html class="dark">
<head>
  <title>Account Details</title>
</head>
<body class="bg-white dark:bg-gray-900 text-black dark:text-white">
  <div class="container mx-auto p-4">
    <h1 class="text-3xl font-bold mb-4">Link Your Bank Account</h1>
    <button id="link-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded dark:bg-gray-900 dark:hover:bg-gray-700 dark:text-white">Link Account</button>
    <script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
    <script>
      document.getElementById('link-button').onclick = function() {
        var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

        fetch('/plaid/create_link_token', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-CSRF-Token': csrfToken
          },
          body: JSON.stringify({})
        })
        .then(response => response.json())
        .then(data => {
          if (data.error) {
            console.error('Error:', data.error);
            return;
          }

          var linkHandler = Plaid.create({
            token: data.link_token,
            onSuccess: function(public_token, metadata) {
              fetch('/plaid/exchange_public_token', {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json',
                  'X-CSRF-Token': csrfToken
                },
                body: JSON.stringify({ public_token: public_token })
              })
              .then(response => response.json())
              .then(data => {
                if (data.error) {
                  console.error('Error:', data.error);
                  return;
                }
                window.location.href = '/plaid/accounts';
              });
            },
            onExit: function(err, metadata) {
              console.log('Plaid link exit', err, metadata);
            }
          });

          linkHandler.open();
        })
        .catch(error => {
          console.error('Fetch error:', error);
        });
      };
    </script>
  </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

app/views/plaid/accounts.html.erb:

<!DOCTYPE html>
<html class="dark">
<head>
  <title>Account Details</title>
</head>
<body class="bg-white dark:bg-gray-900 text-black dark:text-white">

  <div class="container mx-auto p-4">
    <h1 class="text-3xl font-bold mb-4">Account Details</h1>
    <% if @error %>
      <div class="bg-red-500 text-white p-2 rounded mb-4">
        Error: <%= @error %>
      </div>
    <% else %>
      <div class="overflow-x-auto">
        <table class="min-w-full bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700">
          <thead>
            <tr class="bg-gray-200 dark:bg-gray-700">
              <th class="py-2 px-4 border-b border-gray-300 dark:border-gray-600">Name</th>
              <th class="py-2 px-4 border-b border-gray-300 dark:border-gray-600">Type</th>
              <th class="py-2 px-4 border-b border-gray-300 dark:border-gray-600">Subtype</th>
              <th class="py-2 px-4 border-b border-gray-300 dark:border-gray-600">Mask</th>
              <th class="py-2 px-4 border-b border-gray-300 dark:border-gray-600">Balance</th>
            </tr>
          </thead>
          <tbody>
            <% @accounts.each do |account| %>
              <tr class="border-b border-gray-300 dark:border-gray-700">
                <td class="py-2 px-4"><%= account.name %></td>
                <td class="py-2 px-4"><%= account.type %></td>
                <td class="py-2 px-4"><%= account.subtype %></td>
                <td class="py-2 px-4"><%= account.mask %></td>
                <td class="py-2 px-4"><%= number_to_currency(account.balances.available) %></td>
              </tr>
            <% end %>
          </tbody>
        </table>
      </div>
    <% end %>
  </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Conclusion

By following these steps, you can integrate Plaid into your Rails application to link bank accounts and display account details. This guide covers the necessary configuration, routes, controller actions, and views to set up Plaid. Make sure to handle API keys securely and configure the environment properly for production.

💖 💪 🙅 🚩
duartemartins
Duarte Martins

Posted on May 19, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related