From 6edb168cdadfca34bd2f0dc2b08d0e3d9ba02ef9 Mon Sep 17 00:00:00 2001
From: Alex Hoffer
Date: Tue, 16 Dec 2025 17:38:45 -0800
Subject: [PATCH 01/11] WIP: Add user API identity object support for Node.js
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Updates the Node.js quickstart to support the new user creation flow:
- Default to sending identity object with proper schema (name, emails, phone_numbers, addresses)
- Auto-retry with consumer_report_user_identity on INVALID_FIELD error
- Track both user_id and user_token from responses
- Use user_token when available (from consumer_report_user_identity), otherwise user_id
- Remove user.client_user_id when passing user_id/user_token to link token creation
- Update CRA endpoints to use correct identifier (user_token or user_id)
- Income endpoints always use user_token as required
- Upgrade plaid library to v40.0.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5
---
node/index.js | 157 +++++++++++++++++++++++++++++++----------
node/package-lock.json | 45 +++++++-----
node/package.json | 2 +-
3 files changed, 145 insertions(+), 59 deletions(-)
diff --git a/node/index.js b/node/index.js
index 2ca18c525..75ddbc1c1 100644
--- a/node/index.js
+++ b/node/index.js
@@ -50,6 +50,7 @@ const SIGNAL_RULESET_KEY = process.env.SIGNAL_RULESET_KEY || '';
// persistent data store
let ACCESS_TOKEN = null;
let USER_TOKEN = null;
+let USER_ID = null;
let PUBLIC_TOKEN = null;
let ITEM_ID = null;
let ACCOUNT_ID = null;
@@ -128,7 +129,16 @@ app.post('/api/create_link_token', function (request, response, next) {
}
if (PLAID_PRODUCTS.some(product => product.startsWith("cra_"))) {
- configs.user_token = USER_TOKEN;
+ // Use user_token if available, otherwise use user_id
+ if (USER_TOKEN) {
+ configs.user_token = USER_TOKEN;
+ // Remove user object when using user_token
+ delete configs.user;
+ } else if (USER_ID) {
+ configs.user_id = USER_ID;
+ // Remove user object when using user_id
+ delete configs.user;
+ }
configs.cra_options = {
days_requested: 60
};
@@ -147,30 +157,85 @@ app.post('/api/create_user_token', function (request, response, next) {
Promise.resolve()
.then(async function () {
- const request = {
- // Typically this will be a user ID number from your application.
+ const userRequest = {
+ // Typically this will be a user ID number from your application.
client_user_id: 'user_' + uuidv4()
}
if (PLAID_PRODUCTS.some(product => product.startsWith("cra_"))) {
- request.consumer_report_user_identity = {
+ // Default to sending identity object
+ userRequest.identity = {
+ name: {
+ given_name: 'Harry',
+ family_name: 'Potter'
+ },
date_of_birth: '1980-07-31',
- first_name: 'Harry',
- last_name: 'Potter',
- phone_numbers: ['+16174567890'],
- emails: ['harrypotter@example.com'],
- primary_address: {
+ phone_numbers: [{
+ data: '+16174567890',
+ primary: true
+ }],
+ emails: [{
+ data: 'harrypotter@example.com',
+ primary: true
+ }],
+ addresses: [{
+ street_1: '4 Privet Drive',
city: 'New York',
region: 'NY',
- street: '4 Privet Drive',
postal_code: '11111',
- country: 'US'
+ country: 'US',
+ primary: true
+ }]
+ }
+ }
+
+ try {
+ const user = await client.userCreate(userRequest);
+
+ if (user.data.user_token) {
+ USER_TOKEN = user.data.user_token;
+ }
+ if (user.data.user_id) {
+ USER_ID = user.data.user_id;
+ }
+
+ response.json(user.data);
+ } catch (error) {
+ if (error.response && error.response.data &&
+ error.response.data.error_code === 'INVALID_FIELD' &&
+ PLAID_PRODUCTS.some(product => product.startsWith("cra_"))) {
+
+ // Retry with consumer_report_user_identity
+ delete userRequest.identity;
+ userRequest.consumer_report_user_identity = {
+ date_of_birth: '1980-07-31',
+ first_name: 'Harry',
+ last_name: 'Potter',
+ phone_numbers: ['+16174567890'],
+ emails: ['harrypotter@example.com'],
+ primary_address: {
+ city: 'New York',
+ region: 'NY',
+ street: '4 Privet Drive',
+ postal_code: '11111',
+ country: 'US'
+ }
+ }
+
+ const retryUser = await client.userCreate(userRequest);
+
+ if (retryUser.data.user_token) {
+ USER_TOKEN = retryUser.data.user_token;
}
+ if (retryUser.data.user_id) {
+ USER_ID = retryUser.data.user_id;
+ }
+
+ response.json(retryUser.data);
+ } else {
+ throw error;
}
}
- const user = await client.userCreate(request);
- USER_TOKEN = user.data.user_token
- response.json(user.data);
}).catch(next);
});
@@ -681,12 +746,17 @@ app.get('/api/signal_evaluate', function (request, response, next) {
app.get('/api/cra/get_base_report', function (request, response, next) {
Promise.resolve()
.then(async function () {
- const getResponse = await getCraBaseReportWithRetries(client, USER_TOKEN);
+ // Use user_token if available, otherwise use user_id
+ const userIdentifier = USER_TOKEN || USER_ID;
+ const identifierKey = USER_TOKEN ? 'user_token' : 'user_id';
+
+ const getResponse = await getCraBaseReportWithRetries(client, userIdentifier, identifierKey);
prettyPrintResponse(getResponse);
- const pdfResponse = await client.craCheckReportPdfGet({
- user_token: USER_TOKEN,
- }, {
+ const pdfRequest = {};
+ pdfRequest[identifierKey] = userIdentifier;
+
+ const pdfResponse = await client.craCheckReportPdfGet(pdfRequest, {
responseType: 'arraybuffer'
});
@@ -700,16 +770,18 @@ app.get('/api/cra/get_base_report', function (request, response, next) {
const getCraBaseReportWithRetries = (
plaidClient,
- userToken
-) => pollWithRetries(
- async () => {
- return await plaidClient.craCheckReportBaseReportGet(
- {
- user_token: userToken
- }
- )
- }
-);
+ userIdentifier,
+ identifierKey = 'user_token'
+) => {
+ const requestBody = {};
+ requestBody[identifierKey] = userIdentifier;
+
+ return pollWithRetries(
+ async () => {
+ return await plaidClient.craCheckReportBaseReportGet(requestBody)
+ }
+ );
+};
// Retrieve CRA Income Insights and PDF with Insights
// Income insights: https://plaid.com/docs/check/api/#cracheck_reportincome_insightsget
@@ -717,6 +789,7 @@ const getCraBaseReportWithRetries = (
app.get('/api/cra/get_income_insights', async (req, res, next) => {
Promise.resolve()
.then(async function () {
+ // Income endpoints always use user_token
const getResponse = await getCheckInsightsWithRetries(client, USER_TOKEN)
prettyPrintResponse(getResponse);
@@ -754,7 +827,11 @@ const getCheckInsightsWithRetries = (
app.get('/api/cra/get_partner_insights', async (req, res, next) => {
Promise.resolve()
.then(async function () {
- const response = await getCheckParnterInsightsWithRetries(client, USER_TOKEN);
+ // Use user_token if available, otherwise use user_id
+ const userIdentifier = USER_TOKEN || USER_ID;
+ const identifierKey = USER_TOKEN ? 'user_token' : 'user_id';
+
+ const response = await getCheckParnterInsightsWithRetries(client, userIdentifier, identifierKey);
prettyPrintResponse(response);
res.json(response.data);
@@ -765,16 +842,18 @@ app.get('/api/cra/get_partner_insights', async (req, res, next) => {
const getCheckParnterInsightsWithRetries = (
plaidClient,
- userToken
-) => pollWithRetries(
- async () => {
- return await plaidClient.craCheckReportPartnerInsightsGet(
- {
- user_token: userToken
- }
- );
- }
-);
+ userIdentifier,
+ identifierKey = 'user_token'
+) => {
+ const requestBody = {};
+ requestBody[identifierKey] = userIdentifier;
+
+ return pollWithRetries(
+ async () => {
+ return await plaidClient.craCheckReportPartnerInsightsGet(requestBody);
+ }
+ );
+};
// Since this quickstart does not support webhooks, this function can be used to poll
// an API that would otherwise be triggered by a webhook.
diff --git a/node/package-lock.json b/node/package-lock.json
index 7a38936d6..a4551c14f 100644
--- a/node/package-lock.json
+++ b/node/package-lock.json
@@ -11,13 +11,13 @@
"dependencies": {
"body-parser": "^1.20.3",
"cors": "^2.8.5",
- "dotenv": "^8.2.0",
+ "dotenv": "^17.2.3",
"ejs": "^3.1.10",
- "express": "^4.21.1",
+ "express": "^4.21.2",
"moment": "^2.30.1",
- "nodemon": "^3.1.7",
- "plaid": "^39.1.0",
- "uuid": "^9.0.0"
+ "nodemon": "^3.1.10",
+ "plaid": "^40.0.0",
+ "uuid": "^11.1.0"
}
},
"node_modules/abbrev": {
@@ -368,11 +368,15 @@
}
},
"node_modules/dotenv": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
- "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
+ "version": "17.2.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
+ "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+ "license": "BSD-2-Clause",
"engines": {
- "node": ">=10"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
@@ -946,9 +950,10 @@
}
},
"node_modules/nodemon": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
- "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==",
+ "version": "3.1.11",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
+ "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
+ "license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
@@ -1070,9 +1075,10 @@
}
},
"node_modules/plaid": {
- "version": "39.1.0",
- "resolved": "https://registry.npmjs.org/plaid/-/plaid-39.1.0.tgz",
- "integrity": "sha512-i1D3DMXReWwjBC+phMgdW8k9c0OCYw0IH2ZOdnqGM6ZCAFHDeFxgvcLdM9uwNFZdFeu7VEd9MJRj9QTKEQjKBQ==",
+ "version": "40.0.0",
+ "resolved": "https://registry.npmjs.org/plaid/-/plaid-40.0.0.tgz",
+ "integrity": "sha512-w6ro8Bix8GyTmBvHs5zJwClmrlHItfv0o38yjBc/SqJ48zZ4HLvDr9MtkvbGARcuLrWbiFbQ9zgr7oBb5raXUQ==",
+ "license": "MIT",
"dependencies": {
"axios": "^1.7.4"
},
@@ -1366,15 +1372,16 @@
}
},
"node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
+ "license": "MIT",
"bin": {
- "uuid": "dist/bin/uuid"
+ "uuid": "dist/esm/bin/uuid"
}
},
"node_modules/vary": {
diff --git a/node/package.json b/node/package.json
index 58f6be396..92500d738 100644
--- a/node/package.json
+++ b/node/package.json
@@ -19,7 +19,7 @@
"express": "^4.21.2",
"moment": "^2.30.1",
"nodemon": "^3.1.10",
- "plaid": "^39.1.0",
+ "plaid": "^40.0.0",
"uuid": "^11.1.0"
}
}
From 7b7f456a060909e7b24212e54c0c23a74ce9ba28 Mon Sep 17 00:00:00 2001
From: Alex Hoffer
Date: Thu, 18 Dec 2025 17:39:52 -0500
Subject: [PATCH 02/11] Add support for both user_token and user_id in CRA
integrations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Updated all backend implementations (Python, Node.js, Go, Java, Ruby) to support both old-style (user_token) and new-style (user_id) CRA user integrations. Each backend now tries the Identity field first, then falls back to ConsumerReportUserIdentity if that fails. All CRA endpoints (base_report, income_insights, partner_insights) handle both user_token and user_id parameters. Frontend updated to display both user_token and user_id when available.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5
---
frontend/package-lock.json | 246 +-----------------
frontend/src/App.tsx | 12 +-
frontend/src/Components/Headers/index.tsx | 18 +-
frontend/src/Context/index.tsx | 2 +
go/server.go | 187 ++++++++++---
.../quickstart/QuickstartApplication.java | 1 +
.../quickstart/resources/CraResource.java | 39 ++-
.../resources/LinkTokenResource.java | 10 +-
.../resources/UserTokenResource.java | 104 +++++++-
node/index.js | 30 ++-
python/server.py | 168 +++++++++---
ruby/app.rb | 142 ++++++++--
12 files changed, 575 insertions(+), 384 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 8f8b85091..f18e58680 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -47,7 +47,7 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
@@ -112,18 +112,6 @@
"multicast-dns": "cli.js"
}
},
- "node_modules/pkg-up/node_modules/locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dependencies": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
@@ -2410,20 +2398,6 @@
"url": "https://github.com/sponsors/gregberge"
}
},
- "node_modules/pkg-up/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@testing-library/react/node_modules/@types/aria-query": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
@@ -2944,12 +2918,6 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/jest-watch-typeahead/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -3093,15 +3061,6 @@
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true
},
- "node_modules/serve-index/node_modules/depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/is-bigint": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
@@ -3429,18 +3388,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-watch-typeahead/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/is-boolean-object": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
@@ -3980,16 +3927,6 @@
"node": ">=8"
}
},
- "node_modules/static-eval/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "optional": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -4386,18 +4323,6 @@
"postcss": "^8.4"
}
},
- "node_modules/pkg-dir/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/update-browserslist-db": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
@@ -4616,11 +4541,6 @@
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
},
- "node_modules/schema-utils/node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
- },
"node_modules/@types/jest/node_modules/@jest/types": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
@@ -4759,15 +4679,6 @@
"which": "bin/which"
}
},
- "node_modules/sucrase/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/jest-cli": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz",
@@ -4985,35 +4896,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-watch-typeahead/node_modules/jest-message-util": {
- "version": "28.1.3",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz",
- "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==",
- "dev": true,
- "dependencies": {
- "@babel/code-frame": "^7.12.13",
- "@jest/types": "^28.1.3",
- "@types/stack-utils": "^2.0.0",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "micromatch": "^4.0.4",
- "pretty-format": "^28.1.3",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.3"
- },
- "engines": {
- "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
- }
- },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -5291,18 +5173,6 @@
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
},
- "node_modules/svgo/node_modules/css-what": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
- "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -5615,6 +5485,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
"engines": {
"node": ">=6"
}
@@ -5757,21 +5628,6 @@
"postcss": ">=8"
}
},
- "node_modules/jest-watch-typeahead/node_modules/pretty-format": {
- "version": "28.1.3",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz",
- "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==",
- "dev": true,
- "dependencies": {
- "@jest/schemas": "^28.1.3",
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
- },
- "engines": {
- "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
- }
- },
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -5912,6 +5768,7 @@
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
@@ -6641,12 +6498,6 @@
"postcss": "^8.2.15"
}
},
- "node_modules/@eslint/eslintrc/node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "devOptional": true
- },
"node_modules/svgo/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -7728,14 +7579,6 @@
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
}
},
- "node_modules/webpack/node_modules/estraverse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
- "engines": {
- "node": ">=4.0"
- }
- },
"node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -8929,7 +8772,6 @@
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.10.tgz",
"integrity": "sha512-0xbOE6Ht/oj0MTVVXCCdEZzUk7adwW3YB1Tg1ZBm95jrkrUMI0VA4sf3SgxC1TG8p5aKkn3jxT9A2BDw1mM/TQ==",
- "dev": true,
"dependencies": {
"@types/yargs-parser": "*"
}
@@ -9347,12 +9189,6 @@
"postcss": "^8.3"
}
},
- "node_modules/express/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@@ -10096,23 +9932,12 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/pkg-dir/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/file-loader/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -11251,6 +11076,7 @@
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
+ "license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
@@ -11646,14 +11472,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/pkg-up/node_modules/path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/@types/eslint": {
"version": "8.56.12",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
@@ -13567,21 +13385,6 @@
"node": ">= 0.6"
}
},
- "node_modules/pkg-dir/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@emotion/utils": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz",
@@ -13708,12 +13511,6 @@
"wbuf": "^1.7.3"
}
},
- "node_modules/compression/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
"node_modules/eslint-plugin-import/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
@@ -14588,14 +14385,6 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
- "node_modules/@types/jest/node_modules/@types/yargs": {
- "version": "15.0.19",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz",
- "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==",
- "dependencies": {
- "@types/yargs-parser": "*"
- }
- },
"node_modules/sass-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -15989,7 +15778,8 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/data-urls": {
"version": "2.0.0",
@@ -16110,7 +15900,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true
+ "devOptional": true
},
"node_modules/supports-color": {
"version": "7.2.0",
@@ -16405,15 +16195,6 @@
"react": ">=0.14.0 <17.0.0"
}
},
- "node_modules/postcss-svgo/node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
- "dev": true,
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
@@ -18176,17 +17957,6 @@
"integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
"dev": true
},
- "node_modules/pkg-up/node_modules/p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dependencies": {
- "p-limit": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/sass/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index e31a45fe8..cecca0bb1 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -45,7 +45,7 @@ const App = () => {
const generateUserToken = useCallback(async () => {
const response = await fetch("api/create_user_token", { method: "POST" });
if (!response.ok) {
- dispatch({ type: "SET_STATE", state: { userToken: null } });
+ dispatch({ type: "SET_STATE", state: { userToken: null, userId: null } });
return;
}
const data = await response.json();
@@ -60,8 +60,14 @@ const App = () => {
});
return;
}
- dispatch({ type: "SET_STATE", state: { userToken: data.user_token } });
- return data.user_token;
+ dispatch({
+ type: "SET_STATE",
+ state: {
+ userToken: data.user_token || null,
+ userId: data.user_id || null
+ }
+ });
+ return data.user_token || data.user_id;
}
}, [dispatch]);
diff --git a/frontend/src/Components/Headers/index.tsx b/frontend/src/Components/Headers/index.tsx
index d01dfc98d..c175ab799 100644
--- a/frontend/src/Components/Headers/index.tsx
+++ b/frontend/src/Components/Headers/index.tsx
@@ -13,6 +13,7 @@ const Header = () => {
itemId,
accessToken,
userToken,
+ userId,
linkToken,
linkSuccess,
isItemAccess,
@@ -126,7 +127,7 @@ const Header = () => {
.
- ) : userToken ? (
+ ) : userToken || userId ? (
Congrats! You have successfully linked data to a User.
@@ -158,12 +159,21 @@ const Header = () => {
{userToken}
)}
+
+ {userId && (
+
+ user_id
+ {userId}
+
+ )}
- {(isItemAccess || userToken) && (
+ {(isItemAccess || userToken || userId) && (
Now that you have {accessToken && "an access_token"}
- {accessToken && userToken && " and "}
- {userToken && "a user_token"}, you can make all of the
+ {accessToken && (userToken || userId) && " and "}
+ {userToken && "a user_token"}
+ {userToken && userId && " and "}
+ {userId && "a user_id"}, you can make all of the
following requests:
)}
diff --git a/frontend/src/Context/index.tsx b/frontend/src/Context/index.tsx
index a55acdc46..11f44e2ac 100644
--- a/frontend/src/Context/index.tsx
+++ b/frontend/src/Context/index.tsx
@@ -9,6 +9,7 @@ interface QuickstartState {
linkToken: string | null;
accessToken: string | null;
userToken: string | null;
+ userId: string | null;
itemId: string | null;
isError: boolean;
backend: boolean;
@@ -28,6 +29,7 @@ const initialState: QuickstartState = {
isUserTokenFlow: false,
linkToken: "", // Don't set to null or error message will show up briefly when site loads
userToken: null,
+ userId: null,
accessToken: null,
itemId: null,
isError: false,
diff --git a/go/server.go b/go/server.go
index afd532f17..1858fd632 100644
--- a/go/server.go
+++ b/go/server.go
@@ -129,10 +129,11 @@ func main() {
}
}
-// We store the access_token and user_token in memory - in production, store it in a secure
+// We store the access_token, user_token, and user_id in memory - in production, store it in a secure
// persistent data store.
var accessToken string
var userToken string
+var userID string
var itemID string
var paymentID string
@@ -582,12 +583,19 @@ func createLinkToken(c *gin.Context) {
}
func createUserToken(c *gin.Context) {
- userToken, err := userTokenCreate()
+ err := userTokenCreate()
if err != nil {
renderError(c, err)
return
}
- c.JSON(http.StatusOK, gin.H{"user_token": userToken})
+ response := gin.H{}
+ if userToken != "" {
+ response["user_token"] = userToken
+ }
+ if userID != "" {
+ response["user_id"] = userID
+ }
+ c.JSON(http.StatusOK, response)
}
func convertCountryCodes(countryCodeStrs []string) []plaid.CountryCode {
@@ -663,7 +671,19 @@ func linkTokenCreate(
if containsProduct(products, plaid.PRODUCTS_CRA_BASE_REPORT) ||
containsProduct(products, plaid.PRODUCTS_CRA_INCOME_INSIGHTS) ||
containsProduct(products, plaid.PRODUCTS_CRA_PARTNER_INSIGHTS) {
- request.SetUserToken(userToken)
+ // Use user_token if available, otherwise use user_id
+ if userToken != "" {
+ fmt.Println("Creating link token with user_token:", userToken)
+ request.SetUserToken(userToken)
+ // Keep user object when using user_token
+ } else if userID != "" {
+ fmt.Println("Creating link token with user_id:", userID)
+ request.SetUserId(userID)
+ // Remove user object when using user_id
+ request.User = nil
+ } else {
+ fmt.Println("ERROR: Both userToken and userID are empty!")
+ }
request.SetConsumerReportPermissiblePurpose(plaid.CONSUMERREPORTPERMISSIBLEPURPOSE_ACCOUNT_REVIEW_CREDIT)
request.SetCraOptions(*plaid.NewLinkTokenCreateRequestCraOptions(60))
}
@@ -683,7 +703,7 @@ func linkTokenCreate(
// Create a user token which can be used for Plaid Check, Income, or Multi-Item link flows
// https://plaid.com/docs/api/users/#usercreate
-func userTokenCreate() (string, error) {
+func userTokenCreate() error {
ctx := context.Background()
request := plaid.NewUserCreateRequest(
@@ -695,41 +715,107 @@ func userTokenCreate() (string, error) {
if containsProduct(products, plaid.PRODUCTS_CRA_BASE_REPORT) ||
containsProduct(products, plaid.PRODUCTS_CRA_INCOME_INSIGHTS) ||
containsProduct(products, plaid.PRODUCTS_CRA_PARTNER_INSIGHTS) {
+ // Try with Identity field first (new-style)
+ givenName := "Harry"
+ familyName := "Potter"
+ phoneNumber := "+16174567890"
+ emailAddress := "harrypotter@example.com"
+ dateOfBirth := "1980-07-31"
+ primary := true
city := "New York"
region := "NY"
- street := "4 Privet Drive"
postalCode := "11111"
country := "US"
- addressData := plaid.AddressData{
- City: *plaid.NewNullableString(&city),
- Region: *plaid.NewNullableString(®ion),
- Street: street,
- PostalCode: *plaid.NewNullableString(&postalCode),
- Country: *plaid.NewNullableString(&country),
- }
- UserIdentity := plaid.NewConsumerReportUserIdentity(
- "Harry",
- "Potter",
- []string{"+16174567890"},
- []string{"harrypotter@example.com"},
- *plaid.NewNullableString(nil),
- addressData,
- )
- DateOfBirth := "1980-07-31"
- UserIdentity.SetDateOfBirth(DateOfBirth)
- request.SetConsumerReportUserIdentity(*UserIdentity)
+ ClientIdentity := plaid.ClientUserIdentity{
+ Name: *plaid.NewNullableClientUserIdentityName(&plaid.ClientUserIdentityName{
+ GivenName: givenName,
+ FamilyName: familyName,
+ }),
+ DateOfBirth: *plaid.NewNullableString(&dateOfBirth),
+ PhoneNumbers: &[]plaid.ClientUserIdentityPhoneNumber{
+ {
+ Data: phoneNumber,
+ Primary: primary,
+ },
+ },
+ Emails: &[]plaid.ClientUserIdentityEmail{
+ {
+ Data: emailAddress,
+ Primary: primary,
+ },
+ },
+ Addresses: &[]plaid.ClientUserIdentityAddress{
+ {
+ Street1: *plaid.NewNullableString(plaid.PtrString("4 Privet Drive")),
+ City: *plaid.NewNullableString(&city),
+ Region: *plaid.NewNullableString(®ion),
+ PostalCode: *plaid.NewNullableString(&postalCode),
+ Country: country,
+ Primary: primary,
+ },
+ },
+ }
+ request.SetIdentity(ClientIdentity)
}
userCreateResp, _, err := client.PlaidApi.UserCreate(ctx).UserCreateRequest(*request).Execute()
if err != nil {
- return "", err
+ // Retry with ConsumerReportUserIdentity (old-style) on error
+ plaidErr, parseErr := plaid.ToPlaidError(err)
+ if parseErr == nil && plaidErr.ErrorCode == "INVALID_FIELD" {
+ request2 := plaid.NewUserCreateRequest(time.Now().String())
+
+ products := convertProducts(strings.Split(PLAID_PRODUCTS, ","))
+ if containsProduct(products, plaid.PRODUCTS_CRA_BASE_REPORT) ||
+ containsProduct(products, plaid.PRODUCTS_CRA_INCOME_INSIGHTS) ||
+ containsProduct(products, plaid.PRODUCTS_CRA_PARTNER_INSIGHTS) {
+ city := "New York"
+ region := "NY"
+ street := "4 Privet Drive"
+ postalCode := "11111"
+ country := "US"
+ addressData := plaid.AddressData{
+ City: *plaid.NewNullableString(&city),
+ Region: *plaid.NewNullableString(®ion),
+ Street: street,
+ PostalCode: *plaid.NewNullableString(&postalCode),
+ Country: *plaid.NewNullableString(&country),
+ }
+ UserIdentity := plaid.NewConsumerReportUserIdentity(
+ "Harry",
+ "Potter",
+ []string{"+16174567890"},
+ []string{"harrypotter@example.com"},
+ *plaid.NewNullableString(nil),
+ addressData,
+ )
+ DateOfBirth := "1980-07-31"
+ UserIdentity.SetDateOfBirth(DateOfBirth)
+ request2.SetConsumerReportUserIdentity(*UserIdentity)
+ }
+
+ userCreateResp, _, err = client.PlaidApi.UserCreate(ctx).UserCreateRequest(*request2).Execute()
+ if err != nil {
+ return err
+ }
+ } else {
+ return err
+ }
}
- userToken = userCreateResp.GetUserToken()
+ // Store both user_token and user_id
+ if ut, ok := userCreateResp.GetUserTokenOk(); ok {
+ userToken = *ut
+ fmt.Println("Created user with user_token:", userToken)
+ }
+ if uid, ok := userCreateResp.GetUserIdOk(); ok {
+ userID = *uid
+ fmt.Println("Created user with user_id:", userID)
+ }
- return userCreateResp.GetUserToken(), nil
+ return nil
}
func statements(c *gin.Context) {
@@ -825,14 +911,19 @@ func pollForAssetReport(ctx context.Context, client *plaid.APIClient, assetRepor
// PDF: https://plaid.com/docs/check/api/#cracheck_reportpdfget
func getCraBaseReportHandler(c *gin.Context) {
ctx := context.Background()
- getResponse, err := getCraBaseReportWithRetries(ctx, userToken)
+ getResponse, err := getCraBaseReportWithRetries(ctx)
if err != nil {
renderError(c, err)
return
}
pdfRequest := plaid.NewCraCheckReportPDFGetRequest()
- pdfRequest.SetUserToken(userToken)
+ // Use user_token if available, otherwise use user_id
+ if userToken != "" {
+ pdfRequest.SetUserToken(userToken)
+ } else if userID != "" {
+ pdfRequest.SetUserId(userID)
+ }
pdfResponse, _, err := client.PlaidApi.CraCheckReportPdfGet(ctx).CraCheckReportPDFGetRequest(*pdfRequest).Execute()
if err != nil {
renderError(c, err)
@@ -855,10 +946,15 @@ func getCraBaseReportHandler(c *gin.Context) {
})
}
-func getCraBaseReportWithRetries(ctx context.Context, userToken string) (*plaid.CraCheckReportBaseReportGetResponse, error) {
+func getCraBaseReportWithRetries(ctx context.Context) (*plaid.CraCheckReportBaseReportGetResponse, error) {
return pollWithRetries(func() (*plaid.CraCheckReportBaseReportGetResponse, error) {
request := plaid.NewCraCheckReportBaseReportGetRequest()
- request.SetUserToken(userToken)
+ // Use user_token if available, otherwise use user_id
+ if userToken != "" {
+ request.SetUserToken(userToken)
+ } else if userID != "" {
+ request.SetUserId(userID)
+ }
response, _, err := client.PlaidApi.CraCheckReportBaseReportGet(ctx).CraCheckReportBaseReportGetRequest(*request).Execute()
return &response, err
}, 1000, 20)
@@ -869,14 +965,19 @@ func getCraBaseReportWithRetries(ctx context.Context, userToken string) (*plaid.
// PDF w/ income insights: https://plaid.com/docs/check/api/#cracheck_reportpdfget
func getCraIncomeInsightsHandler(c *gin.Context) {
ctx := context.Background()
- getResponse, err := getCraIncomeInsightsWithRetries(ctx, userToken)
+ getResponse, err := getCraIncomeInsightsWithRetries(ctx)
if err != nil {
renderError(c, err)
return
}
pdfRequest := plaid.NewCraCheckReportPDFGetRequest()
- pdfRequest.SetUserToken(userToken)
+ // Use user_token if available, otherwise use user_id
+ if userToken != "" {
+ pdfRequest.SetUserToken(userToken)
+ } else if userID != "" {
+ pdfRequest.SetUserId(userID)
+ }
pdfRequest.SetAddOns([]plaid.CraPDFAddOns{plaid.CRAPDFADDONS_INCOME_INSIGHTS})
pdfResponse, _, err := client.PlaidApi.CraCheckReportPdfGet(ctx).CraCheckReportPDFGetRequest(*pdfRequest).Execute()
if err != nil {
@@ -900,10 +1001,15 @@ func getCraIncomeInsightsHandler(c *gin.Context) {
})
}
-func getCraIncomeInsightsWithRetries(ctx context.Context, userToken string) (*plaid.CraCheckReportIncomeInsightsGetResponse, error) {
+func getCraIncomeInsightsWithRetries(ctx context.Context) (*plaid.CraCheckReportIncomeInsightsGetResponse, error) {
return pollWithRetries(func() (*plaid.CraCheckReportIncomeInsightsGetResponse, error) {
request := plaid.NewCraCheckReportIncomeInsightsGetRequest()
- request.SetUserToken(userToken)
+ // Use user_token if available, otherwise use user_id
+ if userToken != "" {
+ request.SetUserToken(userToken)
+ } else if userID != "" {
+ request.SetUserId(userID)
+ }
response, _, err := client.PlaidApi.CraCheckReportIncomeInsightsGet(ctx).CraCheckReportIncomeInsightsGetRequest(*request).Execute()
return &response, err
}, 1000, 20)
@@ -913,7 +1019,7 @@ func getCraIncomeInsightsWithRetries(ctx context.Context, userToken string) (*pl
// https://plaid.com/docs/check/api/#cracheck_reportpartner_insightsget
func getCraPartnerInsightsHandler(c *gin.Context) {
ctx := context.Background()
- getResponse, err := getCraPartnerInsightsWithRetries(ctx, userToken)
+ getResponse, err := getCraPartnerInsightsWithRetries(ctx)
if err != nil {
renderError(c, err)
return
@@ -924,10 +1030,15 @@ func getCraPartnerInsightsHandler(c *gin.Context) {
})
}
-func getCraPartnerInsightsWithRetries(ctx context.Context, userToken string) (*plaid.CraCheckReportPartnerInsightsGetResponse, error) {
+func getCraPartnerInsightsWithRetries(ctx context.Context) (*plaid.CraCheckReportPartnerInsightsGetResponse, error) {
return pollWithRetries(func() (*plaid.CraCheckReportPartnerInsightsGetResponse, error) {
request := plaid.NewCraCheckReportPartnerInsightsGetRequest()
- request.SetUserToken(userToken)
+ // Use user_token if available, otherwise use user_id
+ if userToken != "" {
+ request.SetUserToken(userToken)
+ } else if userID != "" {
+ request.SetUserId(userID)
+ }
response, _, err := client.PlaidApi.CraCheckReportPartnerInsightsGet(ctx).CraCheckReportPartnerInsightsGetRequest(*request).Execute()
return &response, err
}, 1000, 20)
diff --git a/java/src/main/java/com/plaid/quickstart/QuickstartApplication.java b/java/src/main/java/com/plaid/quickstart/QuickstartApplication.java
index 31b85c034..0c2e4b802 100644
--- a/java/src/main/java/com/plaid/quickstart/QuickstartApplication.java
+++ b/java/src/main/java/com/plaid/quickstart/QuickstartApplication.java
@@ -39,6 +39,7 @@ public class QuickstartApplication extends Application
// persistent data store.
public static String accessToken;
public static String userToken;
+ public static String userId;
public static String itemID;
// The paymentId is only relevant for the UK Payment Initiation product.
// We store the paymentId in memory - in production, store it in a secure
diff --git a/java/src/main/java/com/plaid/quickstart/resources/CraResource.java b/java/src/main/java/com/plaid/quickstart/resources/CraResource.java
index c60638525..639f2a323 100644
--- a/java/src/main/java/com/plaid/quickstart/resources/CraResource.java
+++ b/java/src/main/java/com/plaid/quickstart/resources/CraResource.java
@@ -42,13 +42,23 @@ public CraResource(PlaidApi plaidClient) {
@Path("/get_base_report")
public Map getBaseReport() throws IOException {
CraCheckReportBaseReportGetRequest request = new CraCheckReportBaseReportGetRequest();
- request.setUserToken(QuickstartApplication.userToken);
+ // Use user_token if available, otherwise use user_id
+ if (QuickstartApplication.userToken != null) {
+ request.setUserToken(QuickstartApplication.userToken);
+ } else if (QuickstartApplication.userId != null) {
+ request.setUserId(QuickstartApplication.userId);
+ }
CraCheckReportBaseReportGetResponse baseReportResponse = pollWithRetries(
plaidClient.craCheckReportBaseReportGet(request)
).body();
CraCheckReportPDFGetRequest pdfRequest = new CraCheckReportPDFGetRequest();
- pdfRequest.setUserToken(QuickstartApplication.userToken);
+ // Use user_token if available, otherwise use user_id
+ if (QuickstartApplication.userToken != null) {
+ pdfRequest.setUserToken(QuickstartApplication.userToken);
+ } else if (QuickstartApplication.userId != null) {
+ pdfRequest.setUserId(QuickstartApplication.userId);
+ }
Response pdfResponse = plaidClient.craCheckReportPdfGet(pdfRequest).execute();
String pdfBase64 = Base64.getEncoder().encodeToString(pdfResponse.body().bytes());
@@ -68,13 +78,23 @@ public Map getBaseReport() throws IOException {
@Path("/get_income_insights")
public Map getIncomeInsigts() throws IOException {
CraCheckReportIncomeInsightsGetRequest request = new CraCheckReportIncomeInsightsGetRequest();
- request.setUserToken(QuickstartApplication.userToken);
+ // Use user_token if available, otherwise use user_id
+ if (QuickstartApplication.userToken != null) {
+ request.setUserToken(QuickstartApplication.userToken);
+ } else if (QuickstartApplication.userId != null) {
+ request.setUserId(QuickstartApplication.userId);
+ }
CraCheckReportIncomeInsightsGetResponse baseReportResponse = pollWithRetries(
plaidClient.craCheckReportIncomeInsightsGet(request)
).body();
CraCheckReportPDFGetRequest pdfRequest = new CraCheckReportPDFGetRequest();
- pdfRequest.setUserToken(QuickstartApplication.userToken);
+ // Use user_token if available, otherwise use user_id
+ if (QuickstartApplication.userToken != null) {
+ pdfRequest.setUserToken(QuickstartApplication.userToken);
+ } else if (QuickstartApplication.userId != null) {
+ pdfRequest.setUserId(QuickstartApplication.userId);
+ }
pdfRequest.addAddOnsItem(CraPDFAddOns.INCOME_INSIGHTS);
Response pdfResponse = plaidClient.craCheckReportPdfGet(pdfRequest).execute();
@@ -92,7 +112,12 @@ public Map getIncomeInsigts() throws IOException {
@Path("/get_partner_insights")
public CraCheckReportPartnerInsightsGetResponse getPartnerInsigts() throws IOException {
CraCheckReportPartnerInsightsGetRequest request = new CraCheckReportPartnerInsightsGetRequest();
- request.setUserToken(QuickstartApplication.userToken);
+ // Use user_token if available, otherwise use user_id
+ if (QuickstartApplication.userToken != null) {
+ request.setUserToken(QuickstartApplication.userToken);
+ } else if (QuickstartApplication.userId != null) {
+ request.setUserId(QuickstartApplication.userId);
+ }
return pollWithRetries(plaidClient.craCheckReportPartnerInsightsGet(request)).body();
}
@@ -104,7 +129,9 @@ public CraCheckReportPartnerInsightsGetResponse getPartnerInsigts() throws IOExc
// https://github.com/plaid/pattern
private Response pollWithRetries(Call requestCallback) throws IOException {
for (int i = 0; i <= 20; i++) {
- Response response = requestCallback.execute();
+ // Clone the call for each retry since Retrofit calls can only be executed once
+ Call call = i == 0 ? requestCallback : requestCallback.clone();
+ Response response = call.execute();
if (response.isSuccessful()) {
return response;
diff --git a/java/src/main/java/com/plaid/quickstart/resources/LinkTokenResource.java b/java/src/main/java/com/plaid/quickstart/resources/LinkTokenResource.java
index 541da4b7c..f285b7f1b 100644
--- a/java/src/main/java/com/plaid/quickstart/resources/LinkTokenResource.java
+++ b/java/src/main/java/com/plaid/quickstart/resources/LinkTokenResource.java
@@ -86,7 +86,15 @@ public LinkToken(String linkToken) {
}
if (plaidProducts.stream().anyMatch(product -> product.startsWith("cra_"))) {
- request.userToken(QuickstartApplication.userToken);
+ // Use user_token if available, otherwise use user_id
+ if (QuickstartApplication.userToken != null) {
+ request.userToken(QuickstartApplication.userToken);
+ // Keep user object when using user_token
+ } else if (QuickstartApplication.userId != null) {
+ request.userId(QuickstartApplication.userId);
+ // Remove user object when using user_id
+ request.user(null);
+ }
request.consumerReportPermissiblePurpose(ConsumerReportPermissiblePurpose.ACCOUNT_REVIEW_CREDIT);
LinkTokenCreateRequestCraOptions options = new LinkTokenCreateRequestCraOptions();
options.daysRequested(60);
diff --git a/java/src/main/java/com/plaid/quickstart/resources/UserTokenResource.java b/java/src/main/java/com/plaid/quickstart/resources/UserTokenResource.java
index c147d363e..c092d4c18 100644
--- a/java/src/main/java/com/plaid/quickstart/resources/UserTokenResource.java
+++ b/java/src/main/java/com/plaid/quickstart/resources/UserTokenResource.java
@@ -1,6 +1,11 @@
package com.plaid.quickstart.resources;
import com.plaid.client.model.AddressData;
+import com.plaid.client.model.ClientUserIdentity;
+import com.plaid.client.model.ClientUserIdentityAddress;
+import com.plaid.client.model.ClientUserIdentityEmail;
+import com.plaid.client.model.ClientUserIdentityName;
+import com.plaid.client.model.ClientUserIdentityPhoneNumber;
import com.plaid.client.model.ConsumerReportUserIdentity;
import com.plaid.client.model.UserCreateRequest;
import com.plaid.client.model.UserCreateResponse;
@@ -39,24 +44,97 @@ public UserCreateResponse createUserToken() throws IOException {
.clientUserId("user_" + UUID.randomUUID());
if (plaidProducts.stream().anyMatch(product -> product.startsWith("cra_"))) {
- AddressData addressData = new AddressData()
+ // Try with Identity field first (new-style)
+ ClientUserIdentityName name = new ClientUserIdentityName()
+ .givenName("Harry")
+ .familyName("Potter");
+
+ ClientUserIdentityPhoneNumber phoneNumber = new ClientUserIdentityPhoneNumber()
+ .data("+16174567890")
+ .primary(true);
+
+ ClientUserIdentityEmail email = new ClientUserIdentityEmail()
+ .data("harrypotter@example.com")
+ .primary(true);
+
+ ClientUserIdentityAddress address = new ClientUserIdentityAddress()
+ .street1("4 Privet Drive")
.city("New York")
.region("NY")
- .street("4 Privet Drive")
.postalCode("11111")
- .country("US");
- userCreateRequest.consumerReportUserIdentity(new ConsumerReportUserIdentity()
+ .country("US")
+ .primary(true);
+
+ ClientUserIdentity identity = new ClientUserIdentity()
+ .name(name)
.dateOfBirth(LocalDate.parse("1980-07-31"))
- .firstName("Harry")
- .lastName("Potter")
- .phoneNumbers(Arrays.asList("+16174567890"))
- .emails(List.of("harrypotter@example.com"))
- .primaryAddress(addressData));
+ .phoneNumbers(Arrays.asList(phoneNumber))
+ .emails(Arrays.asList(email))
+ .addresses(Arrays.asList(address));
+
+ userCreateRequest.identity(identity);
}
- Response userResponse = plaidClient.userCreate(userCreateRequest, null).execute();
- // Ideally, we would store this somewhere more persistent
- QuickstartApplication.userToken = userResponse.body().getUserToken();
- return userResponse.body();
+ try {
+ Response userResponse = plaidClient.userCreate(userCreateRequest, null).execute();
+
+ // Check if the response was successful
+ if (!userResponse.isSuccessful()) {
+ System.err.println("User create failed with code: " + userResponse.code());
+ System.err.println("Error body: " + userResponse.errorBody().string());
+ throw new IOException("User create failed: " + userResponse.code());
+ }
+
+ // Store both user_token and user_id
+ if (userResponse.body().getUserToken() != null) {
+ QuickstartApplication.userToken = userResponse.body().getUserToken();
+ }
+ if (userResponse.body().getUserId() != null) {
+ QuickstartApplication.userId = userResponse.body().getUserId();
+ }
+
+ return userResponse.body();
+ } catch (IOException e) {
+ // Retry with ConsumerReportUserIdentity (old-style) if Identity fails
+ if (plaidProducts.stream().anyMatch(product -> product.startsWith("cra_"))) {
+ UserCreateRequest retryRequest = new UserCreateRequest()
+ .clientUserId("user_" + UUID.randomUUID());
+
+ AddressData addressData = new AddressData()
+ .city("New York")
+ .region("NY")
+ .street("4 Privet Drive")
+ .postalCode("11111")
+ .country("US");
+
+ retryRequest.consumerReportUserIdentity(new ConsumerReportUserIdentity()
+ .dateOfBirth(LocalDate.parse("1980-07-31"))
+ .firstName("Harry")
+ .lastName("Potter")
+ .phoneNumbers(Arrays.asList("+16174567890"))
+ .emails(List.of("harrypotter@example.com"))
+ .primaryAddress(addressData));
+
+ Response userResponse = plaidClient.userCreate(retryRequest, null).execute();
+
+ // Check if the response was successful
+ if (!userResponse.isSuccessful()) {
+ System.err.println("User create (retry) failed with code: " + userResponse.code());
+ System.err.println("Error body: " + userResponse.errorBody().string());
+ throw new IOException("User create (retry) failed: " + userResponse.code());
+ }
+
+ // Store both user_token and user_id
+ if (userResponse.body().getUserToken() != null) {
+ QuickstartApplication.userToken = userResponse.body().getUserToken();
+ }
+ if (userResponse.body().getUserId() != null) {
+ QuickstartApplication.userId = userResponse.body().getUserId();
+ }
+
+ return userResponse.body();
+ }
+ throw e;
+ }
}
}
\ No newline at end of file
diff --git a/node/index.js b/node/index.js
index 75ddbc1c1..90dca9236 100644
--- a/node/index.js
+++ b/node/index.js
@@ -132,8 +132,7 @@ app.post('/api/create_link_token', function (request, response, next) {
// Use user_token if available, otherwise use user_id
if (USER_TOKEN) {
configs.user_token = USER_TOKEN;
- // Remove user object when using user_token
- delete configs.user;
+ // Keep user object when using user_token
} else if (USER_ID) {
configs.user_id = USER_ID;
// Remove user object when using user_id
@@ -789,14 +788,18 @@ const getCraBaseReportWithRetries = (
app.get('/api/cra/get_income_insights', async (req, res, next) => {
Promise.resolve()
.then(async function () {
- // Income endpoints always use user_token
- const getResponse = await getCheckInsightsWithRetries(client, USER_TOKEN)
+ // Use user_token if available, otherwise use user_id
+ const userIdentifier = USER_TOKEN || USER_ID;
+ const identifierKey = USER_TOKEN ? 'user_token' : 'user_id';
+
+ const getResponse = await getCheckInsightsWithRetries(client, userIdentifier, identifierKey)
prettyPrintResponse(getResponse);
- const pdfResponse = await client.craCheckReportPdfGet({
- user_token: USER_TOKEN,
- add_ons: ['cra_income_insights']
- }, {
+ const pdfRequest = {};
+ pdfRequest[identifierKey] = userIdentifier;
+ pdfRequest.add_ons = ['cra_income_insights'];
+
+ const pdfResponse = await client.craCheckReportPdfGet(pdfRequest, {
responseType: 'arraybuffer'
});
@@ -811,14 +814,13 @@ app.get('/api/cra/get_income_insights', async (req, res, next) => {
const getCheckInsightsWithRetries = (
plaidClient,
- userToken
+ userIdentifier,
+ identifierKey
) => pollWithRetries(
async () => {
- return await plaidClient.craCheckReportIncomeInsightsGet(
- {
- user_token: userToken
- }
- );
+ const request = {};
+ request[identifierKey] = userIdentifier;
+ return await plaidClient.craCheckReportIncomeInsightsGet(request);
}
);
diff --git a/python/server.py b/python/server.py
index fe8278533..79b8f75e6 100644
--- a/python/server.py
+++ b/python/server.py
@@ -25,6 +25,7 @@
from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser
from plaid.model.user_create_request import UserCreateRequest
from plaid.model.consumer_report_user_identity import ConsumerReportUserIdentity
+from plaid.model.client_user_identity import ClientUserIdentity
from plaid.model.asset_report_create_request import AssetReportCreateRequest
from plaid.model.asset_report_create_request_options import AssetReportCreateRequestOptions
from plaid.model.asset_report_user import AssetReportUser
@@ -128,6 +129,7 @@ def empty_to_none(field):
# We store the user_token in memory - in production, store it in a secure
# persistent data store.
user_token = None
+user_id = None
item_id = None
@@ -208,16 +210,32 @@ def create_link_token_for_payment():
@app.route('/api/create_link_token', methods=['POST'])
def create_link_token():
global user_token
+ global user_id
try:
- request = LinkTokenCreateRequest(
- products=products,
- client_name="Plaid Quickstart",
- country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)),
- language='en',
- user=LinkTokenCreateRequestUser(
- client_user_id=str(time.time())
+ # Build request based on whether we have user_token or user_id
+ cra_products = ["cra_base_report", "cra_income_insights", "cra_partner_insights"]
+ is_cra = any(product in cra_products for product in PLAID_PRODUCTS)
+
+ if is_cra and user_id and not user_token:
+ # For user_id, don't include user field
+ request = LinkTokenCreateRequest(
+ products=products,
+ client_name="Plaid Quickstart",
+ country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)),
+ language='en'
)
- )
+ else:
+ # For user_token or non-CRA products, include user field
+ request = LinkTokenCreateRequest(
+ products=products,
+ client_name="Plaid Quickstart",
+ country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)),
+ language='en',
+ user=LinkTokenCreateRequestUser(
+ client_user_id=str(time.time())
+ )
+ )
+
if PLAID_REDIRECT_URI!=None:
request['redirect_uri']=PLAID_REDIRECT_URI
if Products('statements') in products:
@@ -227,9 +245,12 @@ def create_link_token():
)
request['statements']=statements
- cra_products = ["cra_base_report", "cra_income_insights", "cra_partner_insights"]
- if any(product in cra_products for product in PLAID_PRODUCTS):
- request['user_token'] = user_token
+ if is_cra:
+ # Use user_token if available, otherwise use user_id
+ if user_token:
+ request['user_token'] = user_token
+ elif user_id:
+ request['user_id'] = user_id
request['consumer_report_permissible_purpose'] = ConsumerReportPermissiblePurpose('ACCOUNT_REVIEW_CREDIT')
request['cra_options'] = LinkTokenCreateRequestCraOptions(
days_requested=60
@@ -246,35 +267,83 @@ def create_link_token():
@app.route('/api/create_user_token', methods=['POST'])
def create_user_token():
global user_token
+ global user_id
try:
- consumer_report_user_identity = None
user_create_request = UserCreateRequest(
- # Typically this will be a user ID number from your application.
+ # Typically this will be a user ID number from your application.
client_user_id="user_" + str(uuid.uuid4())
)
cra_products = ["cra_base_report", "cra_income_insights", "cra_partner_insights"]
if any(product in cra_products for product in PLAID_PRODUCTS):
- consumer_report_user_identity = ConsumerReportUserIdentity(
- first_name="Harry",
- last_name="Potter",
+ # Try with identity first (new style)
+ identity = ClientUserIdentity(
+ name={
+ "given_name": "Harry",
+ "family_name": "Potter"
+ },
date_of_birth= date(1980, 7, 31),
- phone_numbers= ['+16174567890'],
- emails= ['harrypotter@example.com'],
- primary_address= {
+ phone_numbers= [{
+ "data": '+16174567890',
+ "primary": True
+ }],
+ emails= [{
+ "data": 'harrypotter@example.com',
+ "primary": True
+ }],
+ addresses= [{
+ "street_1": '4 Privet Drive',
"city": 'New York',
"region": 'NY',
- "street": '4 Privet Drive',
"postal_code": '11111',
- "country": 'US'
- }
+ "country": 'US',
+ "primary": True
+ }]
)
- user_create_request["consumer_report_user_identity"] = consumer_report_user_identity
+ user_create_request["identity"] = identity
user_response = client.user_create(user_create_request)
- user_token = user_response['user_token']
+ # Store both user_token and user_id
+ if 'user_token' in user_response:
+ user_token = user_response['user_token']
+ if 'user_id' in user_response:
+ user_id = user_response['user_id']
return jsonify(user_response.to_dict())
except plaid.ApiException as e:
+ # Retry with consumer_report_user_identity if identity fails
+ error_body = json.loads(e.body)
+ if (error_body.get('error_code') == 'INVALID_FIELD' and
+ any(product in cra_products for product in PLAID_PRODUCTS)):
+ try:
+ # Retry with consumer_report_user_identity (old style)
+ retry_request = UserCreateRequest(
+ client_user_id="user_" + str(uuid.uuid4())
+ )
+ consumer_report_user_identity = ConsumerReportUserIdentity(
+ first_name="Harry",
+ last_name="Potter",
+ date_of_birth= date(1980, 7, 31),
+ phone_numbers= ['+16174567890'],
+ emails= ['harrypotter@example.com'],
+ primary_address= {
+ "city": 'New York',
+ "region": 'NY',
+ "street": '4 Privet Drive',
+ "postal_code": '11111',
+ "country": 'US'
+ }
+ )
+ retry_request["consumer_report_user_identity"] = consumer_report_user_identity
+ user_response = client.user_create(retry_request)
+ # Store both user_token and user_id
+ if 'user_token' in user_response:
+ user_token = user_response['user_token']
+ if 'user_id' in user_response:
+ user_id = user_response['user_id']
+ return jsonify(user_response.to_dict())
+ except plaid.ApiException as retry_error:
+ print(retry_error)
+ return jsonify(json.loads(retry_error.body)), retry_error.status
print(e)
return jsonify(json.loads(e.body)), e.status
@@ -673,14 +742,22 @@ def item():
@app.route('/api/cra/get_base_report', methods=['GET'])
def cra_check_report():
try:
- get_response = poll_with_retries(lambda: client.cra_check_report_base_report_get(
- CraCheckReportBaseReportGetRequest(user_token=user_token, item_ids=[])
- ))
+ # Use user_token if available, otherwise use user_id
+ if user_token:
+ base_report_request = CraCheckReportBaseReportGetRequest(user_token=user_token, item_ids=[])
+ elif user_id:
+ base_report_request = CraCheckReportBaseReportGetRequest(user_id=user_id, item_ids=[])
+
+ get_response = poll_with_retries(lambda: client.cra_check_report_base_report_get(base_report_request))
pretty_print_response(get_response.to_dict())
- pdf_response = client.cra_check_report_pdf_get(
- CraCheckReportPDFGetRequest(user_token=user_token)
- )
+ if user_token:
+ pdf_request = CraCheckReportPDFGetRequest(user_token=user_token)
+ elif user_id:
+ pdf_request = CraCheckReportPDFGetRequest(user_id=user_id)
+
+ pdf_response = client.cra_check_report_pdf_get(pdf_request)
+
return jsonify({
'report': get_response.to_dict()['report'],
'pdf': base64.b64encode(pdf_response.read()).decode('utf-8')
@@ -695,14 +772,23 @@ def cra_check_report():
@app.route('/api/cra/get_income_insights', methods=['GET'])
def cra_income_insights():
try:
- get_response = poll_with_retries(lambda: client.cra_check_report_income_insights_get(
- CraCheckReportIncomeInsightsGetRequest(user_token=user_token))
- )
+ # Use user_token if available, otherwise use user_id
+ insights_request = {}
+ if user_token:
+ insights_request = CraCheckReportIncomeInsightsGetRequest(user_token=user_token)
+ elif user_id:
+ insights_request = CraCheckReportIncomeInsightsGetRequest(user_id=user_id)
+
+ get_response = poll_with_retries(lambda: client.cra_check_report_income_insights_get(insights_request))
pretty_print_response(get_response.to_dict())
- pdf_response = client.cra_check_report_pdf_get(
- CraCheckReportPDFGetRequest(user_token=user_token, add_ons=[CraPDFAddOns('cra_income_insights')]),
- )
+ pdf_request = {}
+ if user_token:
+ pdf_request = CraCheckReportPDFGetRequest(user_token=user_token, add_ons=[CraPDFAddOns('cra_income_insights')])
+ elif user_id:
+ pdf_request = CraCheckReportPDFGetRequest(user_id=user_id, add_ons=[CraPDFAddOns('cra_income_insights')])
+
+ pdf_response = client.cra_check_report_pdf_get(pdf_request)
return jsonify({
'report': get_response.to_dict()['report'],
@@ -717,9 +803,13 @@ def cra_income_insights():
@app.route('/api/cra/get_partner_insights', methods=['GET'])
def cra_partner_insights():
try:
- response = poll_with_retries(lambda: client.cra_check_report_partner_insights_get(
- CraCheckReportPartnerInsightsGetRequest(user_token=user_token)
- ))
+ # Use user_token if available, otherwise use user_id
+ if user_token:
+ partner_request = CraCheckReportPartnerInsightsGetRequest(user_token=user_token)
+ elif user_id:
+ partner_request = CraCheckReportPartnerInsightsGetRequest(user_id=user_id)
+
+ response = poll_with_retries(lambda: client.cra_check_report_partner_insights_get(partner_request))
pretty_print_response(response.to_dict())
return jsonify(response.to_dict())
diff --git a/ruby/app.rb b/ruby/app.rb
index ae0216f29..408908039 100644
--- a/ruby/app.rb
+++ b/ruby/app.rb
@@ -28,10 +28,11 @@
client = Plaid::PlaidApi.new(api_client)
products = ENV['PLAID_PRODUCTS'].split(',')
-# We store the access_token and user_token in memory - in production, store it in a secure
+# We store the access_token, user_token, and user_id in memory - in production, store it in a secure
# persistent data store.
access_token = nil
user_token = nil
+user_id = nil
# The payment_id is only relevant for the UK Payment Initiation product.
# We store the payment_token in memory - in production, store it in a secure
# persistent data store.
@@ -506,9 +507,16 @@
link_token_create_request.cra_options = Plaid::LinkTokenCreateRequestCraOptions.new(
days_requested: 60
)
- link_token_create_request.user_token=user_token
- link_token_create_request.consumer_report_permissible_purpose =Plaid::ConsumerReportPermissiblePurpose::ACCOUNT_REVIEW_CREDIT
-
+ # Use user_token if available, otherwise use user_id
+ if user_token
+ link_token_create_request.user_token = user_token
+ # Keep user object when using user_token
+ elsif user_id
+ link_token_create_request.user_id = user_id
+ # Remove user object when using user_id
+ link_token_create_request.user = nil
+ end
+ link_token_create_request.consumer_report_permissible_purpose = Plaid::ConsumerReportPermissiblePurpose::ACCOUNT_REVIEW_CREDIT
end
link_response = client.link_token_create(link_token_create_request)
pretty_print_response(link_response.to_hash)
@@ -532,26 +540,69 @@
}
if products.any? { |product| product.start_with?("cra_") }
- request_data[:consumer_report_user_identity] = {
- first_name: 'Harry',
- last_name: 'Potter',
+ # Try with Identity field first (new-style)
+ request_data[:identity] = {
+ name: {
+ given_name: 'Harry',
+ family_name: 'Potter'
+ },
date_of_birth: '1980-07-31',
- phone_numbers: ['+16174567890'],
- emails: ['harrypotter@example.com'],
- primary_address: {
+ phone_numbers: [{
+ data: '+16174567890',
+ primary: true
+ }],
+ emails: [{
+ data: 'harrypotter@example.com',
+ primary: true
+ }],
+ addresses: [{
+ street_1: '4 Privet Drive',
city: 'New York',
region: 'NY',
- street: '4 Privet Drive',
postal_code: '11111',
- country: 'US'
- }
+ country: 'US',
+ primary: true
+ }]
}
end
- user = client.user_create(Plaid::UserCreateRequest.new(request_data))
- user_token = user.user_token
- content_type :json
- user.to_hash.to_json
+ begin
+ user = client.user_create(Plaid::UserCreateRequest.new(request_data))
+ # Store both user_token and user_id
+ user_token = user.user_token if user.user_token
+ user_id = user.user_id if user.user_id
+ content_type :json
+ user.to_hash.to_json
+ rescue Plaid::ApiError => e
+ # Retry with ConsumerReportUserIdentity (old-style) if Identity fails
+ if products.any? { |product| product.start_with?("cra_") }
+ retry_request_data = {
+ client_user_id: 'user_' + SecureRandom.uuid,
+ consumer_report_user_identity: {
+ first_name: 'Harry',
+ last_name: 'Potter',
+ date_of_birth: '1980-07-31',
+ phone_numbers: ['+16174567890'],
+ emails: ['harrypotter@example.com'],
+ primary_address: {
+ city: 'New York',
+ region: 'NY',
+ street: '4 Privet Drive',
+ postal_code: '11111',
+ country: 'US'
+ }
+ }
+ }
+ user = client.user_create(Plaid::UserCreateRequest.new(retry_request_data))
+ # Store both user_token and user_id
+ user_token = user.user_token if user.user_token
+ user_id = user.user_id if user.user_id
+ content_type :json
+ user.to_hash.to_json
+ else
+ raise e
+ end
+ end
rescue Plaid::ApiError => e
error_response = format_error(e)
pretty_print_response(error_response)
@@ -665,11 +716,18 @@ def nil_if_empty_envvar(field)
# PDF: https://plaid.com/docs/check/api/#cracheck_reportpdfget
get '/api/cra/get_base_report' do
begin
- get_response = get_cra_base_report_with_retries(client, user_token)
+ get_response = get_cra_base_report_with_retries(client, user_token, user_id)
pretty_print_response(get_response.to_hash)
+ # Use user_token if available, otherwise use user_id
+ pdf_params = {}
+ if user_token
+ pdf_params[:user_token] = user_token
+ elsif user_id
+ pdf_params[:user_id] = user_id
+ end
pdf_response = client.cra_check_report_pdf_get(
- Plaid::CraCheckReportPDFGetRequest.new({ user_token: user_token })
+ Plaid::CraCheckReportPDFGetRequest.new(pdf_params)
)
content_type :json
@@ -685,10 +743,17 @@ def nil_if_empty_envvar(field)
end
end
-def get_cra_base_report_with_retries(plaid_client, user_token)
+def get_cra_base_report_with_retries(plaid_client, user_token, user_id)
poll_with_retries do
+ # Use user_token if available, otherwise use user_id
+ params = {}
+ if user_token
+ params[:user_token] = user_token
+ elsif user_id
+ params[:user_id] = user_id
+ end
plaid_client.cra_check_report_base_report_get(
- Plaid::CraCheckReportBaseReportGetRequest.new({ user_token: user_token })
+ Plaid::CraCheckReportBaseReportGetRequest.new(params)
)
end
end
@@ -698,11 +763,18 @@ def get_cra_base_report_with_retries(plaid_client, user_token)
# PDF w/ income insights: https://plaid.com/docs/check/api/#cracheck_reportpdfget
get '/api/cra/get_income_insights' do
begin
- get_response = get_income_insights_with_retries(client, user_token)
+ get_response = get_income_insights_with_retries(client, user_token, user_id)
pretty_print_response(get_response.to_hash)
+ # Use user_token if available, otherwise use user_id
+ pdf_params = { add_ons: [Plaid::CraPDFAddOns::INCOME_INSIGHTS] }
+ if user_token
+ pdf_params[:user_token] = user_token
+ elsif user_id
+ pdf_params[:user_id] = user_id
+ end
pdf_response = client.cra_check_report_pdf_get(
- Plaid::CraCheckReportPDFGetRequest.new({ user_token: user_token, add_ons: [Plaid::CraPDFAddOns::INCOME_INSIGHTS] })
+ Plaid::CraCheckReportPDFGetRequest.new(pdf_params)
)
content_type :json
@@ -718,10 +790,17 @@ def get_cra_base_report_with_retries(plaid_client, user_token)
end
end
-def get_income_insights_with_retries(plaid_client, user_token)
+def get_income_insights_with_retries(plaid_client, user_token, user_id)
poll_with_retries do
+ # Use user_token if available, otherwise use user_id
+ params = {}
+ if user_token
+ params[:user_token] = user_token
+ elsif user_id
+ params[:user_id] = user_id
+ end
plaid_client.cra_check_report_income_insights_get(
- Plaid::CraCheckReportIncomeInsightsGetRequest.new({ user_token: user_token })
+ Plaid::CraCheckReportIncomeInsightsGetRequest.new(params)
)
end
end
@@ -730,7 +809,7 @@ def get_income_insights_with_retries(plaid_client, user_token)
# https://plaid.com/docs/check/api/#cracheck_reportpartner_insightsget
get '/api/cra/get_partner_insights' do
begin
- response = get_check_partner_insights_with_retries(client, user_token)
+ response = get_check_partner_insights_with_retries(client, user_token, user_id)
pretty_print_response(response.to_hash)
content_type :json
@@ -743,10 +822,17 @@ def get_income_insights_with_retries(plaid_client, user_token)
end
end
-def get_check_partner_insights_with_retries(plaid_client, user_token)
+def get_check_partner_insights_with_retries(plaid_client, user_token, user_id)
poll_with_retries do
+ # Use user_token if available, otherwise use user_id
+ params = {}
+ if user_token
+ params[:user_token] = user_token
+ elsif user_id
+ params[:user_id] = user_id
+ end
plaid_client.cra_check_report_partner_insights_get(
- Plaid::CraCheckReportPartnerInsightsGetRequest.new({ user_token: user_token })
+ Plaid::CraCheckReportPartnerInsightsGetRequest.new(params)
)
end
end
From 1887bd0d466ca175f9cf8b320f1412dd98bd02d3 Mon Sep 17 00:00:00 2001
From: Alex Hoffer
Date: Thu, 18 Dec 2025 18:04:39 -0500
Subject: [PATCH 03/11] Improve error handling and fix client_user_id reuse
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add null checks for Java error body reading to prevent NullPointerException
- Fix Java and Ruby to only retry on INVALID_FIELD errors, not all errors
- Fix duplicate client_user_id generation: now reuses same ID on retry across all backends
- Remove superfluous comments for cleaner, self-documenting code
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5
---
.claude/settings.local.json | 52 ++++++++++
frontend/.claude/settings.local.json | 35 +++++++
go/.claude/settings.local.json | 23 +++++
go/server | Bin 0 -> 23081490 bytes
go/server.go | 7 +-
java/.claude/settings.local.json | 26 +++++
.../resources/UserTokenResource.java | 89 ++++++++++--------
python/server.py | 7 +-
ruby/app.rb | 11 ++-
ruby/start | 3 +
10 files changed, 202 insertions(+), 51 deletions(-)
create mode 100644 .claude/settings.local.json
create mode 100644 frontend/.claude/settings.local.json
create mode 100644 go/.claude/settings.local.json
create mode 100755 go/server
create mode 100644 java/.claude/settings.local.json
create mode 100755 ruby/start
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 000000000..ffded8a6b
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,52 @@
+{
+ "permissions": {
+ "allow": [
+ "WebFetch(domain:plaid.com)",
+ "Bash(sed:*)",
+ "WebFetch(domain:github.com)",
+ "WebFetch(domain:raw.githubusercontent.com)",
+ "Bash(gh repo view:*)",
+ "Bash(npm audit:*)",
+ "Bash(pip-audit:*)",
+ "Bash(/dev/null)",
+ "Bash(bundle audit:*)",
+ "Bash(npm update:*)",
+ "Bash(go get:*)",
+ "Bash(go mod tidy:*)",
+ "Bash(npm install)",
+ "Bash(test:*)",
+ "Bash(cat:*)",
+ "Bash(npm view:*)",
+ "Bash(docker-compose config:*)",
+ "Bash(docker-compose build:*)",
+ "WebSearch",
+ "WebFetch(domain:pypi.org)",
+ "WebFetch(domain:central.sonatype.com)",
+ "WebFetch(domain:pkg.go.dev)",
+ "Bash(npm outdated:*)",
+ "Bash(rm:*)",
+ "Bash(npm install:*)",
+ "Bash(npm run build:*)",
+ "Bash(npx tsc:*)",
+ "Bash(docker-compose:*)",
+ "Bash(docker ps:*)",
+ "Bash(docker logs:*)",
+ "Bash(git log:*)",
+ "WebFetch(domain:www.npmjs.com)",
+ "WebFetch(domain:rubygems.org)",
+ "Bash(go list:*)",
+ "WebFetch(domain:www.dropwizard.io)",
+ "Bash(mvn clean compile:*)",
+ "Bash(find:*)",
+ "WebFetch(domain:mvnrepository.com)",
+ "WebFetch(domain:square.github.io)",
+ "Bash(mvn compile:*)",
+ "Bash(go build:*)",
+ "Bash(npm start)",
+ "Bash(git add:*)",
+ "Bash(git commit:*)"
+ ],
+ "deny": [],
+ "ask": []
+ }
+}
diff --git a/frontend/.claude/settings.local.json b/frontend/.claude/settings.local.json
new file mode 100644
index 000000000..3e117fb84
--- /dev/null
+++ b/frontend/.claude/settings.local.json
@@ -0,0 +1,35 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(kill:*)",
+ "Bash(npm start)",
+ "Bash(curl:*)",
+ "Bash(for i in {1..3})",
+ "Bash(do echo \"=== User $i ===\")",
+ "Bash(done)",
+ "Bash(xargs kill:*)",
+ "Bash(for i in {1..5})",
+ "Bash(do echo \"=== Test User $i ===\")",
+ "Bash(python3:*)",
+ "Bash(source venv/bin/activate)",
+ "Bash(python server.py:*)",
+ "Bash(cd:*)",
+ "Bash(find:*)",
+ "Bash(go build:*)",
+ "Bash(./server)",
+ "Bash(mvn clean package:*)",
+ "Bash(java -jar target/quickstart-1.0-SNAPSHOT.jar server:*)",
+ "Bash(PLAID_CLIENT_ID=58b9a231bdc6a453f37c338f PLAID_SECRET=716d2c2f2cd7f32bfa420810cd204a PLAID_PRODUCTS=cra_base_report,cra_income_insights,cra_partner_insights PLAID_COUNTRY_CODES=US PLAID_ENV=sandbox PLAID_REDIRECT_URI= SIGNAL_RULESET_KEY=default-3 java:*)",
+ "Bash(./start.sh)",
+ "Bash(lsof:*)",
+ "Bash(cat:*)",
+ "Bash(ruby app.rb:*)",
+ "Bash(git add:*)",
+ "Bash(git commit:*)",
+ "Bash(git push:*)",
+ "Bash(gh pr view:*)",
+ "Bash(gh repo view:*)",
+ "WebFetch(domain:github.com)"
+ ]
+ }
+}
diff --git a/go/.claude/settings.local.json b/go/.claude/settings.local.json
new file mode 100644
index 000000000..8385e38a9
--- /dev/null
+++ b/go/.claude/settings.local.json
@@ -0,0 +1,23 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(go build:*)",
+ "Bash(go doc:*)",
+ "Read(//Users/ahoffer/quickstart/java/src/main/java/com/plaid/quickstart/**)",
+ "Read(//Users/ahoffer/quickstart/java/**)",
+ "Bash(mvn clean compile:*)",
+ "Read(//Users/ahoffer/quickstart/node/**)",
+ "Read(//Users/ahoffer/quickstart/python/**)",
+ "Read(//Users/ahoffer/quickstart/ruby/**)",
+ "Bash(ruby -e \"require ''bundler/setup''; require ''plaid''; puts Plaid::CraPDFAddOns.constants\")",
+ "Bash(/usr/libexec/java_home:*)",
+ "Bash(java -version:*)",
+ "Read(//Library/Java/JavaVirtualMachines/**)",
+ "Bash(brew list:*)",
+ "Read(//opt/homebrew/Cellar/openjdk/25.0.1/**)",
+ "Bash(export JAVA_HOME=/opt/homebrew/Cellar/openjdk/25.0.1/libexec/openjdk.jdk/Contents/Home)"
+ ],
+ "deny": [],
+ "ask": []
+ }
+}
diff --git a/go/server b/go/server
new file mode 100755
index 0000000000000000000000000000000000000000..5b7ceed550573c535ea28d80b627994d95932830
GIT binary patch
literal 23081490
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