# Order Flow Example (Hybrid Card + Crypto)

This document shows a complete order flow using curl commands and matches the current hybrid payment behavior.

## Deterministic Node Agent Run

For a deterministic end-to-end CLI run with branch-specific markers:

```bash
node examples/node-agent/index.ts --paymentMethod crypto
node examples/node-agent/index.ts --paymentMethod card
```

Expected output checkpoints include:
- `[CHECKPOINT] quoteId=...`
- `[CHECKPOINT] paymentRequestId=...` (crypto) or `[CHECKPOINT] paymentIntentId=...` (card)
- `[CHECKPOINT] orderId=...`

## Step 1: Get Menu

```bash
curl http://localhost:3000/v1/menu
```

Response (example):
```json
{
  "currency": "USDC",
  "items": [
    {"sku": "PZ-MAR", "name": "Margherita", "priceCents": 900},
    {"sku": "PZ-PEP", "name": "Pepperoni", "priceCents": 1100},
    {"sku": "DR-COL", "name": "Cola 33cl", "priceCents": 250}
  ]
}
```

## Step 2: Create Quote

```bash
curl -X POST http://localhost:3000/v1/orders/quote \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {"sku": "PZ-MAR", "qty": 1},
      {"sku": "DR-COL", "qty": 2}
    ],
    "fulfillment": {"type": "pickup"},
    "contact": {"name": "John Doe", "phone": "+33612345678"}
  }'
```

Response (example):
```json
{
  "quoteId": "550e8400-e29b-41d4-a716-446655440000",
  "currency": "EUR",
  "subtotalCents": 1400,
  "feeCents": 50,
  "totalCents": 1450,
  "lineItems": [...],
  "pickupETA": "2026-02-23T18:15:00.000Z",
  "expiresAt": "2026-02-23T18:30:00.000Z",
  "availablePaymentMethods": ["card", "crypto"],
  "walletSetupUrl": "http://localhost:3000/v1/wallet-setup",
  "paymentInstructions": "Payment required: 14.50 EUR. Pay by card (recommended) or USDC on Base network.",
  "merchantId": "merchant-demo-001",
  "merchantAddress": "123 Example Street, Example City, France",
  "quoteHash": "sha256:...",
  "significance": "medium",
  "riskHints": {"unusual": true, "reasons": ["above_typical_total"], "maxDownsideCents": 1450, "recommendedConfirmation": "confirm"}
}
```

Notes:
- `availablePaymentMethods` tells the agent which branch to offer.
- `merchantId` is the stable merchant-scoped identifier; review `docs/public/merchant-context-parity-checklist.md` before changing how API, MCP, or storefront surfaces use it.
- `quoteHash` can be reused in confirm assertions to bind confirmation to the reviewed quote snapshot.
- `significance` + `riskHints.recommendedConfirmation` help apply risk-proportional friction.
- `walletSetupUrl` can be shown before or during the crypto flow.
- `paymentMethod` is optional on quote and confirm; you can set it explicitly for deterministic behavior.

## Step 3: Choose a Payment Branch

### Card Payment Path (200 with PaymentIntent)

```bash
curl -X POST http://localhost:3000/v1/orders/confirm \
  -H "Content-Type: application/json" \
  -d '{"quoteId": "550e8400-e29b-41d4-a716-446655440000", "paymentMethod": "card"}'
```

Response (200):
```json
{
  "paymentIntentId": "pi_12345",
  "clientSecret": "pi_12345_secret_67890",
  "amountCents": 1450,
  "currency": "EUR",
  "instructions": "Complete payment using the clientSecret with Stripe Elements or Checkout. Order will be created automatically when payment succeeds."
}
```

If payment is still pending, repeated confirm returns:
```json
{
  "paymentIntentId": "pi_12345",
  "instructions": "Payment not yet completed. Complete payment using Stripe Elements or Checkout. Order will be created automatically when payment succeeds."
}
```

Next steps:
- Use `clientSecret` in Stripe Elements/Checkout.
- Order is created by webhook after payment succeeds.
- If payment is already completed, repeating `POST /v1/orders/confirm` with the same `quoteId` + `paymentMethod: "card"` returns a deterministic paid-order response.
- Repeated confirms after payment success are idempotent (no duplicate card order rows).
- Poll `GET /v1/orders/{id}` after you have an order ID from your app flow or merchant tools.

Webhook contract notes:
- `POST /webhooks/stripe` requires the `stripe-signature` header and the exact raw request body for signature verification.
- Invalid Stripe signatures are rejected (`400`) and should be treated as non-acknowledged deliveries.
- Valid Stripe events are acknowledged with `200`; duplicate delivery of already-processed payment intents remains idempotent (`200`, no duplicate order rows).
- `POST /webhooks/twilio` requires `X-Twilio-Signature`; signature verification uses canonical request URL + posted form parameters.
- Invalid Twilio signatures are rejected (`403`); valid callbacks are acknowledged with `200` and are safe for provider retries.

Rate-limit note:
- Public `/v1/*` endpoints can return `429 RATE_LIMITED`; agents should honor the `Retry-After` header and `retryAfterMs` before retrying burst traffic.

### Crypto Payment Path (402 then 200)

First call (expect `402` + `PAYMENT-REQUIRED`):

```bash
curl -i -X POST http://localhost:3000/v1/orders/confirm \
  -H "Content-Type: application/json" \
  -d '{
    "quoteId": "550e8400-e29b-41d4-a716-446655440000",
    "paymentMethod": "crypto",
    "assertions": {
      "expectedQuoteHash": "sha256:...",
      "expectedTotalCents": 1450,
      "maxTotalCents": 1450,
      "expectedCurrency": "USDC",
      "expectedPaymentMethod": "crypto"
    }
  }'
```

Response body (402 example):
```json
{
  "paymentRequestId": "abc123-456",
  "amountCents": 1450,
  "currency": "USDC",
  "chain": "eip155:8453",
  "stablecoin": "USDC",
  "instructions": "Resend this request with header PAYMENT-SIGNATURE: abc123-456",
  "walletSetupGuideUrl": "http://localhost:3000/v1/wallet-setup",
  "paymentHelp": "Don't have a wallet? See setup guide: http://localhost:3000/v1/wallet-setup",
  "paymentRequirements": {
    "scheme": "exact",
    "network": "eip155:8453",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "maxAmountRequired": "14.50",
    "payTo": "0x1111111111111111111111111111111111111111",
    "maxTimeoutSeconds": 600
  }
}
```

Note: `paymentRequirements` is adapter-dependent. Coinbase/x402 responses include it; mock responses may omit it.

For the mock adapter, use `paymentRequestId` as the `PAYMENT-SIGNATURE` header:

```bash
curl -X POST http://localhost:3000/v1/orders/confirm \
  -H "Content-Type: application/json" \
  -H "PAYMENT-SIGNATURE: abc123-456" \
  -d '{"quoteId": "550e8400-e29b-41d4-a716-446655440000", "paymentMethod": "crypto"}'
```

Response (200):
```json
{
  "orderId": "ord_550e8400-...",
  "status": "paid",
  "currency": "USDC",
  "totalCents": 1450,
  "receipt": {"message": "Order paid and sent to kitchen."}
}
```

### Common Error Branches

Assertion mismatch (400):
```json
{
  "error": "Quote assertion failed: total 1450c exceeds max 1400c.",
  "errorCode": "ASSERTION_FAILED_TOTAL",
  "retryable": false
}
```

Payment rail unavailable (503):
```json
{
  "error": "No payment methods are currently available. Configure PAYMENT_METHOD to include at least one working rail (card or crypto).",
  "errorCode": "PAYMENT_UNAVAILABLE",
  "retryable": false
}
```

## Step 4: Check Order Status

```bash
curl http://localhost:3000/v1/orders/ord_550e8400-...
```

Response (example):
```json
{
  "id": "ord_550e8400-...",
  "status": "paid",
  "currency": "USDC",
  "totalCents": 1450,
  "items": [...],
  "contact": {"verified": true},
  "fulfillment": {"type": "pickup"},
  "pickupETA": "2026-02-23T18:15:00.000Z",
  "etaStatus": "provisional"
}
```

Note: `contact` only exposes `verified` in `PUBLIC_AGENT` responses (PII is stripped).
ETA note: `pickupETA` resolution is consistent for both card and crypto orders in `GET /v1/orders/{id}` and `GET /partner/orders/api`.
