Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
Expand Down
24 changes: 17 additions & 7 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Expand All @@ -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, {
Expand All @@ -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]
);
Expand Down Expand Up @@ -83,10 +86,17 @@ const App = () => {
<div className={styles.App}>
<div className={styles.container}>
<Header />
{linkSuccess && isItemAccess && (
{linkSuccess && (
<>
<Products />
<Items />
{isPaymentInitiation && (
<Products />
)}
{isItemAccess && (
<>
<Products />
<Items />
</>
)}
</>
)}
</div>
Expand Down
83 changes: 54 additions & 29 deletions frontend/src/Components/Headers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const Header = () => {
isItemAccess,
backend,
linkTokenError,
isPaymentInitiation,
} = useContext(Context);

return (
Expand Down Expand Up @@ -90,40 +91,64 @@ const Header = () => {
</>
) : (
<>
{isItemAccess ? (
{isPaymentInitiation ? (
<>
<h4 className={styles.subtitle}>
Congrats! By linking an account, you have created an{" "}
<InlineLink
href="http://plaid.com/docs/quickstart/glossary/#item"
target="_blank"
>
Item
</InlineLink>
.
</h4>
) : (
<h4 className={styles.subtitle}>
<Callout warning>
Unable to create an item. Please check your backend server
Congrats! Your payment is now confirmed.
<p/>
<Callout>
You can see information of all your payments in the{' '}
<InlineLink
href="https://dashboard.plaid.com/activity/payments"
target="_blank"
>
Payments Dashboard
</InlineLink>
.
</Callout>
</h4>
)}
<div className={styles.itemAccessContainer}>
<p className={styles.itemAccessRow}>
<span className={styles.idName}>item_id</span>
<span className={styles.tokenText}>{itemId}</span>
</p>

<p className={styles.itemAccessRow}>
<span className={styles.idName}>access_token</span>
<span className={styles.tokenText}>{accessToken}</span>
</p>
</div>
{isItemAccess && (
<p className={styles.requests}>
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:
</p>
</>
) : /* If not using the payment_initiation product, show the item_id and access_token information */ (
<>
{isItemAccess ? (
<h4 className={styles.subtitle}>
Congrats! By linking an account, you have created an{" "}
<InlineLink
href="http://plaid.com/docs/quickstart/glossary/#item"
target="_blank"
>
Item
</InlineLink>
.
</h4>
) : (
<h4 className={styles.subtitle}>
<Callout warning>
Unable to create an item. Please check your backend server
</Callout>
</h4>
)}
<div className={styles.itemAccessContainer}>
<p className={styles.itemAccessRow}>
<span className={styles.idName}>item_id</span>
<span className={styles.tokenText}>{itemId}</span>
</p>

<p className={styles.itemAccessRow}>
<span className={styles.idName}>access_token</span>
<span className={styles.tokenText}>{accessToken}</span>
</p>
</div>
{isItemAccess && (
<p className={styles.requests}>
Now that you have an access_token, you can make all of the
following requests:
</p>
)}
</>
)}
</>
)}
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/Components/Link/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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("", "", "/");
},
Expand Down
74 changes: 41 additions & 33 deletions frontend/src/Components/ProductTypes/Products.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,37 @@ const Products = () => {
transformData={transformPaymentData}
/>
)}
<Endpoint
endpoint="auth"
name="Auth"
categories={authCategories}
schema="/auth/get/"
description="Retrieve account and routing numbers for checking and savings accounts."
transformData={transformAuthData}
/>
<Endpoint
endpoint="transactions"
name="Transactions"
categories={transactionsCategories}
schema="/transactions/sync/"
description="Retrieve transactions or incremental updates for credit and depository accounts."
transformData={transformTransactionsData}
/>
<Endpoint
endpoint="identity"
name="Identity"
categories={identityCategories}
schema="/identity/get/"
description="Retrieve Identity information on file with the bank. Reduce
fraud by comparing user-submitted data to validate identity."
transformData={transformIdentityData}
/>
{products.includes("auth") && (
<Endpoint
endpoint="auth"
name="Auth"
categories={authCategories}
schema="/auth/get/"
description="Retrieve account and routing numbers for checking and savings accounts."
transformData={transformAuthData}
/>
)}
{products.includes("transactions") && (
<Endpoint
endpoint="transactions"
name="Transactions"
categories={transactionsCategories}
schema="/transactions/sync/"
description="Retrieve transactions or incremental updates for credit and depository accounts."
transformData={transformTransactionsData}
/>
)}
{products.includes("identity") && (
<Endpoint
endpoint="identity"
name="Identity"
categories={identityCategories}
schema="/identity/get/"
description="Retrieve Identity information on file with the bank. Reduce
fraud by comparing user-submitted data to validate identity."
transformData={transformIdentityData}
/>
)}
{products.includes("assets") && (
<Endpoint
endpoint="assets"
Expand All @@ -77,15 +83,17 @@ const Products = () => {
transformData={transformAssetsData}
/>
)}
<Endpoint
endpoint="balance"
name="Balance"
categories={balanceCategories}
schema="/accounts/balance/get/"
description="Check balances in real time to prevent non-sufficient funds
{!products.includes("payment_initiation") && (
<Endpoint
endpoint="balance"
name="Balance"
categories={balanceCategories}
schema="/accounts/balance/get/"
description="Check balances in real time to prevent non-sufficient funds
fees."
transformData={transformBalanceData}
/>
transformData={transformBalanceData}
/>
)}
{products.includes("investments") && (
<>
<Endpoint
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/Context/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createContext, useReducer, Dispatch, ReactNode } from "react";
interface QuickstartState {
linkSuccess: boolean;
isItemAccess: boolean;
isPaymentInitiation: boolean;
linkToken: string | null;
accessToken: string | null;
itemId: string | null;
Expand All @@ -19,6 +20,7 @@ interface QuickstartState {
const initialState: QuickstartState = {
linkSuccess: false,
isItemAccess: true,
isPaymentInitiation: false,
linkToken: "", // Don't set to null or error message will show up briefly when site loads
accessToken: null,
itemId: null,
Expand Down
Loading