Require validateCode when requesting replacement cards#51147
Require validateCode when requesting replacement cards#51147
Conversation
|
@jjcoffee this needs an internal review due to some backend changes that aren't live and we can't make them live until this PR is deployed. |
|
@thienlnam Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
Fixed. This was using Screen.Recording.2024-10-29.at.21.49.36.mov |
thienlnam
left a comment
There was a problem hiding this comment.
Code largely looks good, just a comment about the side effect pattern. One more thing is will this work even without the auth changes?
| requestValidateCodeAction(); | ||
| }; | ||
|
|
||
| const handleValidateCodeEntered = (validateCode: string) => { | ||
| CardActions.requestReplacementExpensifyCard(physicalCard.cardID, reason?.key as ReplacementReason, validateCode).then((newCardID) => { | ||
| if (!newCardID) { | ||
| return; | ||
| } | ||
| setIsValidateCodeActionModalVisible(false); | ||
| Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARD.getRoute(newCardID)); | ||
| }); |
There was a problem hiding this comment.
It doesn't look like we did this navigation before, is there a reason we are adding it now?
src/libs/actions/Card.ts
Outdated
| // eslint-disable-next-line rulesdir/no-api-side-effects-method | ||
| API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.REQUEST_REPLACEMENT_EXPENSIFY_CARD, parameters, {optimisticData, successData, failureData}) | ||
| .then((response): string | undefined => { | ||
| if (response?.jsonCode !== CONST.JSON_CODE.SUCCESS) { | ||
| return; | ||
| } | ||
| resolve((response as ExpensifyCardID).newCardID.toString()); | ||
| }) | ||
| .catch(() => reject()); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Returning promises from action methods is a discouraged pattern - it looks like we can do this without the side effects.
We just want the new cardID here, so we could probably just have it update an onyx key and only re-direct if we have that key present and are done loading. Alternatively, it seems like we didn't have a redirect before so maybe we don't need to have it still
There was a problem hiding this comment.
@thienlnam without the side effects we used to redirect to the old card page and we display a "$0" limit which looks like a bug to the user
Screen_Recording_2024-10-23_at_22.04.03-2024-10-30.17_36_55.334.mov
We also use side effects for reveal virtual card details here
Line 197 in 084b104
There was a problem hiding this comment.
We use side effects there because we can't store those details in onyx since they're sensitive - but for this which is just a cardID is fine to store in onyx
There was a problem hiding this comment.
Hmm let me see how I can make this work without side effects and also prevent the bug above.
There was a problem hiding this comment.
👍 lmk if you need some ideas - usually the way we get around this is by adding a loading indicator on a loading key on the onyx key for the page
There was a problem hiding this comment.
@thienlnam IMO I think we can keep the side effect call here, you can see in the comment that our use case here is allowed, we are both calling Marqeta and redirecting based on the response
Also given that we display a spinner and make the user wait, I don't see how is using the optimistic pattern is helpful?
There was a problem hiding this comment.
Yup something along those lines would work - you'd need to have the API command send an onyx update for the newCardID if you wanted to redirect to the new card.
Though thinking about this more though, if you only added the redirect so that the card doesn't show with a $0 limit we could also just not do the redirect because then it forces this flow to happen online.
In App, in the optimistic data you could set a new key called isReplacing or something on the cardID and so when you open that cardID you would see some note that it's been replaced and then when the API completes it would remove the card and have the new card in the list. That's probably easier and then allows the user to take other actions offline with the command queued - what do you think?
There was a problem hiding this comment.
I hear you. However I think that this stuff is out of scope, so it's best if I kept this PR focused on validateCode using the same offline pattern we have now, which means not fixing the $0 bug, I think we can open an issue to fix separately. Sounds good?
There was a problem hiding this comment.
Yeah that works for me - the main concern for me here is to remove the side effect so if we can do that here and have an external contributor handle the UI update seperately
|
Good shout Danny, I agree seeing the error below the footer button felt weird - glad we have the option to easily add it above! |
|
Updated @thienlnam |
|
Looks like there are some lint failures |
|
Ah, I need to remove usage of withOnyx. Will do tomorrow. Also, apologies for late update, I was OOO sick. |
|
Fixed @thienlnam |
Reviewer Checklist
Screenshots/VideosAndroid: NativeAndroid: mWeb ChromeiOS: NativeiOS: mWeb SafariMacOS: Chrome / SafariMacOS: Desktop |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/thienlnam in version: 9.0.60-0 🚀
|
|
@youssef-lr @francoisl we don't have physical card and we can verify with virtual card only. |
|
@youssef-lr is there a way to test with physical cards without actually requesting a replacement on each platform? |
|
I'm not sure there is, cc @NikkiWines. I think we can check this off for now, nothing has changed once a validateCode is valid, things should work the same as before |
|
🤔 I don't think we have a flow for that. Except maybe you could request a replacement then cancel the request internally which might be tricky timing wise and I'm not to sure exactly how you'd do that. It's a little dicey to not test the success flow at all on staging or prod, but I'm not sure how to bypass that here |
|
Maybe we could follow the same / similar steps from when this command was initially added here? |
|
🚀 Deployed to production by https://github.com/francoisl in version: 9.0.60-3 🚀
|
| }; | ||
|
|
||
| function reportVirtualExpensifyCardFraud(card?: Card) { | ||
| function reportVirtualExpensifyCardFraud(card: Card, validateCode: string) { |
There was a problem hiding this comment.
We don't reset validateCodeSent when calling requestReplacementExpensifyCard function. If the users go to the validated code modal for the first time, a new magic code will be sent and the validateCodeSent will be set to true. But if users haven't entered that magic code and go back. And then they come to the validated code modal again, because validateCodeSent still be true, there is no new magic code that will be sent. Bug: #61239

Details
Fixed Issues
$ https://github.com/Expensify/Expensify/issues/434460
$ https://github.com/Expensify/Expensify/issues/434468
Tests
First checkout this Auth PR, and then hardcode these lines:
https://github.com/Expensify/Web-Expensify/blob/4fccc5d6258fc9d7879d5cea6c0ced5663cd3334/lib/CardAPI.php#L652 =>
$response = Auth::requestReplacementExpensifyCard($authToken, $cardID, $reason, validateCode: $validateCode);https://github.com/Expensify/Web-Expensify/blob/4fccc5d6258fc9d7879d5cea6c0ced5663cd3334/lib/CardAPI.php#L405 =>
Auth::requestReplacementExpensifyCard($authToken, $existingVirtualCardID, Card::CARD_REPLACEMENT_REASON_STOLEN, validateCode: $validateCode, skipVirtualCardCreation: true);Have an account with a physical and virtual card.
Report virtual card for fraud, make sure you're asked to enter a validateCode.
Enter a wrong validate code and make sure the request doesn't go through.
Enter the correct magic code and make sure you're redirected to the new card page.
Repeat the same steps by requesting a replacement physical card.
Offline tests
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)myBool && <MyComponent />.src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))Avataris modified, I verified thatAvataris working as expected in all cases)ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2024-10-29.at.21.18.47.mov
MacOS: Desktop