ynap 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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