# Status queries & recovery
URL: /docs/a2a/recovery

Query the final state of a transaction or an order and reverse a card transaction with a technical cancel.

Sometimes a request ends without a clear answer. POS+ may have been closed
mid-transaction, the request may have failed with `PP_A2A_014` (timeout) or
`PP_A2A_007` (unexpected error), or your own app may have crashed after the
customer paid. POS+ always keeps the authoritative record of the order, so
**ask POS+ what happened before you retry**. Retrying a payment blindly can
charge the customer twice.

| Operation               | Method                 | Header fields                  | Result                                                                                 |
| ----------------------- | ---------------------- | ------------------------------ | -------------------------------------------------------------------------------------- |
| `TRANSACTION_RESULT`    | `getTransactionResult` | `order_code`, `transaction_id` | [`PPStartPaymentResponseModel`](/docs/a2a/reference#ppstartpaymentresponsemodel)       |
| `ORDER_RESULT`          | `getOrderResult`       | `order_code`                   | [`PPOrderPaymentResponseModel`](/docs/a2a/reference#pporderpaymentresponsemodel)       |
| `POST_TECHNICAL_CANCEL` | `technicalCancel`      | `order_code`, `transaction_id` | [`PPTechnicalCancelResponseModel`](/docs/a2a/reference#pptechnicalcancelresponsemodel) |

<Callout type="info">
  All three operations carry only a header; there is no `data` block. They are
  available from **Flutter `pluspay_a2a` 0.8.0** and **React Native
  `pluspay-a2a-react-native` 0.4.0**. The native Android package
  (`pluspay_a2a_native`) does not support them yet.
</Callout>

## Query a transaction

`getTransactionResult` looks up the order by `orderCode` and returns the
transaction with the given `transactionId`, in the same shape as a
`startPayment` response. Read `status` to learn the final state (`SUCCESS`,
`FAIL`, `CANCEL`, `WAITING`, `NOT_RESPONSE`, `NONE`). POS+ opens no payment
screen and charges nothing.

`transactionId` is the `id` of the payment response you received earlier. If
you never received a response, you don't know the transaction id, so use
[`getOrderResult`](#query-an-order) instead.

If the order has no transaction with that id, the request fails with
`PP_A2A_016`.

### Parameters

| Parameter       | Type   | Required | Description                                                                     |
| --------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `orderCode`     | String | yes      | Order code of the transaction                                                   |
| `transactionId` | String | yes      | POS+ transaction id (`id` of the payment response)                              |
| `locale`        | String | no       | UI language for this request, see [Language](/docs/a2a/initialization#language) |

<CodeBlockTabs defaultValue="flutter" groupId="pp-platform" persist>
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="flutter">
      Flutter
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="kotlin">
      Kotlin
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="java">
      Java
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="react-native">
      React Native
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="flutter">
    ```dart
    final tx = await _pluspay.getTransactionResult(
      PPTransactionResultRequestModel.toRequest(
        orderCode: 'ORD001',
        transactionId: 'TX-123',
      ),
    );

    print(tx.status);    // e.g. "SUCCESS"
    print(tx.totalPaid);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="kotlin">
    ```kotlin
    // Not available in pluspay_a2a_native yet.
    ```
  </CodeBlockTab>

  <CodeBlockTab value="java">
    ```java
    // Not available in pluspay_a2a_native yet.
    ```
  </CodeBlockTab>

  <CodeBlockTab value="react-native">
    ```ts
    const tx = await client.getTransactionResult(
      PPTransactionResultRequestModel.toRequest({
        orderCode: 'ORD001',
        transactionId: 'TX-123',
      })
    );

    console.log(tx.status); // e.g. "SUCCESS"
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Query an order

`getOrderResult` returns the order totals and **every transaction on the
order**, including transactions that were not started by your app (for example
payments taken on the web or on another device). Unlike
[`startOrderPayment`](/docs/a2a/payments#pay-an-existing-order) it does not
open a payment screen.

For each item in `results`, `completed` is `true` only when the transaction
status is `SUCCESS`, and `data` holds the transaction details. If the order
cannot be fetched (for example an unknown order code), the request fails with
`PP_A2A_012`.

<Callout type="warn">
  Because the list can contain transactions made outside your app, `paymentType`
  and `paymentMethod` may hold values your app never sends. Handle them
  defensively. Since Flutter 0.8.0 unknown values decode as `UNKNOWN` instead
  of throwing.
</Callout>

### Parameters

| Parameter   | Type   | Required | Description                  |
| ----------- | ------ | -------- | ---------------------------- |
| `orderCode` | String | yes      | The order code               |
| `locale`    | String | no       | UI language for this request |

<CodeBlockTabs defaultValue="flutter" groupId="pp-platform" persist>
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="flutter">
      Flutter
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="kotlin">
      Kotlin
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="java">
      Java
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="react-native">
      React Native
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="flutter">
    ```dart
    final order = await _pluspay.getOrderResult(
      PPOrderResultRequestModel.toRequest(orderCode: 'ORD001'),
    );

    print('${order.status} paid=${order.totalPaid} due=${order.amountDue}');
    for (final r in order.results) {
      print('${r.data?.id} completed=${r.completed}');
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="kotlin">
    ```kotlin
    // Not available in pluspay_a2a_native yet.
    ```
  </CodeBlockTab>

  <CodeBlockTab value="java">
    ```java
    // Not available in pluspay_a2a_native yet.
    ```
  </CodeBlockTab>

  <CodeBlockTab value="react-native">
    ```ts
    const order = await client.getOrderResult(
      PPOrderResultRequestModel.toRequest({ orderCode: 'ORD001' })
    );

    console.log(order.status, order.total_paid, order.amount_due);
    order.results?.forEach((r) => console.log(r.data?.id, r.completed));
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Technical cancel

`technicalCancel` reverses a card transaction on the payment terminal and
reports the outcome to PlusPay. Use it when a card payment went through but
your side of the sale could not be completed and the payment must be reversed.
For regular, user-initiated cancellations use
[`cancelPayment`](/docs/a2a/payments#cancel-a-payment).

On success the response is `{ order_code, transaction_id, status: "SUCCESS" }`.
If the reversal fails, the request fails with `PP_A2A_012`.

<Callout type="warn">
  Technical cancel is only supported on terminals whose payment SDK offers a
  reversal (currently PAX and Kozen devices). On other devices the request fails
  with `PP_A2A_012`.
</Callout>

### Parameters

| Parameter       | Type   | Required | Description                    |
| --------------- | ------ | -------- | ------------------------------ |
| `orderCode`     | String | yes      | Order code of the transaction  |
| `transactionId` | String | yes      | POS+ transaction id to reverse |
| `locale`        | String | no       | UI language for this request   |

<CodeBlockTabs defaultValue="flutter" groupId="pp-platform" persist>
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="flutter">
      Flutter
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="kotlin">
      Kotlin
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="java">
      Java
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="react-native">
      React Native
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="flutter">
    ```dart
    final res = await _pluspay.technicalCancel(
      PPTechnicalCancelRequestModel.toRequest(
        orderCode: 'ORD001',
        transactionId: 'TX-123',
      ),
    );

    print(res.status); // "SUCCESS"
    ```
  </CodeBlockTab>

  <CodeBlockTab value="kotlin">
    ```kotlin
    // Not available in pluspay_a2a_native yet.
    ```
  </CodeBlockTab>

  <CodeBlockTab value="java">
    ```java
    // Not available in pluspay_a2a_native yet.
    ```
  </CodeBlockTab>

  <CodeBlockTab value="react-native">
    ```ts
    const res = await client.technicalCancel(
      PPTechnicalCancelRequestModel.toRequest({
        orderCode: 'ORD001',
        transactionId: 'TX-123',
      })
    );

    console.log(res.status); // "SUCCESS"
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Recommended recovery flow

1. A payment request failed with `PP_A2A_014` or `PP_A2A_007`, or your app
   restarted before it received a result. **Do not retry the payment yet.**
2. If you have the `transactionId`, call `getTransactionResult`. Otherwise call
   `getOrderResult` with the `orderCode` you generated.
3. If the transaction is `SUCCESS`, treat it as paid. If you cannot complete
   the sale on your side, reverse it with `technicalCancel` (or
   `cancelPayment`).
4. If there is no transaction, or it is `FAIL` / `CANCEL`, it is safe to retry
   the payment.
5. If a request fails with `PP_A2A_015`, POS+ is still processing an earlier
   request. Wait for that request to finish, then query.

Continue to [Error handling](/docs/a2a/error-handling).