Commit ab07f46
authored
feat(card): cp-7.68.0 Add View PIN option (#26646)
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->
## **Description**
Adds a "View PIN" option to the Card Home manage card section, allowing
users to securely view their card PIN through a PCI-compliant
image-based display.
**Why**: Users need to retrieve their card PIN (e.g. for ATM use or
in-store transactions). The PIN is never transmitted as plain text — it
is rendered as an image via a time-limited, single-use secure token from
the `POST /v1/card/pin/token` endpoint, ensuring PCI compliance.
**What changed**:
- **New SDK method** (`CardSDK.generateCardPinToken`): Calls `POST
/v1/card/pin/token` with optional `customCss` for theming the PIN image.
Mirrors the existing `generateCardDetailsToken` pattern with proper
error handling.
- **React Query integration**: New `cardQueries.pin` key factory and
`pinTokenMutationFn` following the established React Query patterns from
the codebase.
- **`useCardPinToken` hook**: Wraps `useMutation` for PIN token
generation. Automatically applies dark/light theme-aware `customCss`
(background and text colors) so the PIN image matches the app
appearance.
- **`ViewPinBottomSheet` component**: Displays the PIN image in a bottom
sheet with a skeleton loader and `CardScreenshotDeterrent` enabled to
prevent screenshots of sensitive data.
- **`CardHome` integration**: New `ManageCardListItem` for "View PIN"
with biometric authentication gating (matching the "View Card Details"
flow). Falls back to password bottom sheet with a PIN-specific
description when biometrics are not configured. Visible for US users
(all card types) and international users with non-virtual cards.
- **Analytics**: Added `VIEW_PIN_BUTTON` action to `CardActions` enum,
tracked via `CARD_BUTTON_CLICKED` event.
- **Navigation**: Registered `CardViewPinModal` route and added the
`ViewPinBottomSheet` screen to `CardModalsRoutes`.
- **Tests**: Added tests across 5 files — SDK method tests, query layer
tests, hook tests, bottom sheet snapshot/render tests, and 11 new
CardHome integration tests covering visibility conditions, biometric
auth flow, password fallback, and loading guards.
## **Changelog**
CHANGELOG entry: Added "View PIN" option to the Card Home screen,
allowing users to securely view their card PIN via biometric or password
authentication.
## **Related issues**
Fixes:
## **Manual testing steps**
```gherkin
Feature: View card PIN
Scenario: View PIN button is visible for eligible users
Given the user is authenticated with an active card
And the user is a US user OR has a non-virtual (metal) card
When the user navigates to the Card Home screen
Then a "View PIN" option is displayed in the manage card section
Scenario: View PIN button is hidden for international virtual card users
Given the user is an international user with a virtual card
When the user navigates to the Card Home screen
Then the "View PIN" option is NOT displayed
Scenario: View PIN with biometric authentication
Given the user has biometric authentication configured
When the user taps "View PIN"
Then a biometric prompt is displayed
And upon successful authentication, the card PIN is shown as an image in a bottom sheet
And the PIN image matches the current theme (light/dark background)
Scenario: View PIN with password fallback
Given the user does NOT have biometric authentication configured
When the user taps "View PIN"
Then a password bottom sheet appears with the message "Enter your wallet password to view your card PIN."
And upon entering the correct password, the card PIN is shown in a bottom sheet
Scenario: View PIN biometric cancellation
Given the user has biometric authentication configured
When the user taps "View PIN" and cancels the biometric prompt
Then no PIN is displayed and the user returns to Card Home
Scenario: View PIN error handling
Given the user taps "View PIN" and authentication succeeds
When the PIN token request fails
Then an error toast is shown with "Failed to load PIN. Please try again."
Scenario: Screenshot prevention
Given the card PIN bottom sheet is displayed
When the user attempts to take a screenshot
Then the screenshot deterrent is active and prevents capture of the PIN
```
## **Screenshots/Recordings**
<!-- If applicable, add screenshots or recordings of the View PIN flow
-->
### **Before**
<!-- Card Home without View PIN option -->
### **After**
<!-- Card Home with View PIN option + View PIN bottom sheet -->
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adds a new authenticated flow to fetch and display a sensitive card
PIN image via a new SDK endpoint and modal UI, with biometric/password
gating and error handling. Risk is mainly around auth/error-state
handling and the new network call/token lifecycle.
>
> **Overview**
> Adds a new **“View PIN”** manage-card action on `CardHome`, shown only
for eligible users (authenticated, has a card, not loading; US users or
non-virtual cards), gated by `reauthenticate()` with a
password-bottom-sheet fallback when biometrics aren’t configured and
guarded against concurrent loads.
>
> Introduces PIN-token generation plumbing:
`CardSDK.generateCardPinToken` calling `POST /v1/card/pin/token`, React
Query `cardQueries.pin` + `useCardPinToken` (theme-aware `customCss`),
plus a new `ViewPinBottomSheet` modal route
(`Routes.CARD.MODALS.VIEW_PIN`) that renders the PIN image with a
skeleton loader and `CardScreenshotDeterrent` enabled.
>
> Updates analytics (`CardActions.VIEW_PIN_BUTTON`), test IDs, and
English strings, and adds comprehensive tests for the SDK/query/hook,
the new bottom sheet (snapshot/render), and CardHome
visibility/auth/error flows.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
514ae0c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent bee9243 commit ab07f46
20 files changed
Lines changed: 1798 additions & 1 deletion
File tree
- app
- components/UI/Card
- Views/CardHome
- components/ViewPinBottomSheet
- __snapshots__
- hooks
- queries
- routes
- sdk
- util
- locales/languages
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
| 102 | + | |
102 | 103 | | |
103 | 104 | | |
104 | 105 | | |
| |||
274 | 275 | | |
275 | 276 | | |
276 | 277 | | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
277 | 298 | | |
278 | 299 | | |
279 | 300 | | |
| |||
503 | 524 | | |
504 | 525 | | |
505 | 526 | | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
506 | 534 | | |
507 | 535 | | |
508 | 536 | | |
| |||
3938 | 3966 | | |
3939 | 3967 | | |
3940 | 3968 | | |
| 3969 | + | |
| 3970 | + | |
| 3971 | + | |
| 3972 | + | |
| 3973 | + | |
| 3974 | + | |
| 3975 | + | |
| 3976 | + | |
| 3977 | + | |
| 3978 | + | |
| 3979 | + | |
| 3980 | + | |
| 3981 | + | |
| 3982 | + | |
| 3983 | + | |
| 3984 | + | |
| 3985 | + | |
| 3986 | + | |
| 3987 | + | |
| 3988 | + | |
| 3989 | + | |
| 3990 | + | |
| 3991 | + | |
| 3992 | + | |
| 3993 | + | |
| 3994 | + | |
| 3995 | + | |
| 3996 | + | |
| 3997 | + | |
| 3998 | + | |
| 3999 | + | |
| 4000 | + | |
| 4001 | + | |
| 4002 | + | |
| 4003 | + | |
| 4004 | + | |
| 4005 | + | |
| 4006 | + | |
| 4007 | + | |
| 4008 | + | |
| 4009 | + | |
| 4010 | + | |
| 4011 | + | |
| 4012 | + | |
| 4013 | + | |
| 4014 | + | |
| 4015 | + | |
| 4016 | + | |
| 4017 | + | |
| 4018 | + | |
| 4019 | + | |
| 4020 | + | |
| 4021 | + | |
| 4022 | + | |
| 4023 | + | |
| 4024 | + | |
| 4025 | + | |
| 4026 | + | |
| 4027 | + | |
| 4028 | + | |
| 4029 | + | |
| 4030 | + | |
| 4031 | + | |
| 4032 | + | |
| 4033 | + | |
| 4034 | + | |
| 4035 | + | |
| 4036 | + | |
| 4037 | + | |
| 4038 | + | |
| 4039 | + | |
| 4040 | + | |
| 4041 | + | |
| 4042 | + | |
| 4043 | + | |
| 4044 | + | |
| 4045 | + | |
| 4046 | + | |
| 4047 | + | |
| 4048 | + | |
| 4049 | + | |
| 4050 | + | |
| 4051 | + | |
| 4052 | + | |
| 4053 | + | |
| 4054 | + | |
| 4055 | + | |
| 4056 | + | |
| 4057 | + | |
| 4058 | + | |
| 4059 | + | |
| 4060 | + | |
| 4061 | + | |
| 4062 | + | |
| 4063 | + | |
| 4064 | + | |
| 4065 | + | |
| 4066 | + | |
| 4067 | + | |
| 4068 | + | |
| 4069 | + | |
| 4070 | + | |
| 4071 | + | |
| 4072 | + | |
| 4073 | + | |
| 4074 | + | |
| 4075 | + | |
| 4076 | + | |
| 4077 | + | |
| 4078 | + | |
| 4079 | + | |
| 4080 | + | |
| 4081 | + | |
| 4082 | + | |
| 4083 | + | |
| 4084 | + | |
| 4085 | + | |
| 4086 | + | |
| 4087 | + | |
| 4088 | + | |
| 4089 | + | |
| 4090 | + | |
| 4091 | + | |
| 4092 | + | |
| 4093 | + | |
| 4094 | + | |
| 4095 | + | |
| 4096 | + | |
| 4097 | + | |
| 4098 | + | |
| 4099 | + | |
| 4100 | + | |
| 4101 | + | |
| 4102 | + | |
| 4103 | + | |
| 4104 | + | |
| 4105 | + | |
| 4106 | + | |
| 4107 | + | |
| 4108 | + | |
| 4109 | + | |
| 4110 | + | |
| 4111 | + | |
| 4112 | + | |
| 4113 | + | |
| 4114 | + | |
| 4115 | + | |
| 4116 | + | |
| 4117 | + | |
| 4118 | + | |
| 4119 | + | |
| 4120 | + | |
| 4121 | + | |
| 4122 | + | |
| 4123 | + | |
| 4124 | + | |
| 4125 | + | |
| 4126 | + | |
| 4127 | + | |
| 4128 | + | |
| 4129 | + | |
| 4130 | + | |
| 4131 | + | |
| 4132 | + | |
| 4133 | + | |
| 4134 | + | |
| 4135 | + | |
| 4136 | + | |
| 4137 | + | |
| 4138 | + | |
| 4139 | + | |
| 4140 | + | |
| 4141 | + | |
| 4142 | + | |
| 4143 | + | |
| 4144 | + | |
| 4145 | + | |
| 4146 | + | |
| 4147 | + | |
| 4148 | + | |
| 4149 | + | |
| 4150 | + | |
| 4151 | + | |
| 4152 | + | |
| 4153 | + | |
| 4154 | + | |
| 4155 | + | |
| 4156 | + | |
| 4157 | + | |
| 4158 | + | |
| 4159 | + | |
| 4160 | + | |
| 4161 | + | |
| 4162 | + | |
| 4163 | + | |
| 4164 | + | |
| 4165 | + | |
| 4166 | + | |
| 4167 | + | |
| 4168 | + | |
| 4169 | + | |
| 4170 | + | |
| 4171 | + | |
| 4172 | + | |
| 4173 | + | |
| 4174 | + | |
| 4175 | + | |
| 4176 | + | |
| 4177 | + | |
| 4178 | + | |
| 4179 | + | |
| 4180 | + | |
| 4181 | + | |
| 4182 | + | |
| 4183 | + | |
| 4184 | + | |
| 4185 | + | |
| 4186 | + | |
| 4187 | + | |
| 4188 | + | |
| 4189 | + | |
| 4190 | + | |
| 4191 | + | |
| 4192 | + | |
| 4193 | + | |
| 4194 | + | |
| 4195 | + | |
| 4196 | + | |
| 4197 | + | |
| 4198 | + | |
| 4199 | + | |
| 4200 | + | |
| 4201 | + | |
| 4202 | + | |
| 4203 | + | |
| 4204 | + | |
| 4205 | + | |
| 4206 | + | |
| 4207 | + | |
| 4208 | + | |
| 4209 | + | |
| 4210 | + | |
| 4211 | + | |
| 4212 | + | |
| 4213 | + | |
| 4214 | + | |
| 4215 | + | |
| 4216 | + | |
| 4217 | + | |
| 4218 | + | |
| 4219 | + | |
| 4220 | + | |
| 4221 | + | |
| 4222 | + | |
| 4223 | + | |
| 4224 | + | |
| 4225 | + | |
| 4226 | + | |
| 4227 | + | |
| 4228 | + | |
| 4229 | + | |
| 4230 | + | |
| 4231 | + | |
| 4232 | + | |
| 4233 | + | |
| 4234 | + | |
| 4235 | + | |
| 4236 | + | |
| 4237 | + | |
| 4238 | + | |
| 4239 | + | |
| 4240 | + | |
| 4241 | + | |
| 4242 | + | |
| 4243 | + | |
| 4244 | + | |
| 4245 | + | |
| 4246 | + | |
| 4247 | + | |
| 4248 | + | |
| 4249 | + | |
| 4250 | + | |
| 4251 | + | |
| 4252 | + | |
| 4253 | + | |
| 4254 | + | |
| 4255 | + | |
| 4256 | + | |
| 4257 | + | |
| 4258 | + | |
| 4259 | + | |
| 4260 | + | |
| 4261 | + | |
| 4262 | + | |
| 4263 | + | |
| 4264 | + | |
| 4265 | + | |
| 4266 | + | |
| 4267 | + | |
| 4268 | + | |
| 4269 | + | |
| 4270 | + | |
3941 | 4271 | | |
3942 | 4272 | | |
3943 | 4273 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
33 | 34 | | |
0 commit comments