ynap 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 391535eeadf61b5853dce7ef23d54a50e437a81fa9b7ec697415bcc67fc4633d
4
+ data.tar.gz: c5a6c453640005607ebe54c17297059308a22957a1e758d5f8056bccdced37c7
5
+ SHA512:
6
+ metadata.gz: 598c0b8aa91b4277aadc871b6fdf30e02e71b4e5ae7e922b7fcbf1c1ad462161f29e2a19ce6e64bddebbae10518343a3d60ec34fa117969075d4d1d61ae18184
7
+ data.tar.gz: 2583359cb9bff89b659315010dfb86f49c4d3dfb810efdff612a7821134172eda57f15eb52f3d71b8bb6baf22851839b8ef389e4dabc88a8ae263d030097451e
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [1.0] - 2020-10-29
4
+
5
+ ### Added
6
+
7
+ - Everything
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Arnaud Joubay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,266 @@
1
+ # Welcome to YNAP
2
+
3
+ YNAP (You Need A Plaid) is the missing link between Plaid and YNAB.
4
+
5
+ It allows you to automatically import into YNAB the transactions of any bank supported by Plaid.
6
+
7
+ Once you've configured the gem, you'll be able to import transactions for the terminal with a simple `ynap import`.
8
+
9
+ This gem provides:
10
+ * a **simple CLI** to import transactions and check your accounts privately. All your access tokens stay on your computer and are generated for read-only access.
11
+ * a **repackaged plaid web server** to quickly grab your Plaid tokens
12
+ * a guide to set up your own regex to get **clean payees names**
13
+
14
+ 💰 If you don't have a YNAB account yet, you can use my [referral code](https://ynab.com/referral/?ref=CI2L8Hoi7bcZmK4V&utm_source=customer_referral), and we'll both get a free month of YNAB.
15
+
16
+ 🐣 Need help? Follow/DM me on [Twitter](https://twitter.com/sowenjub).
17
+
18
+ ☕️ Ejoying his gem? Buy me a Coffee [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/B0B01FCLB).
19
+
20
+ ## Installation
21
+
22
+ Here's an overview of the steps you'll go through before your first `ynap import`.
23
+
24
+ 1. Install the gem & prepare the config file
25
+ 1. Get your YNAB token
26
+ 2. Setup your banks
27
+ 3. Get your Plaid tokens & match Plaid ids to YNAB ids
28
+ 4. (Optional) Configure the regex used to clean Payees Names
29
+
30
+ ### Get the gem & prepare the config file
31
+
32
+ Install it locally with:
33
+
34
+ $ gem install ynap
35
+
36
+ Download the `config/ynap.yml.example` example file from the repo and save it somewhere safe on your computer. Rename it to `ynap.yml`.
37
+
38
+ It looks like this, and we'll complete it in the next steps.
39
+
40
+ ```yml
41
+ :plaid:
42
+ :client_id: 1234567890a
43
+ :public_key: 1234567890a
44
+ :secret: 1234567890a
45
+ :env: development
46
+ :country_codes: FR
47
+ :redirect_uri: https://{subdomain}.ngrok.io/oauth-response.html
48
+ :web_port: 8000
49
+ :ynab:
50
+ :token: your-token
51
+ :budget_id: your-id-in-ynab-url
52
+ :regex:
53
+ - ^(?:PRLV|VIR|E-VIR)(?:\sSEPA)?\s(?<name>[^,]*)(?:,.*)?$
54
+ - ^CARTE \d{1,2}\/\d{1,2}\/\d{1,2}\s(?<name>[\w\s]*)(?:\sCB\*\d*)?$
55
+ - ^CB\s\d*\s(?<name>.{11}).*$
56
+ :banks:
57
+ - :id: bansky
58
+ :name: Bansky
59
+ :plaid_access_token: access-development-123456789
60
+ :accounts:
61
+ - :plaid_id: account-plaid-id
62
+ :ynab_id: account-ynab-id
63
+ :start_date: "2020-10-23"
64
+ - :id: piggy
65
+ :name: Piggy Bank
66
+ :plaid_access_token: access-development-123456789
67
+ :accounts:
68
+ - :plaid_id: account-plaid-id
69
+ :ynab_id: account-ynab-id
70
+
71
+ ```
72
+
73
+ ### Get your YNAB token
74
+
75
+ We're going to complete the `:ynab:` parts of the config file:
76
+ * Create a new Personal Access Tokens in the [Developer Settings](https://app.youneedabudget.com/settings/developer) (a subsection of your [account settings](https://app.youneedabudget.com/settings))
77
+ * Paste the Personal Access Token into your `ynap.yml` file
78
+
79
+ ### Setup your banks
80
+
81
+ We're going to finish completing the `:ynab:` part of the config file first:
82
+ * Go back to the main YNAB interface, click on Budget. The URL should look something like `https://app.youneedabudget.com/{budget_id}/budget/{year-month}`
83
+ * Copy the `budget_id` from the URL and paste it in `ynap.yml` under `:ynab:`
84
+
85
+ Now, for each bank, under `:banks:`, add a new block with this format:
86
+
87
+ ```yaml
88
+ - :id: bansky
89
+ :name: Bansky
90
+ :plaid_access_token:
91
+ :accounts:
92
+ - :plaid_id: see-below
93
+ :ynab_id: account-id-for-first-account
94
+ :start_date: "2020-10-23"
95
+ - :plaid_id: see-below
96
+ :ynab_id: account-id-for-second-account
97
+ ```
98
+
99
+ * `id` a short string / handle of your choice to easily load a bank (with `Bank.find('bansky')`) or import transactions `ynap import -b bansky`
100
+ * `name` used to print infos, such as balances
101
+ * `plaid_access_token` the access token for that bank. More details in the Plaid section
102
+ * `accounts` is a list of plaid/ynab identifiers used to build the YNAB transactions and save them in the proper account.
103
+ * `start_date` is optional and useful if you already have transactions in YNAB for that account. It indicates the date from which to import transactions. The format is YYYY-MM-DD (year-month-date). If you don't want to bother with this, the script only imports the last 30 days so you can also simply let it import everything & cleanup duplicates (only needed the first time you run the import).
104
+
105
+ `id` and `name` are up to you, the only thing you have to work for is the `ynab_id` of each account.
106
+ To get them, open [YNAB](https://app.youneedabudget.com/) and click on the accounts. The URL will look like this: `https://app.youneedabudget.com/{budget_id}/accounts/{account_id}`.
107
+ Take the `account_id` and paste it as the `:ynab_id:` value.
108
+
109
+
110
+ ### Get your Plaid tokens & match Plaid ids to YNAB ids
111
+
112
+ The following steps will get a local server running that will allow you to retrieve one access token per bank account.
113
+ This is something you only need to do once.
114
+
115
+ **Launch ngrok**
116
+
117
+ The Plaid development environment requires https and a callback URI, so we'll use [ngrok](https://ngrok.com/download) for that.
118
+
119
+ * Launch ngrok on the same port you specified in `ynap.yml` (8000 by default): `ngrok http 8000`
120
+ * Get the URL which should look like this: `https://{randomsubdomain}.ngrok.io`.
121
+ * Go back to `ynap.yml` and complete the `redirect_uri` using that URL. It should look like this `https://{randomsubdomain}.ngrok.io/oauth-response.html` (with oauth-response.html)
122
+ * On Plaid.com, go to [Team Settings > API](https://dashboard.plaid.com/team/api) > Allowed redirect URIs > Configure and paste that same URI (with oauth-response.html) again.
123
+ * Visit https://{randomsubdomain}.ngrok.io
124
+
125
+ **Launch the plaid web server**
126
+
127
+ The plaid server is just an easier to use copy of the official [ruby quickstart app](https://github.com/plaid/quickstart/tree/master/ruby). You can find more about the quickstart app here: https://plaid.com/docs/quickstart/.
128
+
129
+ * Create a [free account](https://dashboard.plaid.com/signup) on Plaid.com
130
+ * Copy the **development** [credentials](https://dashboard.plaid.com/overview/development) (client_id/public_key/secret) into the `:plaid:` section
131
+ * Change the `country_codes` to the one you need. If you have more than one, separate them with a comma, no space (e.g. "FR,GB")
132
+ * Assuming your file is accessible in the current folder as `ynap.yml`, start the server:
133
+
134
+ ``` bash
135
+ ynap plaid
136
+ # or to indicate the path
137
+ ynap plaid -c path/to/ynap.yml
138
+ ```
139
+ * Visit `https://{randomsubdomain}.ngrok.io` and you should see a "Connect with Plaid" button.
140
+ * Click that "Connect with Plaid" button and follow the steps
141
+ * Once you're done, check your console and you should see something like that near the end:
142
+ ```bash
143
+ {
144
+ "access_token": "access-development-random-string",
145
+ "item_id": "otherRandomString",
146
+ "request_id": "moreRandomString"
147
+ }
148
+ ```
149
+ * Paste the `access_token` in front of `:plaid_access_token:` in your `config.yml` file, and `item_id` as the `plaid_id` for your account (Most API requests interact with an Item, which is a Plaid term for a login at a financial institution)
150
+
151
+ **What if you have more than one account at this bank?**
152
+
153
+ I'm not sure, because I don't have such a situation.
154
+ But, having set the access token and the item_id for one account, you can try to call `be bin/ynap plaid_ids boursorama` and see if that gives you all plaid_ids.
155
+ It should, but I can't be sure.
156
+
157
+ ### Configure the regex used to clean Payees Names
158
+
159
+ Part of the magic of YNAB is that they clean bank transaction labels to extract Payees Names.
160
+
161
+ This means that we have to come up with our own regex expressions to parse the transaction labels.
162
+
163
+ Here's how to do it:
164
+
165
+ 1. Find the labels that need some cleaning
166
+ * `ynap payees` will output the payees' names. If you spot one that could be better, than call:
167
+ * `ynap payees -m` it does the same but will output the full memo next to the name so that get the raw memo to test your regex against
168
+
169
+ 2. **Write your regex**
170
+ * To play with regexes, I usually use https://rubular.com or https://regexr.com
171
+ * But you can also play with the console:
172
+ * `ynap console`
173
+ * `> PayeeParser.new(/yourregex/).cleaned_name("YOUR TEST LABEL")`
174
+ * Hint: make sure it starts with `^` and end with `$`
175
+
176
+ 3. **Add the regex to your config file**
177
+ * List your regex in the config file in the `:regex:` section
178
+
179
+ Consider sharing that regex with others by doing a PR or mentioning it in an issue.
180
+
181
+ ### 🎉 Celebrate
182
+
183
+ You're all set!
184
+
185
+ ![](https://media.giphy.com/media/KYElw07kzDspaBOwf9/source.gif)
186
+
187
+ ## Usage
188
+
189
+ ### My routine
190
+
191
+ * `ynap payees` to check the payees' names before import and adjust the regex expressions if needed
192
+ * `ynap import` to import transactions
193
+ * `ynap balances` from time to time, just to check that the Plaid an YNAB balances match
194
+ * `ynap diff bank_handle` if the balances don't match, it can be because there are pending transactions (Plaid will have an up-to-date balance but won't have access to the transactions leading to that balance yet). In that case, I just make sure the latests transactions are a match by comparing the last 10 transactions on both sides (or the last n - up to 60 - transactions with `--limit n` or `-l n`)
195
+
196
+ ### All commands
197
+
198
+ You can get a list of commands by running
199
+ ```bash
200
+ ynap
201
+ ```
202
+
203
+ Most commands accept:
204
+ * `-c path/to/config/file.yml` to point to the config file
205
+ * `-b bank_id` to limit the command to the given bank (the `bank_id` is the `id` you set in your config file), except `transactions` which takes it as a required argument
206
+
207
+ ### Good to know
208
+
209
+ Don't get a headache: don't reconcile your balances in the evenings.
210
+
211
+ At the time, Plaid will have real-time info about your account balances but might not have the latest transactions.
212
+ So you will wonder why the YNAB balance after import doesn't match the Plaid balance.
213
+
214
+ To find the missing transactions, visit your bank website or app.
215
+
216
+ ### Playing with the console
217
+
218
+ If you launch the console `ynap console`, look into the bank and account models to see what you can do.
219
+
220
+ Here are some of the things you'll have acccess to:
221
+
222
+ ``` bash
223
+ bank = Bank.find 'bansky'
224
+ bank.plaid_transactions
225
+ bank.ynab_transactions
226
+ bank.payees
227
+
228
+ account = bank.accounts.first
229
+ account.description # prints the balances, Plaid v. YNAB ex: "Bansky: 1234.56 P✅Y 1234.56 EUR"
230
+ account.plaid_account
231
+ account.ynab_account
232
+ ```
233
+
234
+ ## Known issues / Roadmap
235
+
236
+ * **Pending transactions** are not properly supported. I'm not sure it's an issue in Europe, but I'll improve that just in case
237
+ * **Simulation** I plan to add a way to simulate the import instead of committing it right away.
238
+
239
+ ## Checking what the code does by yourself
240
+
241
+ The server that allows you to fetch Plaid tokens is a copy/paste from [Plaid Quickstart](https://github.com/plaid/quickstart), with a modification to load the configuration from your `ynap.yml` config file. You can find it in `bin/plaid`. It uses the html and public folders to render the pages.
242
+
243
+ The rest is in the lib folder and consists of only a couple of files.
244
+
245
+ ## Development
246
+
247
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
248
+
249
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
250
+
251
+ ## Contributing
252
+
253
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sowenjub/ynap.
254
+
255
+ 1. Fork it ( https://github.com/sowenjub/ynap/fork )
256
+ 1. Create your feature branch (`git checkout -b my-new-feature`)
257
+ 1. Commit your changes (`git commit -am 'Add some feature'`)
258
+ 1. Run the test suite (`bundle exec rake`)
259
+ 1. Push to the branch (`git push origin my-new-feature`)
260
+ 1. Create a new Pull Request
261
+
262
+ To experiment with that code, run `bin/console` for an interactive prompt.
263
+
264
+ ## License
265
+
266
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ynap"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,349 @@
1
+ require 'base64'
2
+ require 'date'
3
+ require 'json'
4
+ require 'plaid'
5
+ require 'sinatra'
6
+ require 'ynap'
7
+
8
+ Ynap.config = ARGV[0]
9
+
10
+ set :port, Ynap.config.dig(:plaid, :web_port) || 8000
11
+
12
+ client = Plaid::Client.new(Ynap.config[:plaid].slice(:env,:client_id,:secret))
13
+
14
+ products = "auth,transactions"
15
+
16
+ # We store the access_token in memory - in production, store it in a secure
17
+ # persistent data store.
18
+ access_token = nil
19
+ # The payment_id is only relevant for the UK Payment Initiation product.
20
+ # We store the payment_token in memory - in production, store it in a secure
21
+ # persistent data store.
22
+
23
+ payment_id = nil
24
+ item_id = nil
25
+
26
+ get '/' do
27
+ File.read('./html/index.html')
28
+ end
29
+
30
+ # This is an endpoint defined for the OAuth flow to redirect to.
31
+ get '/oauth-response.html' do
32
+ erb File.read('./html/oauth-response.html')
33
+ end
34
+
35
+ post '/api/info' do
36
+ content_type :json
37
+
38
+ {
39
+ item_id: item_id,
40
+ access_token: access_token,
41
+ products: products.split(','),
42
+ }.to_json
43
+ end
44
+
45
+ # Exchange token flow - exchange a Link public_token for
46
+ # an API access_token
47
+ # https://plaid.com/docs/#exchange-token-flow
48
+ post '/api/set_access_token' do
49
+ exchange_token_response =
50
+ client.item.public_token.exchange(params['public_token'])
51
+ access_token = exchange_token_response['access_token']
52
+ item_id = exchange_token_response['item_id']
53
+ pretty_print_response(exchange_token_response)
54
+
55
+ content_type :json
56
+ exchange_token_response.to_json
57
+ end
58
+
59
+ # Retrieve Transactions for an Item
60
+ # https://plaid.com/docs/#transactions
61
+ get '/api/transactions' do
62
+ now = Date.today
63
+ thirty_days_ago = (now - 30)
64
+ begin
65
+ product_response =
66
+ client.transactions.get(access_token, thirty_days_ago, now)
67
+ pretty_print_response(product_response)
68
+ content_type :json
69
+ product_response.to_json
70
+ rescue Plaid::PlaidAPIError => e
71
+ error_response = format_error(e)
72
+ pretty_print_response(error_response)
73
+ content_type :json
74
+ error_response.to_json
75
+ end
76
+ end
77
+
78
+ # Retrieve ACH or ETF account numbers for an Item
79
+ # https://plaid.com/docs/#auth
80
+ get '/api/auth' do
81
+ begin
82
+ product_response = client.auth.get(access_token)
83
+ pretty_print_response(product_response)
84
+ content_type :json
85
+ product_response.to_json
86
+ rescue Plaid::PlaidAPIError => e
87
+ error_response = format_error(e)
88
+ pretty_print_response(error_response)
89
+ content_type :json
90
+ error_response.to_json
91
+ end
92
+ end
93
+
94
+ # Retrieve Identity data for an Item
95
+ # https://plaid.com/docs/#identity
96
+ get '/api/identity' do
97
+ begin
98
+ product_response = client.identity.get(access_token)
99
+ pretty_print_response(product_response)
100
+ content_type :json
101
+ { identity: product_response.accounts}.to_json
102
+ rescue Plaid::PlaidAPIError => e
103
+ error_response = format_error(e)
104
+ pretty_print_response(error_response)
105
+ content_type :json
106
+ error_response.to_json
107
+ end
108
+ end
109
+
110
+ # Retrieve real-time balance data for each of an Item's accounts
111
+ # https://plaid.com/docs/#balance
112
+ get '/api/balance' do
113
+ begin
114
+ product_response = client.accounts.balance.get(access_token)
115
+ pretty_print_response(product_response)
116
+ content_type :json
117
+ product_response.to_json
118
+ rescue Plaid::PlaidAPIError => e
119
+ error_response = format_error(e)
120
+ pretty_print_response(error_response)
121
+ content_type :json
122
+ error_response.to_json
123
+ end
124
+ end
125
+
126
+ # Retrieve an Item's accounts
127
+ # https://plaid.com/docs/#accounts
128
+ get '/api/accounts' do
129
+ begin
130
+ product_response = client.accounts.get(access_token)
131
+ pretty_print_response(product_response)
132
+ content_type :json
133
+ product_response.to_json
134
+ rescue Plaid::PlaidAPIError => e
135
+ error_response = format_error(e)
136
+ pretty_print_response(error_response)
137
+ content_type :json
138
+ error_response.to_json
139
+ end
140
+ end
141
+
142
+ # Retrieve Holdings data for an Item
143
+ # https://plaid.com/docs/#investments
144
+ get '/api/holdings' do
145
+ begin
146
+ product_response = client.investments.holdings.get(access_token)
147
+ pretty_print_response(product_response)
148
+ content_type :json
149
+ { holdings: product_response }.to_json
150
+ rescue Plaid::PlaidAPIError => e
151
+ error_response = format_error(e)
152
+ pretty_print_response(error_response)
153
+ content_type :json
154
+ error_response.to_json
155
+ end
156
+ end
157
+
158
+ # Retrieve Investment Transactions for an Item
159
+ # https://plaid.com/docs/#investments
160
+ get '/api/investment_transactions' do
161
+ now = Date.today
162
+ thirty_days_ago = (now - 30)
163
+ begin
164
+ product_response = client.investments.transactions.get(access_token, thirty_days_ago, now)
165
+ pretty_print_response(product_response)
166
+ content_type :json
167
+ { investment_transactions: product_response }.to_json
168
+ rescue Plaid::PlaidAPIError => e
169
+ error_response = format_error(e)
170
+ pretty_print_response(error_response)
171
+ content_type :json
172
+ error_response.to_json
173
+ end
174
+ end
175
+
176
+ # Create and then retrieve an Asset Report for one or more Items. Note that an
177
+ # Asset Report can contain up to 100 items, but for simplicity we're only
178
+ # including one Item here.
179
+ # https://plaid.com/docs/#assets
180
+ # rubocop:disable Metrics/BlockLength
181
+ get '/api/assets' do
182
+ begin
183
+ asset_report_create_response =
184
+ client.asset_report.create([access_token], 10, {})
185
+ pretty_print_response(asset_report_create_response)
186
+ rescue Plaid::PlaidAPIError => e
187
+ error_response = format_error(e)
188
+ pretty_print_response(error_response)
189
+ content_type :json
190
+ error_response.to_json
191
+ end
192
+
193
+ asset_report_token = asset_report_create_response['asset_report_token']
194
+
195
+ asset_report_json = nil
196
+ num_retries_remaining = 20
197
+ while num_retries_remaining > 0
198
+ begin
199
+ asset_report_get_response = client.asset_report.get(asset_report_token)
200
+ asset_report_json = asset_report_get_response['report']
201
+ break
202
+ rescue Plaid::PlaidAPIError => e
203
+ if e.error_code == 'PRODUCT_NOT_READY'
204
+ num_retries_remaining -= 1
205
+ sleep(1)
206
+ next
207
+ end
208
+ error_response = format_error(e)
209
+ pretty_print_response(error_response)
210
+ content_type :json
211
+ return error_response.to_json
212
+ end
213
+ end
214
+
215
+ if asset_report_json.nil?
216
+ content_type :json
217
+ return {
218
+ error: {
219
+ error_code: 0,
220
+ error_message: 'Timed out when polling for Asset Report'
221
+ }
222
+ }.to_json
223
+ end
224
+
225
+ asset_report_pdf = client.asset_report.get_pdf(asset_report_token)
226
+
227
+ content_type :json
228
+ {
229
+ json: asset_report_json,
230
+ pdf: Base64.encode64(asset_report_pdf)
231
+ }.to_json
232
+ end
233
+ # rubocop:enable Metrics/BlockLength
234
+
235
+ # Retrieve high-level information about an Item
236
+ # https://plaid.com/docs/#retrieve-item
237
+ get '/api/item' do
238
+ item_response = client.item.get(access_token)
239
+ institution_response =
240
+ client.institutions.get_by_id(item_response['item']['institution_id'])
241
+ content_type :json
242
+ { item: item_response['item'],
243
+ institution: institution_response['institution'] }.to_json
244
+ end
245
+
246
+ # This functionality is only relevant for the UK Payment Initiation product.
247
+ # Retrieve Payment for a specified Payment ID
248
+ get '/api/payment' do
249
+ begin
250
+ payment_get_response = client.payment_initiation.get_payment(payment_id)
251
+ content_type :json
252
+ { payment: payment_get_response}.to_json
253
+ rescue Plaid::PlaidAPIError => e
254
+ error_response = format_error(e)
255
+ pretty_print_response(error_response)
256
+ content_type :json
257
+ error_response.to_json
258
+ end
259
+ end
260
+
261
+ post '/api/create_link_token' do
262
+ begin
263
+ response = client.link_token.create(
264
+ user: {
265
+ # This should correspond to a unique id for the current user.
266
+ client_user_id: "user-id",
267
+ },
268
+ client_name: "Plaid Quickstart",
269
+ products: products.split(','),
270
+ country_codes: Ynap.config.dig(:plaid, :country_codes).split(','),
271
+ language: "en",
272
+ redirect_uri: Ynap.config.dig(:plaid, :redirect_uri),
273
+ )
274
+
275
+ content_type :json
276
+ { link_token: response.link_token }.to_json
277
+ rescue Plaid::PlaidAPIError => e
278
+ error_response = format_error(e)
279
+
280
+ pretty_print_response(error_response)
281
+ content_type :json
282
+ error_response.to_json
283
+ end
284
+ end
285
+
286
+ # This functionality is only relevant for the UK Payment Initiation product.
287
+ # Sets the payment token in memory on the server side. We generate a new
288
+ # payment token so that the developer is not required to supply one.
289
+ # This makes the quickstart easier to use.
290
+ post '/api/create_link_token_for_payment' do
291
+ begin
292
+ create_recipient_response = client.payment_initiation.create_recipient(
293
+ 'Harry Potter',
294
+ 'GB33BUKB20201555555555',
295
+ {
296
+ street: ['4 Privet Drive'],
297
+ city: 'Little Whinging',
298
+ postal_code: '11111',
299
+ country: 'GB'
300
+ },
301
+ account: '555555',
302
+ )
303
+ recipient_id = create_recipient_response.recipient_id
304
+
305
+ create_payment_response = client.payment_initiation.create_payment(
306
+ recipient_id,
307
+ 'payment_ref',
308
+ currency: 'GBP',
309
+ value: 12.34
310
+ )
311
+ payment_id = create_payment_response.payment_id
312
+ response = client.link_token.create(
313
+ user: {
314
+ # This should correspond to a unique id for the current user.
315
+ client_user_id: "user-id",
316
+ },
317
+ client_name: "Plaid Quickstart",
318
+ products: products.split(','),
319
+ country_codes: Ynap.config.dig(:plaid, :country_codes).split(','),
320
+ language: "en",
321
+ redirect_uri: Ynap.config.dig(:plaid, :redirect_uri),
322
+ payment_initiation: {
323
+ payment_id: payment_id,
324
+ },
325
+ )
326
+
327
+ content_type :json
328
+ { link_token: response.link_token }.to_json
329
+ rescue Plaid::PlaidAPIError => e
330
+ error_response = format_error(e)
331
+ pretty_print_response(error_response)
332
+ content_type :json
333
+ error_response.to_json
334
+ end
335
+ end
336
+
337
+ def format_error(err)
338
+ {
339
+ error: {
340
+ error_code: err.error_code,
341
+ error_message: err.error_message,
342
+ error_type: err.error_type
343
+ }
344
+ }
345
+ end
346
+
347
+ def pretty_print_response(response)
348
+ puts JSON.pretty_generate(response)
349
+ end