Skip to content

feat: Integrate NFC card reading and enhance icon animation - #486

Open
manideepk90 wants to merge 3 commits into
mainfrom
feat/nfc-integration
Open

feat: Integrate NFC card reading and enhance icon animation#486
manideepk90 wants to merge 3 commits into
mainfrom
feat/nfc-integration

Conversation

@manideepk90

Copy link
Copy Markdown
Contributor

Type of Change

  • Bugfix
  • Feature
  • Refactor
  • Chore
  • CI/CD
  • Docs

What & Why

This pull request introduces NFC card reading support to the React Native app by integrating the react-native-nfc-emv library and implementing UI and logic for tap-to-read card functionality. The most significant changes include dependency and configuration updates, a new NFC module abstraction, a new UI button for NFC, and updates to the card entry flow to support NFC-based input.

NFC Integration and Configuration:

  • Added the react-native-nfc-emv library as a dependency and configured it in react-native.config.js to enable native NFC EMV features in the app. [1] [2]

NFC Module Abstraction:

  • Implemented NfcEmvModule.res, a ReScript module that wraps the native NFC EMV API, providing promise-based methods for checking availability, permissions, listening for card data, and handling errors, as well as utility functions for formatting and masking card data.

UI Components and Card Entry Flow:

  • Added NfcEmvButton.res, a new component that manages NFC listening state, handles permission requests, displays a blinking NFC icon while listening, and manages success/error flows for reading card data via NFC.
  • Updated CardElement.res to integrate NFC card reading:
    • Added logic to check NFC availability and handle card data read from NFC, including validation and auto-focusing the next input field.
    • Updated the UI to show an NFC button alongside the scan card button when available, and wired up the NFC read handler to the card form. [1] [2] [3] [4]

These changes collectively enable users to tap their card on an NFC reader to autofill card details in the payment form, improving both usability and speed for supported devices.


Screenshots / Recordings


Affected Area & Impact

  • Client Core
  • Shared Codebase
  • Android
  • iOS

Android PR / status (if any):
iOS PR / status (if any):
Shared Codebase PR / status (if any):

Testing

  • JS bundle built
  • Tested in Android app
  • Tested in iOS app

Notes:


Checklist

  • Tested in consuming Android app
  • Tested in consuming iOS app

open Validation

// Import NFC EMV components
open NfcEmvButton

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rename to NFCEMVButton, its cleaner

Comment thread package.json
"final-form": "^5.0.0",
"react-final-form": "^7.0.0",
"react-native-inappbrowser-reborn": "^3.7.0",
"react-native-nfc-emv": "link:../../../library/react-native-nfc-emv",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should be hosted on npm

Comment thread react-native.config.js
},
dependencies: {
'react-native-nfc-emv': {
root: path.join(__dirname, '..', '..', '..', 'library', 'react-native-nfc-emv'),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can be removed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

also if you don't need react native build system here + if this feature doesn't require any special entitlements/ permissions or takes too much space then it can also be shipped with the SDK with merchant config to toggle

// Mask card number for display
let maskCardNumber = (pan: string): string => {
if pan->String.length < 8 {
pan

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why returning this

} else {
let first4 = pan->String.slice(~start=0, ~end=4)
let last4 = pan->String.slice(~start=-4, ~end=pan->String.length)
`${first4} **** **** ${last4}`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

isn't it better to just have last 4, seems more standard + will cover other edge cases where its 15, 19 (Amex)

@manideepk90
manideepk90 force-pushed the feat/nfc-integration branch from 756fe71 to 80dbed5 Compare April 28, 2026 11:43
@kanikabansal08

Copy link
Copy Markdown

@hyperswitch-client-core-review-bot please review this PR

}
} catch {
| ex => {
Console.log2("TapCardModule not available:", ex)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Console.log2 appears at lines 46 and 74. Only src/utility/logics/Window.res is allowlisted for console logging.

Remove these calls or route through the proper telemetry/logging infrastructure.

if error.code !== "USER_CANCELLED" {
cleanup()
// Small delay before retry to avoid rapid retries
let _ = setTimeout(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ setTimeout at line 116 creates a retry timer that is never cleared. If the component unmounts during the 500ms delay, the callback will still execute and potentially call a stale function reference.

Store the timer ID and clear it in the cleanup effect, following the pattern in PaymentHook.res:42-53.

Comment thread package.json
"final-form": "^5.0.0",
"react-final-form": "^7.0.0",
"react-native-inappbrowser-reborn": "^3.7.0",
"react-native-nfc-emv": "link:../../../library/react-native-nfc-emv",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Local path dependency link:../../../library/react-native-nfc-emv will break builds for anyone without this exact directory structure.

Publish the package to a registry (npm/GitHub Packages) or use a git dependency with commit/tag pinning before merging.

}

// Format expiry date from YYMMDD to MM/YY
let formatExpiryDate = (expiry: string): string => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 formatExpiryDate (lines 242-254) and maskCardNumber (lines 257-265) are defined but never referenced anywhere in the codebase.

Remove dead code or integrate these utilities if they serve a purpose.

}

// Store reference to startNfcListening for retry logic
React.useEffect1(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 startListeningRef changes every render due to the recursive function reference, causing unnecessary effect re-runs.

Wrap startNfcListening in React.useCallback with stable dependencies, then store only the stable callback in the ref.

@hs-agent hs-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: 🔄 Request Changes

⚠️ 3 warnings · 💡 2 suggestions

The NFC card reading feature is well-structured overall, but there are several blocking issues that need addressing before merge:

  1. Console.log calls violate the internal_linter allowlist policy
  2. Missing setTimeout cleanup creates potential memory leaks and stale callback execution
  3. Local path dependency will break CI/builds for anyone without the exact filesystem layout

The dead code and effect stability issues should also be cleaned up for production readiness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants