ynap 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +266 -0
- data/bin/console +14 -0
- data/bin/plaid +349 -0
- data/bin/setup +8 -0
- data/bin/ynap +5 -0
- data/config/ynap.yml.example +29 -0
- data/exe/ynap +6 -0
- data/html/index.html +950 -0
- data/html/oauth-response.html +305 -0
- data/lib/ynap.rb +42 -0
- data/lib/ynap/cli.rb +113 -0
- data/lib/ynap/extensions/float.rb +5 -0
- data/lib/ynap/extensions/integer.rb +5 -0
- data/lib/ynap/extensions/plaid/models/transaction.rb +8 -0
- data/lib/ynap/extensions/ynab/save_transaction.rb +8 -0
- data/lib/ynap/extensions/ynab/transaction_detail.rb +8 -0
- data/lib/ynap/models/account.rb +62 -0
- data/lib/ynap/models/bank.rb +145 -0
- data/lib/ynap/models/bridge_record.rb +9 -0
- data/lib/ynap/payee_parser.rb +12 -0
- data/lib/ynap/values/params_converter.rb +52 -0
- data/lib/ynap/version.rb +3 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -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
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|
data/bin/console
ADDED
@@ -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__)
|
data/bin/plaid
ADDED
@@ -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
|