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.
- 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 [](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
|
+

|
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
|