diff --git a/src/libs/API/parameters/VerifySetupIntentAndRequestPolicyOwnerChangeParams.ts b/src/libs/API/parameters/VerifySetupIntentAndRequestPolicyOwnerChangeParams.ts new file mode 100644 index 000000000000..1deb9f0c7619 --- /dev/null +++ b/src/libs/API/parameters/VerifySetupIntentAndRequestPolicyOwnerChangeParams.ts @@ -0,0 +1,5 @@ +type VerifySetupIntentAndRequestPolicyOwnerChangeParams = { + policyID: string; + accountID: number; +}; +export default VerifySetupIntentAndRequestPolicyOwnerChangeParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 28a0e622689b..0ad5c9644e9f 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -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'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 50a7c8f58286..7adcebbe2872 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -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', @@ -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; diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index 59f1a6b06aed..5276f56d59bd 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -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 = { @@ -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, { @@ -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 */ @@ -600,5 +608,6 @@ export { clearWalletTermsError, setPaymentCardForm, verifySetupIntent, + addPaymentCardGBP, setInvoicingTransferBankAccount, }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 60cab1787700..bdc47386890b 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -8,6 +8,7 @@ import type {ReportExportType} from '@components/ButtonWithDropdownMenu/types'; import * as API from '@libs/API'; import type { AddBillingCardAndRequestWorkspaceOwnerChangeParams, + AddPaymentCardParams, CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteWorkspaceAvatarParams, @@ -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'; @@ -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}); } /** @@ -4755,6 +4817,7 @@ export { updateCompanyCardName, setCompanyCardExportAccount, clearCompanyCardErrorField, + verifySetupIntentAndRequestPolicyOwnerChange, }; export type {NewCustomUnit}; diff --git a/src/libs/shouldRenderTransferOwnerButton/index.native.ts b/src/libs/shouldRenderTransferOwnerButton/index.native.ts new file mode 100644 index 000000000000..5a1d46947f0e --- /dev/null +++ b/src/libs/shouldRenderTransferOwnerButton/index.native.ts @@ -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; +Onyx.connect({ + key: ONYXKEYS.FUND_LIST, + callback: (value) => { + if (!value) { + return; + } + + fundList = value; + }, +}); + +const shouldRenderTransferOwnerButton: ShouldRenderTransferOwnerButton = () => { + return !isEmpty(fundList); +}; + +export default shouldRenderTransferOwnerButton; diff --git a/src/libs/shouldRenderTransferOwnerButton/index.ts b/src/libs/shouldRenderTransferOwnerButton/index.ts new file mode 100644 index 000000000000..12feef5c32b6 --- /dev/null +++ b/src/libs/shouldRenderTransferOwnerButton/index.ts @@ -0,0 +1,5 @@ +import type ShouldRenderTransferOwnerButton from './types'; + +const shouldRenderTransferOwnerButton: ShouldRenderTransferOwnerButton = () => true; + +export default shouldRenderTransferOwnerButton; diff --git a/src/libs/shouldRenderTransferOwnerButton/types.ts b/src/libs/shouldRenderTransferOwnerButton/types.ts new file mode 100644 index 000000000000..22ae5cecdf32 --- /dev/null +++ b/src/libs/shouldRenderTransferOwnerButton/types.ts @@ -0,0 +1,3 @@ +type ShouldRenderTransferOwnerButton = () => boolean; + +export default ShouldRenderTransferOwnerButton; diff --git a/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx b/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx index 6f49e9bd0508..b323c668b0b5 100644 --- a/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx +++ b/src/pages/settings/Subscription/CardAuthenticationModal/index.tsx @@ -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); @@ -37,11 +40,15 @@ function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) { (event: MessageEvent) => { 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(() => { diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 075cfc55a0c8..52814f5da23d 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -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'; @@ -240,14 +241,16 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM )} {isSelectedMemberOwner && isCurrentUserAdmin && !isCurrentUserOwner ? ( -