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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2635,4 +2635,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: d5e281e5370cb0211a104efd90eb5fa7af936e14

COCOAPODS: 1.15.2
COCOAPODS: 1.15.2
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3866,6 +3866,9 @@ const CONST = {
ENABLED: 'ENABLED',
DISABLED: 'DISABLED',
},
STRIPE_GBP_AUTH_STATUSES: {
SUCCEEDED: 'succeeded',
},
TAB: {
NEW_CHAT_TAB_ID: 'NewChatTab',
NEW_CHAT: 'chat',
Expand Down Expand Up @@ -5284,8 +5287,10 @@ const CONST = {
PAYMENT_CARD_CURRENCY: {
USD: 'USD',
AUD: 'AUD',
GBP: 'GBP',
NZD: 'NZD',
},
GBP_AUTHENTICATION_COMPLETE: '3DS-authentication-complete',

SUBSCRIPTION_PRICE_FACTOR: 2,
FEEDBACK_SURVEY_OPTIONS: {
Expand Down
1 change: 1 addition & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string;
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean;
[ONYXKEYS.LAST_VISITED_PATH]: string | undefined;
[ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string;
[ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields;
[ONYXKEYS.UPDATE_REQUIRED]: boolean;
[ONYXKEYS.RESET_REQUIRED]: boolean;
Expand Down
4 changes: 4 additions & 0 deletions src/hooks/useSubscriptionPossibleCostSavings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const POSSIBLE_COST_SAVINGS = {
[CONST.POLICY.TYPE.TEAM]: 1400,
[CONST.POLICY.TYPE.CORPORATE]: 3000,
},
[CONST.PAYMENT_CARD_CURRENCY.GBP]: {
[CONST.POLICY.TYPE.TEAM]: 800,
[CONST.POLICY.TYPE.CORPORATE]: 1400,
},
[CONST.PAYMENT_CARD_CURRENCY.NZD]: {
[CONST.POLICY.TYPE.TEAM]: 1600,
[CONST.POLICY.TYPE.CORPORATE]: 3200,
Expand Down
10 changes: 10 additions & 0 deletions src/hooks/useSubscriptionPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ const SUBSCRIPTION_PRICES = {
[CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1400,
},
},
[CONST.PAYMENT_CARD_CURRENCY.GBP]: {
[CONST.POLICY.TYPE.CORPORATE]: {
[CONST.SUBSCRIPTION.TYPE.ANNUAL]: 700,
[CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1400,
},
[CONST.POLICY.TYPE.TEAM]: {
[CONST.SUBSCRIPTION.TYPE.ANNUAL]: 400,
[CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 800,
},
},
[CONST.PAYMENT_CARD_CURRENCY.NZD]: {
[CONST.POLICY.TYPE.CORPORATE]: {
[CONST.SUBSCRIPTION.TYPE.ANNUAL]: 1600,
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4051,6 +4051,7 @@ export default {
mergedWithCashTransaction: 'matched a receipt to this transaction.',
},
subscription: {
authenticatePaymentCard: 'Authenticate payment card',
mobileReducedFunctionalityMessage: 'You can’t make changes to your subscription in the mobile app.',
badge: {
freeTrial: ({numOfDays}) => `Free trial: ${numOfDays} ${numOfDays === 1 ? 'day' : 'days'} left`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4572,6 +4572,7 @@ export default {
mergedWithCashTransaction: 'encontró un recibo para esta transacción.',
},
subscription: {
authenticatePaymentCard: 'Autenticar tarjeta de pago',
mobileReducedFunctionalityMessage: 'No puedes hacer cambios en tu suscripción en la aplicación móvil.',
badge: {
freeTrial: ({numOfDays}) => `Prueba gratuita: ${numOfDays === 1 ? `queda 1 día` : `quedan ${numOfDays} días`}`,
Expand Down
5 changes: 5 additions & 0 deletions src/libs/API/parameters/VerifySetupIntentParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type VerifySetupIntentParams = {
accountID: number;
isVerifying: boolean;
};
export default VerifySetupIntentParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type {default as ActivatePhysicalExpensifyCardParams} from './ActivatePhysicalExpensifyCardParams';
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 AddPersonalBankAccountParams} from './AddPersonalBankAccountParams';
export type {default as RestartBankAccountSetupParams} from './RestartBankAccountSetupParams';
export type {default as AddSchoolPrincipalParams} from './AddSchoolPrincipalParams';
Expand Down
8 changes: 6 additions & 2 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const WRITE_COMMANDS = {
CHRONOS_REMOVE_OOO_EVENT: 'Chronos_RemoveOOOEvent',
MAKE_DEFAULT_PAYMENT_METHOD: 'MakeDefaultPaymentMethod',
ADD_PAYMENT_CARD: 'AddPaymentCard',
ADD_PAYMENT_CARD_GBP: 'AddPaymentCardGBP',
VERIFY_SETUP_INTENT: 'User_VerifySetupIntent',
TRANSFER_WALLET_BALANCE: 'TransferWalletBalance',
DELETE_PAYMENT_CARD: 'DeletePaymentCard',
UPDATE_PRONOUNS: 'UpdatePronouns',
Expand Down Expand Up @@ -341,6 +343,8 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_EXPENSIFY_CARD_LIMIT]: Parameters.UpdateExpensifyCardLimitParams;
[WRITE_COMMANDS.MAKE_DEFAULT_PAYMENT_METHOD]: Parameters.MakeDefaultPaymentMethodParams;
[WRITE_COMMANDS.ADD_PAYMENT_CARD]: Parameters.AddPaymentCardParams;
[WRITE_COMMANDS.ADD_PAYMENT_CARD_GBP]: Parameters.AddPaymentCardParams;
[WRITE_COMMANDS.VERIFY_SETUP_INTENT]: Parameters.VerifySetupIntentParams;
[WRITE_COMMANDS.DELETE_PAYMENT_CARD]: Parameters.DeletePaymentCardParams;
[WRITE_COMMANDS.UPDATE_PRONOUNS]: Parameters.UpdatePronounsParams;
[WRITE_COMMANDS.UPDATE_DISPLAY_NAME]: Parameters.UpdateDisplayNameParams;
Expand Down Expand Up @@ -757,7 +761,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = {
OPEN_OLD_DOT_LINK: 'OpenOldDotLink',
OPEN_REPORT: 'OpenReport',
RECONNECT_APP: 'ReconnectApp',
ADD_PAYMENT_CARD_GBR: 'AddPaymentCardGBP',
ADD_PAYMENT_CARD_GBP: 'AddPaymentCardGBP',
REVEAL_EXPENSIFY_CARD_DETAILS: 'RevealExpensifyCardDetails',
SWITCH_TO_OLD_DOT: 'SwitchToOldDot',
TWO_FACTOR_AUTH_VALIDATE: 'TwoFactorAuth_Validate',
Expand All @@ -774,7 +778,7 @@ type SideEffectRequestCommandParameters = {
[SIDE_EFFECT_REQUEST_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP]: Parameters.ReconnectAppParams;
[SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN]: Parameters.GenerateSpotnanaTokenParams;
[SIDE_EFFECT_REQUEST_COMMANDS.ADD_PAYMENT_CARD_GBR]: Parameters.AddPaymentCardParams;
[SIDE_EFFECT_REQUEST_COMMANDS.ADD_PAYMENT_CARD_GBP]: Parameters.AddPaymentCardParams;
[SIDE_EFFECT_REQUEST_COMMANDS.ACCEPT_SPOTNANA_TERMS]: null;
[SIDE_EFFECT_REQUEST_COMMANDS.TWO_FACTOR_AUTH_VALIDATE]: Parameters.ValidateTwoFactorAuthParams;
};
Expand Down
34 changes: 28 additions & 6 deletions src/libs/actions/PaymentMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
TransferWalletBalanceParams,
UpdateBillingCurrencyParams,
} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as CardUtils from '@libs/CardUtils';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -253,11 +253,24 @@ function addSubscriptionPaymentCard(cardData: {
},
];

API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, {
optimisticData,
successData,
failureData,
});
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);
});
} else {
// eslint-disable-next-line rulesdir/no-multiple-api-calls
API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, {
optimisticData,
successData,
failureData,
});
}
}

/**
Expand Down Expand Up @@ -288,6 +301,14 @@ function clearPaymentCard3dsVerification() {
Onyx.set(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION, '');
}

/**
* Properly updates the nvp_privateStripeCustomerID onyx data for 3DS payment
*
*/
function verifySetupIntent(accountID: number, isVerifying = true) {
API.write(WRITE_COMMANDS.VERIFY_SETUP_INTENT, {accountID, isVerifying});
}

/**
* Set currency for payments
*
Expand Down Expand Up @@ -533,4 +554,5 @@ export {
clearPaymentCard3dsVerification,
clearWalletTermsError,
setPaymentCardForm,
verifySetupIntent,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import ScreenWrapper from '@components/ScreenWrapper';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as PaymentMethods from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

type CardAuthenticationModalProps = {
/** Title shown in the header of the modal */
headerTitle?: string;
};
function CardAuthenticationModal({headerTitle}: CardAuthenticationModalProps) {
const styles = useThemeStyles();
const [authenticationLink] = useOnyx(ONYXKEYS.VERIFY_3DS_SUBSCRIPTION);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [privateStripeCustomerID] = useOnyx(ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
if (privateStripeCustomerID?.status !== CONST.STRIPE_GBP_AUTH_STATUSES.SUCCEEDED) {
return;
}
PaymentMethods.clearPaymentCard3dsVerification();
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION);
}, [privateStripeCustomerID]);

const handleGBPAuthentication = useCallback(
(event: MessageEvent<string>) => {
const message = event.data;
if (message === CONST.GBP_AUTHENTICATION_COMPLETE) {
PaymentMethods.verifySetupIntent(session?.accountID ?? -1, true);
}
},
[session?.accountID],
);

useEffect(() => {
window.addEventListener('message', handleGBPAuthentication);
return () => {
window.removeEventListener('message', handleGBPAuthentication);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change caused this bug

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component we were not rendering for the native, so while testing in mobile it wasn't crash for me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Pujan92

This component we were not rendering for the native

Why no? We don't have native.tsx file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I see. Thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean it is from the place where we render this component, in this PR PaymentCard component is responsible for calling this and there we have this platform specific file. And if I remember correctly, in the doc it is mentioned that stripe flow won't be allowed in native.

};
}, [handleGBPAuthentication]);

const onModalClose = () => {
PaymentMethods.clearPaymentCard3dsVerification();
};

return (
<Modal
type={CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE}
isVisible={!!authenticationLink}
onClose={onModalClose}
onModalHide={onModalClose}
>
<ScreenWrapper
style={styles.pb0}
includePaddingTop={false}
includeSafeAreaPaddingBottom={false}
testID={CardAuthenticationModal.displayName}
>
<HeaderWithBackButton
title={headerTitle}
shouldShowBorderBottom
shouldShowCloseButton
onCloseButtonPress={onModalClose}
shouldShowBackButton={false}
/>
{isLoading && <FullScreenLoadingIndicator />}
<View style={[styles.flex1]}>
<iframe
src={authenticationLink}
title="Statements"
height="100%"
width="100%"
seamless
style={{border: 'none'}}
onLoad={() => {
setIsLoading(false);
}}
/>
</View>
</ScreenWrapper>
</Modal>
);
}

CardAuthenticationModal.displayName = 'CardAuthenticationModal';

export default CardAuthenticationModal;
2 changes: 2 additions & 0 deletions src/pages/settings/Subscription/PaymentCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import useSubscriptionPrice from '@hooks/useSubscriptionPrice';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
import {convertToShortDisplayString} from '@libs/CurrencyUtils';
import CardAuthenticationModal from '@pages/settings/Subscription/CardAuthenticationModal';
import * as PaymentMethods from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -107,6 +108,7 @@ function AddPaymentCard() {
}
/>
</View>
<CardAuthenticationModal headerTitle={translate('subscription.authenticatePaymentCard')} />
</ScreenWrapper>
);
}
Expand Down
11 changes: 11 additions & 0 deletions src/types/onyx/PrivateStripeCustomer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** Model of private stripe customer ID */
type PrivateStripeCustomer = {
/** Currency of the stripe payment */
[currency: string]: string;
/** paymentMethodID of the stripe payment */
paymentMethodID: string;
/** Status of the stripe payment */
status: string;
};

export default PrivateStripeCustomer;