Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial pass at Plaid EU #1555

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,6 @@ STRIPE_WEBHOOK_SECRET=
#
PLAID_CLIENT_ID=
PLAID_SECRET=
PLAID_ENV=
PLAID_ENV=
PLAID_EU_CLIENT_ID=
PLAID_EU_SECRET=
3 changes: 2 additions & 1 deletion app/controllers/concerns/accountable_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def set_link_token
@link_token = Current.family.get_link_token(
webhooks_url: webhooks_url,
redirect_url: accounts_url,
accountable_type: accountable_type.name
accountable_type: accountable_type.name,
region: Current.family.country.to_s.downcase == "us" ? :us : :eu
)
end

Expand Down
3 changes: 2 additions & 1 deletion app/controllers/plaid_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def create
Current.family.plaid_items.create_from_public_token(
plaid_item_params[:public_token],
item_name: item_name,
region: plaid_item_params[:region]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The create_from_public_token method on PlaidItem will need to be updated to accept this param

)

redirect_to accounts_path, notice: t(".success")
Expand All @@ -29,7 +30,7 @@ def set_plaid_item
end

def plaid_item_params
params.require(:plaid_item).permit(:public_token, metadata: {})
params.require(:plaid_item).permit(:public_token, :region, metadata: {})
end

def item_name
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/controllers/plaid_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static values = {
linkToken: String,
region: { type: String, default: "us" }
};

open() {
Expand Down Expand Up @@ -31,6 +32,7 @@ export default class extends Controller {
plaid_item: {
public_token: public_token,
metadata: metadata,
region: this.regionValue
},
}),
}).then((response) => {
Expand Down
13 changes: 11 additions & 2 deletions app/models/concerns/plaidable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@ module Plaidable
def plaid_provider
Provider::Plaid.new if Rails.application.config.plaid
end

def plaid_eu_provider
Provider::Plaid.new if Rails.application.config.plaid_eu
end

def plaid_provider_for(plaid_item)
return nil unless plaid_item
plaid_item.eu? ? plaid_eu_provider : plaid_provider
end
end

private
def plaid_provider
self.class.plaid_provider
def plaid_provider_for(plaid_item)
self.class.plaid_provider_for(plaid_item)
end
Shpigford marked this conversation as resolved.
Show resolved Hide resolved
end
18 changes: 14 additions & 4 deletions app/models/family.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# rubocop:disable Layout/ElseAlignment, Layout/IndentationWidth
class Family < ApplicationRecord
include Plaidable, Syncable

Expand Down Expand Up @@ -45,14 +46,22 @@ def syncing?
super || accounts.manual.any?(&:syncing?) || plaid_items.any?(&:syncing?)
end

def get_link_token(webhooks_url:, redirect_url:, accountable_type: nil)
return nil unless plaid_provider
def get_link_token(webhooks_url:, redirect_url:, accountable_type: nil, region: :us)
provider = case region
when :eu
self.class.plaid_eu_provider
else
self.class.plaid_provider
end

return nil unless provider

plaid_provider.get_link_token(
provider.get_link_token(
user_id: id,
webhooks_url: webhooks_url,
redirect_url: redirect_url,
accountable_type: accountable_type
accountable_type: accountable_type,
eu: region == :eu
).link_token
end

Expand Down Expand Up @@ -174,3 +183,4 @@ def primary_user
users.order(:created_at).first
end
end
# rubocop:enable Layout/ElseAlignment, Layout/IndentationWidth
8 changes: 6 additions & 2 deletions app/models/plaid_item.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
class PlaidItem < ApplicationRecord
include Plaidable, Syncable

enum :plaid_region, { us: "us", eu: "eu" }
validates :plaid_region, inclusion: { in: plaid_regions.keys }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enum should have this inclusion validation already built-in, so we can remove the second validates here


if Rails.application.credentials.active_record_encryption.present?
encrypts :access_token, deterministic: true
end
Expand Down Expand Up @@ -56,10 +59,11 @@ def destroy_later
private
def fetch_and_load_plaid_data
data = {}
item = plaid_provider.get_item(access_token).item
provider = plaid_provider_for(self)
item = provider.get_item(access_token).item
update!(available_products: item.available_products, billed_products: item.billed_products)

fetched_accounts = plaid_provider.get_item_accounts(self).accounts
fetched_accounts = provider.get_item_accounts(self).accounts
data[:accounts] = fetched_accounts || []

internal_plaid_accounts = fetched_accounts.map do |account|
Expand Down
12 changes: 10 additions & 2 deletions app/models/provider/plaid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ def initialize
@client = self.class.client
end

def get_link_token(user_id:, webhooks_url:, redirect_url:, accountable_type: nil)
def get_link_token(user_id:, webhooks_url:, redirect_url:, accountable_type: nil, eu: false)
request = Plaid::LinkTokenCreateRequest.new({
user: { client_user_id: user_id },
client_name: "Maybe Finance",
products: [ get_primary_product(accountable_type) ],
additional_consented_products: get_additional_consented_products(accountable_type),
country_codes: [ "US" ],
country_codes: get_country_codes(eu),
language: "en",
webhook: webhooks_url,
redirect_uri: redirect_url,
Expand Down Expand Up @@ -198,4 +198,12 @@ def get_primary_product(accountable_type)
def get_additional_consented_products(accountable_type)
MAYBE_SUPPORTED_PLAID_PRODUCTS - [ get_primary_product(accountable_type) ]
end

def get_country_codes(eu)
if eu
[ "ES", "NL", "FR", "IE", "DE", "IT", "PL", "DK", "NO", "SE", "EE", "LT", "LV", "PT", "BE" ] # EU supported countries
else
[ "US" ] # US only
end
end
end
15 changes: 13 additions & 2 deletions app/views/accounts/new/_method_selector.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,27 @@
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
<%= lucide_icon("keyboard", class: "text-gray-500 w-5 h-5") %>
</span>
<%= t("accounts.new.method_selector.manual_entry") %>
<%= t(".manual_entry") %>
<% end %>

<% if link_token.present? %>
<%# Default US-only Link %>
<button data-controller="plaid" data-action="plaid#open modal#close" data-plaid-link-token-value="<%= @link_token %>" class="flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2">
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
<%= lucide_icon("link-2", class: "text-gray-500 w-5 h-5") %>
</span>
<%= t("accounts.new.method_selector.connected_entry") %>
<%= t(".connected_entry") %>
</button>

<%# EU Link %>
<% unless Current.family.country == "US" %>
<button data-controller="plaid" data-action="plaid#open modal#close" data-plaid-link-token-value="<%= Current.family.get_link_token(webhooks_url: webhooks_plaid_url, redirect_url: accounts_url, accountable_type: accountable_type.name, region: :eu) %>" class="flex items-center gap-4 w-full text-center focus:outline-none focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2">
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
<%= lucide_icon("link-2", class: "text-gray-500 w-5 h-5") %>
</span>
<%= t(".connected_entry_eu") %>
</button>
<% end %>
<% end %>
</div>
<% end %>
8 changes: 8 additions & 0 deletions config/initializers/plaid.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
Rails.application.configure do
config.plaid = nil
config.plaid_eu = nil

if ENV["PLAID_CLIENT_ID"].present? && ENV["PLAID_SECRET"].present?
config.plaid = Plaid::Configuration.new
config.plaid.server_index = Plaid::Configuration::Environment[ENV["PLAID_ENV"] || "sandbox"]
config.plaid.api_key["PLAID-CLIENT-ID"] = ENV["PLAID_CLIENT_ID"]
config.plaid.api_key["PLAID-SECRET"] = ENV["PLAID_SECRET"]
end

if ENV["PLAID_EU_CLIENT_ID"].present? && ENV["PLAID_EU_SECRET"].present?
config.plaid_eu = Plaid::Configuration.new
config.plaid_eu.server_index = Plaid::Configuration::Environment[ENV["PLAID_ENV"] || "sandbox"]
config.plaid_eu.api_key["PLAID-CLIENT-ID"] = ENV["PLAID_EU_CLIENT_ID"]
config.plaid_eu.api_key["PLAID-SECRET"] = ENV["PLAID_EU_SECRET"]
end
end
2 changes: 2 additions & 0 deletions config/locales/views/accounts/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ en:
import_accounts: Import accounts
method_selector:
connected_entry: Link account
connected_entry_us: Link US account
connected_entry_eu: Link EU account
manual_entry: Enter account balance
title: How would you like to add it?
title: What would you like to add?
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20241219151540_add_region_to_plaid_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddRegionToPlaidItem < ActiveRecord::Migration[7.2]
def change
add_column :plaid_items, :plaid_region, :string, null: false, default: "us"
end
end
44 changes: 43 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading