# Purchase Intents

Payment intents for purchases — create, track, and settle a payment

## POST /api/v3/purchase-intents

Create a purchase intent (v3 — purchase-service backed)

Create a new purchase intent against the new purchase-service backend. Voucher validation, mode-dispatch, and KDI synthesis stay in karma-common-api; state machine, outbox, and expiry are owned by purchase-service.

**Permissions:** ⚪ `tabs.pay`

Request body:

```ts
{
  locationId: integer
  mode: "upfront" | "items" | "amount" | "remainder"
  tabId?: string // uuid
  tabMemberId?: string // uuid
  kitchenDispatchItemIds?: string[]
  amountCents?: integer
  tipAmountCents?: integer
  voucherCodes?: string[]
  cart?: unknown[]
  paymentMethod: {
    type: string
    savedPaymentMethodId?: string // uuid
    terminalId?: string
    pspParams?: Record<string, unknown>
  }
  savePaymentMethod?: boolean
  idempotencyKey: string
  clientRequestId?: string
  quoteId?: string
  guestSessionId?: string
  metadata?: Record<string, unknown>
}
```

Response 201 — Purchase intent created

```ts
{
  status?: "success"
  data?: {
    purchaseIntentId: string // uuid
    state: string
    expiresAt?: string
    correlationId?: string
    pspInstruction?: Record<string, unknown> | null
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v3/purchase-intents/{id}

Get a purchase intent (v3)

Fetch a purchase intent by id from purchase-service.

**Permissions:** ⚪ `tabs.pay`

Path parameters:
- `id` (string, required)

Response 200 — Purchase intent detail

```ts
{
  status?: "success"
  data?: {
    purchaseIntentId: string // uuid
    state: string
    kind?: string
    amount?: integer
    currency?: string
    correlationId?: string
    metadata?: Record<string, unknown> | null
    locationId?: integer | null
    createdAt?: string
    updatedAt?: string
    expiresAt?: string
    mode?: string
    amountCents?: integer
    tipAmountCents?: integer
    currencyCode?: string
    purchaseId?: integer | null
    tabId?: string | null
    tabMemberId?: string | null
    providerRoute?: string
    resultCode?: string
    action?: Record<string, unknown>
    pspReference?: string
    refusalReason?: string
    refusalReasonCode?: string
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v3/purchase-intents/{id}/cancel

Cancel a purchase intent (v3)

Cancel a purchase intent via purchase-service. State machine + PSP cancel owned downstream.

**Permissions:** ⚪ `tabs.pay`

Path parameters:
- `id` (string, required)

Request body:

```ts
{
  reason?: string
  guestSessionId?: string
  tabMemberId?: string // uuid
  requireConfirmedCancellation?: boolean
}
```

Response 200 — Purchase intent cancelled

```ts
{
  status?: "success"
  data?: {
    purchaseIntentId: string // uuid
    state: string
    kind?: string
    amount?: integer
    currency?: string
    correlationId?: string
    metadata?: Record<string, unknown> | null
    locationId?: integer | null
    createdAt?: string
    updatedAt?: string
    expiresAt?: string
    mode?: string
    amountCents?: integer
    tipAmountCents?: integer
    currencyCode?: string
    purchaseId?: integer | null
    tabId?: string | null
    tabMemberId?: string | null
    providerRoute?: string
    resultCode?: string
    action?: Record<string, unknown>
    pspReference?: string
    refusalReason?: string
    refusalReasonCode?: string
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v3/purchase-intents/{id}/submit-details

Submit additional PSP details (v3)

Advance a purchase intent by submitting PSP-specific details (e.g. Adyen 3DS).

**Permissions:** ⚪ `tabs.pay`

Path parameters:
- `id` (string, required)

Request body:

```ts
{
  details: Record<string, unknown>
  guestSessionId?: string
  tabMemberId?: string // uuid
}
```

Response 200 — Details submitted

```ts
{
  status?: "success"
  data?: {
    purchaseIntentId: string // uuid
    state: string
    kind?: string
    amount?: integer
    currency?: string
    correlationId?: string
    metadata?: Record<string, unknown> | null
    locationId?: integer | null
    createdAt?: string
    updatedAt?: string
    expiresAt?: string
    mode?: string
    amountCents?: integer
    tipAmountCents?: integer
    currencyCode?: string
    purchaseId?: integer | null
    tabId?: string | null
    tabMemberId?: string | null
    providerRoute?: string
    resultCode?: string
    action?: Record<string, unknown>
    pspReference?: string
    refusalReason?: string
    refusalReasonCode?: string
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v3/purchase-intents/{id}/terminal/status

Poll a Cloud Terminal purchase (v3)

Query Adyen through purchase-service using the immutable contract and POI mapping bound to the intent.

**Permissions:** ⚪ `tabs.pay`

Path parameters:
- `id` (string, required)

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    purchaseIntentId: string // uuid
    state: string
    terminalOutcome: "success" | "not_successful" | "in_progress" | "unknown"
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v3/purchase-intents/preview

Compute cart totals (subtotal, surplus service fee, tip, total) without creating a purchase intent.

**Permissions:** ⚪ `tabs.pay`

Request body:

```ts
{
  locationId: integer
  kdiIds: string[]
  tipCents?: integer
}
```

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    subtotalCents?: integer
    discountCents?: integer
    surplusUserFeeCents?: integer
    surplusUserFeeBps?: integer
    tipCents?: integer
    totalCents?: integer
    currencyCode?: string
    hasSurplusItems?: boolean
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```
