readSignedInvoice activates the NFC reader, waits for a merchant’s HCE tag, validates the ECDSA signature and security constraints, and returns a verified PaymentRequest.
Import
import { readSignedInvoice } from '@taprails/tap-to-pay';
Signature
async function readSignedInvoice(): Promise<PaymentRequest>
Returns
A PaymentRequest object after passing all security checks:
interface PaymentRequest {
paymentId: string; // Payment ID (same as NFC tag's `id`)
merchantWallet: string; // Merchant wallet address
amount: string; // USDC decimal string, e.g. "25.00"
currency: 'USDC';
network: 'base';
timestamp: number; // Unix ms timestamp from invoice
transactionId: string; // Alias for paymentId
}
Security validation
Before returning, readSignedInvoice enforces three security checks and throws NFCPaymentError if any fail:
| Check | Constraint | Error code |
|---|
| Expiry | now > invoice.expiresAt | INVALID_DATA |
| Timestamp window | Invoice is more than 5 minutes old (replay attack) | INVALID_DATA |
| ECDSA signature | Signature must verify against merchant’s registered device keys | INVALID_DATA |
The merchant’s active device public keys are fetched from the backend (/api/v1/sdk/merchant-pubkey) and verified locally on the customer’s device.
Throws
| Error | Code | Condition |
|---|
NFCPaymentError | NOT_SUPPORTED | Device has no NFC |
NFCPaymentError | NOT_ENABLED | NFC is turned off |
NFCPaymentError | TIMEOUT | No tag detected within 30 seconds |
NFCPaymentError | INVALID_DATA | Expired, stale, malformed, or bad signature |
NFCPaymentError | USER_CANCELLED | User dismissed the NFC prompt |
NFCPaymentError | READ_FAILED | Generic NFC hardware failure |
Example
import { readSignedInvoice, NFCPaymentError, NFCErrorCode } from '@taprails/tap-to-pay';
const handleScan = async () => {
try {
const payment = await readSignedInvoice();
console.log('Amount:', payment.amount, 'USDC');
console.log('Merchant:', payment.merchantWallet);
// Proceed to processPayment...
} catch (err) {
if (err instanceof NFCPaymentError) {
if (err.code === NFCErrorCode.TIMEOUT) {
Alert.alert('Timed out', 'Move your phone closer and try again');
} else {
Alert.alert('Scan failed', err.message);
}
}
}
};
useNFCCustomer.scanPaymentRequest() calls readSignedInvoice internally and surfaces the result/error via hook state. Prefer the hook over calling readSignedInvoice directly.