diff --git a/.env.example b/.env.example
index bd020eb7..8e48b421 100644
--- a/.env.example
+++ b/.env.example
@@ -1,24 +1,32 @@
-# Get your Plaid API keys from the dashboard: https://dashboard.plaid.com/account/keys
+# Get your Plaid API keys from the dashboard: https://dashboard.plaid.com/team/keys
PLAID_CLIENT_ID=
PLAID_SECRET=
+
# Use 'sandbox' to test with fake credentials in Plaid's Sandbox environment
# Use 'development' to test with real credentials while developing
# Use 'production' to go live with real users
PLAID_ENV=sandbox
+
# PLAID_PRODUCTS is a comma-separated list of products to use when
# initializing Link, e.g. PLAID_PRODUCTS=auth,transactions.
# see https://plaid.com/docs/api/tokens/#link-token-create-request-products for a complete list.
# Only institutions that support ALL listed products will be displayed in Link.
# If you don't see the institution you want in Link, remove any products you aren't using.
-# Important: When moving to Production, make sure to update this list with only the products
+# Important:
+# When moving to Production, make sure to update this list with only the products
# you plan to use. Otherwise, you may be billed for unneeded products.
-# NOTE: Income_verification has to be used seperately from all other products due to the specific
-# flow.
+# NOTE:
+# - 'income_verification' has to be used separately from all other products due to the specific flow.
+# - 'payment_initiation' has to be used separately from all other products due to the specific flow.
PLAID_PRODUCTS=auth,transactions
+
# PLAID_COUNTRY_CODES is a comma-separated list of countries to use when
# initializing Link, e.g. PLAID_COUNTRY_CODES=US,CA.
-# see https://plaid.com/docs/api/tokens/#link-token-create-request-country-codes for a complete list
+# Institutions from all listed countries will be shown. If Link is launched with multiple country codes,
+# only products that you are enabled for in all countries will be used by Link.
+# See https://plaid.com/docs/api/tokens/#link-token-create-request-country-codes for a complete list
PLAID_COUNTRY_CODES=US,CA
+
# Only required for OAuth:
# For sandbox, set PLAID_REDIRECT_URI to 'http://localhost:3000/'
# The OAuth redirect flow requires an endpoint on the developer's website
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 4eaa98df..2984193e 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -8,7 +8,7 @@ import Context from "./Context";
import styles from "./App.module.scss";
const App = () => {
- const { linkSuccess, isItemAccess, dispatch } = useContext(Context);
+ const { linkSuccess, isItemAccess, isPaymentInitiation, dispatch } = useContext(Context);
const getInfo = useCallback(async () => {
const response = await fetch("/api/info", { method: "POST" });
@@ -24,14 +24,16 @@ const App = () => {
type: "SET_STATE",
state: {
products: data.products,
+ isPaymentInitiation: paymentInitiation,
},
});
return { paymentInitiation };
}, [dispatch]);
const generateToken = useCallback(
- async (paymentInitiation) => {
- const path = paymentInitiation
+ async (isPaymentInitiation) => {
+ // Link tokens for 'payment_initiation' use a different creation flow in your backend.
+ const path = isPaymentInitiation
? "/api/create_link_token_for_payment"
: "/api/create_link_token";
const response = await fetch(path, {
@@ -55,7 +57,8 @@ const App = () => {
}
dispatch({ type: "SET_STATE", state: { linkToken: data.link_token } });
}
- localStorage.setItem("link_token", data.link_token); //to use later for Oauth
+ // Save the link_token to be used later in the Oauth flow.
+ localStorage.setItem("link_token", data.link_token);
},
[dispatch]
);
@@ -83,10 +86,17 @@ const App = () => {
- {linkSuccess && isItemAccess && (
+ {linkSuccess && (
<>
-
-
+ {isPaymentInitiation && (
+
+ )}
+ {isItemAccess && (
+ <>
+
+
+ >
+ )}
>
)}
diff --git a/frontend/src/Components/Headers/index.tsx b/frontend/src/Components/Headers/index.tsx
index 1d87f9a7..ea8419c4 100644
--- a/frontend/src/Components/Headers/index.tsx
+++ b/frontend/src/Components/Headers/index.tsx
@@ -17,6 +17,7 @@ const Header = () => {
isItemAccess,
backend,
linkTokenError,
+ isPaymentInitiation,
} = useContext(Context);
return (
@@ -90,40 +91,64 @@ const Header = () => {
>
) : (
<>
- {isItemAccess ? (
+ {isPaymentInitiation ? (
+ <>
- Congrats! By linking an account, you have created an{" "}
-
- Item
-
- .
-
- ) : (
-
-
- Unable to create an item. Please check your backend server
+ Congrats! Your payment is now confirmed.
+
+
+ You can see information of all your payments in the{' '}
+
+ Payments Dashboard
+
+ .
- )}
-
-
- item_id
- {itemId}
-
-
-
- access_token
- {accessToken}
-
-
- {isItemAccess && (
- Now that you have an access_token, you can make all of the
- following requests:
+ Now that the 'payment_id' stored in your server, you can use it to access the payment information:
+ >
+ ) : /* If not using the payment_initiation product, show the item_id and access_token information */ (
+ <>
+ {isItemAccess ? (
+
+ Congrats! By linking an account, you have created an{" "}
+
+ Item
+
+ .
+
+ ) : (
+
+
+ Unable to create an item. Please check your backend server
+
+
+ )}
+
+
+ item_id
+ {itemId}
+
+
+
+ access_token
+ {accessToken}
+
+
+ {isItemAccess && (
+
+ Now that you have an access_token, you can make all of the
+ following requests:
+
+ )}
+ >
)}
>
)}
diff --git a/frontend/src/Components/Link/index.tsx b/frontend/src/Components/Link/index.tsx
index e4d6f00b..f39473b5 100644
--- a/frontend/src/Components/Link/index.tsx
+++ b/frontend/src/Components/Link/index.tsx
@@ -3,14 +3,15 @@ import { usePlaidLink } from "react-plaid-link";
import Button from "plaid-threads/Button";
import Context from "../../Context";
+import {Products} from "plaid";
const Link = () => {
- const { linkToken, dispatch } = useContext(Context);
+ const { linkToken, isPaymentInitiation, dispatch } = useContext(Context);
const onSuccess = React.useCallback(
(public_token: string) => {
- // send public_token to server
- const setToken = async () => {
+ // If the access_token is needed, send public_token to server
+ const exchangePublicTokenForAccessToken = async () => {
const response = await fetch("/api/set_access_token", {
method: "POST",
headers: {
@@ -39,7 +40,14 @@ const Link = () => {
},
});
};
- setToken();
+
+ // 'payment_initiation' products do not require the public_token to be exchanged for an access_token.
+ if (isPaymentInitiation){
+ dispatch({ type: "SET_STATE", state: { isItemAccess: false } });
+ } else {
+ exchangePublicTokenForAccessToken();
+ }
+
dispatch({ type: "SET_STATE", state: { linkSuccess: true } });
window.history.pushState("", "", "/");
},
diff --git a/frontend/src/Components/ProductTypes/Products.tsx b/frontend/src/Components/ProductTypes/Products.tsx
index bec5d920..37c7201b 100644
--- a/frontend/src/Components/ProductTypes/Products.tsx
+++ b/frontend/src/Components/ProductTypes/Products.tsx
@@ -42,31 +42,37 @@ const Products = () => {
transformData={transformPaymentData}
/>
)}
-
-
-
+ {products.includes("auth") && (
+
+ )}
+ {products.includes("transactions") && (
+
+ )}
+ {products.includes("identity") && (
+
+ )}
{products.includes("assets") && (
{
transformData={transformAssetsData}
/>
)}
-
+ transformData={transformBalanceData}
+ />
+ )}
{products.includes("investments") && (
<>
plaidProd
LinkTokenCreateRequestPaymentInitiation paymentInitiation = new LinkTokenCreateRequestPaymentInitiation()
.paymentId(paymentId);
+ // This should correspond to a unique id for the current user.
+ // Typically, this will be a user ID number from your application.
+ // Personally identifiable information, such as an email address or phone number, should not be used here.
String clientUserId = Long.toString((new Date()).getTime());
LinkTokenCreateRequestUser user = new LinkTokenCreateRequestUser()
.clientUserId(clientUserId);
@@ -89,7 +92,9 @@ public LinkTokenWithPaymentResource(PlaidApi plaidClient, List plaidProd
LinkTokenCreateRequest request = new LinkTokenCreateRequest()
.user(user)
.clientName("Quickstart Client")
+ // The 'payment_initiation' product has to be the only element in the 'products' list.
.products(Arrays.asList(Products.PAYMENT_INITIATION))
+ // Institutions from all listed countries will be shown.
.countryCodes(Arrays.asList(CountryCode.GB))
.language("en")
.redirectUri(this.redirectUri)
diff --git a/node/index.js b/node/index.js
index 093138d5..ed9f0d1a 100644
--- a/node/index.js
+++ b/node/index.js
@@ -2,8 +2,9 @@
// read env vars from .env file
require('dotenv').config();
-const { Configuration, PlaidApi, PlaidEnvironments } = require('plaid');
+const { Configuration, PlaidApi, Products, PlaidEnvironments} = require('plaid');
const util = require('util');
+const { v4: uuidv4 } = require('uuid');
const express = require('express');
const bodyParser = require('body-parser');
const moment = require('moment');
@@ -17,7 +18,7 @@ const PLAID_ENV = process.env.PLAID_ENV || 'sandbox';
// PLAID_PRODUCTS is a comma-separated list of products to use when initializing
// Link. Note that this list must contain 'assets' in order for the app to be
// able to create and retrieve asset reports.
-const PLAID_PRODUCTS = (process.env.PLAID_PRODUCTS || 'transactions').split(
+const PLAID_PRODUCTS = (process.env.PLAID_PRODUCTS || Products.Transactions).split(
',',
);
@@ -45,9 +46,9 @@ const PLAID_ANDROID_PACKAGE_NAME = process.env.PLAID_ANDROID_PACKAGE_NAME || '';
let ACCESS_TOKEN = null;
let PUBLIC_TOKEN = null;
let ITEM_ID = null;
-// The payment_id is only relevant for the UK Payment Initiation product.
+// The payment_id is only relevant for the UK/EU Payment Initiation product.
// We store the payment_id in memory - in production, store it in a secure
-// persistent data store
+// persistent data store along with the Payment metadata, such as userId .
let PAYMENT_ID = null;
// The transfer_id is only relevant for Transfer ACH product.
// We store the transfer_id in memory - in production, store it in a secure
@@ -117,8 +118,11 @@ app.post('/api/create_link_token', function (request, response, next) {
.catch(next);
});
-// Create a link token with configs which we can then use to initialize Plaid Link client-side.
-// See https://plaid.com/docs/#payment-initiation-create-link-token-request
+// Create a link token with configs which we can then use to initialize Plaid Link client-side
+// for a 'payment-initiation' flow.
+// See:
+// - https://plaid.com/docs/payment-initiation/
+// - https://plaid.com/docs/#payment-initiation-create-link-token-request
app.post(
'/api/create_link_token_for_payment',
function (request, response, next) {
@@ -149,16 +153,24 @@ app.post(
});
prettyPrintResponse(createPaymentResponse);
const paymentId = createPaymentResponse.data.payment_id;
+
+ // We store the payment_id in memory for demo purposes - in production, store it in a secure
+ // persistent data store along with the Payment metadata, such as userId.
PAYMENT_ID = paymentId;
+
const configs = {
+ client_name: 'Plaid Quickstart',
user: {
// This should correspond to a unique id for the current user.
- client_user_id: 'user-id',
+ // Typically, this will be a user ID number from your application.
+ // Personally identifiable information, such as an email address or phone number, should not be used here.
+ client_user_id: uuidv4(),
},
- client_name: 'Plaid Quickstart',
- products: PLAID_PRODUCTS,
+ // Institutions from all listed countries will be shown.
country_codes: PLAID_COUNTRY_CODES,
language: 'en',
+ // The 'payment_initiation' product has to be the only element in the 'products' list.
+ products: [Products.PaymentInitiation],
payment_initiation: {
payment_id: paymentId,
},
@@ -187,10 +199,11 @@ app.post('/api/set_access_token', function (request, response, next) {
prettyPrintResponse(tokenResponse);
ACCESS_TOKEN = tokenResponse.data.access_token;
ITEM_ID = tokenResponse.data.item_id;
- if (PLAID_PRODUCTS.includes('transfer')) {
+ if (PLAID_PRODUCTS.includes(Products.Transfer)) {
TRANSFER_ID = await authorizeAndCreateTransfer(ACCESS_TOKEN);
}
response.json({
+ // the 'access_token' is a private token, DO NOT pass this token to the frontend in your production environment
access_token: ACCESS_TOKEN,
item_id: ITEM_ID,
error: null,
@@ -345,7 +358,7 @@ app.get('/api/item', function (request, response, next) {
// Also pull information about the institution
const configs = {
institution_id: itemResponse.data.item.institution_id,
- country_codes: ['US'],
+ country_codes: PLAID_COUNTRY_CODES,
};
const instResponse = await client.institutionsGetById(configs);
prettyPrintResponse(itemResponse);
@@ -443,7 +456,7 @@ app.get('/api/transfer', function (request, response, next) {
.catch(next);
});
-// This functionality is only relevant for the UK Payment Initiation product.
+// This functionality is only relevant for the UK/EU Payment Initiation product.
// Retrieve Payment for a specified Payment ID
app.get('/api/payment', function (request, response, next) {
Promise.resolve()
diff --git a/node/package-lock.json b/node/package-lock.json
index 13113e0a..775a9dee 100644
--- a/node/package-lock.json
+++ b/node/package-lock.json
@@ -1211,6 +1211,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
+ "uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
diff --git a/node/package.json b/node/package.json
index fd7906fd..585db933 100644
--- a/node/package.json
+++ b/node/package.json
@@ -19,6 +19,7 @@
"express": "4.16.x",
"moment": "2.22.x",
"nodemon": "^2.0.5",
- "plaid": "^9.9.0"
+ "plaid": "^9.9.0",
+ "uuid": "^9.0.0"
}
}
diff --git a/python/server.py b/python/server.py
index 51187e72..dd20bbe3 100644
--- a/python/server.py
+++ b/python/server.py
@@ -175,13 +175,22 @@ def create_link_token_for_payment():
request
)
pretty_print_response(response.to_dict())
+
+ # We store the payment_id in memory for demo purposes - in production, store it in a secure
+ # persistent data store along with the Payment metadata, such as userId.
payment_id = response['payment_id']
+
linkRequest = LinkTokenCreateRequest(
+ # The 'payment_initiation' product has to be the only element in the 'products' list.
products=[Products('payment_initiation')],
client_name='Plaid Test',
+ # Institutions from all listed countries will be shown.
country_codes=list(map(lambda x: CountryCode(x), PLAID_COUNTRY_CODES)),
language='en',
user=LinkTokenCreateRequestUser(
+ # This should correspond to a unique id for the current user.
+ # Typically, this will be a user ID number from your application.
+ # Personally identifiable information, such as an email address or phone number, should not be used here.
client_user_id=str(time.time())
),
payment_initiation=LinkTokenCreateRequestPaymentInitiation(
diff --git a/ruby/app.rb b/ruby/app.rb
index 1f714653..741be985 100644
--- a/ruby/app.rb
+++ b/ruby/app.rb
@@ -398,10 +398,13 @@ def nil_if_empty_envvar(field)
end
end
-# This functionality is only relevant for the UK Payment Initiation product.
+# This functionality is only relevant for the UK/EU Payment Initiation product.
# Sets the payment token in memory on the server side. We generate a new
# payment token so that the developer is not required to supply one.
# This makes the quickstart easier to use.
+# See:
+# - https://plaid.com/docs/payment-initiation/
+# - https://plaid.com/docs/#payment-initiation-create-link-token-request
post '/api/create_link_token_for_payment' do
begin
payment_initiation_recipient_create_request = Plaid::PaymentInitiationRecipientCreateRequest.new(
@@ -451,12 +454,24 @@ def nil_if_empty_envvar(field)
link_token_create_request = Plaid::LinkTokenCreateRequest.new(
{
- user: { client_user_id: 'user-id' },
- client_name: 'Plaid Quickstart',
- products: ENV['PLAID_PRODUCTS'].split(','),
+ client_name: 'Plaid Quickstart',
+ user: {
+ # This should correspond to a unique id for the current user.
+ # Typically, this will be a user ID number from your application.
+ # Personally identifiable information, such as an email address or phone number, should not be used here.
+ client_user_id: 'user-id'
+ },
+
+ # Institutions from all listed countries will be shown.
country_codes: ENV['PLAID_COUNTRY_CODES'].split(','),
language: 'en',
- payment_initiation: { payment_id: payment_id },
+
+ # The 'payment_initiation' product has to be the only element in the 'products' list.
+ products: ['payment_initiation'],
+
+ payment_initiation: {
+ payment_id: payment_id
+ },
redirect_uri: nil_if_empty_envvar('PLAID_REDIRECT_URI')
}
)