# `Teya.POSLink.Payment`
[🔗](https://github.com/sgerrand/ex_teya/blob/v0.4.0/lib/teya/poslink/payment.ex#L1)

POSLink payment requests — initiate and manage card-present payments at terminals.

A payment request instructs a specific terminal to collect a card payment.
Status transitions: `NEW` → `IN_PROGRESS` → `SUCCESSFUL` | `FAILED` | `CANCELLED`.

Use `create/2` to start a payment and `subscribe/2` to receive real-time
status updates via the terminal's SSE stream.

Required OAuth scopes: `poslink/payment-requests/create`,
`poslink/payment-requests/id/get`, `poslink/payment-requests/id/update`,
`poslink/payment-requests/get`.

## Task lifecycle

`subscribe/2` returns `{:ok, %Task{}}` immediately. The task runs under
`Teya.TaskSupervisor` with `async_nolink`, meaning:

- The task is **not linked** to the caller — a task crash does not take down
  the calling process.
- The supervisor does **not restart** the task if it exits.
- If the **caller process dies**, the task continues running until the SSE
  stream ends or errors, then exits normally.
- If the **SSE stream disconnects** mid-payment (network error, server
  restart), the task sends `{:poslink_payment_error, id, reason}` and exits.
  There is no automatic reconnection. To recover, call `Payment.get/1` or
  `Payment.list/1` to poll the current status, or call `subscribe/2` again to
  open a fresh stream.

# `cancel`

```elixir
@spec cancel(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Teya.Error.t()}
```

Cancels an in-progress payment request.

Sends a `PATCH` to set `status` to `"CANCELLED"`. The terminal will abort
the current payment interaction. The request is idempotent: cancelling an
already-terminal payment (e.g. `SUCCESSFUL`) returns an error.

## Parameters

- `payment_request_id` — UUID returned from `create/2`

## Options

- `:idempotency_key` — override the auto-generated idempotency key

## Examples

    {:ok, %{"status" => "CANCELLING"}} = Teya.POSLink.Payment.cancel(payment_request_id)

# `create`

```elixir
@spec create(
  map(),
  keyword()
) :: {:ok, map()} | {:error, Teya.Error.t()}
```

Creates a payment request at a terminal.

Returns `{:ok, response}` containing the `payment_request_id` and initial
`status` (`"NEW"`). Use the returned `payment_request_id` with `subscribe/2`
to stream status updates as the cardholder interacts with the terminal.

## Required params

- `store_id` — UUID of the store
- `terminal_id` — UUID of the target terminal
- `requested_amount` — `%{"amount" => 1000, "currency" => "GBP"}` (amount
  in minor units); optionally include `"tip"` for tip-enabled terminals

## Optional params

- `merchant_reference` — caller-supplied reference (max 60 chars)
- `transaction_type` — currently only `"SALE"` (default)
- `metadata` — arbitrary key/value pairs (max 10 keys)

## Options

- `:idempotency_key` — override the auto-generated idempotency key

## Examples

    params = %{
      "store_id"         => store_id,
      "terminal_id"      => terminal_id,
      "requested_amount" => %{"amount" => 1000, "currency" => "GBP"}
    }

    {:ok, %{"payment_request_id" => id}} = Teya.POSLink.Payment.create(params)

# `get`

```elixir
@spec get(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Teya.Error.t()}
```

Fetches the current state of a single payment request by its ID.

Useful as a fallback when an SSE stream from `subscribe/2` disconnects before
the payment reaches a terminal state — check the current status, then
re-subscribe if still in progress.

## Parameters

- `payment_request_id` — UUID returned from `create/2`

## Examples

    {:ok, payment} = Teya.POSLink.Payment.get(payment_request_id)
    payment["status"]  # "NEW" | "IN_PROGRESS" | "SUCCESSFUL" | "FAILED" | "CANCELLED"

# `list`

```elixir
@spec list(keyword()) :: {:ok, map()} | {:error, Teya.Error.t()}
```

Lists payment requests with optional filtering.

Returns `{:ok, response}` containing a paginated list of payment request
objects and pagination metadata.

## Optional params (passed as `:params` keyword option)

- `status` — filter by status: `"NEW"`, `"IN_PROGRESS"`, `"SUCCESSFUL"`,
  `"CANCELLING"`, `"CANCELLED"`, `"FAILED"`
- `store_id` — UUID to filter by store
- `from` — ISO 8601 datetime lower bound (inclusive)
- `to` — ISO 8601 datetime upper bound (inclusive)
- `limit` — max results per page (default varies)
- `offset` — pagination offset

## Example

    Teya.POSLink.Payment.list(params: [status: "SUCCESSFUL", limit: 20])

# `subscribe`

```elixir
@spec subscribe(String.t(), pid()) :: {:ok, Task.t()}
```

Subscribes to real-time status updates for a payment request via SSE.

Spawns a supervised task under `Teya.TaskSupervisor` that opens the SSE
stream for `payment_request_id` and forwards parsed events as messages to
`pid` (defaults to `self()`).

## Messages sent to `pid`

- `{:poslink_payment, id, event_type, data}` — a status event where:
  - `id` is the `payment_request_id`
  - `event_type` is `"full"` (complete snapshot) or `"diff"` (partial update)
  - `data` is the decoded JSON map (e.g. `%{"status" => "SUCCESSFUL", ...}`)
- `{:poslink_payment_error, id, reason}` — the stream ended with an error;
  `reason` is a `%Teya.Error{}`, a transport exception, or `:stream_timeout`

The task exits normally when the server closes the stream (terminal payment
state reached) or with an error tuple when the connection fails.

## Example

    {:ok, _task} = Teya.POSLink.Payment.subscribe(payment_request_id)

    receive do
      {:poslink_payment, ^payment_request_id, "full", %{"status" => "SUCCESSFUL"} = data} ->
        handle_success(data)

      {:poslink_payment, ^payment_request_id, _type, %{"status" => "FAILED"} = data} ->
        handle_failure(data)

      {:poslink_payment_error, ^payment_request_id, reason} ->
        handle_error(reason)
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
