├── packages
├── open-banking
│ ├── mx
│ │ ├── .nvmrc
│ │ ├── next.config.mjs
│ │ ├── src
│ │ │ ├── integrations
│ │ │ │ └── index.ts
│ │ │ ├── app
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── page.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── connect-mx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── account-selection
│ │ │ │ │ └── page.tsx
│ │ │ ├── utils
│ │ │ │ ├── index.ts
│ │ │ │ ├── getMissingKeys.ts
│ │ │ │ ├── uuidFromUrl.ts
│ │ │ │ ├── equalsIgnoreCase.ts
│ │ │ │ └── getBaseUrl.ts
│ │ │ ├── theme.ts
│ │ │ ├── createEmotionCache.ts
│ │ │ └── hooks
│ │ │ │ └── useNetworkAlert.ts
│ │ ├── .env.local.example
│ │ ├── tsconfig.json
│ │ ├── .gitignore
│ │ ├── package.json
│ │ └── README.md
│ └── plaid
│ │ ├── src
│ │ ├── integrations
│ │ │ └── index.ts
│ │ ├── app
│ │ │ ├── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── plaid-link
│ │ │ │ └── page.tsx
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ ├── getMissingKeys.ts
│ │ │ ├── uuidFromUrl.ts
│ │ │ ├── equalsIgnoreCase.ts
│ │ │ └── getBaseUrl.ts
│ │ ├── theme.ts
│ │ └── hooks
│ │ │ └── useNetworkAlert.ts
│ │ ├── next.config.mjs
│ │ ├── .env.local.example
│ │ ├── .gitignore
│ │ ├── tsconfig.json
│ │ ├── README.md
│ │ └── package.json
└── secure-token-exchange
│ ├── plaid
│ ├── next.config.js
│ ├── tsconfig.json
│ ├── src
│ │ ├── integrations
│ │ │ ├── index.ts
│ │ │ ├── plaid.ts
│ │ │ └── dwolla.ts
│ │ ├── utils
│ │ │ ├── getMissingKeys.ts
│ │ │ ├── uuidFromUrl.ts
│ │ │ ├── equalsIgnoreCase.ts
│ │ │ ├── index.ts
│ │ │ ├── tryNextResponse.ts
│ │ │ ├── assertRequestMethod.ts
│ │ │ └── assertValidBody.ts
│ │ ├── hooks
│ │ │ ├── useWidgetRef.ts
│ │ │ └── useNetworkAlert.ts
│ │ ├── pages
│ │ │ ├── index.tsx
│ │ │ ├── api
│ │ │ │ ├── plaid
│ │ │ │ │ ├── create-link-token.ts
│ │ │ │ │ └── exchange-public-token.ts
│ │ │ │ └── dwolla
│ │ │ │ │ ├── exchanges.ts
│ │ │ │ │ ├── customers.ts
│ │ │ │ │ └── funding-sources.ts
│ │ │ ├── _app.tsx
│ │ │ ├── _document.tsx
│ │ │ ├── connect-plaid.tsx
│ │ │ └── create-customers.tsx
│ │ ├── theme.ts
│ │ ├── createEmotionCache.ts
│ │ └── layouts
│ │ │ └── MainLayout.tsx
│ ├── next-env.d.ts
│ ├── .env.local.example
│ ├── .gitignore
│ ├── README.md
│ └── package.json
│ ├── mx
│ ├── next.config.js
│ ├── tsconfig.json
│ ├── src
│ │ ├── integrations
│ │ │ ├── index.ts
│ │ │ ├── dwolla.ts
│ │ │ └── mx.ts
│ │ ├── utils
│ │ │ ├── getMissingKeys.ts
│ │ │ ├── uuidFromUrl.ts
│ │ │ ├── equalsIgnoreCase.ts
│ │ │ ├── index.ts
│ │ │ ├── tryNextResponse.ts
│ │ │ ├── assertRequestMethod.ts
│ │ │ └── assertValidBody.ts
│ │ ├── hooks
│ │ │ ├── useWidgetRef.ts
│ │ │ └── useNetworkAlert.ts
│ │ ├── theme.ts
│ │ ├── pages
│ │ │ ├── index.tsx
│ │ │ ├── api
│ │ │ │ ├── mx
│ │ │ │ │ ├── users.ts
│ │ │ │ │ ├── accounts.ts
│ │ │ │ │ └── processor_token.ts
│ │ │ │ └── dwolla
│ │ │ │ │ ├── exchanges.ts
│ │ │ │ │ ├── customers.ts
│ │ │ │ │ └── funding-sources.ts
│ │ │ ├── _app.tsx
│ │ │ ├── _document.tsx
│ │ │ └── connect-mx.tsx
│ │ ├── createEmotionCache.ts
│ │ └── layouts
│ │ │ └── MainLayout.tsx
│ ├── .env.local.example
│ ├── .gitignore
│ ├── README.md
│ └── package.json
│ ├── flinks
│ ├── next.config.js
│ ├── tsconfig.json
│ ├── src
│ │ ├── integrations
│ │ │ ├── index.ts
│ │ │ ├── dwolla.ts
│ │ │ └── flinks.ts
│ │ ├── utils
│ │ │ ├── uuidFromUrl.ts
│ │ │ ├── getMissingKeys.ts
│ │ │ ├── equalsIgnoreCase.ts
│ │ │ ├── index.ts
│ │ │ ├── tryNextResponse.ts
│ │ │ ├── assertRequestMethod.ts
│ │ │ └── assertValidBody.ts
│ │ ├── pages
│ │ │ ├── index.tsx
│ │ │ ├── api
│ │ │ │ ├── flinks
│ │ │ │ │ ├── auth-secret.ts
│ │ │ │ │ ├── request-id.ts
│ │ │ │ │ ├── access-token.ts
│ │ │ │ │ └── accounts-summary.ts
│ │ │ │ └── dwolla
│ │ │ │ │ ├── exchanges.ts
│ │ │ │ │ ├── customers.ts
│ │ │ │ │ └── funding-sources.ts
│ │ │ ├── _app.tsx
│ │ │ ├── _document.tsx
│ │ │ └── create-customers.tsx
│ │ ├── theme.ts
│ │ ├── createEmotionCache.ts
│ │ ├── layouts
│ │ │ └── MainLayout.tsx
│ │ └── hooks
│ │ │ ├── useFlinksConnect.ts
│ │ │ └── useNetworkAlert.ts
│ ├── .env.local.example
│ ├── .gitignore
│ ├── README.md
│ └── package.json
│ └── mastercard
│ ├── next.config.js
│ ├── tsconfig.json
│ ├── src
│ ├── utils
│ │ ├── getMissingKeys.ts
│ │ ├── uuidFromUrl.ts
│ │ ├── index.ts
│ │ ├── tryWithResponse.ts
│ │ ├── assertRequestMethod.ts
│ │ ├── assertValidBody.ts
│ │ └── getFormValidation.ts
│ ├── pages
│ │ ├── index.tsx
│ │ ├── api
│ │ │ ├── mastercard
│ │ │ │ ├── accounts
│ │ │ │ │ └── [customerId].ts
│ │ │ │ ├── customers.ts
│ │ │ │ └── consent.ts
│ │ │ └── dwolla
│ │ │ │ ├── exchanges.ts
│ │ │ │ ├── customers.ts
│ │ │ │ └── funding-sources.ts
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ └── connect-mastercard.tsx
│ ├── theme.ts
│ ├── createEmotionCache.ts
│ ├── layouts
│ │ └── MainLayout.tsx
│ ├── hooks
│ │ ├── useNetworkAlert.ts
│ │ └── useMastercardConnect.ts
│ └── integrations
│ │ ├── dwolla.ts
│ │ └── mastercard.ts
│ ├── .env.local.example
│ ├── .gitignore
│ ├── package.json
│ ├── README.md
│ └── scripts
│ └── generate_mastercard_sdk.sh
├── .gitignore
├── pnpm-workspaces.yaml
├── .npmrc
├── .prettierrc.json
├── .eslintrc.json
├── tsconfig.json
├── package.json
├── LICENSE
└── README.md
/packages/open-banking/mx/.nvmrc:
--------------------------------------------------------------------------------
1 | v18.17
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | node_modules
--------------------------------------------------------------------------------
/pnpm-workspaces.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/*"
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shared-workspace-lockfile=true
2 | shamefully-hoist=true
--------------------------------------------------------------------------------
/packages/open-banking/mx/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/integrations/index.ts:
--------------------------------------------------------------------------------
1 | export const getEnvironmentVariable = (key: string): string => process.env[key] as string;
2 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/integrations/index.ts:
--------------------------------------------------------------------------------
1 | export const getEnvironmentVariable = (key: string): string => process.env[key] as string;
2 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dwolla/integration-examples/HEAD/packages/open-banking/mx/src/app/favicon.ico
--------------------------------------------------------------------------------
/packages/open-banking/plaid/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("next").NextConfig} */
2 | const nextConfig = {};
3 |
4 | module.exports = nextConfig;
5 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("next").NextConfig} */
2 | const nextConfig = {
3 | swcMinify: true
4 | };
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("next").NextConfig} */
2 | const nextConfig = {
3 | swcMinify: true
4 | };
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("next").NextConfig} */
2 | const nextConfig = {
3 | swcMinify: true
4 | };
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 | export default function Home() {
3 | redirect("/create-customer"); // redirect to /create-customer
4 | }
5 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "exclude": ["node_modules"],
4 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 | export default function Home() {
3 | redirect("/create-customer"); // redirect to /create-customer
4 | }
5 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "exclude": ["node_modules"],
4 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "exclude": ["node_modules"],
4 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "exclude": ["node_modules"],
4 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/.env.local.example:
--------------------------------------------------------------------------------
1 | # Dwolla Environment and Client Key and Secret
2 | # https://dashboard-sandbox.dwolla.com/applications-legacy
3 | DWOLLA_ENV=sandbox
4 | DWOLLA_KEY=
5 | DWOLLA_SECRET=
6 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/.env.local.example:
--------------------------------------------------------------------------------
1 | # Dwolla Environment and Client Key and Secret
2 | # https://dashboard-sandbox.dwolla.com/applications-legacy
3 | DWOLLA_ENV=sandbox
4 | DWOLLA_KEY=
5 | DWOLLA_SECRET=
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": true,
4 | "printWidth": 120,
5 | "semi": true,
6 | "singleQuote": false,
7 | "tabWidth": 4,
8 | "trailingComma": "none"
9 | }
10 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/integrations/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 } from "uuid";
2 |
3 | export const getEnvironmentVariable = (key: string): string => process.env[key] as string;
4 |
5 | export const newUuid = (): string => v4();
6 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/integrations/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 } from "uuid";
2 |
3 | export const getEnvironmentVariable = (key: string): string => process.env[key] as string;
4 |
5 | export const newUuid = (): string => v4();
6 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/integrations/index.ts:
--------------------------------------------------------------------------------
1 | import { v4 } from "uuid";
2 |
3 | export const getEnvironmentVariable = (key: string): string => process.env[key] as string;
4 |
5 | export const newUuid = (): string => v4();
6 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { default as equalsIgnoreCase } from "./equalsIgnoreCase";
2 | export { default as getBaseUrl } from "./getBaseUrl";
3 | export { default as getMissingKeys } from "./getMissingKeys";
4 | export { default as uuidFromUrl } from "./uuidFromUrl";
5 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { default as equalsIgnoreCase } from "./equalsIgnoreCase";
2 | export { default as getBaseUrl } from "./getBaseUrl";
3 | export { default as getMissingKeys } from "./getMissingKeys";
4 | export { default as uuidFromUrl } from "./uuidFromUrl";
5 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/utils/getMissingKeys.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets if all the `keys` are defined within an object `T`. Returns all the key(s) that are not present.
3 | */
4 | export default function getMissingKeys(obj: T, keys: Array): Array {
5 | return keys.filter((key) => !(key in obj) || !obj[key]);
6 | }
7 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/utils/uuidFromUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a Dwolla resource location to its UUID by stripping the URL component(s).
3 | */
4 | export default function uuidFromUrl(resourceUrl: string): string {
5 | const lastSlash = resourceUrl.lastIndexOf("/");
6 | return resourceUrl.substring(lastSlash + 1).trim();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/utils/getMissingKeys.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets if all the `keys` are defined within an object `T`. Returns all the key(s) that are not present.
3 | */
4 | export default function getMissingKeys(obj: T, keys: Array): Array {
5 | return keys.filter((key) => !(key in obj) || !obj[key]);
6 | }
7 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/utils/uuidFromUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a Dwolla resource location to its UUID by stripping the URL component(s).
3 | */
4 | export default function uuidFromUrl(resourceUrl: string): string {
5 | const lastSlash = resourceUrl.lastIndexOf("/");
6 | return resourceUrl.substring(lastSlash + 1).trim();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/utils/getMissingKeys.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets if all the `keys` are defined within an object `T`. Returns all the key(s) that are not present.
3 | */
4 | export default function getMissingKeys(obj: T, keys: (keyof T)[]): (keyof T)[] {
5 | return keys.filter((key) => !(key in obj) || !obj[key]);
6 | }
7 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/utils/getMissingKeys.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets if all the `keys` are defined within an object `T`. Returns all the key(s) that are not present.
3 | */
4 | export default function getMissingKeys(obj: T, keys: Array): Array {
5 | return keys.filter((key) => !(key in obj) || !obj[key]);
6 | }
7 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "exclude": [
4 | "node_modules"
5 | ],
6 | "include": [
7 | "next-env.d.ts",
8 | "**/*.ts",
9 | "**/*.tsx",
10 | ".next/types/**/*.ts"
11 | ],
12 | "compilerOptions": {
13 | "plugins": [{ "name": "next" }]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/utils/uuidFromUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a Dwolla resource location to its UUID by stripping the URL component(s).
3 | */
4 | export default function uuidFromUrl(resourceUrl: string): string {
5 | const lastSlash = resourceUrl.lastIndexOf("/");
6 | return resourceUrl.substring(lastSlash + 1).trim();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/utils/getMissingKeys.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets if all the `keys` are defined within an object `T`. Returns all the key(s) that are not present.
3 | */
4 | export default function getMissingKeys(obj: T, keys: Array): Array {
5 | return keys.filter((key) => !(key in obj) || !obj[key]);
6 | }
7 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/utils/uuidFromUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a Dwolla resource location to its UUID by stripping the URL component(s).
3 | */
4 | export default function uuidFromUrl(resourceUrl: string): string {
5 | const lastSlash = resourceUrl.lastIndexOf("/");
6 | return resourceUrl.substring(lastSlash + 1).trim();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/utils/uuidFromUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a Dwolla resource location to its UUID by stripping the URL component(s).
3 | */
4 | export default function uuidFromUrl(resourceUrl: string): string {
5 | const lastSlash = resourceUrl.lastIndexOf("/");
6 | return resourceUrl.substring(lastSlash + 1).trim();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/utils/uuidFromUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a Dwolla resource location to its UUID by stripping the URL component(s).
3 | */
4 | export default function uuidFromUrl(resourceUrl: string): string {
5 | const lastSlash = resourceUrl.lastIndexOf("/");
6 | return resourceUrl.substring(lastSlash + 1).trim();
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/utils/getMissingKeys.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets if all the `keys` are defined within an object `T`. Returns all the key(s) that are not present.
3 | */
4 | export default function getMissingKeys(obj: T, keys: Array): Array {
5 | return keys.filter((key) => !(key in obj) || !obj[key]);
6 | }
7 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/core-web-vitals", "prettier", "plugin:@typescript-eslint/recommended"],
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": ["@typescript-eslint"],
5 | "rules": {
6 | "@typescript-eslint/consistent-type-imports": "error",
7 | "@typescript-eslint/no-explicit-any": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/utils/equalsIgnoreCase.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Compare two strings for equality, ignore case sensitivity, except for accent characters
3 | */
4 | export default function equalsIgnoreCase(a: string | undefined, b: string | undefined): boolean {
5 | if (!a || !b) return false;
6 | return a.localeCompare(b, undefined, { sensitivity: "accent" }) === 0;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/utils/equalsIgnoreCase.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Compare two strings for equality, ignore case sensitivity, except for accent characters
3 | */
4 | export default function equalsIgnoreCase(a: string | undefined, b: string | undefined): boolean {
5 | if (!a || !b) return false;
6 | return a.localeCompare(b, undefined, { sensitivity: "accent" }) === 0;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/utils/equalsIgnoreCase.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Compare two strings for equality, ignore case sensitivity, except for accent characters
3 | */
4 | export default function equalsIgnoreCase(a: string | undefined, b: string | undefined): boolean {
5 | if (!a || !b) return false;
6 | return a.localeCompare(b, undefined, { sensitivity: "accent" }) === 0;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/utils/equalsIgnoreCase.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Compare two strings for equality, ignore case sensitivity, except for accent characters
3 | */
4 | export default function equalsIgnoreCase(a: string | undefined, b: string | undefined): boolean {
5 | if (!a || !b) return false;
6 | return a.localeCompare(b, undefined, { sensitivity: "accent" }) === 0;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/utils/equalsIgnoreCase.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Compare two strings for equality, ignore case sensitivity, except for accent characters
3 | */
4 | export default function equalsIgnoreCase(a: string | undefined, b: string | undefined): boolean {
5 | if (!a || !b) return false;
6 | return a.localeCompare(b, undefined, { sensitivity: "accent" }) === 0;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/hooks/useWidgetRef.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from "react";
2 |
3 | export const useWidgetRef = () => {
4 | const [element, setElement] = useState();
5 | const ref = useCallback<(element: ElementType | undefined) => void>((element) => setElement(element), []);
6 | return { element, ref };
7 | };
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/.env.local.example:
--------------------------------------------------------------------------------
1 | # Dwolla Environment and Client Key and Secret
2 | # https://dashboard-sandbox.dwolla.com/applications-legacy
3 | DWOLLA_ENV=sandbox
4 | DWOLLA_KEY=
5 | DWOLLA_SECRET=
6 |
7 | # Mastercard Partner ID, App Key, and Secret
8 | # https://developer.mastercard.com/dashboard
9 | MASTERCARD_PARTNER_ID=
10 | MASTERCARD_APP_KEY=
11 | MASTERCARD_SECRET=
12 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/hooks/useWidgetRef.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from "react";
2 |
3 | export const useWidgetRef = () => {
4 | const [element, setElement] = useState();
5 | const ref = useCallback<(element: ElementType | undefined) => void>((element) => setElement(element), []);
6 | return { element, ref };
7 | };
8 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from "@mui/material";
2 | import { red } from "@mui/material/colors";
3 |
4 | export default createTheme({
5 | palette: {
6 | primary: {
7 | main: "#2D2D48"
8 | },
9 | secondary: {
10 | main: "#19857B"
11 | },
12 | error: {
13 | main: red.A400
14 | }
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSideProps, NextPage } from "next";
2 |
3 | const HomePage: NextPage = () => null;
4 |
5 | export const getServerSideProps: GetServerSideProps = async () => {
6 | return {
7 | redirect: {
8 | destination: "/create-customers",
9 | permanent: true
10 | }
11 | };
12 | };
13 |
14 | export default HomePage;
15 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from "@mui/material";
2 | import { red } from "@mui/material/colors";
3 |
4 | export default createTheme({
5 | palette: {
6 | primary: {
7 | main: "#2D2D48"
8 | },
9 | secondary: {
10 | main: "#19857B"
11 | },
12 | error: {
13 | main: red.A400
14 | }
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSideProps, NextPage } from "next";
2 |
3 | const HomePage: NextPage = () => null;
4 |
5 | export const getServerSideProps: GetServerSideProps = async () => {
6 | return {
7 | redirect: {
8 | destination: "/create-customers",
9 | permanent: true
10 | }
11 | };
12 | };
13 |
14 | export default HomePage;
15 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSideProps, NextPage } from "next";
2 |
3 | const HomePage: NextPage = () => null;
4 |
5 | export const getServerSideProps: GetServerSideProps = async () => {
6 | return {
7 | redirect: {
8 | destination: "/create-customers",
9 | permanent: true
10 | }
11 | };
12 | };
13 |
14 | export default HomePage;
15 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from "@mui/material";
2 | import { red } from "@mui/material/colors";
3 |
4 | export default createTheme({
5 | palette: {
6 | primary: {
7 | main: "#2D2D48"
8 | },
9 | secondary: {
10 | main: "#19857B"
11 | },
12 | error: {
13 | main: red.A400
14 | }
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/theme.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { createTheme } from "@mui/material";
3 | import { red } from "@mui/material/colors";
4 |
5 | export default createTheme({
6 | palette: {
7 | primary: {
8 | main: "#2D2D48"
9 | },
10 | secondary: {
11 | main: "#19857B"
12 | },
13 | error: {
14 | main: red.A400
15 | }
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSideProps, NextPage } from "next";
2 |
3 | const HomePage: NextPage = () => null;
4 |
5 | export const getServerSideProps: GetServerSideProps = async () => {
6 | return {
7 | redirect: {
8 | destination: "/create-customer",
9 | permanent: true
10 | }
11 | };
12 | };
13 |
14 | export default HomePage;
15 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from "@mui/material";
2 | import { red } from "@mui/material/colors";
3 |
4 | export default createTheme({
5 | palette: {
6 | primary: {
7 | main: "#2D2D48"
8 | },
9 | secondary: {
10 | main: "#19857B"
11 | },
12 | error: {
13 | main: red.A400
14 | }
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/.env.local.example:
--------------------------------------------------------------------------------
1 | # Dwolla Environment and OAuth Keys
2 | # https://dashboard-sandbox.dwolla.com/applications-legacy
3 | DWOLLA_ENV=sandbox
4 | DWOLLA_KEY=[YOUR_DWOLLA_KEY]
5 | DWOLLA_SECRET=[YOUR_DWOLLA_SECRET]
6 |
7 | # Plaid Environment and OAuth Keys
8 | # https://dashboard.plaid.com/team/keys
9 | PLAID_ENV=sandbox
10 | PLAID_CLIENT_ID=[YOUR_PLAID_CLIENT_ID]
11 | PLAID_SECRET=[YOUR_PLAID_SECRET]
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/theme.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { createTheme } from "@mui/material";
3 | import { red } from "@mui/material/colors";
4 |
5 | export default createTheme({
6 | palette: {
7 | primary: {
8 | main: "#2D2D48"
9 | },
10 | secondary: {
11 | main: "#19857B"
12 | },
13 | error: {
14 | main: red.A400
15 | }
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { default as assertRequestMethod } from "./assertRequestMethod";
2 | export { default as assertValidBody } from "./assertValidBody";
3 | export { default as equalsIgnoreCase } from "./equalsIgnoreCase";
4 | export { default as getMissingKeys } from "./getMissingKeys";
5 | export { default as tryNextResponse } from "./tryNextResponse";
6 | export { default as uuidFromUrl } from "./uuidFromUrl";
7 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { default as assertRequestMethod } from "./assertRequestMethod";
2 | export { default as assertValidBody } from "./assertValidBody";
3 | export { default as equalsIgnoreCase } from "./equalsIgnoreCase";
4 | export { default as getMissingKeys } from "./getMissingKeys";
5 | export { default as tryNextResponse } from "./tryNextResponse";
6 | export { default as uuidFromUrl } from "./uuidFromUrl";
7 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { default as assertRequestMethod } from "./assertRequestMethod";
2 | export { default as assertValidBody } from "./assertValidBody";
3 | export { default as equalsIgnoreCase } from "./equalsIgnoreCase";
4 | export { default as getMissingKeys } from "./getMissingKeys";
5 | export { default as tryNextResponse } from "./tryNextResponse";
6 | export { default as uuidFromUrl } from "./uuidFromUrl";
7 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/.env.local.example:
--------------------------------------------------------------------------------
1 | # Dwolla Environment and Client Key and Secret
2 | # https://dashboard-sandbox.dwolla.com/applications-legacy
3 | DWOLLA_ENV=sandbox
4 | DWOLLA_KEY=
5 | DWOLLA_SECRET=
6 |
7 | # Flinks Instance, Customer ID, and API Secret
8 | # Contact Flinks (https://flinks.com/contact/sales/) for more information on how to obtained these values
9 | FLINKS_INSTANCE=toolbox
10 | FLINKS_CUSTOMER_ID=
11 | FLINKS_API_SECRET=
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/utils/getBaseUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Utility function to get the Dwolla base URL for generating API endpoints where needed
3 | */
4 | export default function getDwollaBaseUrl(): string {
5 | const env = process.env.DWOLLA_ENV?.toLowerCase();
6 | switch (env) {
7 | case "production":
8 | return "https://api.dwolla.com";
9 | default:
10 | return `https://api-${env}.dwolla.com`;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/utils/getBaseUrl.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Utility function to get the Dwolla base URL for generating API endpoints where needed
3 | */
4 | export default function getDwollaBaseUrl(): string {
5 | const env = process.env.DWOLLA_ENV?.toLowerCase();
6 | switch (env) {
7 | case "production":
8 | return "https://api.dwolla.com";
9 | default:
10 | return `https://api-${env}.dwolla.com`;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/.env.local.example:
--------------------------------------------------------------------------------
1 | # Dwolla Environment and Client Key and Secret
2 | # https://dashboard-sandbox.dwolla.com/applications-legacy
3 | DWOLLA_ENV=sandbox
4 | DWOLLA_KEY=
5 | DWOLLA_SECRET=
6 |
7 | # MX Base Path, Client ID, and API Key
8 | # API Key and Client ID -> https://dashboard.mx.com
9 | # Base Path -> https://github.com/mxenabled/mx-platform-node#getting-started
10 | MX_BASE_PATH=https://int-api.mx.com
11 | MX_CLIENT_ID=
12 | MX_API_KEY=
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | const isBrowser = typeof document !== "undefined";
4 |
5 | export default function createEmotionCache() {
6 | const insertionPoint: HTMLMetaElement | undefined = isBrowser
7 | ? document.querySelector("meta[name='emotion-insertion-point']") ?? undefined
8 | : undefined;
9 | return createCache({ key: "mui-style", insertionPoint });
10 | }
11 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import assertRequestMethod from "./assertRequestMethod";
2 | import assertValidBody from "./assertValidBody";
3 | import getFormValidation from "./getFormValidation";
4 | import getMissingKeys from "./getMissingKeys";
5 | import tryWithResponse from "./tryWithResponse";
6 | import uuidFromUrl from "./uuidFromUrl";
7 |
8 | export { assertRequestMethod, assertValidBody, getFormValidation, getMissingKeys, tryWithResponse, uuidFromUrl };
9 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | const isBrowser = typeof document !== "undefined";
4 |
5 | export default function createEmotionCache() {
6 | const insertionPoint: HTMLMetaElement | undefined = isBrowser
7 | ? document.querySelector("meta[name='emotion-insertion-point']") ?? undefined
8 | : undefined;
9 | return createCache({ key: "mui-style", insertionPoint });
10 | }
11 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | const isBrowser = typeof document !== "undefined";
4 |
5 | export default function createEmotionCache() {
6 | const insertionPoint: HTMLMetaElement | undefined = isBrowser
7 | ? document.querySelector("meta[name='emotion-insertion-point']") ?? undefined
8 | : undefined;
9 | return createCache({ key: "mui-style", insertionPoint });
10 | }
11 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | const isBrowser = typeof document !== "undefined";
4 |
5 | export default function createEmotionCache() {
6 | const insertionPoint: HTMLMetaElement | undefined = isBrowser
7 | ? document.querySelector("meta[name='emotion-insertion-point']") ?? undefined
8 | : undefined;
9 | return createCache({ key: "mui-style", insertionPoint });
10 | }
11 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | const isBrowser = typeof document !== "undefined";
4 |
5 | export default function createEmotionCache() {
6 | const insertionPoint: HTMLMetaElement | undefined = isBrowser
7 | ? document.querySelector("meta[name='emotion-insertion-point']") ?? undefined
8 | : undefined;
9 | return createCache({ key: "mui-style", insertionPoint });
10 | }
11 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/api/plaid/create-link-token.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import { createLinkToken } from "../../../integrations/plaid";
3 |
4 | export default async function createLinkTokenApi(req: NextApiRequest, res: NextApiResponse) {
5 | if (req.method === "POST") {
6 | const linkToken = await createLinkToken();
7 | res.status(200).json(linkToken);
8 | } else {
9 | res.status(405).end(`Method ${req.method} Not Allowed`);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "esModuleInterop": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "incremental": true,
7 | "isolatedModules": true,
8 | "jsx": "preserve",
9 | "lib": ["dom", "dom.iterable", "esnext"],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "noEmit": true,
13 | "resolveJsonModule": true,
14 | "skipLibCheck": true,
15 | "strict": true,
16 | "target": "es5"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/utils/tryNextResponse.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 |
3 | /**
4 | * Wraps a closure in a `try`/`catch`, and if the function panics, print the stacktrace, inform the user, and end the session.
5 | */
6 | export default async function tryNextResponse(fn: () => R | Promise, res: NextApiResponse) {
7 | try {
8 | await fn();
9 | } catch (err) {
10 | console.trace(err);
11 | res.status(500).json({ error: "Internal Server Error: Check console for more information." });
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/utils/tryNextResponse.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 |
3 | /**
4 | * Wraps a closure in a `try`/`catch`, and if the function panics, print the stacktrace, inform the user, and end the session.
5 | */
6 | export default async function tryNextResponse(fn: () => R | Promise, res: NextApiResponse) {
7 | try {
8 | await fn();
9 | } catch (err) {
10 | console.trace(err);
11 | res.status(500).json({ error: "Internal Server Error: Check console for more information." });
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/utils/tryNextResponse.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 |
3 | /**
4 | * Wraps a closure in a `try`/`catch`, and if the function panics, print the stacktrace, inform the user, and end the session.
5 | */
6 | export default async function tryNextResponse(fn: () => R | Promise, res: NextApiResponse) {
7 | try {
8 | await fn();
9 | } catch (err) {
10 | console.trace(err);
11 | res.status(500).json({ error: "Internal Server Error: Check console for more information." });
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/utils/tryWithResponse.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 |
3 | /**
4 | * Wraps a closure in a `try`/`catch`, and if the function panics, print the stacktrace, inform the user, and end the session.
5 | */
6 | export default async function tryWithResponse(fn: () => R | Promise, res: NextApiResponse): Promise {
7 | try {
8 | await fn();
9 | } catch (err) {
10 | console.trace(err);
11 | res.status(500).json({ error: "Internal Server Error: Check console for more information." });
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/utils/assertRequestMethod.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 |
3 | export type RequestMethod = "GET" | "POST";
4 |
5 | /**
6 | * Asserts that the specified request method was used when the API was called, otherwise inform the caller and end the session.
7 | */
8 | export default function assertRequestMethod(method: RequestMethod, req: NextApiRequest, res: NextApiResponse): boolean {
9 | if (req.method === method) return true;
10 | res.status(405).end(`Method ${req.method} Not Allowed`);
11 | return false;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # openapi sdks
39 | /sdks/
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/utils/assertValidBody.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 | import { getMissingKeys } from "./index";
3 |
4 | /**
5 | * Asserts that all the `keys` are defined in object `T` and, if not, inform the caller and end the session.
6 | */
7 | export default function assertValidBody(obj: T, keys: Array, res: NextApiResponse): boolean {
8 | const missing = getMissingKeys(obj, keys);
9 |
10 | if (missing.length > 0) {
11 | res.status(400).json({ error: `The following JSON properties are missing: ${missing}` });
12 | return false;
13 | }
14 | return true;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/utils/assertRequestMethod.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 |
3 | export type RequestMethod = "GET" | "POST";
4 |
5 | /**
6 | * Asserts that the specified request method was used when the API was called, otherwise inform the caller and end the session.
7 | */
8 | export default function assertRequestMethod(
9 | method: RequestMethod,
10 | req: ReqType,
11 | res: ResType
12 | ): boolean {
13 | if (req.method === method) return true;
14 | res.status(405).end(`Method ${req.method} Not Allowed`);
15 | return false;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/utils/assertRequestMethod.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 |
3 | export type RequestMethod = "GET" | "POST";
4 |
5 | /**
6 | * Asserts that the specified request method was used when the API was called, otherwise inform the caller and end the session.
7 | */
8 | export default function assertRequestMethod(
9 | method: RequestMethod,
10 | req: ReqType,
11 | res: ResType
12 | ): boolean {
13 | if (req.method === method) return true;
14 | res.status(405).end(`Method ${req.method} Not Allowed`);
15 | return false;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/utils/assertRequestMethod.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 |
3 | export type RequestMethod = "GET" | "POST";
4 |
5 | /**
6 | * Asserts that the specified request method was used when the API was called, otherwise inform the caller and end the session.
7 | */
8 | export default function assertRequestMethod(
9 | method: RequestMethod,
10 | req: ReqType,
11 | res: ResType
12 | ): boolean {
13 | if (req.method === method) return true;
14 | res.status(405).end(`Method ${req.method} Not Allowed`);
15 | return false;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/utils/getFormValidation.ts:
--------------------------------------------------------------------------------
1 | import { getMissingKeys } from "./index";
2 |
3 | export interface ValidationResult {
4 | isValid: boolean;
5 | missingKeys: (keyof T)[];
6 | }
7 |
8 | /**
9 | * Gets if the form is valid by ensuring all required fields have a present value.
10 | * @returns a {@link ValidationResult} object that contains if the form was valid as well
11 | * as any missing keys, if any were present
12 | */
13 | export default function getFormValidation(formData: T, keys: (keyof T)[]): ValidationResult {
14 | const missingKeys = getMissingKeys(formData, keys);
15 | const isValid = missingKeys.length === 0;
16 | return { missingKeys, isValid };
17 | }
18 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "incremental": true,
15 | "module": "esnext",
16 | "esModuleInterop": true,
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve",
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | ".next/types/**/*.ts",
30 | "**/*.ts",
31 | "**/*.tsx"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/utils/assertValidBody.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 | import { getMissingKeys } from "./index";
3 |
4 | type AssertedKeys = Required> & Omit;
5 |
6 | /**
7 | * Asserts that all the `keys` are defined in object `T` and, if not, inform the caller and end the session.
8 | */
9 | export default function assertValidBody(
10 | obj: T,
11 | keys: Array,
12 | res: NextApiResponse
13 | ): AssertedKeys | undefined {
14 | const missingKeys = getMissingKeys(obj, keys);
15 |
16 | if (missingKeys.length > 0) {
17 | res.status(400).json({ error: `The following JSON properties are missing: ${missingKeys}` });
18 | return undefined;
19 | }
20 | return obj as unknown as AssertedKeys;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/utils/assertValidBody.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 | import { getMissingKeys } from "./index";
3 |
4 | type AssertedKeys = Required> & Omit;
5 |
6 | /**
7 | * Asserts that all the `keys` are defined in object `T` and, if not, inform the caller and end the session.
8 | */
9 | export default function assertValidBody(
10 | obj: T,
11 | keys: Array,
12 | res: NextApiResponse
13 | ): AssertedKeys | undefined {
14 | const missingKeys = getMissingKeys(obj, keys);
15 |
16 | if (missingKeys.length > 0) {
17 | res.status(400).json({ error: `The following JSON properties are missing: ${missingKeys}` });
18 | return undefined;
19 | }
20 | return obj as unknown as AssertedKeys;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/utils/assertValidBody.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiResponse } from "next";
2 | import { getMissingKeys } from "./index";
3 |
4 | type AssertedKeys = Required> & Omit;
5 |
6 | /**
7 | * Asserts that all the `keys` are defined in object `T` and, if not, inform the caller and end the session.
8 | */
9 | export default function assertValidBody(
10 | obj: T,
11 | keys: Array,
12 | res: NextApiResponse
13 | ): AssertedKeys | undefined {
14 | const missingKeys = getMissingKeys(obj, keys);
15 |
16 | if (missingKeys.length > 0) {
17 | res.status(400).json({ error: `The following JSON properties are missing: ${missingKeys}` });
18 | return undefined;
19 | }
20 | return obj as unknown as AssertedKeys;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/layouts/MainLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Container, styled } from "@mui/material";
2 | import Head from "next/head";
3 | import type { ReactNode } from "react";
4 |
5 | interface Props {
6 | children?: ReactNode;
7 | title: string;
8 | }
9 |
10 | const ParentBox = styled("div", { name: "ParentBox" })(({ theme }) => ({
11 | alignItems: "center",
12 | display: "flex",
13 | flexDirection: "column",
14 | justifyContent: "center",
15 | marginTop: theme.spacing(4)
16 | }));
17 |
18 | const MainLayout: (props: Props) => JSX.Element = ({ children, title }) => (
19 | <>
20 |
21 | {title}
22 |
23 |
24 | {children}
25 |
26 | >
27 | );
28 |
29 | export default MainLayout;
30 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/layouts/MainLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Container, styled } from "@mui/material";
2 | import Head from "next/head";
3 | import type { ReactNode } from "react";
4 |
5 | interface Props {
6 | children?: ReactNode;
7 | title: string;
8 | }
9 |
10 | const ParentBox = styled("div", { name: "ParentBox" })(({ theme }) => ({
11 | alignItems: "center",
12 | display: "flex",
13 | flexDirection: "column",
14 | justifyContent: "center",
15 | marginTop: theme.spacing(4)
16 | }));
17 |
18 | const MainLayout: (props: Props) => JSX.Element = ({ children, title }) => (
19 | <>
20 |
21 | {title}
22 |
23 |
24 | {children}
25 |
26 | >
27 | );
28 |
29 | export default MainLayout;
30 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/layouts/MainLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Container, styled } from "@mui/material";
2 | import Head from "next/head";
3 | import type { ReactNode } from "react";
4 |
5 | interface Props {
6 | children?: ReactNode;
7 | title: string;
8 | }
9 |
10 | const ParentBox = styled("div", { name: "ParentBox" })(({ theme }) => ({
11 | alignItems: "center",
12 | display: "flex",
13 | flexDirection: "column",
14 | justifyContent: "center",
15 | marginTop: theme.spacing(4)
16 | }));
17 |
18 | const MainLayout: (props: Props) => JSX.Element = ({ children, title }) => (
19 | <>
20 |
21 | {title}
22 |
23 |
24 | {children}
25 |
26 | >
27 | );
28 |
29 | export default MainLayout;
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "integration-examples",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "author": "Dwolla, Inc.",
6 | "private": true,
7 | "scripts": {
8 | "clean": "rimraf node_modules",
9 | "clean:all": "pnpm -r clean",
10 | "install:all": "pnpm -r install",
11 | "plaid:dev": "pnpm -r --filter \"@example/plaid-funding-source\" dev",
12 | "mx:dev": "pnpm -r --filter \"@example/mx-token-exchange\" dev"
13 | },
14 | "devDependencies": {
15 | "@typescript-eslint/eslint-plugin": "^5.35.1",
16 | "@typescript-eslint/parser": "^5.35.1",
17 | "eslint": "^8.22.0",
18 | "eslint-config-next": "^12.2.5",
19 | "eslint-config-prettier": "^8.5.0",
20 | "next": "^12.2.3",
21 | "prettier": "^2.7.1",
22 | "rimraf": "^3.0.2",
23 | "typescript": "^4.7.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/api/mastercard/accounts/[customerId].ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { GetCustomerAccountsOptions } from "../../../../integrations/mastercard";
3 | import { getCustomerAccounts } from "../../../../integrations/mastercard";
4 | import { assertRequestMethod, tryWithResponse } from "../../../../utils";
5 |
6 | /**
7 | * GET: Fetches customer accounts via a customer ID
8 | */
9 | export default async function mastercardAccountsApi(req: NextApiRequest, res: NextApiResponse): Promise {
10 | if (!assertRequestMethod("GET", req, res)) return;
11 | const { customerId } = req.query as unknown as GetCustomerAccountsOptions;
12 |
13 | await tryWithResponse(async () => {
14 | const accounts = await getCustomerAccounts({ customerId });
15 | res.status(200).json([...accounts]);
16 | }, res);
17 | }
18 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/hooks/useFlinksConnect.ts:
--------------------------------------------------------------------------------
1 | import { useEventListener } from "usehooks-ts";
2 |
3 | export interface ConnectLaunchOptions {
4 | url: string;
5 | }
6 |
7 | export interface ConnectEvent {
8 | step: string;
9 | }
10 |
11 | export interface ConnectSuccessEvent extends ConnectEvent {
12 | loginId: string;
13 | }
14 |
15 | export const useFlinksConnect = (onMessage: (event: MessageEvent) => void) => {
16 | useEventListener("message", onMessage);
17 |
18 | function applyStyles() {
19 | const style = document.createElement("style");
20 | style.setAttribute("type", "text/css");
21 | style.innerHTML = `
22 | @media (min-width: 768px) {
23 | .flinksconnect { width: 100%; }
24 | }`;
25 |
26 | const head = document.getElementsByTagName("head")[0];
27 | head.appendChild(style);
28 | }
29 | return { applyStyles };
30 | };
31 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/api/mastercard/customers.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateCustomerOptions } from "../../../integrations/mastercard";
3 | import { createTestingCustomer } from "../../../integrations/mastercard";
4 | import { assertRequestMethod, assertValidBody, tryWithResponse } from "../../../utils";
5 |
6 | /**
7 | * POST: Creates a customer record
8 | */
9 | export default async function mastercardCustomersApi(req: NextApiRequest, res: NextApiResponse): Promise {
10 | if (!assertRequestMethod("POST", req, res)) return;
11 | const body = req.body as CreateCustomerOptions;
12 | if (!assertValidBody(body, ["username"], res)) return;
13 |
14 | await tryWithResponse(async () => {
15 | const customerId = await createTestingCustomer({ ...body });
16 | res.status(201).json({ id: customerId });
17 | }, res);
18 | }
19 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/layouts/MainLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Container } from "@mui/material";
2 | import Head from "next/head";
3 | import React from "react";
4 |
5 | interface Props {
6 | children?: React.ReactNode;
7 | title: string;
8 | }
9 |
10 | const MainLayout: (props: Props) => JSX.Element = ({ children, title }) => (
11 | <>
12 |
13 | {title}
14 |
15 |
16 |
25 | {children}
26 |
27 |
28 | >
29 | );
30 |
31 | export default MainLayout;
32 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/api/plaid/exchange-public-token.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import { exchangePublicToken } from "../../../integrations/plaid";
3 |
4 | interface RequestBody {
5 | accountId?: string;
6 | publicToken?: string;
7 | }
8 |
9 | export default async function exchangePublicTokenApi(req: NextApiRequest, res: NextApiResponse) {
10 | if (req.method === "POST") {
11 | const { accountId, publicToken } = req.body as RequestBody;
12 |
13 | if (accountId && publicToken) {
14 | const processorToken = await exchangePublicToken(accountId, publicToken);
15 | return res.status(200).json({ processorToken });
16 | }
17 | return res.status(400).json({ error: "Both accountId and publicToken are required in the JSON body" });
18 | } else {
19 | res.status(405).end(`Method ${req.method} Not Allowed`);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/api/dwolla/exchanges.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateExchangeOptions } from "../../../integrations/dwolla";
3 | import { createExchange } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryWithResponse } from "../../../utils";
5 |
6 | /**
7 | * POST: Creates an exchange resource for a customer
8 | */
9 | export default async function dwollaExchangesApi(req: NextApiRequest, res: NextApiResponse): Promise {
10 | if (!assertRequestMethod("POST", req, res)) return;
11 | const body = req.body as CreateExchangeOptions;
12 | if (!assertValidBody(body, ["customerId", "exchangePartnerHref", "mastercardReceipt"], res)) return;
13 |
14 | await tryWithResponse(async () => {
15 | const location = await createExchange({ ...body });
16 | res.status(200).json({ location });
17 | }, res);
18 | }
19 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/api/dwolla/customers.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateUnverifiedCustomerOptions } from "../../../integrations/dwolla";
3 | import { createUnverifiedCustomer } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryWithResponse } from "../../../utils";
5 |
6 | /**
7 | * POST: Creates an unverified customer record
8 | */
9 | export default async function dwollaCustomersApi(req: NextApiRequest, res: NextApiResponse): Promise {
10 | if (!assertRequestMethod("POST", req, res)) return;
11 | const body = req.body as CreateUnverifiedCustomerOptions;
12 | if (!assertValidBody(body, ["firstName", "lastName", "email"], res)) return;
13 |
14 | await tryWithResponse(async () => {
15 | const location = await createUnverifiedCustomer({ ...body });
16 | res.status(200).json({ location });
17 | }, res);
18 | }
19 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/api/mastercard/consent.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { FetchPartnerConsentOptions } from "../../../integrations/mastercard";
3 | import { fetchPartnerConsent } from "../../../integrations/mastercard";
4 | import { assertRequestMethod, assertValidBody, tryWithResponse } from "../../../utils";
5 |
6 | /**
7 | * POST: Fetches partner consent given a (FI) account and customer ID
8 | */
9 | export default async function mastercardConsentApi(req: NextApiRequest, res: NextApiResponse): Promise {
10 | if (!assertRequestMethod("POST", req, res)) return;
11 | const body = req.body as FetchPartnerConsentOptions;
12 | if (!assertValidBody(body, ["accountId", "customerId"], res)) return;
13 |
14 | await tryWithResponse(async () => {
15 | const receipt = await fetchPartnerConsent(body);
16 | res.status(200).json({ ...receipt });
17 | }, res);
18 | }
19 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/api/dwolla/funding-sources.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import { assertRequestMethod, assertValidBody, tryWithResponse } from "../../../utils";
3 | import type { CreateFundingSourceOptions } from "../../../integrations/dwolla";
4 | import { createFundingSource } from "../../../integrations/dwolla";
5 |
6 | /**
7 | * POST: Create a funding source for a customer using an exchange
8 | */
9 | export default async function dwollaFundingSourcesApi(req: NextApiRequest, res: NextApiResponse): Promise {
10 | if (!assertRequestMethod("POST", req, res)) return;
11 | const body = req.body as CreateFundingSourceOptions;
12 | if (!assertValidBody(body, ["customerId", "exchangeUrl", "name", "type"], res)) return;
13 |
14 | await tryWithResponse(async () => {
15 | const location = await createFundingSource({ ...body });
16 | res.status(200).json({ location });
17 | }, res);
18 | }
19 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Container, styled } from "@mui/material";
3 | import Head from "next/head";
4 |
5 | const ParentBox = styled("div", { name: "ParentBox" })(({ theme }) => ({
6 | alignItems: "center",
7 | display: "flex",
8 | flexDirection: "column",
9 | justifyContent: "center",
10 | marginTop: theme.spacing(4)
11 | }));
12 |
13 | export default function RootLayout({
14 | children
15 | }: Readonly<{
16 | children: React.ReactNode;
17 | }>) {
18 | return (
19 |
20 |
21 | Dwolla Open Banking Example
22 |
23 |
24 |
25 |
26 | {children}
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/api/mx/users.ts:
--------------------------------------------------------------------------------
1 | import type { UserResponse } from "mx-platform-node";
2 | import type { NextApiRequest, NextApiResponse } from "next";
3 | import type { CreateUserOptions } from "../../../integrations/mx";
4 | import { createUser } from "../../../integrations/mx";
5 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
6 |
7 | export interface MXUsersAPIResponse {
8 | readonly user?: UserResponse;
9 | }
10 |
11 | export type MXUsersAPIBody = Partial;
12 |
13 | /**
14 | * POST: Creates an MX User.
15 | */
16 | export default async function mxUsersApi(
17 | req: Omit & { body: MXUsersAPIBody },
18 | res: NextApiResponse
19 | ) {
20 | if (!assertRequestMethod("POST", req, res)) return;
21 | const body = assertValidBody(req.body, ["email"], res);
22 | if (!body) return;
23 |
24 | await tryNextResponse(async () => {
25 | const user = await createUser(body);
26 | res.status(201).json({ user });
27 | }, res);
28 | }
29 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/api/flinks/auth-secret.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { RequestAuthSecretOptions, RequestAuthSecretResponse } from "../../../integrations/flinks";
3 | import { requestAuthSecret } from "../../../integrations/flinks";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export type FlinksAuthSecretAPIBody = Partial;
7 | export type FlinksAuthSecretAPIResponse = Partial;
8 |
9 | export default async function flinksAuthSecretApi(
10 | req: Omit & { body: FlinksAuthSecretAPIBody },
11 | res: NextApiResponse
12 | ) {
13 | if (!assertRequestMethod("POST", req, res)) return;
14 | const body = assertValidBody(req.body, ["nameOfPartner"], res);
15 | if (!body) return;
16 |
17 | await tryNextResponse(async () => {
18 | const authSecret = await requestAuthSecret(body);
19 | res.status(200).json(authSecret);
20 | }, res);
21 | }
22 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/api/flinks/request-id.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { GenerateRequestIdOptions, GenerateRequestIdResponse } from "../../../integrations/flinks";
3 | import { generateRequestId } from "../../../integrations/flinks";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export type FlinksGenerateRequestIdAPIBody = Partial;
7 | export type FlinksGenerateRequestIdAPIResponse = Partial;
8 |
9 | export default async function flinksRequestIdApi(
10 | req: Omit & { body: FlinksGenerateRequestIdAPIBody },
11 | res: NextApiResponse
12 | ) {
13 | if (!assertRequestMethod("POST", req, res)) return;
14 | const body = assertValidBody(req.body, ["loginId"], res);
15 | if (!body) return;
16 |
17 | await tryNextResponse(async () => {
18 | const requestId = await generateRequestId(body);
19 | res.status(200).json(requestId);
20 | }, res);
21 | }
22 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Container, styled } from "@mui/material";
3 | import Head from "next/head";
4 |
5 | const ParentBox = styled("div", { name: "ParentBox" })(({ theme }) => ({
6 | alignItems: "center",
7 | display: "flex",
8 | flexDirection: "column",
9 | justifyContent: "center",
10 | marginTop: theme.spacing(4)
11 | }));
12 |
13 | export default function RootLayout({
14 | children
15 | }: Readonly<{
16 | children: React.ReactNode;
17 | }>) {
18 | return (
19 |
20 |
21 | Dwolla Open Banking Example
22 |
26 |
27 |
28 |
29 | {children}
30 |
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/api/flinks/access-token.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { RequestAccessTokenOptions, RequestAccessTokenResponse } from "../../../integrations/flinks";
3 | import { requestAccessToken } from "../../../integrations/flinks";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export type FlinksAccessTokenAPIBody = Partial;
7 | export type FlinksAccessTokenAPIResponse = Partial;
8 |
9 | export default async function flinksAccessTokenApi(
10 | req: Omit & { body: FlinksAccessTokenAPIBody },
11 | res: NextApiResponse
12 | ) {
13 | if (!assertRequestMethod("POST", req, res)) return;
14 | const body = assertValidBody(req.body, ["loginId", "accountId"], res);
15 | if (!body) return;
16 |
17 | await tryNextResponse(async () => {
18 | const accessToken = await requestAccessToken(body);
19 | res.status(200).json(accessToken);
20 | }, res);
21 | }
22 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/api/flinks/accounts-summary.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { GetAccountsSummaryOptions, GetAccountsSummaryResponse } from "../../../integrations/flinks";
3 | import { getAccountsSummary } from "../../../integrations/flinks";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export type FlinksGetAccountsSummaryAPIBody = Partial;
7 | export type FlinksGetAccountsSummaryAPIResponse = Partial;
8 |
9 | export default async function flinksAccountsSummaryApi(
10 | req: Omit & { body: FlinksGetAccountsSummaryAPIBody },
11 | res: NextApiResponse
12 | ) {
13 | if (!assertRequestMethod("POST", req, res)) return;
14 | const body = assertValidBody(req.body, ["requestId"], res);
15 | if (!body) return;
16 |
17 | await tryNextResponse(async () => {
18 | const accounts = await getAccountsSummary(body);
19 | res.status(200).json(accounts);
20 | }, res);
21 | }
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dwolla, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { EmotionCache } from "@emotion/cache";
2 | import { CacheProvider } from "@emotion/react";
3 | import { CssBaseline, ThemeProvider } from "@mui/material";
4 | import type { AppProps } from "next/app";
5 | import Head from "next/head";
6 | import createEmotionCache from "../createEmotionCache";
7 | import theme from "../theme";
8 |
9 | interface Props extends AppProps {
10 | emotionCache?: EmotionCache;
11 | }
12 |
13 | const clientSideEmotionCache = createEmotionCache();
14 |
15 | function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: Props) {
16 | return (
17 |
18 |
19 | Dwolla Token Exchange Example
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default MyApp;
31 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { EmotionCache } from "@emotion/cache";
2 | import { CacheProvider } from "@emotion/react";
3 | import { CssBaseline, ThemeProvider } from "@mui/material";
4 | import type { AppProps } from "next/app";
5 | import Head from "next/head";
6 | import createEmotionCache from "../createEmotionCache";
7 | import theme from "../theme";
8 |
9 | interface Props extends AppProps {
10 | emotionCache?: EmotionCache;
11 | }
12 |
13 | const clientSideEmotionCache = createEmotionCache();
14 |
15 | function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: Props) {
16 | return (
17 |
18 |
19 | Dwolla Token Exchange Example
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default MyApp;
31 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { EmotionCache } from "@emotion/cache";
2 | import { CacheProvider } from "@emotion/react";
3 | import { CssBaseline, ThemeProvider } from "@mui/material";
4 | import type { AppProps } from "next/app";
5 | import Head from "next/head";
6 | import createEmotionCache from "../createEmotionCache";
7 | import theme from "../theme";
8 |
9 | interface Props extends AppProps {
10 | emotionCache?: EmotionCache;
11 | }
12 |
13 | const clientSideEmotionCache = createEmotionCache();
14 |
15 | function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: Props) {
16 | return (
17 |
18 |
19 | Dwolla Token Exchange Example
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default MyApp;
31 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { EmotionCache } from "@emotion/react";
2 | import { CacheProvider } from "@emotion/react";
3 | import { CssBaseline, ThemeProvider } from "@mui/material";
4 | import type { AppProps } from "next/app";
5 | import Head from "next/head";
6 | import createEmotionCache from "../createEmotionCache";
7 | import theme from "../theme";
8 |
9 | interface Props extends AppProps {
10 | emotionCache?: EmotionCache;
11 | }
12 |
13 | const clientSideEmotionCache = createEmotionCache();
14 |
15 | function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: Props) {
16 | return (
17 |
18 |
19 | Dwolla & Mastercard - Secure Exchange Example
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default MyApp;
31 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/api/dwolla/exchanges.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateExchangeOptions } from "../../../integrations/dwolla";
3 | import { createExchange } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaExchangesAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaExchangesAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Dwolla Exchange for a Customer
14 | */
15 | export default async function dwollaExchangesApi(
16 | req: Omit & { body: DwollaExchangesAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["customerId", "exchangePartnerHref", "token"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createExchange(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/api/dwolla/exchanges.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateExchangeOptions } from "../../../integrations/dwolla";
3 | import { createExchange } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaExchangesAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaExchangesAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Dwolla Exchange for a Customer
14 | */
15 | export default async function dwollaExchangesApi(
16 | req: Omit & { body: DwollaExchangesAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["customerId", "exchangePartnerHref", "token"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createExchange(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/api/dwolla/exchanges.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateExchangeOptions } from "../../../integrations/dwolla";
3 | import { createExchange } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaExchangesAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaExchangesAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Dwolla Exchange for a Customer
14 | */
15 | export default async function dwollaExchangesApi(
16 | req: Omit & { body: DwollaExchangesAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["customerId", "exchangePartnerHref", "authSecret", "accessToken"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createExchange(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/api/dwolla/customers.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateUnverifiedCustomerOptions } from "../../../integrations/dwolla";
3 | import { createUnverifiedCustomer } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaCustomersAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaCustomersAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Dwolla Unverified Customer Record
14 | */
15 | export default async function dwollaCustomersApi(
16 | req: Omit & { body: DwollaCustomersAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["firstName", "lastName", "email"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createUnverifiedCustomer(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/api/dwolla/customers.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateUnverifiedCustomerOptions } from "../../../integrations/dwolla";
3 | import { createUnverifiedCustomer } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaCustomersAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaCustomersAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Dwolla Unverified Customer Record
14 | */
15 | export default async function dwollaCustomersApi(
16 | req: Omit & { body: DwollaCustomersAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["firstName", "lastName", "email"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createUnverifiedCustomer(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/api/dwolla/customers.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateUnverifiedCustomerOptions } from "../../../integrations/dwolla";
3 | import { createUnverifiedCustomer } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaCustomersAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaCustomersAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Dwolla Unverified Customer Record
14 | */
15 | export default async function dwollaCustomersApi(
16 | req: Omit & { body: DwollaCustomersAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["firstName", "lastName", "email"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createUnverifiedCustomer(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/api/dwolla/funding-sources.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateFundingSourceOptions } from "../../../integrations/dwolla";
3 | import { createFundingSource } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaFundingSourcesAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaFundingSourceAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Funding Source for a Customer, using Dwolla's Secure Exchange
14 | */
15 | export default async function dwollaFundingSourcesApi(
16 | req: Omit & { body: DwollaFundingSourceAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["customerId", "exchangeUrl", "name", "type"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createFundingSource(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/api/mx/accounts.ts:
--------------------------------------------------------------------------------
1 | import type { AccountNumberResponse } from "mx-platform-node";
2 | import type { NextApiRequest, NextApiResponse } from "next";
3 | import type { ListVerifiedAccountsOptions } from "../../../integrations/mx";
4 | import { listVerifiedAccounts } from "../../../integrations/mx";
5 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
6 |
7 | export interface MXAccountsAPIResponse {
8 | readonly accounts?: AccountNumberResponse[];
9 | }
10 |
11 | export type MXAccountsAPIQuery = Partial;
12 |
13 | /**
14 | * GET: Fetches an array of verified accounts assigned to a member/user by GUID.
15 | */
16 | export default async function mxAccountsApi(
17 | req: Omit & { query: MXAccountsAPIQuery },
18 | res: NextApiResponse
19 | ) {
20 | if (!assertRequestMethod("GET", req, res)) return;
21 | const query = assertValidBody(req.query, ["memberGuid", "userGuid"], res);
22 | if (!query) return;
23 |
24 | await tryNextResponse(async () => {
25 | const accounts = await listVerifiedAccounts(query);
26 | res.status(200).json({ accounts });
27 | }, res);
28 | }
29 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/api/dwolla/funding-sources.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateFundingSourceOptions } from "../../../integrations/dwolla";
3 | import { createFundingSource } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaFundingSourcesAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaFundingSourceAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Funding Source for a Customer, using Dwolla's Secure Exchange
14 | */
15 | export default async function dwollaFundingSourcesApi(
16 | req: Omit & { body: DwollaFundingSourceAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["customerId", "exchangeUrl", "name", "type"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createFundingSource(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/api/dwolla/funding-sources.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { CreateFundingSourceOptions } from "../../../integrations/dwolla";
3 | import { createFundingSource } from "../../../integrations/dwolla";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface DwollaFundingSourcesAPIResponse {
7 | readonly resourceHref: string;
8 | }
9 |
10 | export type DwollaFundingSourceAPIBody = Partial;
11 |
12 | /**
13 | * POST: Creates a Funding Source for a Customer, using Dwolla's Secure Exchange
14 | */
15 | export default async function dwollaFundingSourcesApi(
16 | req: Omit & { body: DwollaFundingSourceAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["customerId", "exchangeUrl", "name", "type"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const resourceHref = await createFundingSource(body);
25 | res.status(201).json({ resourceHref });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/hooks/useNetworkAlert.ts:
--------------------------------------------------------------------------------
1 | import type { AlertColor } from "@mui/lab";
2 | import { useState } from "react";
3 |
4 | export enum NetworkState {
5 | LOADING = "LOADING",
6 | NOT_LOADING = "NOT_LOADING"
7 | }
8 |
9 | export interface MuiAlert {
10 | message: string;
11 | severity: AlertColor;
12 | }
13 |
14 | export const useNetworkAlert = () => {
15 | /**
16 | * The current MuiAlert state. Could be shown to the user.
17 | */
18 | const [alert, setAlert] = useState(null);
19 |
20 | /**
21 | * The current network state. Could be used to avoid indicate if a resource is loading.
22 | */
23 | const [networkState, setNetworkState] = useState(NetworkState.NOT_LOADING);
24 |
25 | /**
26 | * Update the current network alert by specifying both the updated alert message (or null)
27 | * and whether we have started/finished loading a resource.
28 | */
29 | function updateNetworkAlert(alert: MuiAlert | null, networkState: NetworkState): void {
30 | setAlert(alert);
31 | setNetworkState(networkState);
32 | }
33 |
34 | return {
35 | alert,
36 | networkState,
37 | updateNetworkAlert
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/api/mx/processor_token.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import type { RequestAuthorizationCodeOptions, RequestAuthorizationCodeResponse } from "../../../integrations/mx";
3 | import { requestAuthorizationCode } from "../../../integrations/mx";
4 | import { assertRequestMethod, assertValidBody, tryNextResponse } from "../../../utils";
5 |
6 | export interface MXProcessorTokenAPIResponse {
7 | readonly token?: RequestAuthorizationCodeResponse;
8 | }
9 |
10 | export type MXProcessorTokenAPIBody = Partial;
11 |
12 | /**
13 | * POST: Generates a payment processor token using an account, member, and user GUID.
14 | */
15 | export default async function mxProcessorTokenApi(
16 | req: Omit & { body: MXProcessorTokenAPIBody },
17 | res: NextApiResponse
18 | ) {
19 | if (!assertRequestMethod("POST", req, res)) return;
20 | const body = assertValidBody(req.body, ["accountGuid", "memberGuid", "userGuid"], res);
21 | if (!body) return;
22 |
23 | await tryNextResponse(async () => {
24 | const token = await requestAuthorizationCode(body);
25 | res.status(200).json({ token });
26 | }, res);
27 | }
28 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/hooks/useNetworkAlert.ts:
--------------------------------------------------------------------------------
1 | import type { AlertColor } from "@mui/material";
2 | import { useState } from "react";
3 |
4 | export enum NetworkState {
5 | LOADING = "LOADING",
6 | NOT_LOADING = "NOT_LOADING"
7 | }
8 |
9 | export interface MuiAlert {
10 | message: string;
11 | severity: AlertColor;
12 | }
13 |
14 | export interface UpdateNetworkAlertOptions {
15 | alert?: MuiAlert;
16 | networkState: NetworkState;
17 | }
18 |
19 | export const useNetworkAlert = () => {
20 | /**
21 | * The current alert state. Is usually shown to the user if not `undefined`.
22 | */
23 | const [alert, setAlert] = useState();
24 |
25 | /**
26 | * The current network state. Is usually used to indicate if a resource is loading.
27 | */
28 | const [networkState, setNetworkState] = useState(NetworkState.NOT_LOADING);
29 |
30 | /**
31 | * Update the current alert by specifying both the new message (or none) and whether
32 | * a resource has begun/finished loading.
33 | */
34 | function updateNetworkAlert({ alert, networkState }: UpdateNetworkAlertOptions) {
35 | setAlert(alert);
36 | setNetworkState(networkState);
37 | }
38 |
39 | return {
40 | alert,
41 | networkState,
42 | updateNetworkAlert
43 | };
44 | };
45 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/src/hooks/useNetworkAlert.ts:
--------------------------------------------------------------------------------
1 | import type { AlertColor } from "@mui/material";
2 | import { useState } from "react";
3 |
4 | export enum NetworkState {
5 | LOADING = "LOADING",
6 | NOT_LOADING = "NOT_LOADING"
7 | }
8 |
9 | export interface MuiAlert {
10 | message: string;
11 | severity: AlertColor;
12 | }
13 |
14 | export interface UpdateNetworkAlertOptions {
15 | alert?: MuiAlert;
16 | networkState: NetworkState;
17 | }
18 |
19 | export const useNetworkAlert = () => {
20 | /**
21 | * The current alert state. Is usually shown to the user if not `undefined`.
22 | */
23 | const [alert, setAlert] = useState();
24 |
25 | /**
26 | * The current network state. Is usually used to indicate if a resource is loading.
27 | */
28 | const [networkState, setNetworkState] = useState(NetworkState.NOT_LOADING);
29 |
30 | /**
31 | * Update the current alert by specifying both the new message (or none) and whether
32 | * a resource has begun/finished loading.
33 | */
34 | function updateNetworkAlert({ alert, networkState }: UpdateNetworkAlertOptions) {
35 | setAlert(alert);
36 | setNetworkState(networkState);
37 | }
38 |
39 | return {
40 | alert,
41 | networkState,
42 | updateNetworkAlert
43 | };
44 | };
45 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/hooks/useNetworkAlert.ts:
--------------------------------------------------------------------------------
1 | import type { AlertColor } from "@mui/material";
2 | import { useState } from "react";
3 |
4 | export enum NetworkState {
5 | LOADING = "LOADING",
6 | NOT_LOADING = "NOT_LOADING"
7 | }
8 |
9 | export interface MuiAlert {
10 | message: string;
11 | severity: AlertColor;
12 | }
13 |
14 | export interface UpdateNetworkAlertOptions {
15 | alert?: MuiAlert;
16 | networkState: NetworkState;
17 | }
18 |
19 | export const useNetworkAlert = () => {
20 | /**
21 | * The current alert state. Is usually shown to the user if not `undefined`.
22 | */
23 | const [alert, setAlert] = useState();
24 |
25 | /**
26 | * The current network state. Is usually used to indicate if a resource is loading.
27 | */
28 | const [networkState, setNetworkState] = useState(NetworkState.NOT_LOADING);
29 |
30 | /**
31 | * Update the current alert by specifying both the new message (or none) and whether
32 | * a resource has begun/finished loading.
33 | */
34 | function updateNetworkAlert({ alert, networkState }: UpdateNetworkAlertOptions) {
35 | setAlert(alert);
36 | setNetworkState(networkState);
37 | }
38 |
39 | return {
40 | alert,
41 | networkState,
42 | updateNetworkAlert
43 | };
44 | };
45 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/hooks/useNetworkAlert.ts:
--------------------------------------------------------------------------------
1 | import type { AlertColor } from "@mui/material";
2 | import { useState } from "react";
3 |
4 | export enum NetworkState {
5 | LOADING = "LOADING",
6 | NOT_LOADING = "NOT_LOADING"
7 | }
8 |
9 | export interface MuiAlert {
10 | message: string;
11 | severity: AlertColor;
12 | }
13 |
14 | export interface UpdateNetworkAlertOptions {
15 | alert?: MuiAlert;
16 | networkState: NetworkState;
17 | }
18 |
19 | export const useNetworkAlert = () => {
20 | /**
21 | * The current alert state. Is usually shown to the user if not `undefined`.
22 | */
23 | const [alert, setAlert] = useState();
24 |
25 | /**
26 | * The current network state. Is usually used to indicate if a resource is loading.
27 | */
28 | const [networkState, setNetworkState] = useState(NetworkState.NOT_LOADING);
29 |
30 | /**
31 | * Update the current alert by specifying both the new message (or none) and whether
32 | * a resource has begun/finished loading.
33 | */
34 | function updateNetworkAlert({ alert, networkState }: UpdateNetworkAlertOptions) {
35 | setAlert(alert);
36 | setNetworkState(networkState);
37 | }
38 |
39 | return {
40 | alert,
41 | networkState,
42 | updateNetworkAlert
43 | };
44 | };
45 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/hooks/useNetworkAlert.ts:
--------------------------------------------------------------------------------
1 | import type { AlertColor } from "@mui/material";
2 | import { useState } from "react";
3 |
4 | export enum NetworkState {
5 | LOADING = "LOADING",
6 | NOT_LOADING = "NOT_LOADING"
7 | }
8 |
9 | export interface MuiAlert {
10 | message: string;
11 | severity: AlertColor;
12 | }
13 |
14 | export interface UpdateNetworkAlertOptions {
15 | alert?: MuiAlert;
16 | networkState: NetworkState;
17 | }
18 |
19 | export const useNetworkAlert = () => {
20 | /**
21 | * The current alert state. Is usually shown to the user if not `undefined`.
22 | */
23 | const [alert, setAlert] = useState();
24 |
25 | /**
26 | * The current network state. Is usually used to indicate if a resource is loading.
27 | */
28 | const [networkState, setNetworkState] = useState(NetworkState.NOT_LOADING);
29 |
30 | /**
31 | * Update the current alert by specifying both the new message (or none) and whether
32 | * a resource has begun/finished loading.
33 | */
34 | function updateNetworkAlert({ alert, networkState }: UpdateNetworkAlertOptions) {
35 | setAlert(alert);
36 | setNetworkState(networkState);
37 | }
38 |
39 | return {
40 | alert,
41 | networkState,
42 | updateNetworkAlert
43 | };
44 | };
45 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/README.md:
--------------------------------------------------------------------------------
1 | # Dwolla and Plaid - Open Banking
2 |
3 | This example project, built using [Next.js](https://nextjs.org), demonstrates open banking integration between Dwolla and Plaid. The app shows how to create a Customer in Dwolla and attach a verified Funding Source using Plaid's integration with Dwolla, enabling secure exchange of financial information without you needing to handle sensitive data directly.
4 |
5 | **Note**: Since this project depends on shared dependencies, please ensure that you have executed `pnpm install` in the root directory (`integration-examples`) before continuing.
6 |
7 | ## Prerequisites
8 |
9 | 1. Create a [Dwolla Sandbox Account](https://accounts-sandbox.dwolla.com/sign-up) and obtain the necessary API keys.
10 |
11 | ## Setup
12 |
13 | 1. Rename `.env.local.example` to `.env.local`, and enter the necessary access keys and environment settings for Dwolla.
14 | 2. Run `pnpm install` to download all necessary dependencies.
15 | 3. Run `pnpm dev` to start the Next.js application! (Before connecting a bank account, please see _[Using Plaid Test Credentials](#using-plaid-test-credentials)_.)
16 |
17 | ## Using Plaid Test Credentials
18 |
19 | When using Plaid in the Sandbox environment, you can use the username `user_good` and password `pass_good` to simulate successful login. To test different authentication and connection scenarios, check out the [Plaid docs](https://plaid.com/docs/sandbox/test-credentials/).
20 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/README.md:
--------------------------------------------------------------------------------
1 | # Dwolla and MX - Secure Exchange
2 |
3 | This example project, built using [Next.js](https://nextjs.org), demonstrates how a Funding Source can be created for a Dwolla Customer using Dwolla's integration with MX via Dwolla's [Secure Exchange](https://developers.dwolla.com/docs/balance/secure-exchange) solution. By doing this, Dwolla is able to instantly verify the Funding Source without the need for your application to transmit sensitive data. (All sensitive data is retrieved directly from MX by Dwolla.)
4 |
5 | **Note**: Since this project depends on shared dependencies, please ensure that you have executed `pnpm install` in the root directory (`integration-examples`) before continuing.
6 |
7 | ## Setup
8 |
9 | 1. Create a [Dwolla Sandbox Account](https://accounts-sandbox.dwolla.com/sign-up) and an [MX Account](https://dashboard.mx.com/sign_up).
10 | 2. Rename `.env.local.example` to `.env.local`, and enter the necessary access keys for both Dwolla and MX.
11 | 3. Run `pnpm install` to download all necessary dependencies.
12 | 4. Run `pnpm dev` to start the Next.js application!
13 |
14 | ## Using MX Connect
15 |
16 | When using MX Web Widget SDK to connect a bank account, there are two development options present that work with Dwolla: MX Bank and MX Bank (OAuth). Please keep in mind, to enable MX Bank (OAuth), you must register through the MX Dashboard by completing the provided form. Please see [MX's website](https://docs.mx.com/resources/test-platform/) for all the different testing scenarios and credentials.
17 |
--------------------------------------------------------------------------------
/packages/open-banking/plaid/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@open-banking-example/plaid",
3 | "version": "1.0.0",
4 | "description": "Dwolla integration example that uses open banking with Plaid to verify a customer's bank account.",
5 | "license": "MIT",
6 | "author": "Dwolla, Inc.",
7 | "engines": {
8 | "node": ">=21.7.0"
9 | },
10 | "scripts": {
11 | "dev": "next dev",
12 | "build": "next build",
13 | "start": "next start",
14 | "checks": "../../../node_modules/.bin/tsc --pretty --noEmit && pnpm lint && pnpm prettier:check",
15 | "clean": "../../../node_modules/.bin/rimraf \"{.next,node_modules,tsconfig.tsbuildinfo}\"",
16 | "format": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json --write \"src/**/*.+(ts|tsx)\"",
17 | "lint": "next lint --config ../../../.eslintrc.json",
18 | "lint:fix": "next lint --config ../../../.eslintrc.json --fix",
19 | "prettier:check": "../../../node_modules/.bin/prettier --check --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\"",
20 | "prettier:write": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\" --write"
21 | },
22 | "dependencies": {
23 | "@emotion/react": "^11.13.5",
24 | "@emotion/styled": "^11.13.5",
25 | "@mui/lab": "6.0.0-beta.16",
26 | "@mui/material": "^6.1.8",
27 | "dwolla-v2": "^3.4.0",
28 | "next": "^15.0.3",
29 | "react": "^18.3.1",
30 | "react-plaid-link": "^3.6.1"
31 | },
32 | "devDependencies": {
33 | "@types/node": "22.9.1",
34 | "@types/react": "^18.3.12",
35 | "typescript": "^5.6.3"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/README.md:
--------------------------------------------------------------------------------
1 | # Dwolla and Flinks - Secure Exchange
2 |
3 | This example project, built using [Next.js](https://nextjs.org), demonstrates how a Funding Source can be created for a Dwolla Customer using Dwolla's integration with Flinks via Dwolla's [Secure Exchange](https://developers.dwolla.com/docs/balance/secure-exchange) solution. By doing this, Dwolla is able to instantly verify the Funding Source without the need for your application to transmit sensitive data. (All sensitive data is retrieved directly from Flinks by Dwolla.)
4 |
5 | **Note**: Since this project depends on shared dependencies, please ensure that you have executed `pnpm install` in the root directory (`integration-examples`) before continuing.
6 |
7 | ## Setup
8 |
9 | 1. Create a [Dwolla Sandbox Account](https://accounts-sandbox.dwolla.com/sign-up) and a [Flinks Account](https://docs.flinks.com/docs/welcome).
10 | 2. Rename `.env.local.example` to `.env.local`, and enter the necessary access keys for both Dwolla and Flinks.
11 | 3. Run `pnpm install` to download all necessary dependencies.
12 | 4. Run `pnpm dev` to start the Next.js application! (Before connecting a bank account, please see _[Using Flinks Connect](#using-flinks-connect)_.)
13 |
14 | ## Using Flinks Connect
15 |
16 | When using Flinks Connect in a development environment, search for Flinks Capital and enter username `jane_doe_2_accounts` and password `Everyday`. This username and password combination results in a dummy Flinks account that has a valid US routing number. Other username and password combinations with Flinks Capital may not work in Dwolla's Sandbox environment. For more information, please [see Test Institution on Flinks' website](https://docs.flinks.com/docs/test-institution).
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dwolla Integration Examples
2 |
3 | This repository contains end-to-end example applications that showcase how Dwolla can integrate with other providers, such as Plaid, to provide automatic funding source verification.
4 |
5 | Each application makes use of the latest technologies by building itself on top of NextJS/React and TypeScript, as well as demonstrating best practices regarding error handling, token management, and more.
6 |
7 | ## Getting Started
8 |
9 | Before getting started, please note that many of our example apps share common dependencies—e.g., ESLint, Prettier, and TypeScript. As such, their configurations are held within the root project, with each app referencing its respective root file. This means that before you can use any of the example apps, you must first run `pnpm install` in the project root directory.
10 |
11 | ## Example Applications
12 |
13 | You can find an exhaustive list of all example apps that this repository contains below.
14 |
15 | To get more information on installing and running a specific app, please click on its name, which will redirect you to its respective README.
16 |
17 | ### Open Banking Examples:
18 | * [Plaid](https://github.com/Dwolla/integration-examples/tree/main/packages/open-banking/plaid)
19 | * [MX](https://github.com/Dwolla/integration-examples/tree/main/packages/open-banking/mx)
20 |
21 | ### Secure Exchange Examples
22 | * [Mastercard](https://github.com/Dwolla/integration-examples/tree/main/packages/secure-token-exchange/mastercard)
23 | * [Plaid](https://github.com/Dwolla/integration-examples/tree/main/packages/secure-token-exchange/plaid)
24 | * [MX](https://github.com/Dwolla/integration-examples/tree/main/packages/secure-token-exchange/mx)
25 | * [Flinks](https://github.com/Dwolla/integration-examples/tree/main/packages/secure-token-exchange/flinks)
26 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/README.md:
--------------------------------------------------------------------------------
1 | # Dwolla and Plaid - Secure Exchange
2 |
3 | This example application, built using [Next.js](https://nextjs.org/), demonstrates how a Funding Source can be created for a Dwolla Customer using Dwolla's integration with Plaid via Dwolla's [Secure Exchange](https://developers.dwolla.com/docs/balance/secure-exchange) solution. By doing this, Dwolla is able to instantly verify the Funding Source without the need for your application to transmit sensitive data. (All sensitive data is retrieved directly from Plaid by Dwolla.)
4 |
5 | **Note**: Since this project depends on shared dependencies, please ensure that you have executed `pnpm install` in the root directory (`integration-examples`) before continuing.
6 |
7 | ## Setup
8 |
9 | 1. Create a [Dwolla Sandbox Account](https://accounts-sandbox.dwolla.com/sign-up) and a [Plaid Sandbox Account](https://dashboard.plaid.com/signup).
10 | 2. Rename `.env.local.example` to `.env.local`, and enter the necessary access keys for both Dwolla and Plaid.
11 | 3. On Plaid's dashboard under [Developers -> API](https://dashboard.plaid.com/developers/api), configure Allowed redirect URIs to include `http://localhost:3000`.
12 | 4. Since Dwolla can only verify one account, modify Plaid's [Account Select](https://dashboard.plaid.com/link/account-select) to only be "enabled for one account".
13 | 5. Run `pnpm install` to download all necessary dependencies
14 | 6. Run `pnpm dev` to start the Next.js application!
15 |
16 | ## Using Plaid Link
17 |
18 | When using [Plaid Link](https://plaid.com/docs/link/#introduction-to-link) to connect a bank account, use the following credentials to successfully verify a bank.
19 |
20 | ```bash
21 | username: user_good
22 | password: pass_good
23 | ```
24 |
25 | To simulate different flows, you can use different test credentials that can be found on [Plaid's website](https://plaid.com/docs/sandbox/test-credentials/).
26 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@open-banking-example/mx",
3 | "version": "1.0.0",
4 | "description": "Dwolla integration example that uses open banking with MX to verify a customer's bank account.",
5 | "license": "MIT",
6 | "author": "Dwolla, Inc.",
7 | "engines": {
8 | "node": ">=18.17.0"
9 | },
10 | "scripts": {
11 | "build": "next build",
12 | "checks": "../../../node_modules/.bin/tsc --pretty --noEmit && pnpm lint && pnpm prettier:check",
13 | "clean": "../../../node_modules/.bin/rimraf \"{.next,node_modules,tsconfig.tsbuildinfo}\"",
14 | "dev": "next dev",
15 | "format": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json --write \"src/**/*.+(ts|tsx)\"",
16 | "lint": "next lint --config ../../../.eslintrc.json",
17 | "lint:fix": "next lint --config ../../../.eslintrc.json --fix",
18 | "prettier:check": "../../../node_modules/.bin/prettier --check --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\"",
19 | "prettier:write": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\" --write",
20 | "start": "next start"
21 | },
22 | "dependencies": {
23 | "@emotion/cache": "^11.11.0",
24 | "@emotion/react": "^11.11.4",
25 | "@emotion/styled": "^11.11.0",
26 | "@mui/icons-material": "^6.1.6",
27 | "@mui/lab": " 6.0.0-beta.14",
28 | "@mui/material": "^6.1.6",
29 | "@mui/material-nextjs": "^6.1.6",
30 | "@mxenabled/web-widget-sdk": "^0.0.13",
31 | "dwolla-v2": "^3.4.0",
32 | "next": "14.2.4",
33 | "react": "^18",
34 | "react-dom": "^18",
35 | "uuid": "^11.0.2"
36 | },
37 | "devDependencies": {
38 | "@mxenabled/widget-post-message-definitions": "^1.4.0",
39 | "@types/node": "^22.8.4",
40 | "@types/react": "^18",
41 | "@types/react-dom": "^18",
42 | "@types/uuid": "^10.0.0",
43 | "typescript": "^5"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/plaid-funding-source",
3 | "version": "1.0.0",
4 | "description": "Dwolla integration example that uses Plaid to instantly verify a customer's bank account",
5 | "license": "MIT",
6 | "author": "Dwolla, Inc.",
7 | "scripts": {
8 | "build": "next build",
9 | "checks": "../../../node_modules/.bin/tsc --pretty --noEmit && pnpm lint && pnpm prettier:check",
10 | "clean": "../../../node_modules/.bin/rimraf \"{.next,node_modules,tsconfig.tsbuildinfo}\"",
11 | "dev": "next dev",
12 | "lint": "next lint --config ../../../.eslintrc.json",
13 | "lint:fix": "next lint --config ../../../.eslintrc.json --fix",
14 | "prettier:check": "../../../node_modules/.bin/prettier --check --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\"",
15 | "prettier:write": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\" --write",
16 | "start": "next start"
17 | },
18 | "devDependencies": {
19 | "@types/node": "18.6.3",
20 | "@types/react": "18.0.15",
21 | "@types/react-dom": "18.0.6",
22 | "@types/uuid": "^8.3.4"
23 | },
24 | "dependencies": {
25 | "@emotion/cache": "^11.11.0",
26 | "@emotion/react": "^11.11.4",
27 | "@emotion/server": "^11.11.0",
28 | "@emotion/styled": "^11.11.5",
29 | "@mui/icons-material": "^5.15.15",
30 | "@mui/lab": "5.0.0-alpha.170",
31 | "@mui/material": "^5.15.15",
32 | "@popperjs/core": "^2.11.5",
33 | "bootstrap": "^5.2.0",
34 | "dwolla-v2": "4.0.0-ts-alpha.0",
35 | "form-data": "^4.0.0",
36 | "next": "12.2.3",
37 | "plaid": "^10.9.0",
38 | "react": "18.2.0",
39 | "react-bootstrap": "^2.4.0",
40 | "react-dom": "18.2.0",
41 | "react-plaid-link": "^3.3.2",
42 | "react-usestateref": "^1.0.8",
43 | "uuid": "^8.3.2"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/flinks-token-exchange",
3 | "version": "1.0.0",
4 | "description": "Dwolla integration example that uses the Secure Token Exchange to verify a customer's bank account using Flinks",
5 | "license": "MIT",
6 | "author": "Dwolla, Inc.",
7 | "scripts": {
8 | "build": "next build",
9 | "checks": "../../../node_modules/.bin/tsc --pretty --noEmit && pnpm lint && pnpm prettier:check",
10 | "clean": "../../../node_modules/.bin/rimraf \"{.next,node_modules,tsconfig.tsbuildinfo}\"",
11 | "dev": "next dev",
12 | "format": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json --write \"src/**/*.+(ts|tsx)\"",
13 | "lint": "next lint --config ../../../.eslintrc.json",
14 | "lint:fix": "next lint --config ../../../.eslintrc.json --fix",
15 | "prettier:check": "../../../node_modules/.bin/prettier --check --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\"",
16 | "prettier:write": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\" --write",
17 | "start": "next start"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.19.3",
21 | "@types/node": "18.8.3",
22 | "@types/react": "18.0.21",
23 | "@types/react-dom": "18.0.6",
24 | "@types/uuid": "^8.3.4"
25 | },
26 | "dependencies": {
27 | "@emotion/cache": "^11.10.3",
28 | "@emotion/react": "^11.10.4",
29 | "@emotion/server": "^11.10.0",
30 | "@emotion/styled": "^11.10.4",
31 | "@mui/icons-material": "^5.10.6",
32 | "@mui/lab": "5.0.0-alpha.102",
33 | "@mui/material": "^5.10.8",
34 | "axios": "^0.27.2",
35 | "dwolla-v2": "4.0.0-ts-alpha.0",
36 | "form-data": "^4.0.0",
37 | "next": "12.3.1",
38 | "react": "18.2.0",
39 | "react-dom": "18.2.0",
40 | "usehooks-ts": "^2.9.1",
41 | "uuid": "^9.0.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/mastercard-token-exchange",
3 | "version": "1.0.0",
4 | "description": "Dwolla integration example that uses the Secure Token Exchange to verify a customer's bank account using Mastercard",
5 | "license": "MIT",
6 | "author": "Dwolla, Inc.",
7 | "scripts": {
8 | "build": "next build",
9 | "checks": "../../../node_modules/.bin/tsc --pretty --noEmit && pnpm lint && pnpm prettier:check",
10 | "clean": "../../../node_modules/.bin/rimraf \"{.next,node_modules,sdks,tsconfig.tsbuildinfo}\"",
11 | "dev": "next dev",
12 | "format": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json --write \"src/**/*.+(ts|tsx)\"",
13 | "lint": "next lint --config ../../../.eslintrc.json",
14 | "lint:fix": "next lint --config ../../../.eslintrc.json --fix",
15 | "prettier:check": "../../../node_modules/.bin/prettier --check --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\"",
16 | "prettier:write": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\" --write",
17 | "start": "next start"
18 | },
19 | "devDependencies": {
20 | "@types/node": "20.11.26",
21 | "@types/react": "18.2.65",
22 | "@types/react-dom": "18.2.21"
23 | },
24 | "dependencies": {
25 | "@emotion/cache": "^11.11.0",
26 | "@emotion/react": "^11.11.4",
27 | "@emotion/server": "^11.11.0",
28 | "@emotion/styled": "^11.11.0",
29 | "@mastercard/node-sdk": "link:sdks/mastercard",
30 | "@mui/icons-material": "^5.15.12",
31 | "@mui/lab": "5.0.0-alpha.167",
32 | "@mui/material": "^5.15.12",
33 | "axios": "^1.6.7",
34 | "core-js": "^3.36.0",
35 | "dwolla-v2": "4.0.0-ts-alpha.0",
36 | "form-data": "^4.0.0",
37 | "next": "^12.3.4",
38 | "react": "18.2.0",
39 | "react-dom": "18.2.0",
40 | "react-script-hook": "^1.7.2"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/mx-token-exchange",
3 | "version": "1.0.0",
4 | "description": "Dwolla integration example that uses the Secure Token Exchange to verify a customer's bank account using MX",
5 | "license": "MIT",
6 | "author": "Dwolla, Inc.",
7 | "scripts": {
8 | "build": "next build",
9 | "checks": "../../../node_modules/.bin/tsc --pretty --noEmit && pnpm lint && pnpm prettier:check",
10 | "clean": "../../../node_modules/.bin/rimraf \"{.next,node_modules,tsconfig.tsbuildinfo}\"",
11 | "dev": "next dev",
12 | "format": "../../../node_modules/.bin/prettier --config ../../.prettierrc.json --write \"src/**/*.+(ts|tsx)\"",
13 | "lint": "next lint --config ../../../.eslintrc.json",
14 | "lint:fix": "next lint --config ../../../.eslintrc.json --fix",
15 | "prettier:check": "../../../node_modules/.bin/prettier --check --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\"",
16 | "prettier:write": "../../../node_modules/.bin/prettier --config ../../../.prettierrc.json \"src/**/*.+(ts|tsx)\" --write",
17 | "start": "next start"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.19.3",
21 | "@mxenabled/widget-post-message-definitions": "^1.1.0",
22 | "@types/node": "18.8.3",
23 | "@types/react": "18.0.21",
24 | "@types/react-dom": "18.0.6",
25 | "@types/uuid": "^8.3.4"
26 | },
27 | "dependencies": {
28 | "@emotion/cache": "^11.10.3",
29 | "@emotion/react": "^11.10.4",
30 | "@emotion/server": "^11.10.0",
31 | "@emotion/styled": "^11.10.4",
32 | "@mui/icons-material": "^5.10.6",
33 | "@mui/lab": "5.0.0-alpha.102",
34 | "@mui/material": "^5.10.8",
35 | "@mxenabled/web-widget-sdk": "0.0.10",
36 | "dwolla-v2": "4.0.0-ts-alpha.0",
37 | "form-data": "^4.0.0",
38 | "mx-platform-node": "^0.11.2",
39 | "next": "12.3.1",
40 | "react": "18.2.0",
41 | "react-dom": "18.2.0",
42 | "uuid": "^9.0.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import createEmotionServer from "@emotion/server/create-instance";
2 | import type { DocumentContext } from "next/document";
3 | import Document, { Head, Html, Main, NextScript } from "next/document";
4 | import createEmotionCache from "../createEmotionCache";
5 | import theme from "../theme";
6 |
7 | export default class MyDocument extends Document {
8 | static async getInitialProps(ctx: DocumentContext) {
9 | const originalRenderPage = ctx.renderPage;
10 |
11 | const cache = createEmotionCache();
12 | const { extractCriticalToChunks } = createEmotionServer(cache);
13 |
14 | ctx.renderPage = () =>
15 | originalRenderPage({
16 | enhanceApp: (App) =>
17 | function EnhancedApp(props: any) {
18 | return ;
19 | }
20 | });
21 |
22 | const initialProps = await Document.getInitialProps(ctx);
23 | const emotionStyles = extractCriticalToChunks(initialProps.html);
24 | const emotionStyleTags = emotionStyles.styles.map((style) => (
25 |
30 | ));
31 |
32 | return {
33 | ...initialProps,
34 | emotionStyleTags
35 | };
36 | }
37 |
38 | render() {
39 | return (
40 |
41 |
42 |
46 |
47 |
48 | {(this.props as any).emotionStyleTags}
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import createEmotionServer from "@emotion/server/create-instance";
2 | import type { DocumentContext } from "next/document";
3 | import Document, { Head, Html, Main, NextScript } from "next/document";
4 | import createEmotionCache from "../createEmotionCache";
5 | import theme from "../theme";
6 |
7 | export default class MyDocument extends Document {
8 | static async getInitialProps(ctx: DocumentContext) {
9 | const originalRenderPage = ctx.renderPage;
10 |
11 | const cache = createEmotionCache();
12 | const { extractCriticalToChunks } = createEmotionServer(cache);
13 |
14 | ctx.renderPage = () =>
15 | originalRenderPage({
16 | enhanceApp: (App) =>
17 | function EnhancedApp(props: any) {
18 | return ;
19 | }
20 | });
21 |
22 | const initialProps = await Document.getInitialProps(ctx);
23 | const emotionStyles = extractCriticalToChunks(initialProps.html);
24 | const emotionStyleTags = emotionStyles.styles.map((style) => (
25 |
30 | ));
31 |
32 | return {
33 | ...initialProps,
34 | emotionStyleTags
35 | };
36 | }
37 |
38 | render() {
39 | return (
40 |
41 |
42 |
46 |
47 |
48 | {(this.props as any).emotionStyleTags}
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import createEmotionServer from "@emotion/server/create-instance";
2 | import type { DocumentContext } from "next/document";
3 | import Document, { Head, Html, Main, NextScript } from "next/document";
4 | import createEmotionCache from "../createEmotionCache";
5 | import theme from "../theme";
6 |
7 | export default class MyDocument extends Document {
8 | static async getInitialProps(ctx: DocumentContext) {
9 | const originalRenderPage = ctx.renderPage;
10 |
11 | const cache = createEmotionCache();
12 | const { extractCriticalToChunks } = createEmotionServer(cache);
13 |
14 | ctx.renderPage = () =>
15 | originalRenderPage({
16 | enhanceApp: (App) =>
17 | function EnhancedApp(props: any) {
18 | return ;
19 | }
20 | });
21 |
22 | const initialProps = await Document.getInitialProps(ctx);
23 | const emotionStyles = extractCriticalToChunks(initialProps.html);
24 | const emotionStyleTags = emotionStyles.styles.map((style) => (
25 |
30 | ));
31 |
32 | return {
33 | ...initialProps,
34 | emotionStyleTags
35 | };
36 | }
37 |
38 | render() {
39 | return (
40 |
41 |
42 |
46 |
47 |
48 | {(this.props as any).emotionStyleTags}
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import createEmotionServer from "@emotion/server/create-instance";
2 | import type { DocumentContext } from "next/document";
3 | import Document, { Head, Html, Main, NextScript } from "next/document";
4 | import createEmotionCache from "../createEmotionCache";
5 | import theme from "../theme";
6 |
7 | export default class MyDocument extends Document {
8 | static async getInitialProps(ctx: DocumentContext) {
9 | const originalRenderPage = ctx.renderPage;
10 |
11 | const cache = createEmotionCache();
12 | const { extractCriticalToChunks } = createEmotionServer(cache);
13 |
14 | ctx.renderPage = () =>
15 | originalRenderPage({
16 | enhanceApp: (App) =>
17 | function EnhancedApp(props: any) {
18 | return ;
19 | }
20 | });
21 |
22 | const initialProps = await Document.getInitialProps(ctx);
23 | const emotionStyles = extractCriticalToChunks(initialProps.html);
24 | const emotionStyleTags = emotionStyles.styles.map((style) => (
25 |
30 | ));
31 |
32 | return {
33 | ...initialProps,
34 | emotionStyleTags
35 | };
36 | }
37 |
38 | render() {
39 | return (
40 |
41 |
42 |
46 |
47 |
48 | {(this.props as any).emotionStyleTags}
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/integrations/plaid.ts:
--------------------------------------------------------------------------------
1 | import type { LinkTokenCreateRequest, ProcessorTokenCreateRequest } from "plaid";
2 | import {
3 | Configuration,
4 | CountryCode,
5 | PlaidApi,
6 | PlaidEnvironments,
7 | ProcessorTokenCreateRequestProcessorEnum,
8 | Products
9 | } from "plaid";
10 | import { v4 as uuidv4 } from "uuid";
11 | import { getEnvironmentVariable } from "./index";
12 |
13 | const plaidClient = new PlaidApi(
14 | new Configuration({
15 | basePath: PlaidEnvironments[getEnvironmentVariable("PLAID_ENV")],
16 | baseOptions: {
17 | headers: {
18 | "PLAID-CLIENT-ID": getEnvironmentVariable("PLAID_CLIENT_ID"),
19 | "PLAID-SECRET": getEnvironmentVariable("PLAID_SECRET"),
20 | "Plaid-Version": "2020-09-14"
21 | }
22 | }
23 | })
24 | );
25 |
26 | /**
27 | * Create a Plaid Link Token
28 | */
29 | export const createLinkToken = async () => {
30 | const request: LinkTokenCreateRequest = {
31 | client_name: "Dwolla-Plaid Integration Example",
32 | country_codes: [CountryCode.Us],
33 | language: "en",
34 | products: [Products.Auth],
35 | user: {
36 | // TODO: Generate a user ID. In a production environment, this should be fetched from a database.
37 | client_user_id: uuidv4()
38 | },
39 | redirect_uri: "http://localhost:3000"
40 | };
41 |
42 | try {
43 | const response = await plaidClient.linkTokenCreate(request);
44 | return response.data;
45 | } catch (err) {
46 | console.error("Create Link Token Failed: ", err);
47 | }
48 | };
49 |
50 | /**
51 | * Taking a Plaid Public Token and Account, exchange it for a Processor Token
52 | */
53 | export const exchangePublicToken = async (accountId: string, publicToken: string) => {
54 | try {
55 | const tokenResponse = await plaidClient.itemPublicTokenExchange({ public_token: publicToken });
56 | const accessToken = tokenResponse.data.access_token;
57 |
58 | const processTokenResponse = await plaidClient.processorTokenCreate({
59 | access_token: accessToken,
60 | account_id: accountId,
61 | processor: ProcessorTokenCreateRequestProcessorEnum.Dwolla
62 | } as ProcessorTokenCreateRequest);
63 |
64 | return processTokenResponse.data.processor_token;
65 | } catch (err) {
66 | console.error("Exchanging Public Token Failed: ", err);
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/README.md:
--------------------------------------------------------------------------------
1 | # Dwolla and Mastercard - Secure Exchange
2 |
3 | This example project, built using [Next.js](https://nextjs.org/), demonstrates how a Funding Source can be created for a Dwolla Customer using Dwolla's integration with Mastercard's [Open Banking](https://developer.mastercard.com/open-banking-us/documentation) and Dwolla's [Secure Exchange](https://developers.dwolla.com/docs/balance/secure-exchange) solution. By doing this, Dwolla is able to instantly verify the Funding Source without the need for your application to transmit sensitive data. (All the sensitive data is retrieved directly from Mastercard.)
4 |
5 | ## Notes
6 |
7 | 1. This project depends on some shared dependencies. As such, please ensure that you have executed `pnpm install` in the root directory (`integration-examples`).
8 | 2. In order to use Mastercard's Node SDK, you will need to build it yourself. A Bash script has been provided for ease of use; however, please note that the script requires [pnpm](https://pnpm.io/) as a package manager. Although you could use others, e.g. `npm` or `yarn`, it would require that the script file is modified first.
9 |
10 | ## Setup
11 |
12 | 1. Create a [Dwolla Sandbox Account](https://accounts-sandbox.dwolla.com/sign-up) and a [Mastercard Developers Account](https://developer.mastercard.com/open-banking-us/documentation/quick-start-guide/#1-generate-your-credentials).
13 | 2. Rename `.env.local.example` to `.env.local`, and enter the necessary access keys for both Dwolla and Mastercard.
14 | 3. In your terminal, `cd` into the project directory (`mastercard-token-exchange`) and execute the following commands:
15 | 1. `pnpm install` - This will install all necessary dependencies, minus Mastercard's SDK
16 | 2. `cd scripts` - This will change your working directory to `scripts`
17 | 3. `./generate_mastercard_sdk.sh` - This will generate and symlink Mastercard's Node SDK based on their OpenAPI spec. As mentioned before, please note that this script requires `pnpm` on your machine.
18 | 4. `cd ../` - This will change your working directory back to the project root
19 | 5. `pnpm dev` - This will start your Next development server
20 | 4. Once the server is running, you can now navigate to `localhost:3000` to begin using Dwolla's Secure Token Exchange integration with Mastercard!
21 |
22 | ## Using Mastercard Connect
23 |
24 | When using Mastercard Connect in a development environment, you may search for `FinBank Profiles – A` or `FinBank Profiles – B` and enter anything for the username and *profile_09* for the password. For more information, please [see bank account profiles on Mastercard's website](https://developer.mastercard.com/open-banking-us/documentation/test-the-apis/#bank-account-profiles).
25 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/scripts/generate_mastercard_sdk.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Raw API URL for Mastercard's OpenAPI YAML file
4 | # Extracted from https://developer.mastercard.com/open-banking-us/documentation/quick-start-guide/#5-generate-an-api-client-library
5 | API_RAW_URL=https://static.developer.mastercard.com/content/open-banking-us/swagger/openbanking-us.yaml
6 |
7 | # SDK directory path, relative to root
8 | PACKAGE_DIR=sdks/mastercard
9 |
10 | # Path to output directory, relative to scripts
11 | OUTPUT_DIR="../$PACKAGE_DIR"
12 |
13 | # OpenAPI Generator package name
14 | # Used for downloading OpenAPI Generator if it is not already installed for the user
15 | OPENAPI_GENERATOR_PACKAGE=@openapitools/openapi-generator-cli
16 |
17 | enter_to_exit() {
18 | # shellcheck disable=SC2162
19 | read -p "Press [Enter] to exit..."
20 | exit
21 | }
22 |
23 | # Check if the output directory already exists
24 | if [[ -d $OUTPUT_DIR ]]; then
25 | echo "The directory ${OUTPUT_DIR} already exists."
26 | read -r -p "Would you like to remove it (y/n)? " SHOULD_REMOVE
27 | SHOULD_REMOVE=$(echo "$SHOULD_REMOVE" | tr '[:upper:]' '[:lower:]')
28 |
29 | if [[ "$SHOULD_REMOVE" == "y" ]]; then
30 | rm -rf $OUTPUT_DIR
31 | else
32 | enter_to_exit
33 | fi
34 | fi
35 |
36 | # Check if both Node and pnpm are installed
37 | if ! which node >/dev/null || ! which pnpm >/dev/null; then
38 | echo "Both Node and pnpm must be installed in order to execute this script..."
39 | enter_to_exit
40 | fi
41 |
42 | # Create a new directory and cd into it
43 | mkdir -p $OUTPUT_DIR
44 | cd $OUTPUT_DIR || exit
45 |
46 | # Download Mastercard API from GitHub
47 | curl -L -o mastercard-open-banking-us-openapi.yaml $API_RAW_URL
48 |
49 | # Check if OpenAPI Generator is already installed
50 | pnpm list --depth 0 -g | grep -q $OPENAPI_GENERATOR_PACKAGE
51 | IS_OPENAPI_GENERATOR_INSTALLED=$?
52 |
53 | # If OpenAPI wasn't previously installed, install it
54 | if [[ "$IS_OPENAPI_GENERATOR_INSTALLED" == 1 ]]; then
55 | pnpm add -g $OPENAPI_GENERATOR_PACKAGE
56 | fi
57 |
58 | # Generate the SDK using OpenAPI Generator
59 | openapi-generator-cli generate -g typescript-axios \
60 | --additional-properties=npmName=@mastercard/node-sdk,withInterfaces=true,withNodeImports=true \
61 | -i ./*.yaml \
62 | -o ./
63 |
64 | # Remove old SDK, if one was present
65 | cd ../../
66 | pnpm remove @mastercard/node-sdk
67 |
68 | # Build new SDK
69 | cd $PACKAGE_DIR || exit
70 | pnpm install
71 |
72 | # CD back to project root and symlink new SDK
73 | cd ../../
74 | pnpm add "./$PACKAGE_DIR"
75 |
76 | # If the script installed OpenAPI Generator, uninstall it
77 | if [[ "$IS_OPENAPI_GENERATOR_INSTALLED" == 1 ]]; then
78 | pnpm remove -g $OPENAPI_GENERATOR_PACKAGE
79 | fi
80 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/integrations/dwolla.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "dwolla-v2";
2 | import { equalsIgnoreCase } from "../utils";
3 | import { getEnvironmentVariable } from "./";
4 |
5 | export interface CreateExchangeOptions {
6 | customerId: string;
7 | exchangePartnerHref: string;
8 | token: string;
9 | }
10 |
11 | export interface CreateFundingSourceOptions {
12 | customerId: string;
13 | exchangeUrl: string;
14 | name: string;
15 | type: "checking" | "savings";
16 | }
17 |
18 | export interface CreateUnverifiedCustomerOptions {
19 | firstName: string;
20 | lastName: string;
21 | email: string;
22 | }
23 |
24 | const client = new Client({
25 | environment: getEnvironmentVariable("DWOLLA_ENV").toLowerCase() as "production" | "sandbox",
26 | key: getEnvironmentVariable("DWOLLA_KEY"),
27 | secret: getEnvironmentVariable("DWOLLA_SECRET")
28 | });
29 |
30 | /**
31 | * Creates a customer exchange resource using the token that was retrieved from MX.
32 | */
33 | export async function createExchange({
34 | customerId,
35 | exchangePartnerHref,
36 | token
37 | }: CreateExchangeOptions): Promise {
38 | const response = await client.post(`/customers/${customerId}/exchanges`, {
39 | _links: {
40 | "exchange-partner": {
41 | href: exchangePartnerHref
42 | }
43 | },
44 | token
45 | });
46 | return response.headers.get("Location");
47 | }
48 |
49 | /**
50 | * Creates a funding source for a customer using an exchange URL.
51 | */
52 | export async function createFundingSource({
53 | customerId,
54 | exchangeUrl,
55 | name,
56 | type
57 | }: CreateFundingSourceOptions): Promise {
58 | const response = await client.post(`/customers/${customerId}/funding-sources`, {
59 | _links: {
60 | exchange: {
61 | href: exchangeUrl
62 | }
63 | },
64 | bankAccountType: type,
65 | name
66 | });
67 | return response.headers.get("Location");
68 | }
69 |
70 | /**
71 | * Creates an unverified customer record.
72 | */
73 | export async function createUnverifiedCustomer(options: CreateUnverifiedCustomerOptions): Promise {
74 | const response = await client.post("customers", { ...options });
75 | return response.headers.get("Location");
76 | }
77 |
78 | /**
79 | * Gets MX's exchange partner href (link) within Dwolla's systems.
80 | */
81 | export async function getExchangeHref(): Promise {
82 | const response = await client.get("/exchange-partners");
83 | const partnersList = response.body._embedded["exchange-partners"];
84 | const mxPartner = partnersList.filter((obj: { name: string }) => equalsIgnoreCase(obj.name, "MX"))[0];
85 | return mxPartner._links.self.href;
86 | }
87 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/integrations/dwolla.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "dwolla-v2";
2 | import { equalsIgnoreCase } from "../utils";
3 | import { getEnvironmentVariable } from "./";
4 |
5 | export interface CreateExchangeOptions {
6 | customerId: string;
7 | exchangePartnerHref: string;
8 | token: string;
9 | }
10 |
11 | export interface CreateFundingSourceOptions {
12 | customerId: string;
13 | exchangeUrl: string;
14 | name: string;
15 | type: "checking" | "savings";
16 | }
17 |
18 | export interface CreateUnverifiedCustomerOptions {
19 | firstName: string;
20 | lastName: string;
21 | email: string;
22 | }
23 |
24 | const client = new Client({
25 | environment: getEnvironmentVariable("DWOLLA_ENV").toLowerCase() as "production" | "sandbox",
26 | key: getEnvironmentVariable("DWOLLA_KEY"),
27 | secret: getEnvironmentVariable("DWOLLA_SECRET")
28 | });
29 |
30 | /**
31 | * Creates a customer exchange resource using the token that was retrieved from Plaid.
32 | */
33 | export async function createExchange({
34 | customerId,
35 | exchangePartnerHref,
36 | token
37 | }: CreateExchangeOptions): Promise {
38 | const response = await client.post(`/customers/${customerId}/exchanges`, {
39 | _links: {
40 | "exchange-partner": {
41 | href: exchangePartnerHref
42 | }
43 | },
44 | token
45 | });
46 | return response.headers.get("Location");
47 | }
48 |
49 | /**
50 | * Creates a funding source for a customer using an exchange URL.
51 | */
52 | export async function createFundingSource({
53 | customerId,
54 | exchangeUrl,
55 | name,
56 | type
57 | }: CreateFundingSourceOptions): Promise {
58 | const response = await client.post(`/customers/${customerId}/funding-sources`, {
59 | _links: {
60 | exchange: {
61 | href: exchangeUrl
62 | }
63 | },
64 | bankAccountType: type,
65 | name
66 | });
67 | return response.headers.get("Location");
68 | }
69 |
70 | /**
71 | * Creates an unverified customer record.
72 | */
73 | export async function createUnverifiedCustomer(options: CreateUnverifiedCustomerOptions): Promise {
74 | const response = await client.post("customers", { ...options });
75 | return response.headers.get("Location");
76 | }
77 |
78 | /**
79 | * Gets Plaid's exchange partner href (link) within Dwolla's systems.
80 | */
81 | export async function getExchangeHref(): Promise {
82 | const response = await client.get("/exchange-partners");
83 | const partnersList = response.body._embedded["exchange-partners"];
84 | const plaidPartner = partnersList.filter((obj: { name: string }) => equalsIgnoreCase(obj.name, "PLAID"))[0];
85 | return plaidPartner._links.self.href;
86 | }
87 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/integrations/dwolla.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "dwolla-v2";
2 |
3 | export interface CreateExchangeOptions extends WithCustomerId {
4 | exchangePartnerHref: string;
5 | mastercardReceipt: unknown;
6 | }
7 |
8 | export interface CreateFundingSourceOptions extends WithCustomerId {
9 | exchangeUrl: string;
10 | name: string;
11 | type: "checking" | "savings";
12 | }
13 |
14 | export interface CreateUnverifiedCustomerOptions {
15 | firstName: string;
16 | lastName: string;
17 | email: string;
18 | }
19 |
20 | interface WithCustomerId {
21 | customerId: string;
22 | }
23 |
24 | const client = new Client({
25 | environment: (process.env.DWOLLA_ENV as string).toLowerCase() as "production" | "sandbox",
26 | key: process.env.DWOLLA_KEY as string,
27 | secret: process.env.DWOLLA_SECRET as string
28 | });
29 |
30 | /**
31 | * Creates a customer exchange resource using the receipt that was retrieved from Mastercard.
32 | */
33 | export async function createExchange({
34 | customerId,
35 | exchangePartnerHref,
36 | mastercardReceipt
37 | }: CreateExchangeOptions): Promise {
38 | const response = await client.post(`/customers/${customerId}/exchanges`, {
39 | _links: {
40 | "exchange-partner": {
41 | href: exchangePartnerHref
42 | }
43 | },
44 | finicity: mastercardReceipt
45 | });
46 | return response.headers.get("Location");
47 | }
48 |
49 | /**
50 | * Creates a funding source for a customer using an exchange URL.
51 | */
52 | export async function createFundingSource({
53 | customerId,
54 | exchangeUrl,
55 | name,
56 | type
57 | }: CreateFundingSourceOptions): Promise {
58 | const response = await client.post(`/customers/${customerId}/funding-sources`, {
59 | _links: {
60 | exchange: {
61 | href: exchangeUrl
62 | }
63 | },
64 | bankAccountType: type,
65 | name
66 | });
67 | return response.headers.get("Location");
68 | }
69 |
70 | /**
71 | * Creates an unverified customer record.
72 | */
73 | export async function createUnverifiedCustomer(options: CreateUnverifiedCustomerOptions): Promise {
74 | const response = await client.post("customers", { ...options });
75 | return response.headers.get("Location");
76 | }
77 |
78 | /**
79 | * Gets Mastercard's exchange partner ID within Dwolla's systems.
80 | */
81 | export async function getExchangePartnerHref(): Promise {
82 | const response = await client.get("/exchange-partners");
83 | const partnersList = response.body._embedded["exchange-partners"];
84 | const mastercardPartner = partnersList.filter((obj: { name: string }) => obj.name === "Finicity")[0];
85 | return mastercardPartner._links.self.href;
86 | }
87 |
--------------------------------------------------------------------------------
/packages/open-banking/mx/src/app/connect-mx/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useEffect, useRef } from "react";
3 | import { useRouter, useSearchParams } from "next/navigation";
4 | import { ConnectWidget } from "@mxenabled/web-widget-sdk";
5 | import { Box } from "@mui/material";
6 | import { useWidgetRef } from "../../../../../secure-token-exchange/mx/src/hooks/useWidgetRef";
7 |
8 | /**
9 | * ConnectMXPage Component
10 | *
11 | * Renders the MX Connect Widget, allowing users to link their financial institutions.
12 | * This component manages widget setup, event handling, and lifecycle operations.
13 | *
14 | * Key features:
15 | * - Initializes the MX Connect Widget using the provided URL.
16 | * - Listens for widget events, including connection completion.
17 | * - Manages the widget lifecycle, ensuring proper unmounting.
18 | * - Redirects to the account selection page upon a successful connection.
19 | *
20 | * The component utilizes the useWidgetRef hook to manage the widget container
21 | * and useRef to maintain the widget instance across re-renders.
22 | */
23 |
24 | export default function Page() {
25 | const router = useRouter();
26 | const searchParams = useSearchParams();
27 | const widgetUrl = searchParams.get("widgetUrl");
28 | const { element: widgetElement, ref: widgetRef } = useWidgetRef();
29 | const widgetInstance = useRef(null);
30 |
31 | useEffect(() => {
32 | if (widgetElement && widgetUrl) {
33 | const options = {
34 | container: widgetElement,
35 | url: widgetUrl,
36 | onConnectedPrimaryAction: handleConnectedPrimaryAction
37 | // Add other event handlers as needed. Refer to this document for a list of MX events and their payloads - https://github.com/mxenabled/web-widget-sdk/blob/master/docs/widget_callback_props.md
38 | };
39 |
40 | // Mount the widget
41 | widgetInstance.current = new ConnectWidget(options);
42 | }
43 |
44 | // Unmount the widget when the component unmounts
45 | return () => {
46 | if (widgetInstance.current) {
47 | widgetInstance.current.unmount();
48 | widgetInstance.current = null;
49 | }
50 | };
51 | }, [widgetElement, widgetUrl]);
52 |
53 | /**
54 | * Handles the mx/connect/connected/primaryAction event.
55 | * This event indicates that the connection process is complete.
56 | */
57 | const handleConnectedPrimaryAction = () => {
58 | if (widgetInstance.current) {
59 | widgetInstance.current.unmount();
60 | widgetInstance.current = null;
61 | }
62 | router.push("/account-selection");
63 | };
64 |
65 | return (
66 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/integrations/dwolla.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "dwolla-v2";
2 | import { equalsIgnoreCase } from "../utils";
3 | import { getEnvironmentVariable } from "./";
4 |
5 | export interface CreateExchangeOptions {
6 | customerId: string;
7 | exchangePartnerHref: string;
8 | authSecret: string;
9 | accessToken: string;
10 | }
11 |
12 | export interface CreateFundingSourceOptions {
13 | customerId: string;
14 | exchangeUrl: string;
15 | name: string;
16 | type: "checking" | "savings";
17 | }
18 |
19 | export interface CreateUnverifiedCustomerOptions {
20 | firstName: string;
21 | lastName: string;
22 | email: string;
23 | }
24 |
25 | const client = new Client({
26 | environment: getEnvironmentVariable("DWOLLA_ENV").toLowerCase() as "production" | "sandbox",
27 | key: getEnvironmentVariable("DWOLLA_KEY"),
28 | secret: getEnvironmentVariable("DWOLLA_SECRET")
29 | });
30 |
31 | /**
32 | * Creates a customer exchange resource using the token that was retrieved from Flinks.
33 | */
34 | export async function createExchange({
35 | customerId,
36 | exchangePartnerHref,
37 | authSecret,
38 | accessToken
39 | }: CreateExchangeOptions): Promise {
40 | const response = await client.post(`/customers/${customerId}/exchanges`, {
41 | _links: {
42 | "exchange-partner": {
43 | href: exchangePartnerHref
44 | }
45 | },
46 | token: tokenifyFlinksAuth(authSecret, accessToken)
47 | });
48 | return response.headers.get("Location");
49 | }
50 |
51 | /**
52 | * Creates a funding source for a customer using an exchange URL.
53 | */
54 | export async function createFundingSource({
55 | customerId,
56 | exchangeUrl,
57 | name,
58 | type
59 | }: CreateFundingSourceOptions): Promise {
60 | const response = await client.post(`/customers/${customerId}/funding-sources`, {
61 | _links: {
62 | exchange: {
63 | href: exchangeUrl
64 | }
65 | },
66 | bankAccountType: type,
67 | name
68 | });
69 | return response.headers.get("Location");
70 | }
71 |
72 | /**
73 | * Creates an unverified customer record.
74 | */
75 | export async function createUnverifiedCustomer(options: CreateUnverifiedCustomerOptions): Promise {
76 | const response = await client.post("customers", { ...options });
77 | return response.headers.get("Location");
78 | }
79 |
80 | /**
81 | * Gets Flinks' exchange partner href (link) within Dwolla's systems.
82 | */
83 | export async function getExchangePartnerHref(): Promise {
84 | const response = await client.get("/exchange-partners");
85 | const partnersList = response.body._embedded["exchange-partners"];
86 | const flinksPartner = partnersList.filter((obj: { name: string }) => equalsIgnoreCase(obj.name, "Flinks"))[0];
87 | return flinksPartner._links.self.href;
88 | }
89 |
90 | /**
91 | * Combine Flinks AuthSecret and AccessToken in Basic Auth format and then Base64 encode.
92 | *
93 | * This token is used to create a Dwolla exchange.
94 | */
95 | function tokenifyFlinksAuth(authSecret: string, accessToken: string): string {
96 | return Buffer.from(`${authSecret}:${accessToken}`, "utf-8").toString("base64");
97 | }
98 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/plaid/src/pages/connect-plaid.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import { useRouter } from "next/router";
3 | import type { ParsedUrlQuery } from "querystring";
4 | import MainLayout from "../layouts/MainLayout";
5 | import type { PlaidLinkOnSuccess } from "react-plaid-link";
6 | import { usePlaidLink } from "react-plaid-link";
7 | import { useCallback, useEffect, useState } from "react";
8 | import { Card, CardHeader, CardContent, LinearProgress, Typography } from "@mui/material";
9 |
10 | interface Query extends ParsedUrlQuery {
11 | dwollaCustomerId: string;
12 | }
13 |
14 | export const ConnectPlaidPage: NextPage = () => {
15 | const router = useRouter();
16 | const { dwollaCustomerId } = router.query as Query;
17 | const [showProgress, setShowProgress] = useState(false);
18 |
19 | /**
20 | * The link token that is returned from Plaid used when instantiating Instant Account Verification
21 | */
22 | const [linkToken, setLinkToken] = useState(null);
23 |
24 | /**
25 | * Create a Plaid Link token and persist it in our component state
26 | */
27 | const createLinkToken = useCallback(async () => {
28 | const response = await fetch("/api/plaid/create-link-token", { method: "POST" });
29 | const data = await response.json();
30 | setLinkToken(data.link_token);
31 | console.log(`Created Link Token: ${data.link_token}`);
32 | }, [setLinkToken]);
33 |
34 | /**
35 | * Exchange Plaid's Link Token for a Process Token when Link finishes successfully
36 | */
37 | const handlePlaidLinkSuccess: PlaidLinkOnSuccess = async (publicToken, metadata) => {
38 | setShowProgress(true);
39 |
40 | /**
41 | * Calls our Next API to request a payment processor token from Plaid
42 | */
43 | const processorTokenResponse = await fetch("/api/plaid/exchange-public-token", {
44 | method: "POST",
45 | headers: {
46 | "Content-Type": "application/json"
47 | },
48 | body: JSON.stringify({
49 | accountId: metadata.accounts[0].id,
50 | publicToken
51 | })
52 | });
53 |
54 | const { processorToken } = await processorTokenResponse.json();
55 |
56 | if (!processorToken) {
57 | alert("No payment processor token returned.");
58 | return;
59 | }
60 |
61 | await router.push({
62 | pathname: "/connect-exchange",
63 | query: { dwollaCustomerId, processorToken }
64 | });
65 | };
66 |
67 | const { open: openPlaidLink, ready: isPlaidLinkReady } = usePlaidLink({
68 | onSuccess: handlePlaidLinkSuccess,
69 | token: linkToken
70 | });
71 |
72 | /**
73 | * Create a Link Token if one does not already exist
74 | */
75 | useEffect(() => {
76 | if (createLinkToken && !linkToken) {
77 | createLinkToken().catch((err) => console.error(err));
78 | }
79 | }, [createLinkToken, linkToken]);
80 |
81 | /**
82 | * Open Plaid Link when page mounts
83 | */
84 | useEffect(() => {
85 | // Check if PlaidLink is ready before opening it
86 | if (isPlaidLinkReady && openPlaidLink) {
87 | openPlaidLink();
88 | }
89 | }, [isPlaidLinkReady, openPlaidLink]);
90 |
91 | return (
92 |
93 | {showProgress && (
94 |
95 |
96 |
97 |
98 |
99 | Generating a Plaid Processor token.
100 |
101 |
102 |
103 | )}
104 |
105 | );
106 | };
107 |
108 | export default ConnectPlaidPage;
109 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mx/src/integrations/mx.ts:
--------------------------------------------------------------------------------
1 | import type { AccountNumberResponse, UserResponse } from "mx-platform-node";
2 | import { Configuration, MxPlatformApi } from "mx-platform-node";
3 | import { getEnvironmentVariable, newUuid } from "./";
4 |
5 | export interface CreateUserOptions {
6 | email: string;
7 | }
8 |
9 | export interface GenerateWidgetOptions {
10 | userGuid: string;
11 | }
12 |
13 | export interface ListVerifiedAccountsOptions {
14 | memberGuid: string;
15 | userGuid: string;
16 | }
17 |
18 | export interface RequestAuthorizationCodeOptions {
19 | accountGuid: string;
20 | memberGuid: string;
21 | userGuid: string;
22 | }
23 |
24 | export interface RequestAuthorizationCodeResponse {
25 | readonly code?: string;
26 | }
27 |
28 | export interface RequestAuthorizationCodeResponseBody {
29 | readonly authorization_code: RequestAuthorizationCodeResponse;
30 | }
31 |
32 | const ACCEPT_HEADER = "application/vnd.mx.api.v1+json";
33 | const API_KEY = getEnvironmentVariable("MX_API_KEY");
34 | const BASE_PATH = getEnvironmentVariable("MX_BASE_PATH");
35 | const CLIENT_ID = getEnvironmentVariable("MX_CLIENT_ID");
36 |
37 | const configuration = new Configuration({
38 | baseOptions: {
39 | headers: {
40 | Accept: ACCEPT_HEADER
41 | }
42 | },
43 | basePath: BASE_PATH,
44 | username: CLIENT_ID,
45 | password: API_KEY
46 | });
47 |
48 | const client = new MxPlatformApi(configuration);
49 |
50 | /**
51 | * Creates an MX user.
52 | * @see {@link https://docs.mx.com/processor_token/guides/client_guide#create_user|Create a User - MX}
53 | */
54 | export async function createUser({ email }: CreateUserOptions): Promise {
55 | return (
56 | await client.createUser({
57 | user: {
58 | id: newUuid(),
59 | email
60 | }
61 | })
62 | ).data.user;
63 | }
64 |
65 | /**
66 | * Generates a Connect URL that allows the user to connect their FI.
67 | * @see {@link https://docs.mx.com/processor_token/guides/client_guide#connect_institution|Connect the User to an Institution - MX}
68 | */
69 | export async function generateWidgetUrl({ userGuid }: GenerateWidgetOptions): Promise {
70 | return (
71 | (
72 | await client.requestConnectWidgetURL(userGuid, {
73 | config: {
74 | mode: "verification",
75 | ui_message_version: 4
76 | }
77 | })
78 | ).data.user?.connect_widget_url ?? undefined
79 | );
80 | }
81 |
82 | /**
83 | * Lists verified accounts, identified by a member/user GUID.
84 | * @see {@link https://docs.mx.com/processor_token/guides/client_guide#verified_institution|Retrieve a List of Verified Accounts - MX}
85 | */
86 | export async function listVerifiedAccounts({
87 | memberGuid,
88 | userGuid
89 | }: ListVerifiedAccountsOptions): Promise {
90 | return (await client.listAccountNumbersByMember(memberGuid, userGuid)).data.account_numbers;
91 | }
92 |
93 | /**
94 | * Requests an authorization code from MX that is sent to Dwolla.
95 | *
96 | * **Note**: This is currently making an Axios request instead of using MX's SDK since this method has not
97 | * yet been implemented in their OpenAPI spec. If it's implemented at a later date, though, then this function
98 | * should get switched out for the SDK method, provided that one exists.
99 | *
100 | * @see {@link https://docs.mx.com/api#processor_token_client_endpoints_authorization_code|Request an Authorization Code - MX}
101 | */
102 | export async function requestAuthorizationCode(
103 | options: RequestAuthorizationCodeOptions
104 | ): Promise {
105 | return (
106 | await client["axios"].post(
107 | "/authorization_code",
108 | {
109 | authorization_code: {
110 | scope: `account-guid:${options.accountGuid} member-guid:${options.memberGuid} user-guid:${options.userGuid} read-protected`
111 | }
112 | },
113 | {
114 | auth: {
115 | username: CLIENT_ID,
116 | password: API_KEY
117 | },
118 | baseURL: BASE_PATH,
119 | headers: {
120 | Accept: ACCEPT_HEADER,
121 | "Content-Type": "application/json"
122 | }
123 | }
124 | )
125 | ).data.authorization_code;
126 | }
127 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/flinks/src/integrations/flinks.ts:
--------------------------------------------------------------------------------
1 | import type { AxiosRequestConfig } from "axios";
2 | import axios from "axios";
3 | import { getEnvironmentVariable } from ".";
4 |
5 | export interface Account {
6 | readonly Id: string;
7 | }
8 |
9 | export interface GenerateRequestIdOptions {
10 | loginId: string;
11 | mostRecentCached?: boolean;
12 | }
13 |
14 | export interface GenerateRequestIdResponse {
15 | readonly RequestId: string;
16 | }
17 |
18 | export interface GetAccountsSummaryOptions {
19 | requestId: string;
20 | withBalance?: boolean;
21 | }
22 |
23 | export interface GetAccountsSummaryResponse {
24 | readonly Accounts: Account | Account[];
25 | }
26 |
27 | export interface RequestAuthSecretOptions {
28 | nameOfPartner: string;
29 | }
30 |
31 | export interface RequestAuthSecretResponse {
32 | readonly AuthSecret: string;
33 | }
34 |
35 | export interface RequestAccessTokenOptions {
36 | loginId: string;
37 | accountId: string;
38 | }
39 |
40 | export interface RequestAccessTokenResponse {
41 | readonly AccessToken: string;
42 | }
43 |
44 | const DWOLLA_ENV = getEnvironmentVariable("DWOLLA_ENV").toLowerCase() as "production" | "sandbox";
45 | const API_SECRET = getEnvironmentVariable("FLINKS_API_SECRET");
46 | const INSTANCE = getEnvironmentVariable("FLINKS_INSTANCE");
47 | const CUSTOMER_ID = getEnvironmentVariable("FLINKS_CUSTOMER_ID");
48 |
49 | const FLINKS_BASE_URI = `https://${INSTANCE}-api.private.fin.ag/v3/${CUSTOMER_ID}`;
50 |
51 | const axiosRequestConfig: AxiosRequestConfig = {
52 | headers: {
53 | Accept: "application/json"
54 | }
55 | };
56 |
57 | const axiosInstance = axios.create(axiosRequestConfig);
58 |
59 | /**
60 | * Builds a IFrame URL that allows the user to connect their FI.
61 | * @see {@link https://docs.flinks.com/docs/connecting-accounts-widget|Flinks Connect Widget}
62 | */
63 | export function buildConnectWidget(): { url: string; isDemo: boolean } {
64 | const useDemo = Boolean(DWOLLA_ENV === "sandbox");
65 | return { url: `https://${INSTANCE}-iframe.private.fin.ag/v2/?demo=${useDemo}`, isDemo: useDemo };
66 | }
67 |
68 | /**
69 | * Generates a Flinks requestId which is used to call Flinks' accounts summary API.
70 | *
71 | * @see {@link https://docs.flinks.com/reference/authorize|/Authorize}
72 | */
73 | export async function generateRequestId(options: GenerateRequestIdOptions): Promise {
74 | const response = await axiosInstance.post(
75 | `${FLINKS_BASE_URI}/BankingServices/Authorize`,
76 | {
77 | LoginId: options.loginId,
78 | MostRecentCached: options.mostRecentCached ?? true
79 | },
80 | {
81 | headers: {
82 | "Content-Type": "application/json"
83 | }
84 | }
85 | );
86 | return { RequestId: response.data.RequestId };
87 | }
88 |
89 | /**
90 | * Generates a Flinks requestId which is used to call Flinks' accounts summary API.
91 | *
92 | * @see {@link https://docs.flinks.com/reference/getaccountssummary|/GetAccountsSummary}
93 | */
94 | export async function getAccountsSummary(options: GetAccountsSummaryOptions): Promise {
95 | const response = await axiosInstance.post(
96 | `${FLINKS_BASE_URI}/BankingServices/GetAccountsSummary`,
97 | {
98 | RequestId: options.requestId,
99 | WithBalance: options.withBalance
100 | },
101 | {
102 | headers: {
103 | "Content-Type": "application/json"
104 | }
105 | }
106 | );
107 | return { Accounts: response.data.Accounts };
108 | }
109 |
110 | /**
111 | * Requests an AuthSecret from Flinks that is sent to Dwolla.
112 | *
113 | * @see {@link https://docs.flinks.com/reference/authsecret|/AuthSecret}
114 | */
115 | export async function requestAuthSecret(options: RequestAuthSecretOptions): Promise {
116 | const response = await axiosInstance.get(`${FLINKS_BASE_URI}/partnerdata/authsecret/${options.nameOfPartner}`, {
117 | headers: {
118 | Authorization: `Bearer ${API_SECRET}`
119 | }
120 | });
121 | return { AuthSecret: response.data.AuthSecret };
122 | }
123 |
124 | /**
125 | * Requests an AccessToken from Flinks that is sent to Dwolla.
126 | *
127 | * @see {@link https://docs.flinks.com/reference/partnerdata-client|/PartnerData}
128 | */
129 | export async function requestAccessToken(options: RequestAccessTokenOptions): Promise {
130 | const response = await axiosInstance.get(`${FLINKS_BASE_URI}/partnerdata/${options.loginId}/${options.accountId}`, {
131 | headers: {
132 | Authorization: `Bearer ${API_SECRET}`
133 | }
134 | });
135 | return { AccessToken: response.data.AccessToken };
136 | }
137 |
--------------------------------------------------------------------------------
/packages/secure-token-exchange/mastercard/src/hooks/useMastercardConnect.ts:
--------------------------------------------------------------------------------
1 | import useScript from "react-script-hook";
2 |
3 | export interface CommonExitEvent {
4 | code: number;
5 | reason: string;
6 | }
7 |
8 | interface CommonOptions {
9 | overlay?: string;
10 | selector?: string;
11 | }
12 |
13 | export interface ConnectLaunchOptions {
14 | destroyPrevious?: boolean;
15 | url: string;
16 | }
17 |
18 | export interface ConnectOptions extends CommonOptions {
19 | onCancel: (event: ConnectCancelEvent) => void;
20 | onError: (event: ConnectErrorEvent) => void;
21 | onSuccess: (event: ConnectSuccessEvent) => void;
22 | }
23 |
24 | export interface ConnectSuccessEvent {
25 | code: number;
26 | reason: string;
27 | reportData: [
28 | {
29 | portfolioId: string;
30 | reportId: string;
31 | type: string;
32 | }
33 | ];
34 | }
35 |
36 | export type ConnectCancelEvent = CommonExitEvent;
37 |
38 | export type ConnectErrorEvent = CommonExitEvent;
39 |
40 | type FinicityWindow = Window &
41 | typeof globalThis & {
42 | finicityConnect: {
43 | destroy: () => void;
44 |
45 | launch: (
46 | url: string,
47 | options: CommonOptions & {
48 | cancel: (event: ConnectCancelEvent) => void;
49 | error: (event: ConnectErrorEvent) => void;
50 | success: (event: ConnectSuccessEvent) => void;
51 | }
52 | ) => void;
53 | };
54 | };
55 |
56 | // https://connect2.finicity.com/assets/sdk/finicity-connect.min.js
57 | const MASTERCARD_CONNECT_URL = "https://connect2.finicity.com/assets/sdk/finicity-connect.min.js";
58 |
59 | // Extracted from MASTERCARD_CONNECT_URL above
60 | const MASTERCARD_IFRAME_ID = "finicityConnectIframe";
61 |
62 | const noop = () => undefined;
63 |
64 | export const useMastercardConnect = (options: ConnectOptions) => {
65 | // Load Mastercard's script from their CDN
66 | const [loading, error] = useScript({
67 | checkForExisting: true,
68 | src: MASTERCARD_CONNECT_URL
69 | });
70 |
71 | /**
72 | * Creates a `