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

# Architecture Overview

> Runtime components, authentication boundaries, data model, and privacy handling for Age Verification.

# Architecture Overview

## Runtime components

| Component | Implemented responsibility |
| - | - |
| React Router 7 server | Embedded admin routes, app-proxy handlers, identity-session orchestration, billing, webhooks, privacy operations, and POS endpoints |
| Theme app extension | Storefront date-of-birth modal (`age-gate.liquid`) and the cart-page **Verify ID for checkout** block (`cart-verify.liquid`) |
| Checkout UI extension | Shopify Plus checkout blocking backstop plus thank-you and customer-account order-status nudges |
| POS UI extension | Register tile, QR modal, and post-purchase account nudge |
| PostgreSQL with Prisma | Shopify sessions, shops, subscriptions, settings, customers, verifications, webhook deduplication, rate limits, exports, and audit records. RLS is the final tenant-isolation backstop. |
| Identity providers | Document-verification sessions and signed result callbacks. Didit is the production-selected provider; Stripe Identity is the rollback path. |

The embedded document loads App Bridge and Polaris web components from Shopify's CDN. Admin navigation is rendered with `s-app-nav`; route loaders and actions authenticate with `authenticate.admin`.

## Request and trust boundaries

| Surface | Authentication |
| - | - |
| Embedded admin | Shopify online session authentication |
| Storefront app proxy | HMAC-SHA256 signature, timestamp freshness, normalized shop domain, and exact `/apps/age-verification` path-prefix validation |
| Shopify POS API | `authenticate.public.pos`; the shop comes from the signed token's `dest` claim |
| Shopify webhooks | `authenticate.webhook` |
| Didit webhook | Raw-body HMAC-SHA256 verification using `x-signature-v2` plus timestamp freshness |
| Stripe Identity webhook (rollback path) | Stripe raw-body signature verification |
| Outbound merchant event webhook | HMAC-SHA256 over the raw JSON body in `X-Pcnaid-Hmac-Sha256` |

The storefront does not trust a customer ID supplied in JSON. The server uses Shopify's signed `logged_in_customer_id` from the app-proxy request.

## Storefront and checkout flow

The theme app embed fetches public configuration through the signed app proxy. In Simple mode, the browser evaluates the entered date of birth and sends a bounded self-certification event to the server, which recalculates the applicable minimum age before recording a successful pass.

Enhanced document verification is server-gated by the Enterprise entitlement, enabled settings, and provider readiness. A successful provider callback updates the verification record. For logged-in customers, the app publishes a `$app.identity_status` customer metafield. For guests, the storefront writes `_age_identity_verified` and `_age_verified_at` cart attributes.

Checkout enforcement is published as a `$app.checkout_gate` shop metafield. The all-plan cart block reads enforcement and customer state through `/apps/age-verification/status`. The checkout UI extension uses the same shop and customer metafields, but that checkout-step surface renders only on Shopify Plus.

Both checkout surfaces no-op when enforcement is unavailable. They never treat a missing configuration or a network failure as a verified identity result.

## Data model and PII minimization

The main Prisma records are:

* `Shop`, `ShopSubscription`, and `AppSettings` for installation, billing, and merchant configuration.
* `Customer` and `Verification` for Shopify-linked customers and verification lifecycle state.
* `VerificationWebhookEvent` for provider-event deduplication.
* `VerificationAttempt` and `VerificationRateLimitCounter` for abuse controls.
* `CustomerDataExport` for Shopify customer-data requests.
* `AuditLog` and `ComplianceAuditLog` for operational and tamper-evident audit history.
* `MerchantApiCredential` for hashed bearer API credentials (Enterprise).

The `Verification` model stores provider session identifiers, status, the minimum age used, and an optional derived verified age. It has no date-of-birth, document-image, or biometric field. The identity provider remains the system of record for identity documents. The Didit callback stores the derived age for a verified decision, not the raw date of birth. On the Stripe rollback path, the callback reads date-of-birth data to make the age decision without persisting that date.

## Shopify privacy webhooks

The three compliance topics perform data work:

* `CUSTOMERS_DATA_REQUEST` creates a `CustomerDataExport` for merchant review.
* `CUSTOMERS_REDACT` requests provider redaction, deletes the customer's verification and export records, removes linked operational audit rows, and deletes the local customer record.
* `SHOP_REDACT` requests redaction for the shop's verification sessions, deletes stored sessions, purges the protected compliance chain through its authorized whole-shop function, and deletes the shop.

`APP_UNINSTALLED` marks the shop uninstalled and removes sessions. The scheduled cleanup later cancels stale identity sessions, requests redaction for retained verification sessions, deletes uninstalled shops after the configured retention period, and prunes expired rate-limit counters.

## Billing

Billing is app-managed through Shopify's Billing API. Plans: Free $0, Pro $2.99/mo ($29.90/yr), Enterprise $9.99/mo (\$99.90/yr). Each shop gets one 14-day trial on the first active paid subscription.

## Deployment

The app runs as a containerized web service. A separate scheduled job handles post-uninstall data cleanup, and database migrations are applied before each release.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.