> ## Documentation Index
> Fetch the complete documentation index at: https://docs.taprails.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# readSignedInvoice

> Read and cryptographically verify a payment invoice from an NFC tag.

`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

```ts theme={null}
import { readSignedInvoice } from '@taprails/tap-to-pay';
```

## Signature

```ts theme={null}
async function readSignedInvoice(): Promise<PaymentRequest>
```

## Returns

A `PaymentRequest` object after passing all security checks:

```ts theme={null}
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

```tsx theme={null}
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);
      }
    }
  }
};
```

<Note>
  `useNFCCustomer.scanPaymentRequest()` calls `readSignedInvoice` internally and surfaces the result/error via hook state. Prefer the hook over calling `readSignedInvoice` directly.
</Note>
