# Cross-Repo Contract

## 1. Identifiers & Mapping (DynamoDB `customer_links`)
- **PK**: `api_key_id` (string)
- **Attributes**:
  - `stripe_customer_id` (string, required)
  - `cognito_user_id` (string, optional)
  - `plan` (`free`|`metered`)
  - `usage_plan_id` (`FREE_10K`|`METERED`)
  - `metered_subscription_item_id` (string, required for metered reporting)
  - `last_reported_usage_ts` (ISO8601)
  - `current_month_reported_units` (int)
- **GSI**: `gsi_stripe_customer` (PK=`stripe_customer_id`); Projection=INCLUDE(`api_key_id`,`usage_plan_id`,`plan`,`metered_subscription_item_id`).

## 2. Pricing Model (Stripe)
- Product: `API Access`
- Price (metered): `API calls`, aggregation=`sum`, tiers:
  - 0–10,000 units: $0
  - >10,000: $0.10 per 1,000 units (graduated)

## 3. Usage Semantics
- Unit: 1 API request = 1 unit.
- Window: `[since, until)` default hourly; configurable.
- Source: API Gateway `GetUsage` per **Usage Plan + API Key**.
- Idempotency: `idempotency_key = "{subscription_item_id}:{period_end_iso}:{reported_total}"`.

## 4. Events & Reactions (EventBridge partner bus)
- Card added: `payment_method.attached` / `setup_intent.succeeded` → ensure **metered subscription** (capture **subscription_item_id**); plan target=METERED.
- Subscription status: `customer.subscription.created|updated` → if active|trialing → plan target=METERED else FREE.
- Payment issue: `invoice.payment_failed` → plan target=FREE.

## 5. Handler Contracts
- **Library `handle_stripe_event(evt)` returns**: `{"target_plan": "metered"|"free", "stripe_customer_id": "...", "subscription_item_id": "subitem_xxx" | null}`.
- **Infra handler must**:
  - Move API key to target usage plan.
  - Persist `plan`, `usage_plan_id`, and `metered_subscription_item_id` (if provided).
  - Be idempotent and retry-safe.
