-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
[Payment history] add "Request early cancelation" menu item to the Subscription page (UI) #44763
Changes from 29 commits
3255500
3f7797b
012c728
6b8ffab
42c47a5
15c6bca
cbaac92
c2fd78b
2d341cc
fd7690f
b9f24a0
7b2f96e
dcf2747
47e5093
ec5b10d
488d7c3
a956c5a
eb301ab
ec7fd66
c360a75
df63a14
8713805
3378b78
0f40810
2e9a5e5
f9b9258
8c3741a
57c3620
eccabbc
43f58f7
9339f31
2731480
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import {useEffect, useMemo, useRef, useState} from 'react'; | ||
import {useOnyx} from 'react-native-onyx'; | ||
import type {CancellationType} from '@src/CONST'; | ||
import CONST from '@src/CONST'; | ||
import ONYXKEYS from '@src/ONYXKEYS'; | ||
|
||
function useCancellationType(): CancellationType | undefined { | ||
const [cancellationDetails] = useOnyx(ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS); | ||
|
||
const [cancellationType, setCancellationType] = useState<CancellationType | undefined>(); | ||
|
||
// Store initial cancellation details array in a ref for comparison | ||
const previousCancellationDetails = useRef(cancellationDetails); | ||
|
||
const memoizedCancellationType = useMemo(() => { | ||
const pendingManualCancellation = cancellationDetails?.filter((detail) => detail.cancellationType === CONST.CANCELLATION_TYPE.MANUAL).find((detail) => !detail.cancellationDate); | ||
|
||
// There is a pending manual cancellation - return manual cancellation type | ||
if (pendingManualCancellation) { | ||
return CONST.CANCELLATION_TYPE.MANUAL; | ||
} | ||
|
||
// There are no new items in the cancellation details NVP | ||
if (previousCancellationDetails.current?.length === cancellationDetails?.length) { | ||
return; | ||
} | ||
|
||
// There is a new item in the cancellation details NVP, it has to be an automatic cancellation, as pending manual cancellations are handled above | ||
return CONST.CANCELLATION_TYPE.AUTOMATIC; | ||
}, [cancellationDetails]); | ||
|
||
useEffect(() => { | ||
if (!memoizedCancellationType) { | ||
return; | ||
} | ||
|
||
setCancellationType(memoizedCancellationType); | ||
}, [memoizedCancellationType]); | ||
|
||
return cancellationType; | ||
} | ||
|
||
export default useCancellationType; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4214,6 +4214,33 @@ export default { | |
whatsMainReason: "What's the main reason you're disabling auto-renew?", | ||
renewsOn: ({date}) => `Renews on ${date}.`, | ||
}, | ||
requestEarlyCancellation: { | ||
title: 'Request early cancellation', | ||
subtitle: 'What’s the main reason you’re requesting early cancellation?', | ||
subscriptionCanceled: { | ||
title: 'Subscription canceled', | ||
subtitle: 'Your annual subscription has been canceled.', | ||
info: 'If you want to keep using your workspace(s) on a pay-per-use basis, you’re all set.', | ||
preventFutureActivity: { | ||
part1: 'If you’d like to prevent future activity and charges, you must ', | ||
link: 'delete your workspace(s)', | ||
part2: '. Note that when you delete your workspace(s), you’ll be charged for any outstanding activity that was incurred during the current calendar month.', | ||
}, | ||
}, | ||
requestSubmitted: { | ||
title: 'Request submitted', | ||
subtitle: { | ||
part1: 'Thanks for letting us know you’re interested in canceling your subscription. We’re reviewing your request and will be in touch soon via your chat with ', | ||
link: 'Concierge', | ||
part2: '.', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need period in the translation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, according to the doc |
||
}, | ||
}, | ||
acknowledgement: { | ||
part1: 'By requesting early cancellation, I acknowledge and agree that Expensify has no obligation to grant such request under the Expensify ', | ||
link: 'Terms of Service', | ||
part2: ' or other applicable services agreement between me and Expensify and that Expensify retains sole discretion with regard to granting any such request.', | ||
}, | ||
}, | ||
}, | ||
feedbackSurvey: { | ||
tooLimited: 'Functionality needs improvement', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4737,6 +4737,33 @@ export default { | |
whatsMainReason: '¿Cuál es la razón principal por la que deseas desactivar la auto-renovación?', | ||
renewsOn: ({date}) => `Se renovará el ${date}.`, | ||
}, | ||
requestEarlyCancellation: { | ||
title: 'Solicitar cancelación anticipada', | ||
subtitle: '¿Cuál es la razón principal por la que solicitas la cancelación anticipada?', | ||
subscriptionCanceled: { | ||
title: 'Suscripción cancelada', | ||
subtitle: 'Tu suscripción anual ha sido cancelada.', | ||
info: 'Ya puedes seguir utilizando tu(s) espacio(s) de trabajo en la modalidad de pago por uso.', | ||
preventFutureActivity: { | ||
part1: 'Si quieres evitar actividad y cargos futuros, debes ', | ||
link: 'eliminar tu(s) espacio(s) de trabajo.', | ||
part2: ' Ten en cuenta que cuando elimines tu(s) espacio(s) de trabajo, se te cobrará cualquier actividad pendienteque se haya incurrido durante el mes en curso.', | ||
}, | ||
}, | ||
requestSubmitted: { | ||
title: 'Solicitud enviada', | ||
subtitle: { | ||
part1: 'Gracias por hacernos saber que deseas cancelar tu suscripción. Estamos revisando tu solicitud y nos comunicaremos contigo en breve a través de tu chat con ', | ||
link: 'Concierge', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you think of moving There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, we can do that if you want! I just thought that if we are composing this sentence from parts, it's better to keep them all in once place, but I can change that, let me know what you think |
||
part2: '.', | ||
}, | ||
}, | ||
acknowledgement: { | ||
part1: 'Al solicitar la cancelación anticipada, reconozco y acepto que Expensify no tiene ninguna obligación de conceder dicha solicitud en virtud de las ', | ||
link: 'Condiciones de Servicio', | ||
part2: ' de Expensify u otro acuerdo de servicios aplicable entre Expensify y yo, y que Expensify se reserva el derecho exclusivo a conceder dicha solicitud.', | ||
}, | ||
}, | ||
}, | ||
feedbackSurvey: { | ||
tooLimited: 'Hay que mejorar la funcionalidad', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type {FeedbackSurveyOptionID} from '@src/CONST'; | ||
|
||
type CancelBillingSubscriptionParams = { | ||
cancellationReason: FeedbackSurveyOptionID; | ||
cancellationNote: string; | ||
}; | ||
|
||
export default CancelBillingSubscriptionParams; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,26 @@ | ||
import type {OnyxUpdate} from 'react-native-onyx'; | ||
import Onyx from 'react-native-onyx'; | ||
import * as API from '@libs/API'; | ||
import type {UpdateSubscriptionAddNewUsersAutomaticallyParams, UpdateSubscriptionAutoRenewParams, UpdateSubscriptionTypeParams} from '@libs/API/parameters'; | ||
import type {CancelBillingSubscriptionParams, UpdateSubscriptionAddNewUsersAutomaticallyParams, UpdateSubscriptionAutoRenewParams, UpdateSubscriptionTypeParams} from '@libs/API/parameters'; | ||
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; | ||
import CONST from '@src/CONST'; | ||
import type {FeedbackSurveyOptionID, SubscriptionType} from '@src/CONST'; | ||
import ONYXKEYS from '@src/ONYXKEYS'; | ||
import type {CancellationDetails} from '@src/types/onyx'; | ||
import type {OnyxData} from '@src/types/onyx/Request'; | ||
|
||
let cancellationDetails: CancellationDetails[] = []; | ||
Onyx.connect({ | ||
key: ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS, | ||
callback: (value) => { | ||
if (!value) { | ||
return; | ||
} | ||
|
||
cancellationDetails = value; | ||
}, | ||
}); | ||
|
||
/** | ||
* Fetches data when the user opens the SubscriptionSettingsPage | ||
*/ | ||
|
@@ -279,6 +292,39 @@ function clearOutstandingBalance() { | |
API.write(WRITE_COMMANDS.CLEAR_OUTSTANDING_BALANCE, null, onyxData); | ||
} | ||
|
||
function cancelBillingSubscription(cancellationReason: FeedbackSurveyOptionID, cancellationNote: string) { | ||
const successData: OnyxUpdate[] = [ | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS, | ||
value: [ | ||
...cancellationDetails, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For both success and failure we're setting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this onyx key is an array, it contains a history of previous cancellations, it has to remain unchanged. Hope that answers your question! |
||
{ | ||
cancellationReason, | ||
}, | ||
], | ||
blimpich marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
]; | ||
|
||
const failureData: OnyxUpdate[] = [ | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS, | ||
value: [...cancellationDetails], | ||
}, | ||
]; | ||
|
||
const parameters: CancelBillingSubscriptionParams = { | ||
cancellationReason, | ||
cancellationNote, | ||
}; | ||
|
||
API.write(WRITE_COMMANDS.CANCEL_BILLING_SUBSCRIPTION, parameters, { | ||
successData, | ||
failureData, | ||
}); | ||
} | ||
|
||
export { | ||
openSubscriptionPage, | ||
updateSubscriptionAutoRenew, | ||
|
@@ -287,4 +333,5 @@ export { | |
clearUpdateSubscriptionSizeError, | ||
updateSubscriptionType, | ||
clearOutstandingBalance, | ||
cancelBillingSubscription, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the first glance this looked a bit complex to me. Can we add a comment on why we need the ref? Also in useMemo do we need to update the
ref.current
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment