Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🍒 Cherry pick PR #6259 to staging 🍒 #6571

Merged
merged 3 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 1001011704
versionName "1.1.17-4"
versionCode 1001011705
versionName "1.1.17-5"
}
splits {
abi {
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.1.17.4</string>
<string>1.1.17.5</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensifyTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.1.17.4</string>
<string>1.1.17.5</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
"version": "1.1.17-4",
"version": "1.1.17-5",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
Expand Down
3 changes: 2 additions & 1 deletion src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const IOU_BILL_CURRENCY = `${IOU_BILL}/currency`;
const IOU_SEND_CURRENCY = `${IOU_SEND}/currency`;

export default {
BANK_ACCOUNT: 'bank-account/:stepToOpen?',
BANK_ACCOUNT: 'bank-account',
BANK_ACCOUNT_WITH_STEP_TO_OPEN: 'bank-account/:stepToOpen?',
BANK_ACCOUNT_PERSONAL: 'bank-account/personal',
getBankAccountRoute: (stepToOpen = '') => `bank-account/${stepToOpen}`,
HOME: '',
Expand Down
49 changes: 40 additions & 9 deletions src/components/AddPlaidBankAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ const propTypes = {

/** Additional text to display */
text: PropTypes.string,

/** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */
receivedRedirectURI: PropTypes.string,

/** During the OAuth flow we need to use the plaidLink token that we initially connected with */
plaidLinkOAuthToken: PropTypes.string,
};

const defaultProps = {
Expand All @@ -82,13 +88,16 @@ const defaultProps = {
onExitPlaid: () => {},
onSubmit: () => {},
text: '',
receivedRedirectURI: null,
plaidLinkOAuthToken: '',
};

class AddPlaidBankAccount extends React.Component {
constructor(props) {
super(props);

this.selectAccount = this.selectAccount.bind(this);
this.getPlaidLinkToken = this.getPlaidLinkToken.bind(this);

this.state = {
selectedIndex: undefined,
Expand All @@ -100,6 +109,12 @@ class AddPlaidBankAccount extends React.Component {
}

componentDidMount() {
// If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken
// Otherwise, clear the existing token and fetch a new one
if (this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) {
return;
}

BankAccounts.clearPlaidBankAccountsAndToken();
BankAccounts.fetchPlaidLinkToken();
}
Expand All @@ -113,6 +128,19 @@ class AddPlaidBankAccount extends React.Component {
return lodashGet(this.props.plaidBankAccounts, 'accounts', []);
}

/**
* @returns {String}
*/
getPlaidLinkToken() {
if (!_.isEmpty(this.props.plaidLinkToken)) {
return this.props.plaidLinkToken;
}

if (this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) {
return this.props.plaidLinkOAuthToken;
}
}

/**
* @returns {Boolean}
*/
Expand All @@ -136,27 +164,29 @@ class AddPlaidBankAccount extends React.Component {
this.props.onSubmit({
bankName,
account,
plaidLinkToken: this.props.plaidLinkToken,
plaidLinkToken: this.getPlaidLinkToken(),
});
}

render() {
const accounts = this.getAccounts();
const token = this.getPlaidLinkToken();
const options = _.map(accounts, (account, index) => ({
value: index, label: `${account.addressName} ${account.accountNumber}`,
}));
const {icon, iconSize} = getBankIcon(this.state.institution.name);

return (
<>
{(!this.props.plaidLinkToken || this.props.plaidBankAccounts.loading)
&& (
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter]}>
<ActivityIndicator color={themeColors.spinner} size="large" />
</View>
)}
{!_.isEmpty(this.props.plaidLinkToken) && (
{(!token || this.props.plaidBankAccounts.loading)
&& (
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter]}>
<ActivityIndicator color={themeColors.spinner} size="large" />
</View>
)}
{token && (
<PlaidLink
token={this.props.plaidLinkToken}
token={token}
onSuccess={({publicToken, metadata}) => {
Log.info('[PlaidLink] Success!');
BankAccounts.getPlaidBankAccounts(publicToken, metadata.institution.name);
Expand All @@ -169,6 +199,7 @@ class AddPlaidBankAccount extends React.Component {
// User prematurely exited the Plaid flow
// eslint-disable-next-line react/jsx-props-no-multi-spaces
onExit={this.props.onExitPlaid}
receivedRedirectURI={this.props.receivedRedirectURI}
/>
)}
{accounts.length > 0 && (
Expand Down
4 changes: 4 additions & 0 deletions src/components/PlaidLink/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const PlaidLink = (props) => {
onEvent: (event, metadata) => {
Log.info('[PlaidLink] Event: ', false, {event, metadata});
},

// The redirect URI with an OAuth state ID. Needed to re-initialize the PlaidLink after directing the
// user to their respective bank platform
receivedRedirectUri: props.receivedRedirectURI,
});

useEffect(() => {
Expand Down
5 changes: 5 additions & 0 deletions src/components/PlaidLink/plaidLinkPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ const plaidLinkPropTypes = {

// Callback to execute when the user leaves the Plaid widget flow without entering any information
onExit: PropTypes.func,

// The redirect URI with an OAuth state ID. Needed to re-initialize the PlaidLink after directing the
// user to their respective bank platform
receivedRedirectURI: PropTypes.string,
};

const plaidLinkDefaultProps = {
onSuccess: () => {},
onError: () => {},
onExit: () => {},
receivedRedirectURI: null,
};

export {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default {
path: ROUTES.WORKSPACE_INVITE,
},
ReimbursementAccount: {
path: ROUTES.BANK_ACCOUNT,
path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN,
exact: true,
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/libs/getPlaidLinkTokenParameters/index.android.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import CONST from '../../CONST';

export default () => ({android_name: CONST.ANDROID_PACKAGE_NAME});
export default () => ({android_package: CONST.ANDROID_PACKAGE_NAME});
8 changes: 7 additions & 1 deletion src/libs/getPlaidLinkTokenParameters/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export default () => ({});
import ROUTES from '../../ROUTES';
import CONFIG from '../../CONFIG';

export default () => {
const bankAccountRoute = window.location.href.includes('personal') ? ROUTES.BANK_ACCOUNT_PERSONAL : ROUTES.BANK_ACCOUNT;
return {redirect_uri: `${CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH}/${bankAccountRoute}`};
};
17 changes: 17 additions & 0 deletions src/libs/getPlaidOAuthReceivedRedirectURI/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* After a user authenticates their bank in the Plaid OAuth flow, Plaid returns us to the redirectURI we
* gave them along with a stateID param. We hand off the receivedRedirectUri to PlaidLink to finish connecting
* the user's account.
* @returns {String | null}
*/
export default () => {
const receivedRedirectURI = window.location.href;
const receivedRedirectSearchParams = (new URL(window.location.href)).searchParams;
const oauthStateID = receivedRedirectSearchParams.get('oauth_state_id');

// If no stateID passed in then we are either not in OAuth flow or flow is broken
if (!oauthStateID) {
return null;
}
return receivedRedirectURI;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => null;
25 changes: 24 additions & 1 deletion src/pages/AddPersonalBankAccountPage.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
import ScreenWrapper from '../components/ScreenWrapper';
import Navigation from '../libs/Navigation/Navigation';
import * as BankAccounts from '../libs/actions/BankAccounts';
import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
import AddPlaidBankAccount from '../components/AddPlaidBankAccount';
import getPlaidOAuthReceivedRedirectURI from '../libs/getPlaidOAuthReceivedRedirectURI';
import compose from '../libs/compose';
import ONYXKEYS from '../ONYXKEYS';

const propTypes = {
...withLocalizePropTypes,

/** Plaid SDK token to use to initialize the widget */
plaidLinkToken: PropTypes.string,
};

const defaultProps = {
plaidLinkToken: '',
};

const AddPersonalBankAccountPage = props => (
Expand All @@ -21,10 +33,21 @@ const AddPersonalBankAccountPage = props => (
BankAccounts.addPersonalBankAccount(account, password, plaidLinkToken);
}}
onExitPlaid={Navigation.dismissModal}
receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()}
plaidLinkOAuthToken={props.plaidLinkToken}
/>
</ScreenWrapper>
);

AddPersonalBankAccountPage.propTypes = propTypes;
AddPersonalBankAccountPage.defaultProps = defaultProps;
AddPersonalBankAccountPage.displayName = 'AddPersonalBankAccountPage';
export default withLocalize(AddPersonalBankAccountPage);

export default compose(
withLocalize,
withOnyx({
plaidLinkToken: {
key: ONYXKEYS.PLAID_LINK_TOKEN,
},
}),
)(AddPersonalBankAccountPage);
20 changes: 19 additions & 1 deletion src/pages/ReimbursementAccount/BankAccountStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'underscore';
import React from 'react';
import {View, Image} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
import MenuItem from '../../components/MenuItem';
import * as Expensicons from '../../components/Icon/Expensicons';
Expand Down Expand Up @@ -31,9 +32,20 @@ const propTypes = {
// eslint-disable-next-line react/no-unused-prop-types
reimbursementAccount: reimbursementAccountPropTypes.isRequired,

/** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */
receivedRedirectURI: PropTypes.string,

/** During the OAuth flow we need to use the plaidLink token that we initially connected with */
plaidLinkOAuthToken: PropTypes.string,

...withLocalizePropTypes,
};

const defaultProps = {
receivedRedirectURI: null,
plaidLinkOAuthToken: '',
};

class BankAccountStep extends React.Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -158,7 +170,9 @@ class BankAccountStep extends React.Component {
// Disable bank account fields once they've been added in db so they can't be changed
const isFromPlaid = this.props.achData.setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID;
const shouldDisableInputs = Boolean(this.props.achData.bankAccountID) || isFromPlaid;
const subStep = this.props.achData.subStep;
const shouldReinitializePlaidLink = this.props.plaidLinkOAuthToken && this.props.receivedRedirectURI;
const subStep = shouldReinitializePlaidLink ? CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID : this.props.achData.subStep;

return (
<View style={[styles.flex1, styles.justifyContentBetween]}>
<HeaderWithCloseButton
Expand Down Expand Up @@ -238,6 +252,8 @@ class BankAccountStep extends React.Component {
text={this.props.translate('bankAccount.plaidBodyCopy')}
onSubmit={this.addPlaidAccount}
onExitPlaid={() => BankAccounts.setBankAccountSubStep(null)}
receivedRedirectURI={this.props.receivedRedirectURI}
plaidLinkOAuthToken={this.props.plaidLinkOAuthToken}
/>
)}
{subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL && (
Expand Down Expand Up @@ -293,6 +309,8 @@ class BankAccountStep extends React.Component {
}

BankAccountStep.propTypes = propTypes;
BankAccountStep.defaultProps = defaultProps;

export default compose(
withLocalize,
withOnyx({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import compose from '../../libs/compose';
import styles from '../../styles/styles';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import Text from '../../components/Text';
import getPlaidOAuthReceivedRedirectURI from '../../libs/getPlaidOAuthReceivedRedirectURI';

// Steps
import BankAccountStep from './BankAccountStep';
Expand Down Expand Up @@ -203,14 +204,15 @@ class ReimbursementAccountPage extends React.Component {
</ScreenWrapper>
);
}

return (
<ScreenWrapper>
<KeyboardAvoidingView>
{currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT && (
<BankAccountStep
achData={achData}
isPlaidDisabled={this.props.reimbursementAccount.isPlaidDisabled}
receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()}
plaidLinkOAuthToken={this.props.plaidLinkToken}
/>
)}
{currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && (
Expand Down Expand Up @@ -251,6 +253,9 @@ export default compose(
betas: {
key: ONYXKEYS.BETAS,
},
plaidLinkToken: {
key: ONYXKEYS.PLAID_LINK_TOKEN,
},
}),
withLocalize,
)(ReimbursementAccountPage);