# Identity (Account & Business)

PayPM Identity is the internal system of record for people, businesses,
memberships, verified contacts, consent, mandates, compliance references, and
OIDC subject links.

ZITADEL authenticates subjects only. Identity owns account and business facts.
Product services own their own roles and lifecycle authorization.

The Identity API has **no public ingress**. Service consumers call it on the
private cluster network with a ZITADEL service JWT.

## Ownership

| Owner | Owns |
| --- | --- |
| ZITADEL (`auth.paypm.net`) | Login, tokens, subjects |
| Identity | People, businesses, memberships, OIDC subject links, wallet end-user references |
| Marketplace / wallet / portal | Product roles, money movement, UI |

Identity never stores passwords, PINs, OTPs, product roles, balances, or raw KYC
documents.

## Service authentication

Callers present:

```http
Authorization: Bearer <service-access-token>
```

The token is issued by ZITADEL client-credentials for a dedicated service
account. Its audience must match Identity `IDENTITY_OIDC_AUDIENCE` (the PayPM
project audience in sandbox).

## Resolve an authenticated subject

```http
POST /api/internal/v1/authentication-subjects/resolve
Content-Type: application/json
```

```json
{
  "issuer": "https://auth.paypm.net",
  "subject": "opaque-zitadel-subject",
  "businessId": "optional-active-business-uuid"
}
```

```json
{
  "personId": "uuid",
  "status": "active",
  "business": {
    "businessId": "uuid",
    "publicReference": "tenant-or-business-ref",
    "membershipStatus": "active"
  },
  "externalReferences": {
    "walletEndUserId": "optional-uuid"
  }
}
```

Unknown or inactive subjects fail closed. The response never includes product
roles such as `marketplace:supplier`.

## Bind Account & Business (write)

```http
POST /api/internal/v1/authentication-subjects/bind
Content-Type: application/json
```

Idempotently creates or reactivates person, business, membership, subject link,
and optional wallet end-user reference:

```json
{
  "issuer": "https://auth.paypm.net",
  "subject": "opaque-zitadel-subject",
  "displayName": "Ben Ouattara",
  "businessPublicReference": "tenant-sandbox-marketplace-001",
  "businessLegalName": "Sandbox Supplier Business",
  "personId": "optional-uuid",
  "businessId": "optional-uuid",
  "walletEndUserId": "optional-uuid"
}
```

Returns `201` with the bound identifiers. CLI equivalent:

```bash
npm run identity:bind-subject -- binding.json
```

## Account reads and updates

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/api/internal/v1/people` | Create person |
| `GET` | `/api/internal/v1/people/:personId` | Active person + business ids + wallet link |
| `PATCH` | `/api/internal/v1/people/:personId` | Update `displayName` |
| `GET` | `/api/internal/v1/people/:personId/memberships` | Active business memberships |
| `POST` | `/api/internal/v1/people/:personId/authentication-subjects` | Link OIDC issuer/subject |
| `DELETE` | `/api/internal/v1/people/:personId/authentication-subjects` | Unlink OIDC issuer/subject |
| `PUT` | `/api/internal/v1/people/:personId/external-references/wallet-end-user` | Set wallet end-user link |
| `DELETE` | `/api/internal/v1/people/:personId/external-references/wallet-end-user` | Clear wallet end-user link |
| `DELETE` | `/api/internal/v1/people/:personId` | Soft-deactivate person |
| `POST` | `/api/internal/v1/businesses` | Create business |
| `GET` | `/api/internal/v1/businesses/:businessId` | Active business + member person ids |
| `PATCH` | `/api/internal/v1/businesses/:businessId` | Update `legalName` |
| `POST` | `/api/internal/v1/businesses/:businessId/memberships` | Add person membership `{ "personId" }` |
| `DELETE` | `/api/internal/v1/businesses/:businessId/memberships/:personId` | Soft-remove membership |
| `DELETE` | `/api/internal/v1/businesses/:businessId` | Soft-deactivate business |

Unknown active records return `404`. Missing service JWT returns `401`.

## Marketplace consumption

Sandbox Marketplace uses `IDENTITY_DIRECTORY_MODE=http` and calls the private
Identity ClusterIP. Marketplace participant roles (`supplier`, `operator`) stay
in the Marketplace database. Identity supplies person/business linkage only.

## Not yet public

There is no public Account & Business web UI on `identity.paypm.net` yet. That
host remains a transitional operator redirect to ZITADEL console until an
explicit product cutover.
