# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

PHP application demonstrating Cardinal Commerce integration for Visa Payment Passkey (VPP) / FIDO enrollment and authentication. Uses JWT-based iframe communication with Cardinal's staging APIs to walk through the complete VPP flow step by step.

## Development

```bash
php -S 0.0.0.0:8000          # Must use 0.0.0.0, not localhost — Cardinal needs to POST callbacks to your IP
```

Entry point: `http://localhost:8000/pktest.php`

No package manager, no build step, no test suite. Test by stepping through the UI with test cards: `4761120010000492`, `4761209980011439`, `4051069302200121`.

## Architecture

### Page Flow

The app is a linear multi-page wizard. Each page performs one API step, displays the request/response, then navigates to the next:

```
pktest.php → dx.php → cmpi_lookup.php (enrollment only) → passkey_challenge.php
```

**Flow determination** happens in `dx.php`: the Data Exchange API returns `Payload.Account.FIDO.FlowType` as either `"ENROLLMENT"` or `"AUTHENTICATION"`. Enrollment goes through CMPI Lookup (3DS); authentication skips directly to passkey challenge.

### Communication Pattern

Cardinal APIs use a **hidden iframe + JWT POST** pattern throughout:

1. PHP generates an HS256 JWT signed with the API key
2. A hidden `<form>` POSTs the JWT to a Cardinal endpoint inside an `<iframe>`
3. Cardinal responds via either:
   - **POST callback** to `return.php` (for FIDO/Init) — writes a `[TrxId].txt` file that `check_file.php` serves to the polling client
   - **`postMessage`** from the iframe (for DDC Collect and StepUp) — handled by `window.addEventListener("message", ...)`

### JWT Construction

Every page builds JWTs manually (no library):
- `base64url_encode()` is redefined in each PHP file
- Headers: `{alg: 'HS256', typ: 'JWT'}`
- Signed with `hash_hmac('sha256', ...)` using the API key from `config.php`
- The `jti` claim is a UUID v4 used as the transaction ID

### Key Data Flow Between Pages

- `pktest.php` → `dx.php`: passes `ReferenceId` via `?ref=` query param (extracted from Cardinal's callback JWT `Payload.ReferenceId`)
- `dx.php` → `cmpi_lookup.php`: passes DDC `SessionId` via `?ref=` query param
- `cmpi_lookup.php` → `passkey_challenge.php`: passes original `ReferenceId` via `?ReferenceId=` query param
- `dx.php` → `passkey_challenge.php` (auth flow): same `?ReferenceId=` query param, skipping CMPI

### Callback Mechanism (pktest.php)

`pktest.php` uses a file-based polling pattern for Cardinal's FIDO/Init response:
1. `return.php` receives Cardinal's POST, validates the JWT, writes decoded payload to `[TrxId].txt`
2. Client-side JS polls `check_file.php?txnId=...` every second (2-minute timeout)
3. After 30 seconds with no real response, auto-creates a mock via `create_mock_response.php`

### Data Exchange Signature (dx.php)

The Data Exchange API uses a different auth scheme than JWT — it uses `SHA-256` hash (not HMAC) of `timestamp + transactionId + apiKey`, base64-encoded as the `Signature` field.

### Configuration

`config.php` returns a structured array with API credentials, test account data, persona info, payment details, and endpoint URLs. Cardinal staging endpoints are hardcoded. The API key (`754be3dc-...`) is used both for JWT signing and Data Exchange signatures.

### Shared Utilities

- `utils.php`: `getBaseUrl()`, `getReturnUrl()`, `getMerchantOrigin()` — dynamic URL generation for callbacks
- `browser.js`: `BrowserInfo` object collecting device fingerprint data (used by `cmpi_lookup.php` for 3DS)
- `assets/script.js`, `assets/style.css`, `assets/custom.css`, `assets/animate.css`: shared UI components

### Cardinal Staging Endpoints

| Endpoint | URL |
|----------|-----|
| FIDO Init | `https://centinelapistag.cardinalcommerce.com/V2/FIDO/Init` |
| FIDO Challenge | `https://centinelapistag.cardinalcommerce.com/V2/FIDO/Challenge` |
| DDC Collect | `https://centinelapistag.cardinalcommerce.com/V2/Cruise/Collect` |
| StepUp | `https://centinelapistag.cardinalcommerce.com/V2/Cruise/StepUp` |
| Data Exchange | `https://dataexchangestag.cardinalcommerce.com/V1/AccountNumber/GetInfo` |
| CMPI Lookup | `https://centineltest.cardinalcommerce.com/maps/txns.asp` |

## Testing Notes

- Once a card is enrolled on a device, that card-device binding is permanent in Cardinal's system. To re-test enrollment, unenroll at [sandbox.auth.visa.com](https://sandbox.auth.visa.com/).
- All pages have hardcoded fallback ReferenceIds for direct-access testing (bypassing the normal flow).
- `create_mock_response.php` and `create_mock_failed_response.php` generate fake Cardinal callback files for testing the polling mechanism without a real Cardinal response.
