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
data/bin/setup
ADDED
data/bin/ynap
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
:plaid:
|
2
|
+
:client_id: 1234567890a
|
3
|
+
:public_key: 1234567890a
|
4
|
+
:secret: 1234567890a
|
5
|
+
:env: development
|
6
|
+
:country_codes: FR
|
7
|
+
:redirect_uri: https://{subdomain}.ngrok.io/oauth-response.html
|
8
|
+
:web_port: 8000
|
9
|
+
:ynab:
|
10
|
+
:token: your-token
|
11
|
+
:budget_id: your-id-in-ynab-url
|
12
|
+
:regex:
|
13
|
+
- ^(?:PRLV|VIR|E-VIR)(?:\sSEPA)?\s(?<name>[^,]*)(?:,.*)?$ # VIR/PRLV SEPA PAYEE NAME(, something)
|
14
|
+
- ^CARTE \d{1,2}\/\d{1,2}\/\d{1,2}\s(?<name>[\w\s]*)(?:\sCB\*\d*)?$ # CARTE 16/10/20 PAYEE NAME CB*6825
|
15
|
+
- ^CB\s\d*\s(?<name>.{11}).*$ # CB 123123 PAYEE NAME 11CITY LE 1111 - PAYEE NAME IS 11 chars
|
16
|
+
:banks:
|
17
|
+
- :id: bansky
|
18
|
+
:name: Bansky
|
19
|
+
:plaid_access_token: access-development-123456789
|
20
|
+
:accounts:
|
21
|
+
- :plaid_id: account-plaid-id
|
22
|
+
:ynab_id: account-ynab-id
|
23
|
+
:start_date: "2020-10-23"
|
24
|
+
- :id: piggy
|
25
|
+
:name: Piggy Bank
|
26
|
+
:plaid_access_token: access-development-123456789
|
27
|
+
:accounts:
|
28
|
+
- :plaid_id: account-plaid-id
|
29
|
+
:ynab_id: account-ynab-id
|
data/exe/ynap
ADDED
data/html/index.html
ADDED
@@ -0,0 +1,950 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Plaid Quickstart Example</title>
|
6
|
+
<link rel="stylesheet" href="https://threads.plaid.com/threads.css">
|
7
|
+
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
9
|
+
<style>
|
10
|
+
/*
|
11
|
+
|
12
|
+
Base
|
13
|
+
|
14
|
+
*/
|
15
|
+
|
16
|
+
body {
|
17
|
+
background: #ffffff;
|
18
|
+
}
|
19
|
+
|
20
|
+
.main {
|
21
|
+
max-width: 960px;
|
22
|
+
margin-right: auto;
|
23
|
+
margin-left: auto;
|
24
|
+
padding: 80px 40px;
|
25
|
+
}
|
26
|
+
|
27
|
+
|
28
|
+
/*
|
29
|
+
|
30
|
+
Content at the top of the view
|
31
|
+
|
32
|
+
*/
|
33
|
+
.loading-indicator {
|
34
|
+
-webkit-animation: rot 1200ms infinite cubic-bezier(0.23, 1.2, 0.32, 1);
|
35
|
+
animation: rot 1200ms infinite cubic-bezier(0.23, 1.2, 0.32, 1);
|
36
|
+
border-bottom: 2px solid #e3e3e3;
|
37
|
+
border-left: 2px solid #e3e3e3;
|
38
|
+
border-radius: 100%;
|
39
|
+
border-right: 2px solid #e3e3e3;
|
40
|
+
border-top: 2px solid #7e7e7e;
|
41
|
+
font-size: 100%;
|
42
|
+
font: inherit;
|
43
|
+
height: 45px;
|
44
|
+
left: calc(50% - (45px / 2));
|
45
|
+
margin: 0;
|
46
|
+
padding: 0;
|
47
|
+
position: absolute;
|
48
|
+
top: 80%;
|
49
|
+
vertical-align: baseline;
|
50
|
+
width: 45px;
|
51
|
+
box-sizing: border-box
|
52
|
+
}
|
53
|
+
@keyframes rot {
|
54
|
+
from {
|
55
|
+
-webkit-transform: rotate(0deg);
|
56
|
+
transform: rotate(0deg);
|
57
|
+
}
|
58
|
+
to {
|
59
|
+
-webkit-transform: rotate(359deg);
|
60
|
+
transform: rotate(359deg);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
@-webkit-keyframes rot {
|
64
|
+
from {
|
65
|
+
-webkit-transform: rotate(0deg);
|
66
|
+
transform: rotate(0deg);
|
67
|
+
}
|
68
|
+
to {
|
69
|
+
-webkit-transform: rotate(359deg);
|
70
|
+
transform: rotate(359deg);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
.everpresent-content {
|
75
|
+
border-bottom-width: 1px;
|
76
|
+
border-bottom-color: #E3E9EF;
|
77
|
+
border-bottom-style: solid;
|
78
|
+
margin-bottom: 16px;
|
79
|
+
padding-bottom: 16px;
|
80
|
+
}
|
81
|
+
|
82
|
+
.everpresent-content__heading {
|
83
|
+
margin-bottom: 16px;
|
84
|
+
}
|
85
|
+
|
86
|
+
.everpresent-content__subheading {
|
87
|
+
font-size: 24px;
|
88
|
+
font-weight: 300;
|
89
|
+
color: #527084;
|
90
|
+
line-height: 32px;
|
91
|
+
margin-bottom: 0;
|
92
|
+
}
|
93
|
+
|
94
|
+
/*
|
95
|
+
|
96
|
+
Item overview
|
97
|
+
|
98
|
+
*/
|
99
|
+
|
100
|
+
.item-overview {
|
101
|
+
padding-bottom: 16px;
|
102
|
+
}
|
103
|
+
|
104
|
+
.item-overview__column {
|
105
|
+
width: 50%;
|
106
|
+
float: left;
|
107
|
+
}
|
108
|
+
|
109
|
+
.item-overview__column:nth-child(1) {
|
110
|
+
padding-right: 8px;
|
111
|
+
}
|
112
|
+
|
113
|
+
.item-overview__column:nth-child(2) {
|
114
|
+
padding-left: 8px;
|
115
|
+
}
|
116
|
+
|
117
|
+
.item-overview__heading {
|
118
|
+
font-weight: bold;
|
119
|
+
text-transform: uppercase;
|
120
|
+
font-size: 12px;
|
121
|
+
margin-bottom: 0;
|
122
|
+
}
|
123
|
+
|
124
|
+
.item-overview__id {
|
125
|
+
text-overflow: ellipsis;
|
126
|
+
overflow-x: hidden;
|
127
|
+
}
|
128
|
+
|
129
|
+
/*
|
130
|
+
|
131
|
+
One off tweaks to the layout for tables
|
132
|
+
|
133
|
+
*/
|
134
|
+
|
135
|
+
.response-row {
|
136
|
+
}
|
137
|
+
|
138
|
+
.response-row--is-identity td {
|
139
|
+
width: 25%;
|
140
|
+
display: inline-block;
|
141
|
+
overflow-wrap: break-word;
|
142
|
+
vertical-align: top;
|
143
|
+
}
|
144
|
+
|
145
|
+
/*
|
146
|
+
|
147
|
+
Display content inside of box
|
148
|
+
|
149
|
+
*/
|
150
|
+
|
151
|
+
.box {
|
152
|
+
border-radius: 4px;
|
153
|
+
border-width: 1px;
|
154
|
+
border-color: #E3E9EF;
|
155
|
+
border-style: solid;
|
156
|
+
border-radius: 4px;
|
157
|
+
margin-bottom: 24px;
|
158
|
+
overflow: hidden;
|
159
|
+
box-shadow: 0 1px 2px rgba(3,49,86,0.2);
|
160
|
+
}
|
161
|
+
|
162
|
+
.box__heading {
|
163
|
+
padding: 16px 24px;
|
164
|
+
border-bottom-width: 1px;
|
165
|
+
border-bottom-color: #E3E9EF;
|
166
|
+
border-bottom-style: solid;
|
167
|
+
background-color: #FAFCFD;
|
168
|
+
margin-bottom: 0;
|
169
|
+
}
|
170
|
+
|
171
|
+
|
172
|
+
/*
|
173
|
+
|
174
|
+
Item row
|
175
|
+
|
176
|
+
*/
|
177
|
+
.item-data-row {
|
178
|
+
border-bottom-width: 1px;
|
179
|
+
border-bottom-color: #E3E9EF;
|
180
|
+
border-bottom-style: solid;
|
181
|
+
padding-top: 24px;
|
182
|
+
padding-bottom: 24px;
|
183
|
+
padding-left: 24px;
|
184
|
+
padding-right: 24px;
|
185
|
+
}
|
186
|
+
|
187
|
+
.item-data-row:after {
|
188
|
+
clear: both;
|
189
|
+
content: '';
|
190
|
+
display: table;
|
191
|
+
}
|
192
|
+
|
193
|
+
.item-data-row:last-child {
|
194
|
+
margin-bottom: 0;
|
195
|
+
border-bottom: 0;
|
196
|
+
}
|
197
|
+
|
198
|
+
.item-data-row__request-type {
|
199
|
+
background-color
|
200
|
+
font-size: 12px;
|
201
|
+
color: #02B1F8;
|
202
|
+
letter-spacing: 0;
|
203
|
+
background: #D9F3FE;
|
204
|
+
font-weight: 600;
|
205
|
+
text-transform: uppercase;
|
206
|
+
border-radius: 4px;
|
207
|
+
height: 32px;
|
208
|
+
line-height: 32px;
|
209
|
+
text-align: center;
|
210
|
+
font-size: 14px;
|
211
|
+
}
|
212
|
+
|
213
|
+
.item-data-row__endpoint {
|
214
|
+
font-family: "monaco", courier;
|
215
|
+
font-size: 14px;
|
216
|
+
line-height: 32px;
|
217
|
+
display: inline-block;
|
218
|
+
}
|
219
|
+
|
220
|
+
.item-data-row__nicename {
|
221
|
+
font-size: 16px;
|
222
|
+
line-height: 32px;
|
223
|
+
display: inline-block;
|
224
|
+
font-weight: 500;
|
225
|
+
color: #033156;
|
226
|
+
}
|
227
|
+
|
228
|
+
|
229
|
+
.item-data-row__left {
|
230
|
+
width: 13%;
|
231
|
+
float: left;
|
232
|
+
}
|
233
|
+
|
234
|
+
.item-data-row__center {
|
235
|
+
width: 62%;
|
236
|
+
float: left;
|
237
|
+
padding-left: 16px;
|
238
|
+
}
|
239
|
+
|
240
|
+
.item-data-row__right {
|
241
|
+
width: 25%;
|
242
|
+
float: left;
|
243
|
+
}
|
244
|
+
|
245
|
+
/*
|
246
|
+
|
247
|
+
Hide things
|
248
|
+
|
249
|
+
*/
|
250
|
+
|
251
|
+
#app {
|
252
|
+
display: none;
|
253
|
+
}
|
254
|
+
|
255
|
+
#steps {
|
256
|
+
display: none;
|
257
|
+
}
|
258
|
+
|
259
|
+
/*
|
260
|
+
|
261
|
+
Errors
|
262
|
+
|
263
|
+
*/
|
264
|
+
|
265
|
+
.alert {
|
266
|
+
padding: 15px;
|
267
|
+
margin-top: 20px;
|
268
|
+
margin-bottom: 20px;
|
269
|
+
border: 1px solid transparent;
|
270
|
+
border-radius: 4px;
|
271
|
+
}
|
272
|
+
|
273
|
+
.alert-danger {
|
274
|
+
color: #a94442;
|
275
|
+
background-color: #f2dede;
|
276
|
+
border-color: #ebccd1;
|
277
|
+
}
|
278
|
+
|
279
|
+
.alert-danger a {
|
280
|
+
color: #a94442;
|
281
|
+
font-weight: bold;
|
282
|
+
}
|
283
|
+
</style>
|
284
|
+
</head>
|
285
|
+
<body>
|
286
|
+
<main class="main">
|
287
|
+
<div class="grid">
|
288
|
+
<div class="grid__column grid__column--is-twelve-columns">
|
289
|
+
<div id="banner" class="everpresent-content">
|
290
|
+
<h1 class="everpresent-content__heading">Plaid Quickstart</h1>
|
291
|
+
<p id="intro" class="everpresent-content__subheading">
|
292
|
+
An example application that outlines an end-to-end integration with Plaid
|
293
|
+
</p>
|
294
|
+
<p id="steps" class="everpresent-content__subheading">
|
295
|
+
Success! You just created an Item by linking your account.
|
296
|
+
</p>
|
297
|
+
</div>
|
298
|
+
|
299
|
+
<div id="container" class="initial-view">
|
300
|
+
<p class="initial-view__description">
|
301
|
+
Click the button below to open a list of Institutions. After you select one, you’ll be guided through an authentication process. Upon completion, a public_token will be passed back to the server and exchanged for access_token.
|
302
|
+
</p>
|
303
|
+
|
304
|
+
<button id="link-btn" class="button button--is-primary" disabled>Connect with Plaid</button>
|
305
|
+
<div class="loading-indicator"></div>
|
306
|
+
|
307
|
+
</div>
|
308
|
+
|
309
|
+
<div id="app" class="connected-view">
|
310
|
+
<div class="item-overview">
|
311
|
+
<div class="item-overview__column">
|
312
|
+
<h3 class="item-overview__heading">item_id</h3>
|
313
|
+
<p class="item-overview__id" id="item_id">san.asjsansakjsakjasjksajkas</p>
|
314
|
+
</div>
|
315
|
+
<div class="item-overview__column">
|
316
|
+
<h3 class="item-overview__heading">access_token</h3>
|
317
|
+
<p class="item-overview__id" id="access_token">••••••••hsakjsl</p>
|
318
|
+
</div>
|
319
|
+
<div style="clear: both"></div>
|
320
|
+
</div>
|
321
|
+
|
322
|
+
<p>Now that you have an access_token you can make all of the following API requests:</p>
|
323
|
+
|
324
|
+
<div class="box">
|
325
|
+
<h3 class="box__heading">Products</h3>
|
326
|
+
|
327
|
+
<!-- Auth -->
|
328
|
+
<div class="item-data-row">
|
329
|
+
<div class="item-data-row__left">
|
330
|
+
<div class="item-data-row__request-type">post</div>
|
331
|
+
</div>
|
332
|
+
<div class="item-data-row__center">
|
333
|
+
<div class="item-data-row__nicename">Auth</div>
|
334
|
+
<div class="item-data-row__endpoint">/auth/get</div>
|
335
|
+
<div class="item-data-row__description">Retrieve account and routing numbers for checking and savings accounts.</div>
|
336
|
+
</div>
|
337
|
+
<div class="item-data-row__right">
|
338
|
+
<button id="get-auth-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
339
|
+
</div>
|
340
|
+
<div class="item-data-row__response">
|
341
|
+
<table><tbody id="get-auth-data"></tbody></table>
|
342
|
+
</div>
|
343
|
+
</div>
|
344
|
+
|
345
|
+
<!--Transactions -->
|
346
|
+
<div class="item-data-row">
|
347
|
+
<div class="item-data-row__left">
|
348
|
+
<div class="item-data-row__request-type">post</div>
|
349
|
+
</div>
|
350
|
+
<div class="item-data-row__center">
|
351
|
+
<div class="item-data-row__nicename">Transactions</div>
|
352
|
+
<div class="item-data-row__endpoint">/transactions/get</div>
|
353
|
+
<div class="item-data-row__description">Retrieve transactions for credit and depository accounts.</div>
|
354
|
+
</div>
|
355
|
+
<div class="item-data-row__right">
|
356
|
+
<button id="get-transactions-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
357
|
+
</div>
|
358
|
+
<div class="item-data-row__response">
|
359
|
+
<table><tbody id="get-transactions-data"></tbody></table>
|
360
|
+
</div>
|
361
|
+
</div>
|
362
|
+
|
363
|
+
<!-- Identity -->
|
364
|
+
<div class="item-data-row">
|
365
|
+
<div class="item-data-row__left">
|
366
|
+
<div class="item-data-row__request-type">post</div>
|
367
|
+
</div>
|
368
|
+
<div class="item-data-row__center">
|
369
|
+
<div class="item-data-row__nicename">Identity</div>
|
370
|
+
<div class="item-data-row__endpoint">/identity/get</div>
|
371
|
+
<div class="item-data-row__description">Retrieve Identity information on file with the bank. Reduce fraud by comparing user-submitted data to validate identity.</div>
|
372
|
+
</div>
|
373
|
+
<div class="item-data-row__right">
|
374
|
+
<button id="get-identity-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
375
|
+
</div>
|
376
|
+
<div class="item-data-row__response">
|
377
|
+
<table><tbody id="get-identity-data"></tbody></table>
|
378
|
+
</div>
|
379
|
+
</div>
|
380
|
+
|
381
|
+
<!-- Balance -->
|
382
|
+
<div class="item-data-row">
|
383
|
+
<div class="item-data-row__left">
|
384
|
+
<div class="item-data-row__request-type">post</div>
|
385
|
+
</div>
|
386
|
+
<div class="item-data-row__center">
|
387
|
+
<div class="item-data-row__nicename">Balance</div>
|
388
|
+
<div class="item-data-row__endpoint">/accounts/balance/get</div>
|
389
|
+
<div class="item-data-row__description">Check balances in real time to prevent non-sufficient funds fees.</div>
|
390
|
+
</div>
|
391
|
+
<div class="item-data-row__right">
|
392
|
+
<button id="get-balance-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
393
|
+
</div>
|
394
|
+
<div class="item-data-row__response">
|
395
|
+
<table><tbody id="get-balance-data"></tbody></table>
|
396
|
+
</div>
|
397
|
+
</div>
|
398
|
+
|
399
|
+
<!-- Assets (hidden unless 'assets' is included in the product list) -->
|
400
|
+
<div id='assets' class="item-data-row" style='display:none;'>
|
401
|
+
<div class="item-data-row__left">
|
402
|
+
<div class="item-data-row__request-type">post</div>
|
403
|
+
</div>
|
404
|
+
<div class="item-data-row__center">
|
405
|
+
<div class="item-data-row__nicename">Assets</div>
|
406
|
+
<div class="item-data-row__endpoint">/asset_report/*</div>
|
407
|
+
<div class="item-data-row__description">Create a point-in-time snapshot of a user's assets.</div>
|
408
|
+
</div>
|
409
|
+
|
410
|
+
<div class="item-data-row__right">
|
411
|
+
<button id="get-assets-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
412
|
+
|
413
|
+
<a id="download-assets-pdf-btn" class="button button--is-small button--is-primary button--is-full-width" style="display: none; margin: 10px 0px;";>Download as PDF</a>
|
414
|
+
</div>
|
415
|
+
|
416
|
+
<div class="item-data-row__response">
|
417
|
+
<table><tbody id="get-assets-data"></tbody></table>
|
418
|
+
</div>
|
419
|
+
</div>
|
420
|
+
|
421
|
+
<!-- Holdings -->
|
422
|
+
<div class="item-data-row">
|
423
|
+
<div class="item-data-row__left">
|
424
|
+
<div class="item-data-row__request-type">post</div>
|
425
|
+
</div>
|
426
|
+
<div class="item-data-row__center">
|
427
|
+
<div class="item-data-row__nicename">Holdings</div>
|
428
|
+
<div class="item-data-row__endpoint">/investments/holdings/get</div>
|
429
|
+
<div class="item-data-row__description">Retrieve investment holdings on file with the bank, brokerage, or investment institution. Analyze over-exposure to market segments.</div>
|
430
|
+
</div>
|
431
|
+
<div class="item-data-row__right">
|
432
|
+
<button id="get-holdings-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
433
|
+
</div>
|
434
|
+
<div class="item-data-row__response">
|
435
|
+
<table><tbody id="get-holdings-data"></tbody></table>
|
436
|
+
</div>
|
437
|
+
</div>
|
438
|
+
|
439
|
+
<!-- Investment Transactions -->
|
440
|
+
<div class="item-data-row">
|
441
|
+
<div class="item-data-row__left">
|
442
|
+
<div class="item-data-row__request-type">post</div>
|
443
|
+
</div>
|
444
|
+
<div class="item-data-row__center">
|
445
|
+
<div class="item-data-row__nicename">Investment Transactions</div>
|
446
|
+
<div class="item-data-row__endpoint">/investments/transactions/get</div>
|
447
|
+
<div class="item-data-row__description">Retrieve investment transactions to analyze cash flow and trading performance.</div>
|
448
|
+
</div>
|
449
|
+
<div class="item-data-row__right">
|
450
|
+
<button id="get-investment-transactions-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
451
|
+
</div>
|
452
|
+
<div class="item-data-row__response">
|
453
|
+
<table><tbody id="get-investment-transactions-data"></tbody></table>
|
454
|
+
</div>
|
455
|
+
</div>
|
456
|
+
|
457
|
+
<!-- UK Payment Initiation (hidden unless 'payment_initiation' is included in the product list) -->
|
458
|
+
<div class="payment_initiation item-data-row" style='display:none;'>
|
459
|
+
<div class="item-data-row__left">
|
460
|
+
<div class="item-data-row__request-type">post</div>
|
461
|
+
</div>
|
462
|
+
<div class="item-data-row__center">
|
463
|
+
<div class="item-data-row__nicename">Payment Initiation</div>
|
464
|
+
<div class="item-data-row__endpoint">/payment_initiation/payment/get</div>
|
465
|
+
<div class="item-data-row__description">Retrieve payment initiation status for the payment you just initiated.</div>
|
466
|
+
</div>
|
467
|
+
|
468
|
+
<div class="item-data-row__right">
|
469
|
+
<button id="get-payment-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
470
|
+
</div>
|
471
|
+
|
472
|
+
<div class="item-data-row__response">
|
473
|
+
<table><tbody id="get-payment-data"></tbody></table>
|
474
|
+
</div>
|
475
|
+
</div>
|
476
|
+
|
477
|
+
</div>
|
478
|
+
|
479
|
+
<div class="box">
|
480
|
+
<h3 class="box__heading">Item management</h3>
|
481
|
+
|
482
|
+
<div class="item-data-row">
|
483
|
+
<div class="item-data-row__left">
|
484
|
+
<div class="item-data-row__request-type">post</div>
|
485
|
+
</div>
|
486
|
+
<div class="item-data-row__center">
|
487
|
+
<div class="item-data-row__endpoint">/item/get</div>
|
488
|
+
<div class="item-data-row__description">Retrieve information about an Item, like the institution, billed products, available products, and webhook information.</div>
|
489
|
+
</div>
|
490
|
+
|
491
|
+
<div class="item-data-row__right">
|
492
|
+
<button id="get-item-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
493
|
+
</div>
|
494
|
+
|
495
|
+
<div class="item-data-row__response">
|
496
|
+
<table><tbody id="get-item-data"></tbody></table>
|
497
|
+
</div>
|
498
|
+
</div>
|
499
|
+
|
500
|
+
<div class="item-data-row">
|
501
|
+
<div class="item-data-row__left">
|
502
|
+
<div class="item-data-row__request-type">post</div>
|
503
|
+
</div>
|
504
|
+
<div class="item-data-row__center">
|
505
|
+
<div class="item-data-row__endpoint">/accounts/get</div>
|
506
|
+
<div class="item-data-row__description">Retrieve high-level information about all accounts associated with an Item.</div>
|
507
|
+
</div>
|
508
|
+
|
509
|
+
<div class="item-data-row__right">
|
510
|
+
<button id="get-accounts-btn" class="button button--is-small button--is-default button--is-full-width">Send request</button>
|
511
|
+
</div>
|
512
|
+
|
513
|
+
<div class="item-data-row__response">
|
514
|
+
<table><tbody id="get-accounts-data"></tbody></table>
|
515
|
+
</div>
|
516
|
+
</div>
|
517
|
+
</div>
|
518
|
+
</div>
|
519
|
+
</div>
|
520
|
+
</div>
|
521
|
+
</main>
|
522
|
+
|
523
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
|
524
|
+
<script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
|
525
|
+
<script>
|
526
|
+
function render_page($, page_info) {
|
527
|
+
// Handles redirect from the oauth response page for the oauth flow.
|
528
|
+
if (document.referrer != null && document.referrer.includes('http://localhost:8000/oauth-response.html') && page_info.item_id != null && page_info.item_id.length > 0) {
|
529
|
+
$('#container').fadeOut('fast', function() {
|
530
|
+
$('#item_id').text(page_info.item_id);
|
531
|
+
$('#access_token').text(page_info.access_token);
|
532
|
+
$('#intro').hide();
|
533
|
+
$('#app, #steps').fadeIn('slow');
|
534
|
+
});
|
535
|
+
}
|
536
|
+
|
537
|
+
var products = page_info.products;
|
538
|
+
if (products.includes('assets')) {
|
539
|
+
$('#assets').show();
|
540
|
+
}
|
541
|
+
// This functionality is only relevant for the UK Payment Initiation product.
|
542
|
+
if (products.includes('payment_initiation')) {
|
543
|
+
$('.payment_initiation').show();
|
544
|
+
$.post('/api/create_link_token_for_payment', {}, function(data) {
|
545
|
+
if (data.error != null) {
|
546
|
+
$('.loading-indicator').hide();
|
547
|
+
displayError($('#container'), data.error);
|
548
|
+
return;
|
549
|
+
}
|
550
|
+
localStorage.setItem('link_token', data.link_token);
|
551
|
+
// In the case of payment_initiation product, we need to wait for a
|
552
|
+
// link token configured with payment initiation to be generated before
|
553
|
+
// the Link handler can be initialized.
|
554
|
+
handler = Plaid.create({
|
555
|
+
token: data.link_token,
|
556
|
+
onSuccess: function(public_token) {
|
557
|
+
// This public token exchange step is not relevant for the
|
558
|
+
// payment_initiation product and should be skipped.
|
559
|
+
$.post('/api/set_access_token', {
|
560
|
+
public_token: public_token
|
561
|
+
}, function(data) {
|
562
|
+
$('#container').fadeOut('fast', function() {
|
563
|
+
$('#item_id').text(data.item_id);
|
564
|
+
$('#access_token').text(data.access_token);
|
565
|
+
$('#intro').hide();
|
566
|
+
$('#app, #steps').fadeIn('slow');
|
567
|
+
});
|
568
|
+
});
|
569
|
+
},
|
570
|
+
});
|
571
|
+
$('#link-btn').attr('disabled', false);
|
572
|
+
$('.loading-indicator').hide();
|
573
|
+
});
|
574
|
+
} else {
|
575
|
+
var handler = null;
|
576
|
+
$.post('/api/create_link_token', {}, function(data){
|
577
|
+
if (data.error != null) {
|
578
|
+
$('.loading-indicator').hide();
|
579
|
+
displayError($('#container'), data.error);
|
580
|
+
return;
|
581
|
+
}
|
582
|
+
localStorage.setItem('link_token', data.link_token);
|
583
|
+
handler = Plaid.create({
|
584
|
+
token: data.link_token,
|
585
|
+
onSuccess: function(public_token) {
|
586
|
+
$.post('/api/set_access_token', {
|
587
|
+
public_token: public_token
|
588
|
+
}, function(data) {
|
589
|
+
$('#container').fadeOut('fast', function() {
|
590
|
+
$('#item_id').text(data.item_id);
|
591
|
+
$('#access_token').text(data.access_token);
|
592
|
+
$('#intro').hide();
|
593
|
+
$('#app, #steps').fadeIn('slow');
|
594
|
+
});
|
595
|
+
});
|
596
|
+
},
|
597
|
+
});
|
598
|
+
$('#link-btn').attr('disabled', false);
|
599
|
+
$('.loading-indicator').hide();
|
600
|
+
});
|
601
|
+
}
|
602
|
+
|
603
|
+
var accessToken = qs('access_token');
|
604
|
+
if (accessToken != null && accessToken !== '') {
|
605
|
+
$.post('/api/set_access_token', {
|
606
|
+
access_token: accessToken
|
607
|
+
}, function(data) {
|
608
|
+
$('#container').fadeOut('fast', function() {
|
609
|
+
$('#item_id').text(data.item_id);
|
610
|
+
$('#access_token').text(accessToken);
|
611
|
+
$('#intro').hide();
|
612
|
+
$('#app, #steps').fadeIn('slow');
|
613
|
+
});
|
614
|
+
});
|
615
|
+
}
|
616
|
+
|
617
|
+
$('#link-btn').on('click', function(e) {
|
618
|
+
if (handler != null) {
|
619
|
+
handler.open();
|
620
|
+
}
|
621
|
+
});
|
622
|
+
|
623
|
+
$('#get-accounts-btn').on('click', function(e) {
|
624
|
+
$.get('/api/accounts', function(data) {
|
625
|
+
|
626
|
+
$('#get-accounts-data').slideUp(function() {
|
627
|
+
if (data.error != null) {
|
628
|
+
displayError(this, data.error);
|
629
|
+
return;
|
630
|
+
}
|
631
|
+
var html = '<tr><td><strong>Name</strong></td><td><strong>Balances</strong></td><td><strong>Subtype</strong></td><td><strong>Mask</strong></td></tr>';
|
632
|
+
data.accounts.forEach(function(account, idx) {
|
633
|
+
html += '<tr>';
|
634
|
+
html += '<td>' + account.name + '</td>';
|
635
|
+
html += '<td>$' + (account.balances.available != null ? account.balances.available : account.balances.current) + '</td>';
|
636
|
+
html += '<td>' + account.subtype + '</td>';
|
637
|
+
html += '<td>' + account.mask + '</td>';
|
638
|
+
html += '</tr>';
|
639
|
+
});
|
640
|
+
|
641
|
+
$(this).html(html).slideDown();
|
642
|
+
});
|
643
|
+
});
|
644
|
+
});
|
645
|
+
|
646
|
+
$('#get-auth-btn').on('click', function(e) {
|
647
|
+
$.get('/api/auth', function(data) {
|
648
|
+
$('#get-auth-data').slideUp(function() {
|
649
|
+
if (data.error != null) {
|
650
|
+
displayError(this, data.error);
|
651
|
+
return;
|
652
|
+
}
|
653
|
+
var isAch = data.numbers.ach.length > 0;
|
654
|
+
var routingLabel = isAch ? 'Routing #' : 'Institution and Branch #';
|
655
|
+
|
656
|
+
var html = '<tr><td><strong>Name</strong></td><td><strong>Balance</strong></td><td><strong>Account #</strong></td><td><strong>'+ routingLabel +'</strong></td></tr>';
|
657
|
+
if (isAch) {
|
658
|
+
data.numbers.ach.forEach(function(achNumbers, idx) {
|
659
|
+
// Find the account associated with this set of account and routing numbers
|
660
|
+
var account = data.accounts.filter(function(a) {
|
661
|
+
return a.account_id === achNumbers.account_id;
|
662
|
+
})[0];
|
663
|
+
html += '<tr>';
|
664
|
+
html += '<td>' + account.name + '</td>';
|
665
|
+
html += '<td>$' + (account.balances.available != null ? account.balances.available : account.balances.current) + '</td>';
|
666
|
+
html += '<td>' + achNumbers.account + '</td>';
|
667
|
+
html += '<td>' + achNumbers.routing + '</td>';
|
668
|
+
html += '</tr>';
|
669
|
+
});
|
670
|
+
} else {
|
671
|
+
data.numbers.eft.forEach(function(eftNumber, idx) {
|
672
|
+
// Find the account associated with this set of account and routing numbers
|
673
|
+
var account = data.accounts.filter(function(a) {
|
674
|
+
return a.account_id === eftNumber.account_id;
|
675
|
+
})[0];
|
676
|
+
html += '<tr>';
|
677
|
+
html += '<td>' + account.name + '</td>';
|
678
|
+
html += '<td>$' + (account.balances.available != null ? account.balances.available : account.balances.current) + '</td>';
|
679
|
+
html += '<td>' + eftNumber.account + '</td>';
|
680
|
+
html += '<td>' + eftNumber.institution + ' ' + eftNumber.branch + '</td>';
|
681
|
+
html += '</tr>';
|
682
|
+
});
|
683
|
+
}
|
684
|
+
$(this).html(html).slideDown();
|
685
|
+
});
|
686
|
+
});
|
687
|
+
});
|
688
|
+
|
689
|
+
$('#get-identity-btn').on('click', function(e) {
|
690
|
+
$.get('/api/identity', function(data) {
|
691
|
+
$('#get-identity-data').slideUp(function() {
|
692
|
+
if (data.error != null) {
|
693
|
+
displayError(this, data.error);
|
694
|
+
return;
|
695
|
+
}
|
696
|
+
var html = '<tr class="response-row response-row--is-identity"><td><strong>Names</strong></td><td><strong>Emails</strong></td><td><strong>Phone numbers</strong></td><td><strong>Addresses</strong></td></tr><tr class="response-row response-row--is-identity">';
|
697
|
+
var identityData = data.identity[0];
|
698
|
+
var html = '<tr class="response-row response-row--is-identity"><td><strong>Names</strong></td><td><strong>Emails</strong></td><td><strong>Phone numbers</strong></td><td><strong>Addresses</strong></td></tr><tr class="response-row response-row--is-identity">';
|
699
|
+
|
700
|
+
identityData.owners.forEach(function(owner, idx) {
|
701
|
+
html += '<td>';
|
702
|
+
owner.names.forEach(function(name, idx) {
|
703
|
+
html += name + '<br />';
|
704
|
+
});
|
705
|
+
html += '</td><td>';
|
706
|
+
owner.emails.forEach(function(email, idx) {
|
707
|
+
html += email.data + '<br />';
|
708
|
+
});
|
709
|
+
html += '</td><td>';
|
710
|
+
owner.phone_numbers.forEach(function(number, idx) {
|
711
|
+
html += number.data + '<br />';
|
712
|
+
});
|
713
|
+
html += '</td><td>';
|
714
|
+
owner.addresses.forEach(function(address, idx) {
|
715
|
+
html += address.data.street + '<br />';
|
716
|
+
html += address.data.city + ', ' + address.data.region + ' ' + address.data.postal_code + '<br />';
|
717
|
+
});
|
718
|
+
html += '</td>';
|
719
|
+
html += '</tr>';
|
720
|
+
|
721
|
+
});
|
722
|
+
|
723
|
+
$(this).html(html).slideDown();
|
724
|
+
});
|
725
|
+
});
|
726
|
+
});
|
727
|
+
|
728
|
+
$('#get-item-btn').on('click', function(e) {
|
729
|
+
$.get('/api/item', function(data) {
|
730
|
+
$('#get-item-data').slideUp(function() {
|
731
|
+
if (data.error != null) {
|
732
|
+
displayError(this, data.error);
|
733
|
+
return;
|
734
|
+
}
|
735
|
+
var html = '';
|
736
|
+
html += '<tr><td>Institution name</td><td>' + data.institution.name + '</td></tr>';
|
737
|
+
html += '<tr><td>Billed products</td><td>' + data.item.billed_products.join(', ') + '</td></tr>';
|
738
|
+
html += '<tr><td>Available products</td><td>' + data.item.available_products.join(', ') + '</td></tr>';
|
739
|
+
|
740
|
+
$(this).html(html).slideDown();
|
741
|
+
});
|
742
|
+
});
|
743
|
+
});
|
744
|
+
|
745
|
+
$('#get-transactions-btn').on('click', function(e) {
|
746
|
+
$.get('/api/transactions', function(data) {
|
747
|
+
if (data.error != null && data.error.error_code != null) {
|
748
|
+
// Format the error
|
749
|
+
var errorHtml = '<div class="inner"><p>' +
|
750
|
+
'<strong>' + data.error.error_code + ':</strong> ' +
|
751
|
+
(data.error.display_message == null ? data.error.error_message : data.error.display_message) + '</p></div>';
|
752
|
+
|
753
|
+
if (data.error.error_code === 'PRODUCT_NOT_READY') {
|
754
|
+
// Add additional context for `PRODUCT_NOT_READY` errors
|
755
|
+
errorHtml += '<div class="inner"><p>Note: The PRODUCT_NOT_READY ' +
|
756
|
+
'error is returned when a request to retrieve Transaction data ' +
|
757
|
+
'is made before Plaid finishes the <a href="https://plaid.com/' +
|
758
|
+
'docs/quickstart/#transaction-data-with-webhooks">initial ' +
|
759
|
+
'transaction pull.</a></p></div>';
|
760
|
+
}
|
761
|
+
// Render the error
|
762
|
+
$('#get-transactions-data').slideUp(function() {
|
763
|
+
$(this).slideUp(function() {
|
764
|
+
$(this).html(errorHtml).slideDown();
|
765
|
+
});
|
766
|
+
});
|
767
|
+
return;
|
768
|
+
}
|
769
|
+
$('#get-transactions-data').slideUp(function () {
|
770
|
+
var html = '<tr><td><strong>Name</strong></td><td><strong>Amount</strong></td><td><strong>Date</strong></td></tr>';
|
771
|
+
data.transactions.forEach(function (txn, idx) {
|
772
|
+
html += '<tr>';
|
773
|
+
html += '<td>' + txn.name + '</td>';
|
774
|
+
html += '<td>$' + txn.amount + '</td>';
|
775
|
+
html += '<td>' + txn.date + '</td>';
|
776
|
+
html += '</tr>';
|
777
|
+
});
|
778
|
+
|
779
|
+
$(this).slideUp(function () {
|
780
|
+
$(this).html(html).slideDown();
|
781
|
+
});
|
782
|
+
});
|
783
|
+
});
|
784
|
+
});
|
785
|
+
|
786
|
+
$('#get-balance-btn').on('click', function(e) {
|
787
|
+
$.get('/api/balance', function(data) {
|
788
|
+
$('#get-balance-data').slideUp(function() {
|
789
|
+
if (data.error != null) {
|
790
|
+
displayError(this, data.error);
|
791
|
+
return;
|
792
|
+
}
|
793
|
+
var html = '<tr><td><strong>Name</strong></td><td><strong>Balance</strong></td><td><strong>Subtype</strong></td><td><strong>Mask</strong></td></tr>';
|
794
|
+
data.accounts.forEach(function(account, idx) {
|
795
|
+
html += '<tr>';
|
796
|
+
html += '<td>' + account.name + '</td>';
|
797
|
+
html += '<td>$' + (account.balances.available != null ? account.balances.available : account.balances.current) + '</td>'
|
798
|
+
html += '<td>' + account.subtype + '</td>';
|
799
|
+
html += '<td>' + account.mask + '</td>';
|
800
|
+
html += '</tr>';
|
801
|
+
});
|
802
|
+
|
803
|
+
$(this).html(html).slideDown();
|
804
|
+
});
|
805
|
+
});
|
806
|
+
});
|
807
|
+
|
808
|
+
$('#get-holdings-btn').on('click', function(e) {
|
809
|
+
$.get('/api/holdings', function(data) {
|
810
|
+
$('#get-holdings-data').slideUp(function() {
|
811
|
+
if (data.error != null) {
|
812
|
+
displayError(this, data.error);
|
813
|
+
return;
|
814
|
+
}
|
815
|
+
// Organize by Account
|
816
|
+
var holdingsData = data.holdings.holdings.sort(function(a, b) {
|
817
|
+
if (a.account_id > b.account_id) return 1;
|
818
|
+
return -1;
|
819
|
+
});
|
820
|
+
var html = '<tr class="response-row response-row--is-holdings"></tr><td><strong>Account Mask</strong></td><td><strong>Name</strong></td><td><strong>Quantity</strong></td><td><strong>Close Price</strong></td><td><strong>Value</strong></td><tr class="response-row response-row--is-holding">';
|
821
|
+
holdingsData.forEach(function(h, idx) {
|
822
|
+
const account = data.holdings.accounts.filter(a => a.account_id === h.account_id)[0];
|
823
|
+
const security = data.holdings.securities.filter(s => s.security_id === h.security_id)[0];
|
824
|
+
if (account == null) {
|
825
|
+
displayError(this, {
|
826
|
+
code: 500,
|
827
|
+
type: 'Internal',
|
828
|
+
display_message: 'Holding lacks a account',
|
829
|
+
});
|
830
|
+
}
|
831
|
+
if (security == null) {
|
832
|
+
displayError(this, {
|
833
|
+
code: 500,
|
834
|
+
type: 'Internal',
|
835
|
+
display_message: 'Holding lacks a security',
|
836
|
+
});
|
837
|
+
}
|
838
|
+
html += '<tr>';
|
839
|
+
html += '<td>' + account.mask + '</td>';
|
840
|
+
html += '<td>' + security.name + '</td>';
|
841
|
+
html += '<td>' + h.quantity + '</td>';
|
842
|
+
html += '<td>$' + security.close_price + '</td>';
|
843
|
+
html += '<td>$' + h.quantity * security.close_price + '</td>';
|
844
|
+
html += '</tr>';
|
845
|
+
});
|
846
|
+
$(this).html(html).slideDown();
|
847
|
+
});
|
848
|
+
});
|
849
|
+
});
|
850
|
+
|
851
|
+
$('#get-investment-transactions-btn').on('click', function(e) {
|
852
|
+
$.get('/api/investment_transactions', function(data) {
|
853
|
+
$('#get-investment-transactions-data').slideUp(function() {
|
854
|
+
if (data.error != null) {
|
855
|
+
displayError(this, data.error);
|
856
|
+
return;
|
857
|
+
}
|
858
|
+
investmentTransactionData = data.investment_transactions.investment_transactions;
|
859
|
+
var html = '<tr class="response-row response-row--is-investment-transactions"></tr><td><strong>Name</strong></td><td><strong>Amount</strong></td><td><strong>Date</strong></td><tr class="response-row response-row--is-investment-transaction">';
|
860
|
+
investmentTransactionData.forEach(function(invTxn, idx) {
|
861
|
+
html += '<tr>';
|
862
|
+
html += '<td>' + invTxn.name + '</td>';
|
863
|
+
html += '<td>$' + invTxn.amount + '</td>';
|
864
|
+
html += '<td>' + invTxn.date + '</td>';
|
865
|
+
html += '</tr>';
|
866
|
+
});
|
867
|
+
$(this).html(html).slideDown();
|
868
|
+
});
|
869
|
+
});
|
870
|
+
});
|
871
|
+
|
872
|
+
$('#get-assets-btn').on('click', function(e) {
|
873
|
+
$.get('/api/assets', function(data) {
|
874
|
+
$('#get-assets-data').slideUp(function() {
|
875
|
+
if (data.error != null) {
|
876
|
+
displayError(this, data.error);
|
877
|
+
return;
|
878
|
+
}
|
879
|
+
var reportData = data.json;
|
880
|
+
var html = `
|
881
|
+
<tr>
|
882
|
+
<td><strong>Account</strong></td>
|
883
|
+
<td><strong>Balance</strong></td>
|
884
|
+
<td><strong># Transactions</strong></td>
|
885
|
+
<td><strong># Days Available</strong></td>
|
886
|
+
</tr>`;
|
887
|
+
reportData.items.forEach(function(item, itemIdx) {
|
888
|
+
item.accounts.forEach(function(account, accountIdx) {
|
889
|
+
html += '<tr>';
|
890
|
+
html += '<td>' + account.name + '</td>';
|
891
|
+
html += '<td>$' + account.balances.current + '</td>'
|
892
|
+
html += '<td>' + account.transactions.length + '</td>';
|
893
|
+
html += '<td>' + account.days_available + '</td>';
|
894
|
+
html += '</tr>';
|
895
|
+
});
|
896
|
+
});
|
897
|
+
|
898
|
+
$('#download-assets-pdf-btn')
|
899
|
+
.attr('href', `data:application/pdf;base64,${data.pdf}`)
|
900
|
+
.attr('download', 'Asset Report.pdf')
|
901
|
+
.show();
|
902
|
+
|
903
|
+
$(this).html(html).slideDown();
|
904
|
+
});
|
905
|
+
});
|
906
|
+
});
|
907
|
+
|
908
|
+
// This functionality is only relevant for the UK Payment Initiation product.
|
909
|
+
$('#get-payment-btn').on('click', function(e) {
|
910
|
+
$.get('/api/payment', function(data) {
|
911
|
+
$('#get-payment-data').slideUp(function() {
|
912
|
+
if (data.error != null) {
|
913
|
+
displayError(this, data.error);
|
914
|
+
return;
|
915
|
+
}
|
916
|
+
var paymentData = data.payment;
|
917
|
+
var html = '';
|
918
|
+
html += '<tr><td>Payment ID</td><td>' + paymentData.payment_id + '</td></tr>';
|
919
|
+
html += '<tr><td>Amount</td><td>' + (paymentData.amount.currency + ' ' + paymentData.amount.value) + '</td></tr>';
|
920
|
+
html += '<tr><td>Status</td><td>' + paymentData.status + '</td></tr>';
|
921
|
+
html += '<tr><td>Last Status Update</td><td>' + paymentData.last_status_update + '</td></tr>';
|
922
|
+
html += '<tr><td>Recipient ID</td><td>' + paymentData.recipient_id + '</td></tr>';
|
923
|
+
$(this).html(html).slideDown();
|
924
|
+
});
|
925
|
+
});
|
926
|
+
});
|
927
|
+
}
|
928
|
+
$.post('/api/info', {}, function(result) {
|
929
|
+
render_page(jQuery, result);
|
930
|
+
});
|
931
|
+
|
932
|
+
function qs(key) {
|
933
|
+
key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
|
934
|
+
var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)"));
|
935
|
+
return match && decodeURIComponent(match[1].replace(/\+/g, " "));
|
936
|
+
}
|
937
|
+
|
938
|
+
function displayError(element, error) {
|
939
|
+
var html = `
|
940
|
+
<div class="alert alert-danger">
|
941
|
+
<p><strong>Error Code:</strong> ${error.error_code}</p>
|
942
|
+
<p><strong>Error Type:</strong> ${error.error_type}</p>
|
943
|
+
<p><strong>Error Message:</strong> ${error.display_message == null ? error.error_message : error.display_message}</p>
|
944
|
+
<div>Check out our <a href="https://plaid.com/docs/#errors-overview">errors documentation</a> for more information.</div>
|
945
|
+
</div>`;
|
946
|
+
$(element).html(html).slideDown();
|
947
|
+
}
|
948
|
+
</script>
|
949
|
+
</body>
|
950
|
+
</html>
|