Skip to content

Commit

Permalink
Merge pull request #49411 from waterim/feat-42490-sca-transfer-owner
Browse files Browse the repository at this point in the history
  • Loading branch information
blimpich committed Sep 30, 2024
2 parents 1c28c37 + 02497ca commit d589746
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type VerifySetupIntentAndRequestPolicyOwnerChangeParams = {
policyID: string;
accountID: number;
};
export default VerifySetupIntentAndRequestPolicyOwnerChangeParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type {default as ActivatePhysicalExpensifyCardParams} from './ActivatePhy
export type {default as AddNewContactMethodParams} from './AddNewContactMethodParams';
export type {default as AddPaymentCardParams} from './AddPaymentCardParams';
export type {default as VerifySetupIntentParams} from './VerifySetupIntentParams';
export type {default as VerifySetupIntentAndRequestPolicyOwnerChangeParams} from './VerifySetupIntentAndRequestPolicyOwnerChangeParams';
export type {default as AddPersonalBankAccountParams} from './AddPersonalBankAccountParams';
export type {default as RestartBankAccountSetupParams} from './RestartBankAccountSetupParams';
export type {default as AddSchoolPrincipalParams} from './AddSchoolPrincipalParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const WRITE_COMMANDS = {
ADD_PAYMENT_CARD: 'AddPaymentCard',
ADD_PAYMENT_CARD_GBP: 'AddPaymentCardGBP',
VERIFY_SETUP_INTENT: 'User_VerifySetupIntent',
VERIFY_SETUP_INTENT_AND_REQUEST_POLICY_OWNER_CHANGE: 'VerifySetupIntentAndRequestPolicyOwnerChange',
TRANSFER_WALLET_BALANCE: 'TransferWalletBalance',
DELETE_PAYMENT_CARD: 'DeletePaymentCard',
UPDATE_PRONOUNS: 'UpdatePronouns',
Expand Down Expand Up @@ -439,6 +440,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.ADD_PAYMENT_CARD]: Parameters.AddPaymentCardParams;
[WRITE_COMMANDS.ADD_PAYMENT_CARD_GBP]: Parameters.AddPaymentCardParams;
[WRITE_COMMANDS.VERIFY_SETUP_INTENT]: Parameters.VerifySetupIntentParams;
[WRITE_COMMANDS.VERIFY_SETUP_INTENT_AND_REQUEST_POLICY_OWNER_CHANGE]: Parameters.VerifySetupIntentAndRequestPolicyOwnerChangeParams;
[WRITE_COMMANDS.DELETE_PAYMENT_CARD]: Parameters.DeletePaymentCardParams;
[WRITE_COMMANDS.UPDATE_PRONOUNS]: Parameters.UpdatePronounsParams;
[WRITE_COMMANDS.UPDATE_DISPLAY_NAME]: Parameters.UpdateDisplayNameParams;
Expand Down
27 changes: 18 additions & 9 deletions src/libs/actions/PaymentMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {BankAccountList, FundList} from '@src/types/onyx';
import type {AccountData} from '@src/types/onyx/Fund';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type PaymentMethod from '@src/types/onyx/PaymentMethod';
import type {OnyxData} from '@src/types/onyx/Request';
import type {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer';

type KYCWallRef = {
Expand Down Expand Up @@ -255,15 +256,7 @@ function addSubscriptionPaymentCard(cardData: {
];

if (currency === CONST.PAYMENT_CARD_CURRENCY.GBP) {
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.ADD_PAYMENT_CARD_GBP, parameters, {optimisticData, successData, failureData}).then((response) => {
if (response?.jsonCode !== CONST.JSON_CODE.SUCCESS) {
return;
}

// We are using this onyx key to open Modal and preview iframe. Potentially we can save the whole object which come from side effect
Onyx.set(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION, (response as {authenticationLink: string}).authenticationLink);
});
addPaymentCardGBP(parameters, {optimisticData, successData, failureData});
} else {
// eslint-disable-next-line rulesdir/no-multiple-api-calls
API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, {
Expand All @@ -274,6 +267,21 @@ function addSubscriptionPaymentCard(cardData: {
}
}

/**
* Calls the API to add a new GBP card.
* Updates verify3dsSubscription Onyx key with a new authentication link for 3DS.
*/
function addPaymentCardGBP(params: AddPaymentCardParams, onyxData: OnyxData = {}) {
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.ADD_PAYMENT_CARD_GBP, params, onyxData).then((response) => {
if (response?.jsonCode !== CONST.JSON_CODE.SUCCESS) {
return;
}
// We are using this onyx key to open Modal and preview iframe. Potentially we can save the whole object which come from side effect
Onyx.set(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION, (response as {authenticationLink: string}).authenticationLink);
});
}

/**
* Resets the values for the add payment card form back to their initial states
*/
Expand Down Expand Up @@ -600,5 +608,6 @@ export {
clearWalletTermsError,
setPaymentCardForm,
verifySetupIntent,
addPaymentCardGBP,
setInvoicingTransferBankAccount,
};
85 changes: 74 additions & 11 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {ReportExportType} from '@components/ButtonWithDropdownMenu/types';
import * as API from '@libs/API';
import type {
AddBillingCardAndRequestWorkspaceOwnerChangeParams,
AddPaymentCardParams,
CreateWorkspaceFromIOUPaymentParams,
CreateWorkspaceParams,
DeleteWorkspaceAvatarParams,
Expand Down Expand Up @@ -73,6 +74,7 @@ import * as ReportConnection from '@libs/ReportConnection';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import type {PolicySelector} from '@pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover';
import * as PaymentMethods from '@userActions/PaymentMethods';
import * as PersistedRequests from '@userActions/PersistedRequests';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -836,18 +838,78 @@ function addBillingCardAndRequestPolicyOwnerChange(
},
];

const params: AddBillingCardAndRequestWorkspaceOwnerChangeParams = {
policyID,
cardNumber,
cardYear,
cardMonth,
cardCVV,
addressName,
addressZip,
currency,
};
if (currency === CONST.PAYMENT_CARD_CURRENCY.GBP) {
const params: AddPaymentCardParams = {
cardNumber,
cardYear,
cardMonth,
cardCVV,
addressName,
addressZip,
currency,
isP2PDebitCard: false,
};
PaymentMethods.addPaymentCardGBP(params);
} else {
const params: AddBillingCardAndRequestWorkspaceOwnerChangeParams = {
policyID,
cardNumber,
cardYear,
cardMonth,
cardCVV,
addressName,
addressZip,
currency,
};
// eslint-disable-next-line rulesdir/no-multiple-api-calls
API.write(WRITE_COMMANDS.ADD_BILLING_CARD_AND_REQUEST_WORKSPACE_OWNER_CHANGE, params, {optimisticData, successData, failureData});
}
}

API.write(WRITE_COMMANDS.ADD_BILLING_CARD_AND_REQUEST_WORKSPACE_OWNER_CHANGE, params, {optimisticData, successData, failureData});
/**
* Properly updates the nvp_privateStripeCustomerID onyx data for 3DS payment
*
*/
function verifySetupIntentAndRequestPolicyOwnerChange(policyID: string) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
errorFields: null,
isLoading: true,
isChangeOwnerSuccessful: false,
isChangeOwnerFailed: false,
},
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
isLoading: false,
isChangeOwnerSuccessful: true,
isChangeOwnerFailed: false,
owner: sessionEmail,
ownerAccountID: sessionAccountID,
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
isLoading: false,
isChangeOwnerSuccessful: false,
isChangeOwnerFailed: true,
},
},
];
API.write(WRITE_COMMANDS.VERIFY_SETUP_INTENT_AND_REQUEST_POLICY_OWNER_CHANGE, {accountID: sessionAccountID, policyID}, {optimisticData, successData, failureData});
}

/**
Expand Down Expand Up @@ -4755,6 +4817,7 @@ export {
updateCompanyCardName,
setCompanyCardExportAccount,
clearCompanyCardErrorField,
verifySetupIntentAndRequestPolicyOwnerChange,
};

export type {NewCustomUnit};
24 changes: 24 additions & 0 deletions src/libs/shouldRenderTransferOwnerButton/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import isEmpty from 'lodash/isEmpty';
import Onyx from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import type {FundList} from '@src/types/onyx';
import type ShouldRenderTransferOwnerButton from './types';

let fundList: OnyxEntry<FundList>;
Onyx.connect({
key: ONYXKEYS.FUND_LIST,
callback: (value) => {
if (!value) {
return;
}

fundList = value;
},
});

const shouldRenderTransferOwnerButton: ShouldRenderTransferOwnerButton = () => {
return !isEmpty(fundList);
};

export default shouldRenderTransferOwnerButton;
5 changes: 5 additions & 0 deletions src/libs/shouldRenderTransferOwnerButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type ShouldRenderTransferOwnerButton from './types';

const shouldRenderTransferOwnerButton: ShouldRenderTransferOwnerButton = () => true;

export default shouldRenderTransferOwnerButton;
3 changes: 3 additions & 0 deletions src/libs/shouldRenderTransferOwnerButton/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type ShouldRenderTransferOwnerButton = () => boolean;

export default ShouldRenderTransferOwnerButton;
13 changes: 10 additions & 3 deletions src/pages/settings/Subscription/CardAuthenticationModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import Modal from '@components/Modal';
import ScreenWrapper from '@components/ScreenWrapper';
import useThemeStyles from '@hooks/useThemeStyles';
import * as PaymentMethods from '@userActions/PaymentMethods';
import * as PolicyActions from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

type CardAuthenticationModalProps = {
/** Title shown in the header of the modal */
headerTitle?: string;

policyID?: string;
};
function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) {
function CardAuthenticationModal({headerTitle, policyID}: CardAuthenticationModalProps) {
const styles = useThemeStyles();
const [authenticationLink] = useOnyx(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION);
const [session] = useOnyx(ONYXKEYS.SESSION);
Expand All @@ -37,11 +40,15 @@ function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) {
(event: MessageEvent<string>) => {
const message = event.data;
if (message === CONST.GBP_AUTHENTICATION_COMPLETE) {
PaymentMethods.verifySetupIntent(session?.accountID ?? -1, true);
if (policyID) {
PolicyActions.verifySetupIntentAndRequestPolicyOwnerChange(policyID);
} else {
PaymentMethods.verifySetupIntent(session?.accountID ?? -1, true);
}
onModalClose();
}
},
[onModalClose, session?.accountID],
[onModalClose, policyID, session?.accountID],
);

useEffect(() => {
Expand Down
19 changes: 11 additions & 8 deletions src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import shouldRenderTransferOwnerButton from '@libs/shouldRenderTransferOwnerButton';
import Navigation from '@navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
Expand Down Expand Up @@ -240,14 +241,16 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
</Text>
)}
{isSelectedMemberOwner && isCurrentUserAdmin && !isCurrentUserOwner ? (
<Button
text={translate('workspace.people.transferOwner')}
onPress={startChangeOwnershipFlow}
isDisabled={isOffline}
icon={Expensicons.Transfer}
iconStyles={StyleUtils.getTransformScaleStyle(0.8)}
style={styles.mv5}
/>
shouldRenderTransferOwnerButton() && (
<Button
text={translate('workspace.people.transferOwner')}
onPress={startChangeOwnershipFlow}
isDisabled={isOffline}
icon={Expensicons.Transfer}
iconStyles={StyleUtils.getTransformScaleStyle(0.8)}
style={styles.mv5}
/>
)
) : (
<Button
text={translate('workspace.people.removeWorkspaceMemberButtonTitle')}
Expand Down
29 changes: 18 additions & 11 deletions src/pages/workspace/members/WorkspaceOwnerChangeWrapperPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand All @@ -9,11 +10,13 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import CardAuthenticationModal from '@pages/settings/Subscription/CardAuthenticationModal';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy';
import * as MemberActions from '@userActions/Policy/Member';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import WorkspaceOwnerChangeCheck from './WorkspaceOwnerChangeCheck';
Expand All @@ -24,10 +27,12 @@ type WorkspaceOwnerChangeWrapperPageProps = WithPolicyOnyxProps & StackScreenPro
function WorkspaceOwnerChangeWrapperPage({route, policy}: WorkspaceOwnerChangeWrapperPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const [privateStripeCustomerID] = useOnyx(ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID);
const policyID = route.params.policyID;
const accountID = route.params.accountID;
const error = route.params.error;
const isAuthRequired = privateStripeCustomerID?.status === CONST.STRIPE_GBP_AUTH_STATUSES.CARD_AUTHENTICATION_REQUIRED;
const shouldShowPaymentCardForm = error === CONST.POLICY.OWNERSHIP_ERRORS.NO_BILLING_CARD || isAuthRequired;

useEffect(() => {
if (!policy || policy?.isLoading) {
Expand Down Expand Up @@ -71,16 +76,18 @@ function WorkspaceOwnerChangeWrapperPage({route, policy}: WorkspaceOwnerChangeWr
/>
<View style={[styles.containerWithSpaceBetween, error !== CONST.POLICY.OWNERSHIP_ERRORS.NO_BILLING_CARD ? styles.ph5 : styles.ph0, styles.pb0]}>
{policy?.isLoading && <FullScreenLoadingIndicator />}
{!policy?.isLoading &&
(error === CONST.POLICY.OWNERSHIP_ERRORS.NO_BILLING_CARD ? (
<WorkspaceOwnerPaymentCardForm policy={policy} />
) : (
<WorkspaceOwnerChangeCheck
policy={policy}
accountID={accountID}
error={error}
/>
))}
{shouldShowPaymentCardForm && <WorkspaceOwnerPaymentCardForm policy={policy} />}
{!policy?.isLoading && !shouldShowPaymentCardForm && (
<WorkspaceOwnerChangeCheck
policy={policy}
accountID={accountID}
error={error}
/>
)}
<CardAuthenticationModal
headerTitle={translate('subscription.authenticatePaymentCard')}
policyID={policyID}
/>
</View>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function WorkspaceOwnerPaymentCardForm({policy}: WorkspaceOwnerPaymentCardFormPr
cardCVV: values.securityCode,
addressName: values.nameOnCard,
addressZip: values.addressZipCode,
currency: CONST.CURRENCY.USD,
currency: values.currency,
};

PolicyActions.addBillingCardAndRequestPolicyOwnerChange(policyID, cardData);
Expand Down

0 comments on commit d589746

Please sign in to comment.