diff --git a/restapi/restendpoints.md b/restapi/restendpoints.md index 7938554..d25c7d6 100644 --- a/restapi/restendpoints.md +++ b/restapi/restendpoints.md @@ -749,41 +749,61 @@ curl -X GET \ -## Endpoints Not Requiring a Payment Terminal +## Device Control Commands -The following endpoints operate entirely through the Cloud API and do **not** require a connected payment terminal. +> **Note:** +> The following commands are available for all devices running Android SDK version 7.1006.0 or later. ---- +Command Endpoint Format -### Tip Adjustment +All device control commands follow this endpoint structure: +```https://cloud.handpoint.io/devices/{deviceType}/{serialNumber}/{command}``` +Where: +- `{deviceType}` is the type of the device (e.g., PAXIM30) +- `{serialNumber}` is the serial number of the device (e.g., 1640013848) +- `{command}` is the specific command to execute -`TipAdjustment` +Common Parameters -POST endpoint used to execute a tip adjustment operation. +All commands share these common parameters: -A tip adjustment operation allows merchants to adjust the tip amount of a sale transaction before the batch of transactions is settled by the processor at the end of the day. Note: This functionality is only available for the restaurant industry in the United States and the processors currently supporting this functionality are TSYS and VANTIV. +| Parameter | Notes | +| ----------- | ----------- | +| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | +| `Header: Content-Type` Required
*String* | Must be set to `application/json` | -Note: If two tip adjustments are sent for the same original transaction, only the second one will be taken into account. Each new tip adjustment will override the previous one. A tip adjustment will be rejected if the original transaction has already been batched out by the acquirer. +Common Response Codes -**Parameters** +| Response Code | Description | +|--------------|-------------| +| 202 | Request accepted, command will be executed | +| 403 | Authentication failed | +| 422 | Invalid request | +| 400 | Invalid parameter value (when applicable) | -| Parameter | Notes | -| ----------- | ----------- | -| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | -| `Path parameter: guid` Required
*String* | The guid of the transaction to be adjusted. | -| `Request Body: Tip Adjustment` Required
[TipAdjustment](restobjects.md#tip-adjustment) | Object containing the tip amount (as a *String* in MAJOR units, e.g. `"5.25"`) and currency of the tip adjustment. | +--- -**Returns** +:::caution +For the Commands to work properly, the Handpoint Payments App **MUST** be in **Integrated Mode** (enabled via **Handpoint TMS** and controlled by the merchant in the **Handpoint Payments App** Settings).
+::: +### Set Unattended Mode -| Response | Response Code | -| ----------- | ----------- | -| **OK** | Response code 200. | -| **BadRequest** | Response code 400. | +`POST /devices/{deviceType}/{serialNumber}/set-unattended-mode` +Enables or disables unattended mode on the device. +Unattended mode will disable the Bottom navigation bar containing the Home, back, recent buttons. +The Payment screen will be the only visible screen in the Handpoint Payments App. (Settings, Hisotry and Analytic tabs will not be accessible) -**Code Example** + +**Request Body Parameters** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `status` | boolean | `true` to enable unattended mode, `false` to disable | + +**Example Request** **Requests** @@ -791,12 +811,13 @@ Note: If two tip adjustments are sent for the same original transaction, only th ```shell -curl --location --request POST 'https://cloud.handpoint.com/transactions/ff6da784-8b57-11ed-9891-ebe2a88ff071/tip-adjustment' \ ---header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "amount": "5.25" //5 => 5.00 -}' +curl -X POST \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -H "Content-Type: application/json" \ + -d '{ + "status": false + }' \ + "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-unattended-mode" ``` @@ -805,59 +826,49 @@ curl --location --request POST 'https://cloud.handpoint.com/transactions/ff6da78 **Responses** - + -```json -{ - "statusMessage": "tip adjusted" -} -``` +No response body is returned. - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": "Invalid guid [fake-guid]" - } + "error": { + "statusCode": 400, + "name": "BadRequestError", + "message": { + "error": 1002, + "message": "No device listening at the other end of the secure channel" + } + } } ``` - -### Get Card Token {#transactionsguidtoken} - -`DeferredTokenization` - -GET endpoint used to retrieve a card token from a previously completed transaction. This operation, known as **deferred tokenization**, allows merchants to obtain a card token without requiring tokenization to be enabled at the time of the original transaction. The returned `cardToken` can be used for subsequent operations such as cardholder identification or MOTO payments. See the supported transaction types below. +### Set Locale -:::note -Deferred tokenization is supported for the following transaction types: `sale`, `refund`, `preAuthorizationCapture`, `moToSale` and `moToRefund`. Other transaction types will return a `400` error. -::: +`POST /devices/{deviceType}/{serialNumber}/set-locale` -:::warning -Tokenization requests must be made within **12 months** of the original transaction. Requests for transactions older than 12 months will not be processed. -::: +Sets the locale of the target device. -**Parameters** +**Request Body Parameters** -| Parameter | Notes | -| ----------- | ----------- | -| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | -| `Path parameter: guid` Required
*String* | The GUID of the completed card-present transaction from which to retrieve the token. | +| Parameter | Type | Description | +|-----------|--------|-----------------------------------------------------------------------------| +| `locale` | string | IETF BCP 47 language tag (e.g., `en_US`). Two-letter language and country code.| -**Returns** +**Response Codes** -| Response | Response Code | -| ----------- | ----------- | -| [DeferredTokenizationResponse](restobjects.md#deferredTokenizationResponse) | Response code 200. | -| **BadRequest** | Response code 400. Returned when the transaction type is not eligible for deferred tokenization. | +| Code | Description | +|------|---------------------------------------------| +| 202 | The request is accepted and will be executed| +| 403 | Authentication failed | +| 422 | Invalid request | -**Code Example** +**Example Request** **Requests** @@ -865,11 +876,13 @@ Tokenization requests must be made within **12 months** of the original transact ```shell -curl -X GET \ - -H "ApiKeyCloud: MeRcHaNt-ApIkEy" \ - -H "Content-Type: application/json" \ - "https://cloud.handpoint.com/transactions/75413c40-21db-11f1-991b-6f80eaf25911/token" (production) - "https://cloud.handpoint.io/transactions/75413c40-21db-11f1-991b-6f80eaf25911/token" (development) +curl -X POST \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -H "Content-Type: application/json" \ + -d '{ + "locale": "en_CA" + }' \ + "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-locale" ``` @@ -878,264 +891,132 @@ curl -X GET \ **Responses** - + -```json -{ - "agreementNumber": "123456789010102", - "cardToken": "665630867", - "cardTokenizationGuid": "7df78050-21dc-11f1-991b-6f80eaf25911", - "expiryDateMMYY": "0927", - "httpStatus": "200", - "maskedCardNumber": "************3555", - "serverDateTime": "20260317083711509", - "transactionReference": "75413c40-21db-11f1-991b-6f80eaf25911" -} -``` +No response body is returned. - + ```json { "error": { - "details": { - "body": { - "error": { - "errorCode": "3112", - "errorGuid": "624d05e0-21dd-11f1-991b-6f80eaf25911", - "httpStatus": "403", - "reason": "Transaction type is not eligible for deferred tokenization" - } - }, - "status": 403 - }, - "message": "Viscus operation failed", + "statusCode": 400, "name": "BadRequestError", - "statusCode": 400 + "message": { + "error": 1002, + "message": "No device listening at the other end of the secure channel" + } } } ``` - --- -### Reversal +### Set Password Protected -:::info -*Depending on the acquirer* Only applies to reversals that don't require the card to be presented. -::: +`POST /devices/{deviceType}/{serialNumber}/set-password-protected` -This endpoint allows to Cancel/Void/Reverse a previous transaction without a reader. +Enables or disables password protection on the device. -**Parameters** +**Request Body Parameters** -| Parameter | Notes | -| ----------- | ----------- | -| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | -| `Body: originalGuid` Required
*String* | The GUID of the previously completed transaction to reverse. | -| `Body: amount` Optional
*String* | Decimal amount in String, ISO 4217; Required for partial-reversals. *Only if your acquirer supports partial-reversals*. | -| `Body: currency` Optional
*String* | Required for partial-reversals *Only if your acquirer supports partial-reversals*. | -| `Body: messageReasonCode` Optional
*String* | default: CUSTOMER_CANCELLATION. See [allowed values](restapi/restobjects.md#messageReasonCode)| +| Parameter | Type | Description | +|-----------|---------|-----------------------------------------------------| +| `status` | boolean | `true` to enable password protection, `false` to disable | -**Returns** +**Response Codes** -| Response | Response Code | -| ----------- | ----------- | -| [DeferredTokenizationResponse](restobjects.md#deferredTokenizationResponse) | Response code 200. | -| **BadRequest** | Response code 400. Returned when the transaction type is not eligible for deferred tokenization. | +| Code | Description | +|------|---------------------------------------------| +| 202 | The request is accepted and will be executed| +| 403 | Authentication failed | +| 422 | Invalid request | -**Code Example** +**Example Request** **Requests** - + ```shell -curl --location --request POST 'https://cloud.handpoint.io/reversal' \ ---header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "originalGuid": "bb6e0b90-420f-11f1-b809-51c9c7fda18b" -}' +curl -X POST \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -H "Content-Type: application/json" \ + -d '{ + "status": true + }' \ + "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-password-protected" ``` + - +**Responses** -```shell -curl --location --request POST 'https://cloud.handpoint.io/reversal' \ ---header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "originalGuid": "bb6e0b90-420f-11f1-b809-51c9c7fda18b", - "amount": "15.00", - "currency": "EUR" -}' -``` + + - +No response body is returned. - - -```shell -curl --location --request POST 'https://cloud.handpoint.io/reversal' \ ---header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "originalGuid": "bb6e0b90-420f-11f1-b809-51c9c7fda18b", - "messageReasonCode": "TIMEOUT_WAITING_FOR_RESPONSE" -}' -``` - - - - -**Responses** - - - - -```json -{ - "agreementNumber": "123456789010102", - "cardToken": "665630867", - "cardTokenizationGuid": "7df78050-21dc-11f1-991b-6f80eaf25911", - "expiryDateMMYY": "0927", - "httpStatus": "200", - "maskedCardNumber": "************3555", - "serverDateTime": "20260317083711509", - "transactionReference": "75413c40-21db-11f1-991b-6f80eaf25911" -} -``` - - - + + ```json { "error": { - "details": { - "body": { - "error": { - "errorCode": "3112", - "errorGuid": "624d05e0-21dd-11f1-991b-6f80eaf25911", - "httpStatus": "403", - "reason": "Transaction type is not eligible for deferred tokenization" - } - }, - "status": 403 - }, - "message": "Viscus operation failed", + "statusCode": 400, "name": "BadRequestError", - "statusCode": 400 + "message": { + "error": 1002, + "message": "No device listening at the other end of the secure channel" + } } } ``` - --- -:::info -Pre-authorization operations are only available for acquirers that support pre-authorization flows. Contact your Handpoint relationship manager to confirm support for your acquirer. -::: - -Pre-authorization operations allow you to **manage open pre-authorizations** remotely via Cloud API. A pre-authorization reserves funds on a card without charging them; the increase and capture endpoints let you adjust or finalize that reservation without requiring a physical payment terminal. - -Cloud API currently supports the following pre-authorization operations: - -- `POST /preauthorization/increase` — increases (or decreases, with `subtract: "1"`) the authorized amount of an open pre-authorization. -- `POST /preauthorization/capture` — finalizes (captures) an open pre-authorization, charging the captured amount. - -All request and response payloads are defined in the corresponding [Pre-authorization objects](restobjects#preauthorization). - ---- - -### Preauthorization Increase / Decrease - -`PreauthIncrease` - -`POST /preauthorization/increase` is used to **modify the authorized amount** of an existing open pre-authorization. The operation is linked to the original pre-authorization via `originalGuid`. - -Pass `subtract: "1"` to decrease the authorized amount instead of increasing it. +### Reboot -#### Parameters +`POST /devices/{deviceType}/{serialNumber}/reboot` -| Parameter | Notes | -| --------- | ----- | -| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: PreauthIncreaseRequest` Required | [PreauthIncreaseRequest](restobjects#preauthIncreaseRequest) object containing the original pre-authorization GUID and the amount delta. | +Reboots the device with an optional force parameter. -Typical fields in the request body (see [PreauthIncreaseRequest](restobjects#preauthIncreaseRequest) for full details): +**Request Body Parameters** -- `originalGuid` Required – GUID of the original pre-authorization transaction. -- `increaseAmount` Required – Amount delta to apply (for example, `"10.00"`). -- `tipAmount` Optional – Tip amount to add (for example, `"2.00"`). -- `subtract` Optional – Pass `"1"` to decrease the authorized amount instead of increasing it. -- `customerReference` Optional – Integrator-defined reference, forwarded as-is to the gateway. +| Parameter | Type | Description | +|-----------|---------|-----------------------------------------------------------------------------| +| `force` | boolean | `true` to force reboot even during transaction, `false` to check status first| -#### Returns +**Response Codes** -| Result | Notes | -| ------ | ----- | -| `200` | Pre-authorization increase accepted. Response body is a parsed gateway object. | -| `400` | Business rule error from the gateway (for example, unknown `originalGuid` or pre-authorization no longer open). Returned as `BadRequestError` with `error.code` and `error.details`. | -| `403` | Forbidden — the API key does not belong to a merchant. Partner keys are not accepted by this endpoint. | -| `422` | Payload validation error (`VALIDATION_FAILED`) — `originalGuid` or `increaseAmount` is missing. | +| Code | Description | +|------|---------------------------------------------| +| 202 | The request is accepted and will be executed| +| 403 | Authentication failed | +| 422 | Invalid request | -**Code Example** +**Example Request** **Requests** - - -```shell -curl -X POST \ - -H "Content-Type: application/json" \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ - -d '{ - "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", - "increaseAmount": "10.00" - }' \ - "https://cloud.handpoint.io/preauthorization/increase" -``` - - - + ```shell curl -X POST \ - -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ - -d '{ - "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", - "increaseAmount": "5.00", - "subtract": "1" - }' \ - "https://cloud.handpoint.io/preauthorization/increase" -``` - - - - -```shell -curl -X POST \ -H "Content-Type: application/json" \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", - "increaseAmount": "10.00", - "tipAmount": "2.00", - "customerReference": "table-12-increase" + "force": false }' \ - "https://cloud.handpoint.io/preauthorization/increase" + "https://cloud.handpoint.io/devices/PAXIM30/0000000000/reboot" ``` @@ -1144,146 +1025,144 @@ curl -X POST \ **Responses** - + -```json -{ - "httpStatus": 200, - "customFields": { - "tenderType": "Credit", - "issuerResponseCode": "200" - }, - "acquirerTid": "0821599465", - "actionCode": "0000", - "agreementNumber": "123456789010102", - "approvalCode": "010119", - "batchNumber": "1", - "cardTypeName": "VISA", - "currency": "USD", - "expiryDateMMYY": "1027", - "holdAmount": "102.00", - "issuerResponseCode": "00", - "issuerResponseText": "COMPLETED", - "maskedCardNumber": "************0936", - "nonce": "1776156307790", - "originalAmount": "1.00", - "preAuthorizationGuid": "664ec4c0-37e6-11f1-bc66-3114cb76dabf", - "serverDateTime": "20260414094416641", - "terminalDateTime": "20260414094416433", - "transNumber": "000001", - "increaseAmount": "100.00", - "preAuthorizationIncreaseGuid": "80b2e710-37e6-11f1-bc66-3114cb76dabf" -} -``` +No response body is returned. - + ```json { "error": { "statusCode": 400, "name": "BadRequestError", - "message": "Original pre-auth is voided, not approved or already captured", - "code": "3211", - "details": { - "errorCode": "3211", - "errorGuid": "e8b35da0-37ea-11f1-bc66-3114cb76dabf", - "httpStatus": 403, - "reason": "Original pre-auth is voided, not approved or already captured" + "message": { + "error": 1002, + "message": "No device listening at the other end of the secure channel" } } } ``` - - - - -```json -{ - "error": { - "statusCode": 422, - "name": "UnprocessableEntityError", - "message": "The request body is invalid.", - "code": "VALIDATION_FAILED", - "details": [ - { - "path": "", - "code": "required", - "message": "must have required property 'increaseAmount'", - "info": { "missingProperty": "increaseAmount" } - } - ] - } -} -``` - --- -### Preauthorization Capture - -`PreauthCapture` - -`POST /preauthorization/capture` is used to **finalize (capture) an open pre-authorization**, charging the `capturedAmount` to the cardholder. Once captured, the pre-authorization is settled and the held funds are transferred. +### Set Screen Brightness -#### Parameters +`POST /devices/{deviceType}/{serialNumber}/set-screen-brightness` -| Parameter | Notes | -| --------- | ----- | -| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: PreauthCaptureRequest` Required | [PreauthCaptureRequest](restobjects#preauthCaptureRequest) object containing the original pre-authorization GUID and the amount to capture. | +Sets the screen brightness levels. -Typical fields in the request body (see [PreauthCaptureRequest](restobjects#preauthCaptureRequest) for full details): +**Request Body Parameters** -- `originalGuid` Required – GUID of the original pre-authorization transaction. -- `capturedAmount` Required – Amount to capture and charge (for example, `"120.00"`). -- `tipAmount` Optional – Tip amount to include in the captured total (for example, `"5.00"`). -- `customerReference` Optional – Integrator-defined reference, forwarded as-is to the gateway. +| Parameter | Type | Description | +|--------------------------|---------|------------------------------------| +| `minimumBrightnessLevel` | integer | Value between 0 and 100 | +| `maximumBrightnessLevel` | integer | Value between 0 and 100 | -#### Returns +**Response Codes** -| Result | Notes | -| ------ | ----- | -| `200` | Pre-authorization capture accepted. Response body is a parsed gateway object. | -| `400` | Business rule error from the gateway (for example, unknown `originalGuid`, pre-authorization already captured, or captured amount exceeds authorized amount). Returned as `BadRequestError` with `error.code` and `error.details`. | -| `403` | Forbidden — the API key does not belong to a merchant. Partner keys are not accepted by this endpoint. | -| `422` | Payload validation error (`VALIDATION_FAILED`) — `originalGuid` or `capturedAmount` is missing. | +| Code | Description | +|------|---------------------------------------------| +| 202 | The request is accepted and will be executed| +| 403 | Authentication failed | +| 422 | Invalid request | +| 400 | Value is outside the valid range | -**Code Example** +**Example Request** **Requests** - + ```shell curl -X POST \ - -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -H "Content-Type: application/json" \ -d '{ - "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", - "capturedAmount": "120.00" + "minimumBrightnessLevel": 20, + "maximumBrightnessLevel": 100 }' \ - "https://cloud.handpoint.io/preauthorization/capture" + "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-screen-brightness" ``` - + + +**Responses** + + + + +No response body is returned. + + + + +```json +{ + "error": { + "statusCode": 400, + "name": "BadRequestError", + "message": { + "error": 1002, + "message": "No device listening at the other end of the secure channel" + } + } +} +``` + + + +--- + +### Set Reboot Time + +:::note +This feature is only enabled for production devices. +::: + +`POST /devices/{deviceType}/{serialNumber}/set-reboot-time` + +Sets the daily reboot time for the device. The actual reboot will occur at a random minute within the specified hour. + +**Request Body Parameters** + +| Parameter | Type | Description | +|-----------|---------|-----------------------------------------------------| +| `hour` | integer | Hour of the day (0-23) when device should reboot | + +:::tip +If hour is set to 22, the device will reboot at a random time between 22:01 and 22:59. +::: + +**Response Codes** + +| Code | Description | +|------|---------------------------------------------| +| 202 | The request is accepted and will be executed| +| 403 | Authentication failed | +| 422 | Invalid request | +| 400 | Value is outside the valid range | + +**Example Request** + +**Requests** + + + ```shell curl -X POST \ - -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -H "Content-Type: application/json" \ -d '{ - "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", - "capturedAmount": "120.00", - "tipAmount": "5.00", - "customerReference": "hotel-folio-4422" + "hour": 22 }' \ - "https://cloud.handpoint.io/preauthorization/capture" + "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-reboot-time" ``` @@ -1292,144 +1171,77 @@ curl -X POST \ **Responses** - + -```json -{ - "httpStatus": 200, - "customFields": { - "tenderType": "Credit", - "RRN": "513815902180", - "issuerResponseCode": "200" - }, - "acquirerTid": "0821599465", - "actionCode": "0000", - "agreementNumber": "123456789010102", - "approvalCode": "010119", - "batchNumber": "1", - "cardTypeName": "VISA", - "currency": "USD", - "expiryDateMMYY": "1027", - "holdAmount": "102.00", - "issuerResponseCode": "00", - "issuerResponseText": "COMPLETED", - "maskedCardNumber": "************0936", - "nonce": "1776156307790", - "originalAmount": "1.00", - "preAuthorizationGuid": "664ec4c0-37e6-11f1-bc66-3114cb76dabf", - "serverDateTime": "20260414094532102", - "terminalDateTime": "20260414094532085", - "transNumber": "000001", - "capturedAmount": "100.00", - "preAuthorizationCaptureGuid": "adad5660-37e6-11f1-9d29-81969834b189" -} -``` +No response body is returned. - + + ```json { - "error": { - "statusCode": 422, - "name": "UnprocessableEntityError", - "message": "The request body is invalid.", - "code": "VALIDATION_FAILED", - "details": [ - { - "path": "", - "code": "required", - "message": "must have required property 'capturedAmount'", - "info": { "missingProperty": "capturedAmount" } - } - ] - } + "error": { + "statusCode": 400, + "name": "BadRequestError", + "message": { + "error": 1002, + "message": "No device listening at the other end of the secure channel" + } + } } ``` -## **MOTO Operations** -`MOTO (Mail Order / Telephone Order)` operations can also be processed **without a payment reader**, using information -that is already stored in the gateway (tokens and references to previous transactions). - -These endpoints are intended for MOTO scenarios where the merchant does **not** need to collect or handle sensitive -card data (PAN, expiry date, CVV) in their own systems: - -- `/moto/sale` performs a sale using a **previously generated card token** (`cardToken`) that represents card details stored in the gateway. -- `/moto/refund` performs a refund of a **previous operation**, using the card associated with that original operation (`originalGuid`). -- `/moto/reversal` performs a reversal (void) of a **previous operation**, passing only its identifier (`originalGuid`). - -:::tip -Unlike the standard MOTO operations that use `/transactions` and a physical terminal, these endpoints: - -- Do **not** require `serial_number` or `terminal_type`. -- Do **not** receive raw card data in the request. -- Rely on `cardToken` and `originalGuid` to reference card information already stored in the gateway. -::: +## Endpoints Not Requiring a Payment Terminal -All request and response payloads are defined in the corresponding [Moto objects](restobjects#moto). +The following endpoints operate entirely through the Cloud API and do **not** require a connected payment terminal. --- -### MOTO Sale +### Tip Adjustment -`MotoSale` -`POST /moto/sale` is used to perform a **MOTO sale without a payment reader**, using a **card token** (`cardToken`) -that was generated previously (for example, by a `saleAndTokenizeCard` operation). +`TipAdjustment` -The card details are *not* sent in the request; they are resolved by the gateway using the `cardToken`. +POST endpoint used to execute a tip adjustment operation. -Typical flow: +A tip adjustment operation allows merchants to adjust the tip amount of a sale transaction before the batch of transactions is settled by the processor at the end of the day. Note: This functionality is only available for the restaurant industry in the United States and the processors currently supporting this functionality are TSYS and VANTIV. -1. A card is captured securely in a previous flow, for example through the [`/transactions`](restendpoints#transactions) endpoint using a - `saleAndTokenizeCard` operation. -2. The gateway returns a `cardToken` (e.g. `665630867`). -3. The integrator can later perform one or more MOTO sales using that `cardToken`, without handling PAN/CVV again. +Note: If two tip adjustments are sent for the same original transaction, only the second one will be taken into account. Each new tip adjustment will override the previous one. A tip adjustment will be rejected if the original transaction has already been batched out by the acquirer. -#### Parameters +**Parameters** -| Parameter | Notes | -| --------- | ----- | -| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: MotoSaleRequest` Required | [MotoSaleRequest](restobjects#motoSaleRequest) object containing `cardToken`, `amount`, `currency` and optional merchant references. | +| Parameter | Notes | +| ----------- | ----------- | +| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | +| `Path parameter: guid` Required
*String* | The guid of the transaction to be adjusted. | +| `Request Body: Tip Adjustment` Required
[TipAdjustment](restobjects.md#tip-adjustment) | Object containing the tip amount (as a *String* in MAJOR units, e.g. `"5.25"`) and currency of the tip adjustment. | -Typical fields in the request body (see [MotoSaleRequest](restobjects#motoSaleRequest) for full details): +**Returns** -- `cardToken` Required – Token representing the card stored in the gateway (e.g. `"665630867"`). -- `amount` Required – String amount in MAJOR units (e.g. `"20.00"` for 20.00). Must be a positive integer string. -- `currency` Required – 3-character ISO 4217 currency code (e.g. `"EUR"`). -- Optional references for reconciliation: `customerReference`, `transactionReference`, etc. -#### Returns +| Response | Response Code | +| ----------- | ----------- | +| **OK** | Response code 200. | +| **BadRequest** | Response code 400. | -| Result | Notes | -| ------ | ----- | -| `200` | Sale successfully processed. The response body is a `motoSaleResponse` [Moto Transaction Response](restobjects#motoTransactionResponse) with the authorization result (approved/declined), authorization code, masked card details, acquirer TID, timestamps, etc. | -| `400` | Business rule error from the payment gateway (for example, CVV required, card token failure). Returned as `BadRequestError`, with `error.code` and `error.details` containing the gateway error code and description. | -| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (invalid amount (must be a positive integer string in minor units), currency length, etc.). | -| `5xx` | Internal error or gateway unavailability. The final outcome may be unknown and may require reconciliation. | -#### Code Example +**Code Example** **Requests** - + ```shell -curl -X POST \ - -H "Content-Type: application/json" \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ - -d '{ - "amount": "20.00", // 20 => 20.00 - "currency": "EUR", - "cardToken": "665630867", - "customerReference": "order-12345", - "transactionReference": "b7b2360d-3e9e-4b62-9a3a-2e6ef6c5cd01" - }' \ - "https://cloud.handpoint.io/moto/sale" +curl --location --request POST 'https://cloud.handpoint.com/transactions/ff6da784-8b57-11ed-9891-ebe2a88ff071/tip-adjustment' \ +--header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "amount": "5.25" //5 => 5.00 +}' ``` @@ -1438,69 +1250,23 @@ curl -X POST \ **Responses** - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": "CVV required", - "code": "3107", - "details": { - "errorGuid": "dae59b20-cf71-11f0-b588-a122fae316de", - "errorCode": "3107", - "description": "CVV required", - "httpStatus": 400 - } - } + "statusMessage": "tip adjusted" } ``` -:::note -A very common cause of **3107** is having "CVV/CV2 input mandatory" enabled for Card Not Present. -::: - - + ```json { "error": { - "code": "5252", - "details": { - "description": "Card token failure", - "errorCode": "5252", - "errorGuid": "10a3d120-cf75-11f0-95b2-770b7d1d8e67", - "httpStatus": 404 - }, - "message": "Card token failure", + "statusCode": 400, "name": "BadRequestError", - "statusCode": 400 - } -} -``` - - - - -```json -{ - "error": { - "code": "VALIDATION_FAILED", - "details": [ - { - "code": "required", - "info": { - "missingProperty": "amount" - }, - "message": "must have required property 'amount'", - "path": "" - } - ], - "message": "The request body is invalid. See error object `details` property for more info.", - "name": "UnprocessableEntityError", - "statusCode": 422 + "message": "Invalid guid [fake-guid]" } } ``` @@ -1508,60 +1274,47 @@ A very common cause of **3107** is having "CVV/CV2 input mandatory" enabled for ---- - -### MOTO Refund - -`MotoRefund` - -`POST /moto/refund` is used to perform a **MOTO refund without a payment reader**. +### Get Card Token {#transactionsguidtoken} -The refund is **linked** to a previous MOTO sale via `originalGuid`, and the gateway reuses the card associated with that -original transaction. No card data is passed in the refund request. +`DeferredTokenization` -#### Parameters +GET endpoint used to retrieve a card token from a previously completed transaction. This operation, known as **deferred tokenization**, allows merchants to obtain a card token without requiring tokenization to be enabled at the time of the original transaction. The returned `cardToken` can be used for subsequent operations such as cardholder identification or MOTO payments. See the supported transaction types below. -| Parameter | Notes | -| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | -| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: MotoRefundRequest` Required | [MotoRefundRequest](restobjects#motoRefundRequest) object containing `originalGuid`, `amount`, `currency` and optional merchant references. | +:::note +Deferred tokenization is supported for the following transaction types: `sale`, `refund`, `preAuthorizationCapture`, `moToSale` and `moToRefund`. Other transaction types will return a `400` error. +::: -Typical fields (see [MotoRefundRequest](restobjects#motoRefundRequest) for full details): +:::warning +Tokenization requests must be made within **12 months** of the original transaction. Requests for transactions older than 12 months will not be processed. +::: -* `originalGuid` Required – GUID of the original sale to be refunded (e.g. `"1a41d9f0-cf72-11f0-95b2-770b7d1d8e67"`). -* `amount` Required – String amount to be refunded in MAJOR units (e.g. `"5.00"` for $5.00). Must be a positive integer string. -* `currency` Required – 3-character ISO 4217 code (e.g. `"EUR"`, `"USD"`). -* Optional: `customerReference`, `transactionReference`. +**Parameters** -Supports full and partial refunds, depending on acquirer configuration. +| Parameter | Notes | +| ----------- | ----------- | +| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | +| `Path parameter: guid` Required
*String* | The GUID of the completed card-present transaction from which to retrieve the token. | -#### Returns +**Returns** -| Result | Notes | -| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `200` | Refund successfully processed. The response body is a `motoRefundResponse` [Moto Transaction Response](restobjects#motoTransactionResponse) including `guid`, `originalGuid`, `amount`, `currency`, `maskedCardNumber`, `approvalCode`, `issuerResponseText`, etc. | -| `400` | Business rule error from the payment gateway (for example, currency mismatch, refund amount greater than the original sale). Returned as `BadRequestError`, with `error.code` and `error.details` describing the gateway error. | -| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or invalid (missing `originalGuid`, invalid amount, invalid currency format, etc.). | +| Response | Response Code | +| ----------- | ----------- | +| [DeferredTokenizationResponse](restobjects.md#deferredTokenizationResponse) | Response code 200. | +| **BadRequest** | Response code 400. Returned when the transaction type is not eligible for deferred tokenization. | -#### Code Example +**Code Example** **Requests** - + ```shell -curl -X POST \ - -H "Content-Type: application/json" \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ - -d '{ - "originalGuid": "1a41d9f0-cf72-11f0-95b2-770b7d1d8e67", - "amount": "5.00", // 5 => 5.00 - "currency": "EUR", - "customerReference": "refund-98765", - "transactionReference": "a1fe8db5-69a4-4b4d-a704-94ac2570f9b0" - }' \ - "https://cloud.handpoint.io/moto/refund" +curl -X GET \ + -H "ApiKeyCloud: MeRcHaNt-ApIkEy" \ + -H "Content-Type: application/json" \ + "https://cloud.handpoint.com/transactions/75413c40-21db-11f1-991b-6f80eaf25911/token" (production) + "https://cloud.handpoint.io/transactions/75413c40-21db-11f1-991b-6f80eaf25911/token" (development) ``` @@ -1570,107 +1323,118 @@ curl -X POST \ **Responses** - + ```json { - "error": { - "code": "3210", - "details": { - "description": "Original and linked currency do not match", - "errorCode": "3210", - "errorGuid": "35a2b220-cf7a-11f0-b588-a122fae316de", - "httpStatus": 400 - }, - "message": "Original and linked currency do not match", - "name": "BadRequestError", - "statusCode": 400 - } + "agreementNumber": "123456789010102", + "cardToken": "665630867", + "cardTokenizationGuid": "7df78050-21dc-11f1-991b-6f80eaf25911", + "expiryDateMMYY": "0927", + "httpStatus": "200", + "maskedCardNumber": "************3555", + "serverDateTime": "20260317083711509", + "transactionReference": "75413c40-21db-11f1-991b-6f80eaf25911" } ``` - + ```json { - "error": { - "code": "3209", - "details": { - "description": "The requested refund amount is greater than the initial sale amount", - "errorCode": "3209", - "errorGuid": "e72ea940-cf7a-11f0-b588-a122fae316de", - "httpStatus": 400 - }, - "message": "The requested refund amount is greater than the initial sale amount", - "name": "BadRequestError", - "statusCode": 400 - } + "error": { + "details": { + "body": { + "error": { + "errorCode": "3112", + "errorGuid": "624d05e0-21dd-11f1-991b-6f80eaf25911", + "httpStatus": "403", + "reason": "Transaction type is not eligible for deferred tokenization" + } + }, + "status": 403 + }, + "message": "Viscus operation failed", + "name": "BadRequestError", + "statusCode": 400 + } } ``` - - - -Missing `originalGuid`, missing `amount`, invalid `currency` length, or invalid `amount` pattern — returned as `422 VALIDATION_FAILED` with one or more entries in `error.details`. - --- -### MOTO Reversal +### Reversal + +:::info +*Depending on the acquirer* Only applies to reversals that don't require the card to be presented. +::: + +This endpoint allows to Cancel/Void/Reverse a previous transaction without a reader. -`MotoReversal` +**Parameters** -`POST /moto/reversal` is used to **reverse (void)** a previous MOTO operation processed via the no-reader MOTO endpoints. +| Parameter | Notes | +| ----------- | ----------- | +| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | +| `Body: originalGuid` Required
*String* | The GUID of the previously completed transaction to reverse. | +| `Body: amount` Optional
*String* | Decimal amount in String, ISO 4217; Required for partial-reversals. *Only if your acquirer supports partial-reversals*. | +| `Body: currency` Optional
*String* | Required for partial-reversals *Only if your acquirer supports partial-reversals*. | +| `Body: messageReasonCode` Optional
*String* | default: CUSTOMER_CANCELLATION. See [allowed values](restapi/restobjects.md#messageReasonCode)| -The request is linked to the original sale via `originalGuid`. No card data is sent; the gateway uses the original -transaction information. +**Returns** -Reversals are typically used to cancel a MOTO sale shortly after authorization, subject to acquirer rules. +| Response | Response Code | +| ----------- | ----------- | +| [DeferredTokenizationResponse](restobjects.md#deferredTokenizationResponse) | Response code 200. | +| **BadRequest** | Response code 400. Returned when the transaction type is not eligible for deferred tokenization. | -#### Parameters +**Code Example** -| Parameter | Notes | -| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: MotoReversalRequest` Required | [MotoReversalRequest](restobjects#motoReversalRequest) object referencing the original MOTO transaction (`originalGuid`) and including the reversal `amount` and `currency`. | +**Requests** -Typical fields (see [MotoReversalRequest](restobjects#motoReversalRequest) for full details): + + -* `originalGuid` Required – GUID of the original sale to be reversed. -* `amount` Required – String amount to reverse in MAJOR units (e.g. `"20.00"` for $20.00). Must be a positive integer string. -* `currency` Optional – 3-character ISO 4217 code; if provided, must respect `minLength = 3`, `maxLength = 3` and may need to match the original transaction’s currency. -* Optional merchant references: `customerReference`, `transactionReference`. +```shell +curl --location --request POST 'https://cloud.handpoint.io/reversal' \ +--header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "originalGuid": "bb6e0b90-420f-11f1-b809-51c9c7fda18b" +}' +``` -#### Returns + -| Result | Notes | -| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `200` | Reversal accepted and processed. Response body is a `motoReversalResponse` [Moto Transaction Response](restobjects#motoTransactionResponse) indicating whether the reversal was approved, with fields such as `guid`, `originalGuid`, `issuerResponseCode`, `issuerResponseText`, `f25`, etc. | -| `400` | Business rule error from the payment gateway (for example, unknown `originalGuid`, reversal not allowed). Returned as `BadRequestError`, with `error.code` and `error.details` describing the gateway error (for example, code `3153`). | -| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or invalid (missing `originalGuid`, missing `amount`, invalid `currency`, invalid `amount` pattern). | + -#### Code Example +```shell +curl --location --request POST 'https://cloud.handpoint.io/reversal' \ +--header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "originalGuid": "bb6e0b90-420f-11f1-b809-51c9c7fda18b", + "amount": "15.00", + "currency": "EUR" +}' +``` -**Requests** + - - + ```shell -curl -X POST \ - -H "Content-Type: application/json" \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ - -d '{ - "originalGuid": "b28bdb10-cf87-11f0-b588-a122fae316de", - "amount": "20.00", //20 => 20.00 || 2000 => 2000.00 - "currency": "EUR", - "customerReference": "void-001", - "transactionReference": "4d7b1a2c-5bfd-4a30-9b6f-123456789abc" - }' \ - "https://cloud.handpoint.io/moto/reversal" +curl --location --request POST 'https://cloud.handpoint.io/reversal' \ +--header 'ApiKeyCloud: MeRcHaNt-ApI-KeY' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "originalGuid": "bb6e0b90-420f-11f1-b809-51c9c7fda18b", + "messageReasonCode": "TIMEOUT_WAITING_FOR_RESPONSE" +}' ``` @@ -1679,161 +1443,201 @@ curl -X POST \ **Responses** - + ```json { - "type": "motoReversalResponse", - "httpStatus": 200, - "amount": "20.00", - "currency": "EUR", - "guid": "3f7772a0-cf88-11f0-b588-a122fae316de", - "originalGuid": "b28bdb10-cf87-11f0-b588-a122fae316de", - "issuerResponseCode": "00", - "issuerResponseText": "Successful", - "maskedCardNumber": "************3555", - "f25": "4000" + "agreementNumber": "123456789010102", + "cardToken": "665630867", + "cardTokenizationGuid": "7df78050-21dc-11f1-991b-6f80eaf25911", + "expiryDateMMYY": "0927", + "httpStatus": "200", + "maskedCardNumber": "************3555", + "serverDateTime": "20260317083711509", + "transactionReference": "75413c40-21db-11f1-991b-6f80eaf25911" } ``` - + ```json { - "error": { - "code": "3153", - "details": { - "description": "Unable to find message to reverse.", - "errorCode": "3153", - "errorGuid": "633a4370-cf88-11f0-b588-a122fae316de", - "httpStatus": 404 - }, - "message": "Unable to find message to reverse.", - "name": "BadRequestError", - "statusCode": 400 - } + "error": { + "details": { + "body": { + "error": { + "errorCode": "3112", + "errorGuid": "624d05e0-21dd-11f1-991b-6f80eaf25911", + "httpStatus": "403", + "reason": "Transaction type is not eligible for deferred tokenization" + } + }, + "status": 403 + }, + "message": "Viscus operation failed", + "name": "BadRequestError", + "statusCode": 400 + } } ``` - - - -Missing `originalGuid`, missing `amount`, invalid `currency` length, or invalid `amount` pattern — returned as `422 VALIDATION_FAILED` with one or more entries in `error.details`. - -## Batch Operations Beta {#batch-operations} +--- :::info -Batch Operations are not available for all acquirers. Contact your Handpoint relationship manager to find out if this feature is supported for your acquirer. +Pre-authorization operations are only available for acquirers that support pre-authorization flows. Contact your Handpoint relationship manager to confirm support for your acquirer. ::: -Batch operations allow you to remotely **manage batches on a specific payment terminal** using Cloud API. - -These endpoints are typically used in scenarios where the acquirer or merchant workflow is batch-based (for example, -daily settlement batches per terminal). - -Cloud API currently supports the following batch operations: - -- `POST /batch/close` — requests the **closure of a batch** for a given terminal (`deviceType`, `serialNumber`) and `batchNumber`. -- `POST /batch/summary` — retrieves a **summary of a batch** for a given terminal (`deviceType`, `serialNumber`) and `batchNumber`. -- `POST /batch/detail` — retrieves **batch detail** (including a list of transactions) for a given terminal (`deviceType`, `serialNumber`) and `batchNumber` (and optional `rrn`). +Pre-authorization operations allow you to **manage open pre-authorizations** remotely via Cloud API. A pre-authorization reserves funds on a card without charging them; the increase and capture endpoints let you adjust or finalize that reservation without requiring a physical payment terminal. +Cloud API currently supports the following pre-authorization operations: -All request and response payloads are defined in the corresponding [Batch objects](restobjects#batch). +- `POST /preauthorization/increase` — increases (or decreases, with `subtract: "1"`) the authorized amount of an open pre-authorization. +- `POST /preauthorization/capture` — finalizes (captures) an open pre-authorization, charging the captured amount. -:::info -In MOTO scenarios, the `deviceType` and `serialNumber` fields can refer to a **virtual terminal** (`VT`) instead of a physical device. Use `VT` as the `deviceType` and the serial number of the virtual terminal assigned to the merchant. The serial number of the virtual terminal can be retrieved from the response of the [/initialize](restendpoints#initialize) endpoint. -::: +All request and response payloads are defined in the corresponding [Pre-authorization objects](restobjects#preauthorization). --- -### Close Batch - -`BatchClose` +### Preauthorization Increase / Decrease -`POST /batch/close` is used to request **closure of a batch** for a specific payment terminal, identified by its -`deviceType` and `serialNumber`, and a `batchNumber`. +`PreauthIncrease` -Typical use cases: +`POST /preauthorization/increase` is used to **modify the authorized amount** of an existing open pre-authorization. The operation is linked to the original pre-authorization via `originalGuid`. -- End-of-day batch closure triggered from a back-office or back-office job. -- Manual batch close as part of a support or reconciliation process. +Pass `subtract: "1"` to decrease the authorized amount instead of increasing it. #### Parameters | Parameter | Notes | | --------- | ----- | | `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: BatchCloseRequest` Required | [BatchCloseRequest](restobjects#batchCloseRequest) object containing `deviceType`, `serialNumber` and `batchNumber`. | +| `Request Body: PreauthIncreaseRequest` Required | [PreauthIncreaseRequest](restobjects#preauthIncreaseRequest) object containing the original pre-authorization GUID and the amount delta. | -Typical fields in the request body (see [BatchCloseRequest](restobjects#batchCloseRequest) for full details): +Typical fields in the request body (see [PreauthIncreaseRequest](restobjects#preauthIncreaseRequest) for full details): -- `deviceType` Required – Terminal model identifier (for example, `"PAXA920MAX"`). -- `serialNumber` Required – Serial number of the payment terminal (for example, `"2740013262"`). -- `batchNumber` Required – Identifier of the batch to close (for example, `"1"`). +- `originalGuid` Required – GUID of the original pre-authorization transaction. +- `increaseAmount` Required – Amount delta to apply (for example, `"10.00"`). +- `tipAmount` Optional – Tip amount to add (for example, `"2.00"`). +- `subtract` Optional – Pass `"1"` to decrease the authorized amount instead of increasing it. +- `customerReference` Optional – Integrator-defined reference, forwarded as-is to the gateway. #### Returns | Result | Notes | | ------ | ----- | -| `200` | Batch close request accepted. The response body is a [BatchCloseResponse](restobjects#batchCloseResponse) with the batch number, a unique `closeBatchGuid`, timestamps and an issuer-style response code/text. | -| `400` | Business rule error or generic gateway error. Returned as `BadRequestError`, with `error.code`, `error.message` and `error.details` describing the problem. | -| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (for example, missing `batchNumber`). | -| `5xx` | Internal error or service unavailability. The final outcome of the batch close may be unknown and may require reconciliation or a retry at a later time. | +| `200` | Pre-authorization increase accepted. Response body is a parsed gateway object. | +| `400` | Business rule error from the gateway (for example, unknown `originalGuid` or pre-authorization no longer open). Returned as `BadRequestError` with `error.code` and `error.details`. | +| `403` | Forbidden — the API key does not belong to a merchant. Partner keys are not accepted by this endpoint. | +| `422` | Payload validation error (`VALIDATION_FAILED`) — `originalGuid` or `increaseAmount` is missing. | **Code Example** **Requests** - + ```shell curl -X POST \ -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "deviceType": "PAXA920MAX", - "serialNumber": "2740013262", - "batchNumber": "1" + "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", + "increaseAmount": "10.00" }' \ - "https://cloud.handpoint.io/batch/close" + "https://cloud.handpoint.io/preauthorization/increase" ``` - + ```shell curl -X POST \ -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "deviceType": "PAXA920MAX", - "serialNumber": "2740013262" + "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", + "increaseAmount": "5.00", + "subtract": "1" }' \ - "https://cloud.handpoint.io/batch/close" + "https://cloud.handpoint.io/preauthorization/increase" +``` + + + + +```shell +curl -X POST \ + -H "Content-Type: application/json" \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -d '{ + "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", + "increaseAmount": "10.00", + "tipAmount": "2.00", + "customerReference": "table-12-increase" + }' \ + "https://cloud.handpoint.io/preauthorization/increase" +``` + + + + +**Responses** + + + + +```json +{ + "httpStatus": 200, + "customFields": { + "tenderType": "Credit", + "issuerResponseCode": "200" + }, + "acquirerTid": "0821599465", + "actionCode": "0000", + "agreementNumber": "123456789010102", + "approvalCode": "010119", + "batchNumber": "1", + "cardTypeName": "VISA", + "currency": "USD", + "expiryDateMMYY": "1027", + "holdAmount": "102.00", + "issuerResponseCode": "00", + "issuerResponseText": "COMPLETED", + "maskedCardNumber": "************0936", + "nonce": "1776156307790", + "originalAmount": "1.00", + "preAuthorizationGuid": "664ec4c0-37e6-11f1-bc66-3114cb76dabf", + "serverDateTime": "20260414094416641", + "terminalDateTime": "20260414094416433", + "transNumber": "000001", + "increaseAmount": "100.00", + "preAuthorizationIncreaseGuid": "80b2e710-37e6-11f1-bc66-3114cb76dabf" +} ``` - - -**Responses** - - - + ```json { - "batchNumber": "1", - "closeBatchGuid": "14431ad0-d0dc-11f0-9ed0-695d1a368668", - "closedAt": "20251204064010281", - "customerReference": {}, - "httpStatus": "200", - "issuerResponseCode": "00", - "issuerResponseText": "ACCEPTED" + "error": { + "statusCode": 400, + "name": "BadRequestError", + "message": "Original pre-auth is voided, not approved or already captured", + "code": "3211", + "details": { + "errorCode": "3211", + "errorGuid": "e8b35da0-37ea-11f1-bc66-3114cb76dabf", + "httpStatus": 403, + "reason": "Original pre-auth is voided, not approved or already captured" + } + } } ``` @@ -1843,20 +1647,18 @@ curl -X POST \ ```json { "error": { + "statusCode": 422, + "name": "UnprocessableEntityError", + "message": "The request body is invalid.", "code": "VALIDATION_FAILED", "details": [ { + "path": "", "code": "required", - "info": { - "missingProperty": "batchNumber" - }, - "message": "must have required property 'batchNumber'", - "path": "" + "message": "must have required property 'increaseAmount'", + "info": { "missingProperty": "increaseAmount" } } - ], - "message": "The request body is invalid. See error object `details` property for more info.", - "name": "UnprocessableEntityError", - "statusCode": 422 + ] } } ``` @@ -1864,72 +1666,69 @@ curl -X POST \ -### Batch Summary - -`BatchSummary` +--- -`POST /batch/summary` is used to retrieve a **summary of a batch** for a specific payment terminal, identified by its -`deviceType`, `serialNumber`, and `batchNumber`. +### Preauthorization Capture -Typical use cases: +`PreauthCapture` -- Back-office reconciliation after a batch close. -- Reporting and dashboards that need batch-level totals (number of transactions, net amount, status). -- Support tools that need to check the status of a batch on a given terminal. +`POST /preauthorization/capture` is used to **finalize (capture) an open pre-authorization**, charging the `capturedAmount` to the cardholder. Once captured, the pre-authorization is settled and the held funds are transferred. #### Parameters | Parameter | Notes | | --------- | ----- | | `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: BatchSummaryRequest` Required | [BatchSummaryRequest](restobjects#batchSummaryRequest) object containing `deviceType`, `serialNumber` and `batchNumber`. | +| `Request Body: PreauthCaptureRequest` Required | [PreauthCaptureRequest](restobjects#preauthCaptureRequest) object containing the original pre-authorization GUID and the amount to capture. | -Typical fields in the request body (see [BatchSummaryRequest](restobjects#batchSummaryRequest) for full details): +Typical fields in the request body (see [PreauthCaptureRequest](restobjects#preauthCaptureRequest) for full details): -- `deviceType` Required – Terminal model identifier (for example, `"PAXA920MAX"`). -- `serialNumber` Required – Serial number of the payment terminal (for example, `"2740013262"`). -- `batchNumber` Required – Identifier of the batch whose summary is being requested (for example, `"1"`). +- `originalGuid` Required – GUID of the original pre-authorization transaction. +- `capturedAmount` Required – Amount to capture and charge (for example, `"120.00"`). +- `tipAmount` Optional – Tip amount to include in the captured total (for example, `"5.00"`). +- `customerReference` Optional – Integrator-defined reference, forwarded as-is to the gateway. #### Returns | Result | Notes | | ------ | ----- | -| `200` | Batch summary successfully retrieved. The response body is a [BatchSummaryResponse](restobjects#batchSummaryResponse) with batch status, transaction counts, net amount and optional custom fields. | -| `400` | Business / lookup error (for example, the batch cannot be summarised for the given terminal). Returned as `BadRequestError`, with `error.code`, `error.message` and optional `error.details`. | -| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (for example, missing `batchNumber`). | -| `5xx` | Internal error or service unavailability. The batch itself is not modified; the integrator may retry later or reconcile through other means. | +| `200` | Pre-authorization capture accepted. Response body is a parsed gateway object. | +| `400` | Business rule error from the gateway (for example, unknown `originalGuid`, pre-authorization already captured, or captured amount exceeds authorized amount). Returned as `BadRequestError` with `error.code` and `error.details`. | +| `403` | Forbidden — the API key does not belong to a merchant. Partner keys are not accepted by this endpoint. | +| `422` | Payload validation error (`VALIDATION_FAILED`) — `originalGuid` or `capturedAmount` is missing. | **Code Example** **Requests** - + ```shell curl -X POST \ -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "deviceType": "PAXA920MAX", - "serialNumber": "2740013262", - "batchNumber": "1" + "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", + "capturedAmount": "120.00" }' \ - "https://cloud.handpoint.io/batch/summary" + "https://cloud.handpoint.io/preauthorization/capture" ``` - + ```shell curl -X POST \ -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "deviceType": "PAXA920MAX", - "serialNumber": "2740013262" + "originalGuid": "0c9d9df0-48ec-11eb-81a1-470a19c80d3a", + "capturedAmount": "120.00", + "tipAmount": "5.00", + "customerReference": "hotel-folio-4422" }' \ - "https://cloud.handpoint.io/batch/summary" + "https://cloud.handpoint.io/preauthorization/capture" ``` @@ -1942,129 +1741,140 @@ curl -X POST \ ```json { - "batchNumber": "1", - "batchStatus": "CLOSED", - "batchSummaryGuid": "61573ba0-08ac-11f1-b002-eb225f134f40", - "customFields": { - "entry": [ - { - "key": "salesCount", - "value": "155" - }, - { - "key": "refundsCount", - "value": "3" - }, - { - "key": "issuerBatchCloseLocalTimestamp", - "value": "2025-12-05T11:00:00" - } - ] - }, - "httpStatus": "200", - "issuerResponseCode": "00", - "issuerResponseText": "DATA RETRIEVED", - "netAmount": "245.00", - "transactionCount": "158" + "httpStatus": 200, + "customFields": { + "tenderType": "Credit", + "RRN": "513815902180", + "issuerResponseCode": "200" + }, + "acquirerTid": "0821599465", + "actionCode": "0000", + "agreementNumber": "123456789010102", + "approvalCode": "010119", + "batchNumber": "1", + "cardTypeName": "VISA", + "currency": "USD", + "expiryDateMMYY": "1027", + "holdAmount": "102.00", + "issuerResponseCode": "00", + "issuerResponseText": "COMPLETED", + "maskedCardNumber": "************0936", + "nonce": "1776156307790", + "originalAmount": "1.00", + "preAuthorizationGuid": "664ec4c0-37e6-11f1-bc66-3114cb76dabf", + "serverDateTime": "20260414094532102", + "terminalDateTime": "20260414094532085", + "transNumber": "000001", + "capturedAmount": "100.00", + "preAuthorizationCaptureGuid": "adad5660-37e6-11f1-9d29-81969834b189" } ``` -Key fields: - -* `batchStatus` – Current status of the batch (for example, `"CLOSED"`). -* `transactionCount` – Total number of transactions in the batch. -* `netAmount` – Net amount for the batch in major units as a string (for example, `"245.00"`). -* `customFields.entry` – Optional list of key/value pairs with acquirer-specific metrics (for example, `salesCount`, `refundsCount`, `issuerBatchCloseLocalTimestamp`). - - ```json { "error": { + "statusCode": 422, + "name": "UnprocessableEntityError", + "message": "The request body is invalid.", "code": "VALIDATION_FAILED", "details": [ { + "path": "", "code": "required", - "info": { - "missingProperty": "batchNumber" - }, - "message": "must have required property 'batchNumber'", - "path": "" + "message": "must have required property 'capturedAmount'", + "info": { "missingProperty": "capturedAmount" } } - ], - "message": "The request body is invalid. See error object `details` property for more info.", - "name": "UnprocessableEntityError", - "statusCode": 422 + ] } } ``` - -### Batch Detail +## **MOTO Operations** -`BatchDetail` -A Batch Detail allows the user to retrieve information about a specific batch (including a list of transacctions) included in the batch for a specific payment terminal. +`MOTO (Mail Order / Telephone Order)` operations can also be processed **without a payment reader**, using information +that is already stored in the gateway (tokens and references to previous transactions). -`POST /batch/detail` is used to retrieve information about a specific batch (including a list of transacctions) included in the batch for a specific payment terminal, identified by its -`deviceType`, `serialNumber`, `batchNumber` and `RRN`. +These endpoints are intended for MOTO scenarios where the merchant does **not** need to collect or handle sensitive +card data (PAN, expiry date, CVV) in their own systems: + +- `/moto/sale` performs a sale using a **previously generated card token** (`cardToken`) that represents card details stored in the gateway. +- `/moto/refund` performs a refund of a **previous operation**, using the card associated with that original operation (`originalGuid`). +- `/moto/reversal` performs a reversal (void) of a **previous operation**, passing only its identifier (`originalGuid`). + +:::tip +Unlike the standard MOTO operations that use `/transactions` and a physical terminal, these endpoints: + +- Do **not** require `serial_number` or `terminal_type`. +- Do **not** receive raw card data in the request. +- Rely on `cardToken` and `originalGuid` to reference card information already stored in the gateway. +::: + +All request and response payloads are defined in the corresponding [Moto objects](restobjects#moto). + +--- + +### MOTO Sale + +`MotoSale` + +`POST /moto/sale` is used to perform a **MOTO sale without a payment reader**, using a **card token** (`cardToken`) +that was generated previously (for example, by a `saleAndTokenizeCard` operation). + +The card details are *not* sent in the request; they are resolved by the gateway using the `cardToken`. + +Typical flow: + +1. A card is captured securely in a previous flow, for example through the [`/transactions`](restendpoints#transactions) endpoint using a + `saleAndTokenizeCard` operation. +2. The gateway returns a `cardToken` (e.g. `665630867`). +3. The integrator can later perform one or more MOTO sales using that `cardToken`, without handling PAN/CVV again. #### Parameters | Parameter | Notes | | --------- | ----- | -| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | -| `Request Body: BatchDetailRequest` Required | [BatchDetailRequest](restobjects#batchDetailRequest) object containing `deviceType`, `serialNumber`, `batchNumber` and `rrn`. | +| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | +| `Request Body: MotoSaleRequest` Required | [MotoSaleRequest](restobjects#motoSaleRequest) object containing `cardToken`, `amount`, `currency` and optional merchant references. | -Typical fields in the request body (see [BatchDetailRequest](restobjects#batchDetailRequest) for full details): +Typical fields in the request body (see [MotoSaleRequest](restobjects#motoSaleRequest) for full details): -- `deviceType` Required – Terminal model identifier (for example, `"PAXA920MAX"`). -- `serialNumber` Required – Serial number of the payment terminal (for example, `"2740013262"`). -- `batchNumber` Required – Identifier of the batch whose summary is being requested (for example, `"1"`). -- `rrn` Optional – Retrieval Reference Number, unique number assigned by the acquirer (for example, `"123"`). +- `cardToken` Required – Token representing the card stored in the gateway (e.g. `"665630867"`). +- `amount` Required – String amount in MAJOR units (e.g. `"20.00"` for 20.00). Must be a positive integer string. +- `currency` Required – 3-character ISO 4217 currency code (e.g. `"EUR"`). +- Optional references for reconciliation: `customerReference`, `transactionReference`, etc. #### Returns | Result | Notes | | ------ | ----- | -| `200` | Batch detail successfully retrieved. The response body is a [BatchDetailResponse](restobjects#batchDetailResponse) with batch status and optional details fields. | -| `400` | Business / lookup error (for example, the batch cannot be summarised for the given terminal). Returned as `BadRequestError`, with `error.code`, `error.message` and optional `error.details`. | -| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (for example, missing `batchNumber`). | -| `5xx` | Internal error or service unavailability. The batch itself is not modified; the integrator may retry later or reconcile through other means. | - -**Code Example** - -**Requests** - - - +| `200` | Sale successfully processed. The response body is a `motoSaleResponse` [Moto Transaction Response](restobjects#motoTransactionResponse) with the authorization result (approved/declined), authorization code, masked card details, acquirer TID, timestamps, etc. | +| `400` | Business rule error from the payment gateway (for example, CVV required, card token failure). Returned as `BadRequestError`, with `error.code` and `error.details` containing the gateway error code and description. | +| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (invalid amount (must be a positive integer string in minor units), currency length, etc.). | +| `5xx` | Internal error or gateway unavailability. The final outcome may be unknown and may require reconciliation. | -```shell -curl -X POST \ - -H "Content-Type: application/json" \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ - -d '{ - "deviceType": "PAXA920MAX", - "serialNumber": "2740013262", - "batchNumber": "1" - }' \ - "https://cloud.handpoint.io/batch/detail" -``` +#### Code Example - - +**Requests** + + + ```shell curl -X POST \ -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "deviceType": "PAXA920MAX", - "serialNumber": "2740013262" + "amount": "20.00", // 20 => 20.00 + "currency": "EUR", + "cardToken": "665630867", + "customerReference": "order-12345", + "transactionReference": "b7b2360d-3e9e-4b62-9a3a-2e6ef6c5cd01" }' \ - "https://cloud.handpoint.io/batch/detail" + "https://cloud.handpoint.io/moto/sale" ``` @@ -2073,43 +1883,48 @@ curl -X POST \ **Responses** - + ```json { - "httpStatus": "200", - "batchNumber": "1", - "closedAt": "20260213135114884", - "issuerResponseCode": "00", - "issuerResponseText": "Batch detail retrieved", - "details": [ - { - "transactionType": "SALE", - "amount": "100.00", - "batchDetailElementGuid": "2fac8676-396a-4cf1-a5ab-650f3f79e923" - }, - { - "transactionType": "SALE", - "retrievalReferenceNumber": "RRN08236", - "amount": "50.00", - "batchDetailElementGuid": "dcb718ef-59f0-4de8-b414-41048782aff9" - }, - { - "transactionType": "REFUND", - "retrievalReferenceNumber": "RRN08237", - "amount": "25.00", - "batchDetailElementGuid": "d1a7ef06-c429-4a57-a07b-b461482bcafa" + "error": { + "statusCode": 400, + "name": "BadRequestError", + "message": "CVV required", + "code": "3107", + "details": { + "errorGuid": "dae59b20-cf71-11f0-b588-a122fae316de", + "errorCode": "3107", + "description": "CVV required", + "httpStatus": 400 } - ], - "batchDetailGuid": "10360390-08e4-11f1-8bbe-a982e87fcbf2", - "batchStatus": "CLOSED" + } } ``` -Key fields: +:::note +A very common cause of **3107** is having "CVV/CV2 input mandatory" enabled for Card Not Present. +::: -* `batchStatus` – Current status of the batch (for example, `"CLOSED"`). -* `details.entry` – Optional list with transaction info (for example, `transactionType`, `amount`, `retrievalReferenceNumber`, `batchDetailElementGuid`). + + + +```json +{ + "error": { + "code": "5252", + "details": { + "description": "Card token failure", + "errorCode": "5252", + "errorGuid": "10a3d120-cf75-11f0-95b2-770b7d1d8e67", + "httpStatus": 404 + }, + "message": "Card token failure", + "name": "BadRequestError", + "statusCode": 400 + } +} +``` @@ -2122,9 +1937,9 @@ Key fields: { "code": "required", "info": { - "missingProperty": "batchNumber" + "missingProperty": "amount" }, - "message": "must have required property 'batchNumber'", + "message": "must have required property 'amount'", "path": "" } ], @@ -2138,76 +1953,60 @@ Key fields: +--- -## Device Control Commands - -> **Note:** -> The following commands are available for all devices running Android SDK version 7.1006.0 or later. - -Command Endpoint Format - -All device control commands follow this endpoint structure: -```https://cloud.handpoint.io/devices/{deviceType}/{serialNumber}/{command}``` - -Where: -- `{deviceType}` is the type of the device (e.g., PAXIM30) -- `{serialNumber}` is the serial number of the device (e.g., 1640013848) -- `{command}` is the specific command to execute - -Common Parameters - -All commands share these common parameters: - -| Parameter | Notes | -| ----------- | ----------- | -| `Header: ApiKeyCloud` Required
*String* | Api key used to authenticate the merchant. (UNIQUE per Merchant) | -| `Header: Content-Type` Required
*String* | Must be set to `application/json` | +### MOTO Refund -Common Response Codes +`MotoRefund` -| Response Code | Description | -|--------------|-------------| -| 202 | Request accepted, command will be executed | -| 403 | Authentication failed | -| 422 | Invalid request | -| 400 | Invalid parameter value (when applicable) | +`POST /moto/refund` is used to perform a **MOTO refund without a payment reader**. ---- +The refund is **linked** to a previous MOTO sale via `originalGuid`, and the gateway reuses the card associated with that +original transaction. No card data is passed in the refund request. -:::caution -For the Commands to work properly, the Handpoint Payments App **MUST** be in **Integrated Mode** (enabled via **Handpoint TMS** and controlled by the merchant in the **Handpoint Payments App** Settings).
-::: +#### Parameters -### Set Unattended Mode +| Parameter | Notes | +| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | +| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | +| `Request Body: MotoRefundRequest` Required | [MotoRefundRequest](restobjects#motoRefundRequest) object containing `originalGuid`, `amount`, `currency` and optional merchant references. | -`POST /devices/{deviceType}/{serialNumber}/set-unattended-mode` +Typical fields (see [MotoRefundRequest](restobjects#motoRefundRequest) for full details): -Enables or disables unattended mode on the device. -Unattended mode will disable the Bottom navigation bar containing the Home, back, recent buttons. -The Payment screen will be the only visible screen in the Handpoint Payments App. (Settings, Hisotry and Analytic tabs will not be accessible) +* `originalGuid` Required – GUID of the original sale to be refunded (e.g. `"1a41d9f0-cf72-11f0-95b2-770b7d1d8e67"`). +* `amount` Required – String amount to be refunded in MAJOR units (e.g. `"5.00"` for $5.00). Must be a positive integer string. +* `currency` Required – 3-character ISO 4217 code (e.g. `"EUR"`, `"USD"`). +* Optional: `customerReference`, `transactionReference`. +Supports full and partial refunds, depending on acquirer configuration. -**Request Body Parameters** +#### Returns -| Parameter | Type | Description | -|-----------|------|-------------| -| `status` | boolean | `true` to enable unattended mode, `false` to disable | +| Result | Notes | +| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `200` | Refund successfully processed. The response body is a `motoRefundResponse` [Moto Transaction Response](restobjects#motoTransactionResponse) including `guid`, `originalGuid`, `amount`, `currency`, `maskedCardNumber`, `approvalCode`, `issuerResponseText`, etc. | +| `400` | Business rule error from the payment gateway (for example, currency mismatch, refund amount greater than the original sale). Returned as `BadRequestError`, with `error.code` and `error.details` describing the gateway error. | +| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or invalid (missing `originalGuid`, invalid amount, invalid currency format, etc.). | -**Example Request** +#### Code Example **Requests** - + ```shell curl -X POST \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -H "Content-Type: application/json" \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "status": false + "originalGuid": "1a41d9f0-cf72-11f0-95b2-770b7d1d8e67", + "amount": "5.00", // 5 => 5.00 + "currency": "EUR", + "customerReference": "refund-98765", + "transactionReference": "a1fe8db5-69a4-4b4d-a704-94ac2570f9b0" }' \ - "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-unattended-mode" + "https://cloud.handpoint.io/moto/refund" ``` @@ -2216,130 +2015,107 @@ curl -X POST \ **Responses** - - -No response body is returned. - - - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": { - "error": 1002, - "message": "No device listening at the other end of the secure channel" - } - } + "error": { + "code": "3210", + "details": { + "description": "Original and linked currency do not match", + "errorCode": "3210", + "errorGuid": "35a2b220-cf7a-11f0-b588-a122fae316de", + "httpStatus": 400 + }, + "message": "Original and linked currency do not match", + "name": "BadRequestError", + "statusCode": 400 + } } ``` - - - -### Set Locale - -`POST /devices/{deviceType}/{serialNumber}/set-locale` - -Sets the locale of the target device. - -**Request Body Parameters** - -| Parameter | Type | Description | -|-----------|--------|-----------------------------------------------------------------------------| -| `locale` | string | IETF BCP 47 language tag (e.g., `en_US`). Two-letter language and country code.| - -**Response Codes** - -| Code | Description | -|------|---------------------------------------------| -| 202 | The request is accepted and will be executed| -| 403 | Authentication failed | -| 422 | Invalid request | - -**Example Request** - -**Requests** - - - - -```shell -curl -X POST \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ - -H "Content-Type: application/json" \ - -d '{ - "locale": "en_CA" - }' \ - "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-locale" -``` - - - - -**Responses** - - - - -No response body is returned. - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": { - "error": 1002, - "message": "No device listening at the other end of the secure channel" - } - } + "error": { + "code": "3209", + "details": { + "description": "The requested refund amount is greater than the initial sale amount", + "errorCode": "3209", + "errorGuid": "e72ea940-cf7a-11f0-b588-a122fae316de", + "httpStatus": 400 + }, + "message": "The requested refund amount is greater than the initial sale amount", + "name": "BadRequestError", + "statusCode": 400 + } } ``` + + + + +Missing `originalGuid`, missing `amount`, invalid `currency` length, or invalid `amount` pattern — returned as `422 VALIDATION_FAILED` with one or more entries in `error.details`. + --- -### Set Password Protected +### MOTO Reversal -`POST /devices/{deviceType}/{serialNumber}/set-password-protected` +`MotoReversal` -Enables or disables password protection on the device. +`POST /moto/reversal` is used to **reverse (void)** a previous MOTO operation processed via the no-reader MOTO endpoints. -**Request Body Parameters** +The request is linked to the original sale via `originalGuid`. No card data is sent; the gateway uses the original +transaction information. -| Parameter | Type | Description | -|-----------|---------|-----------------------------------------------------| -| `status` | boolean | `true` to enable password protection, `false` to disable | +Reversals are typically used to cancel a MOTO sale shortly after authorization, subject to acquirer rules. -**Response Codes** +#### Parameters -| Code | Description | -|------|---------------------------------------------| -| 202 | The request is accepted and will be executed| -| 403 | Authentication failed | -| 422 | Invalid request | +| Parameter | Notes | +| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | +| `Request Body: MotoReversalRequest` Required | [MotoReversalRequest](restobjects#motoReversalRequest) object referencing the original MOTO transaction (`originalGuid`) and including the reversal `amount` and `currency`. | -**Example Request** +Typical fields (see [MotoReversalRequest](restobjects#motoReversalRequest) for full details): + +* `originalGuid` Required – GUID of the original sale to be reversed. +* `amount` Required – String amount to reverse in MAJOR units (e.g. `"20.00"` for $20.00). Must be a positive integer string. +* `currency` Optional – 3-character ISO 4217 code; if provided, must respect `minLength = 3`, `maxLength = 3` and may need to match the original transaction’s currency. +* Optional merchant references: `customerReference`, `transactionReference`. + +#### Returns + +| Result | Notes | +| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `200` | Reversal accepted and processed. Response body is a `motoReversalResponse` [Moto Transaction Response](restobjects#motoTransactionResponse) indicating whether the reversal was approved, with fields such as `guid`, `originalGuid`, `issuerResponseCode`, `issuerResponseText`, `f25`, etc. | +| `400` | Business rule error from the payment gateway (for example, unknown `originalGuid`, reversal not allowed). Returned as `BadRequestError`, with `error.code` and `error.details` describing the gateway error (for example, code `3153`). | +| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or invalid (missing `originalGuid`, missing `amount`, invalid `currency`, invalid `amount` pattern). | + +#### Code Example **Requests** - + ```shell curl -X POST \ - -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -H "Content-Type: application/json" \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "status": true + "originalGuid": "b28bdb10-cf87-11f0-b588-a122fae316de", + "amount": "20.00", //20 => 20.00 || 2000 => 2000.00 + "currency": "EUR", + "customerReference": "void-001", + "transactionReference": "4d7b1a2c-5bfd-4a30-9b6f-123456789abc" }' \ - "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-password-protected" + "https://cloud.handpoint.io/moto/reversal" ``` @@ -2348,65 +2124,142 @@ curl -X POST \ **Responses** - + -No response body is returned. +```json +{ + "type": "motoReversalResponse", + "httpStatus": 200, + "amount": "20.00", + "currency": "EUR", + "guid": "3f7772a0-cf88-11f0-b588-a122fae316de", + "originalGuid": "b28bdb10-cf87-11f0-b588-a122fae316de", + "issuerResponseCode": "00", + "issuerResponseText": "Successful", + "maskedCardNumber": "************3555", + "f25": "4000" +} +``` - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": { - "error": 1002, - "message": "No device listening at the other end of the secure channel" - } - } + "error": { + "code": "3153", + "details": { + "description": "Unable to find message to reverse.", + "errorCode": "3153", + "errorGuid": "633a4370-cf88-11f0-b588-a122fae316de", + "httpStatus": 404 + }, + "message": "Unable to find message to reverse.", + "name": "BadRequestError", + "statusCode": 400 + } } ``` + + + + +Missing `originalGuid`, missing `amount`, invalid `currency` length, or invalid `amount` pattern — returned as `422 VALIDATION_FAILED` with one or more entries in `error.details`. + +## Batch Operations Beta {#batch-operations} + +:::info +Batch Operations are not available for all acquirers. Contact your Handpoint relationship manager to find out if this feature is supported for your acquirer. +::: + +Batch operations allow you to remotely **manage batches on a specific payment terminal** using Cloud API. + +These endpoints are typically used in scenarios where the acquirer or merchant workflow is batch-based (for example, +daily settlement batches per terminal). + +Cloud API currently supports the following batch operations: + +- `POST /batch/close` — requests the **closure of a batch** for a given terminal (`deviceType`, `serialNumber`). +- `POST /batch/summary` — retrieves a **summary of a batch** for a given terminal (`deviceType`, `serialNumber`) and `batchNumber`. +- `POST /batch/detail` — retrieves **batch detail** (including a list of transactions) for a given terminal (`deviceType`, `serialNumber`) and `batchNumber` (and optional `rrn`). + + +All request and response payloads are defined in the corresponding [Batch objects](restobjects#batch). + +:::info +In MOTO scenarios, the `deviceType` and `serialNumber` fields can refer to a **virtual terminal** (`VT`) instead of a physical device. Use `VT` as the `deviceType` and the serial number of the virtual terminal assigned to the merchant. The serial number of the virtual terminal can be retrieved from the response of the [/initialize](restendpoints#initialize) endpoint. +::: + --- -### Reboot +### Close Batch -`POST /devices/{deviceType}/{serialNumber}/reboot` +`BatchClose` -Reboots the device with an optional force parameter. +`POST /batch/close` is used to request **closure of a batch** for a specific payment terminal, identified by its +`deviceType` and `serialNumber`, and optionally a `batchNumber`. -**Request Body Parameters** +Typical use cases: -| Parameter | Type | Description | -|-----------|---------|-----------------------------------------------------------------------------| -| `force` | boolean | `true` to force reboot even during transaction, `false` to check status first| +- End-of-day batch closure triggered from a back-office or back-office job. +- Manual batch close as part of a support or reconciliation process. -**Response Codes** +#### Parameters -| Code | Description | -|------|---------------------------------------------| -| 202 | The request is accepted and will be executed| -| 403 | Authentication failed | -| 422 | Invalid request | +| Parameter | Notes | +| --------- | ----- | +| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | +| `Request Body: BatchCloseRequest` Required | [BatchCloseRequest](restobjects#batchCloseRequest) object containing `deviceType`, `serialNumber` and `batchNumber`. | -**Example Request** +Typical fields in the request body (see [BatchCloseRequest](restobjects#batchCloseRequest) for full details): + +- `deviceType` Required – Terminal model identifier (for example, `"PAXA920MAX"`). +- `serialNumber` Required – Serial number of the payment terminal (for example, `"2740013262"`). +- `batchNumber` Optional – Identifier of the batch to close (for example, `"1"`), if not specified, the current open batch for that Terminal will be closed. + +#### Returns + +| Result | Notes | +| ------ | ----- | +| `200` | Batch close request accepted. The response body is a [BatchCloseResponse](restobjects#batchCloseResponse) with the batch number, a unique `closeBatchGuid`, timestamps and an issuer-style response code/text. | +| `400` | Business rule error or generic gateway error. Returned as `BadRequestError`, with `error.code`, `error.message` and `error.details` describing the problem. | +| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (for example, missing `serialNumber`). | +| `5xx` | Internal error or service unavailability. The final outcome of the batch close may be unknown and may require reconciliation or a retry at a later time. | + +**Code Example** **Requests** - + ```shell curl -X POST \ + -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -d '{ + "deviceType": "PAXA920MAX", + "serialNumber": "2740013262", + "batchNumber": "1" + }' \ + "https://cloud.handpoint.io/batch/close" +``` + + + + +```shell +curl -X POST \ -H "Content-Type: application/json" \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "force": false + "deviceType": "PAXA920MAX", + "serialNumber": "2740013262" }' \ - "https://cloud.handpoint.io/devices/PAXIM30/0000000000/reboot" + "https://cloud.handpoint.io/batch/close" ``` @@ -2415,68 +2268,135 @@ curl -X POST \ **Responses** - + -No response body is returned. +```json +{ + "batchNumber": "1", + "closeBatchGuid": "14431ad0-d0dc-11f0-9ed0-695d1a368668", + "closedAt": "20251204064010281", + "customerReference": {}, + "httpStatus": "200", + "issuerResponseCode": "00", + "issuerResponseText": "ACCEPTED" +} +``` - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": { - "error": 1002, - "message": "No device listening at the other end of the secure channel" + "httpStatus": "200", + "customerReference": {}, + "customFields": { + "entry": { + "key": "issuerBatchCloseLocalTimestamp", + "value": "2026-05-07T10:02:12" } - } + }, + "batchNumber": "1", + "closeBatchGuid": "d1988a50-49fb-11f1-b64d-2969d719a012", + "closedAt": "20260507100212859", + "issuerResponseCode": "00", + "issuerResponseText": "ACCEPTED", + "batchStatus": "CLOSED" +} +``` + + + + +```json +{ + "error": { + "code": "VALIDATION_FAILED", + "details": [ + { + "code": "required", + "info": { + "missingProperty": "batchNumber" + }, + "message": "must have required property 'batchNumber'", + "path": "" + } + ], + "message": "The request body is invalid. See error object `details` property for more info.", + "name": "UnprocessableEntityError", + "statusCode": 422 + } } ``` + ---- +### Batch Summary -### Set Screen Brightness +`BatchSummary` + +`POST /batch/summary` is used to retrieve a **summary of a batch** for a specific payment terminal, identified by its +`deviceType`, `serialNumber`, and `batchNumber`. + +Typical use cases: + +- Back-office reconciliation after a batch close. +- Reporting and dashboards that need batch-level totals (number of transactions, net amount, status). +- Support tools that need to check the status of a batch on a given terminal. -`POST /devices/{deviceType}/{serialNumber}/set-screen-brightness` +#### Parameters -Sets the screen brightness levels. +| Parameter | Notes | +| --------- | ----- | +| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | +| `Request Body: BatchSummaryRequest` Required | [BatchSummaryRequest](restobjects#batchSummaryRequest) object containing `deviceType`, `serialNumber` and `batchNumber`. | -**Request Body Parameters** +Typical fields in the request body (see [BatchSummaryRequest](restobjects#batchSummaryRequest) for full details): -| Parameter | Type | Description | -|--------------------------|---------|------------------------------------| -| `minimumBrightnessLevel` | integer | Value between 0 and 100 | -| `maximumBrightnessLevel` | integer | Value between 0 and 100 | +- `deviceType` Required – Terminal model identifier (for example, `"PAXA920MAX"`). +- `serialNumber` Required – Serial number of the payment terminal (for example, `"2740013262"`). +- `batchNumber` Required – Identifier of the batch whose summary is being requested (for example, `"1"`). -**Response Codes** +#### Returns -| Code | Description | -|------|---------------------------------------------| -| 202 | The request is accepted and will be executed| -| 403 | Authentication failed | -| 422 | Invalid request | -| 400 | Value is outside the valid range | +| Result | Notes | +| ------ | ----- | +| `200` | Batch summary successfully retrieved. The response body is a [BatchSummaryResponse](restobjects#batchSummaryResponse) with batch status, transaction counts, net amount and optional custom fields. | +| `400` | Business / lookup error (for example, the batch cannot be summarised for the given terminal). Returned as `BadRequestError`, with `error.code`, `error.message` and optional `error.details`. | +| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (for example, missing `batchNumber`). | +| `5xx` | Internal error or service unavailability. The batch itself is not modified; the integrator may retry later or reconcile through other means. | -**Example Request** +**Code Example** **Requests** - + ```shell curl -X POST \ + -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -d '{ + "deviceType": "PAXA920MAX", + "serialNumber": "2740013262", + "batchNumber": "1" + }' \ + "https://cloud.handpoint.io/batch/summary" +``` + + + + +```shell +curl -X POST \ -H "Content-Type: application/json" \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "minimumBrightnessLevel": 20, - "maximumBrightnessLevel": 100 + "deviceType": "PAXA920MAX", + "serialNumber": "2740013262" }' \ - "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-screen-brightness" + "https://cloud.handpoint.io/batch/summary" ``` @@ -2485,74 +2405,133 @@ curl -X POST \ **Responses** - + -No response body is returned. +```json +{ + "batchNumber": "1", + "batchStatus": "CLOSED", + "batchSummaryGuid": "61573ba0-08ac-11f1-b002-eb225f134f40", + "customFields": { + "entry": [ + { + "key": "salesCount", + "value": "155" + }, + { + "key": "refundsCount", + "value": "3" + }, + { + "key": "issuerBatchCloseLocalTimestamp", + "value": "2025-12-05T11:00:00" + } + ] + }, + "httpStatus": "200", + "issuerResponseCode": "00", + "issuerResponseText": "DATA RETRIEVED", + "netAmount": "245.00", + "transactionCount": "158" +} +``` + +Key fields: + +* `batchStatus` – Current status of the batch (for example, `"CLOSED"`). +* `transactionCount` – Total number of transactions in the batch. +* `netAmount` – Net amount for the batch in major units as a string (for example, `"245.00"`). +* `customFields.entry` – Optional list of key/value pairs with acquirer-specific metrics (for example, `salesCount`, `refundsCount`, `issuerBatchCloseLocalTimestamp`). - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": { - "error": 1002, - "message": "No device listening at the other end of the secure channel" - } - } + "error": { + "code": "VALIDATION_FAILED", + "details": [ + { + "code": "required", + "info": { + "missingProperty": "batchNumber" + }, + "message": "must have required property 'batchNumber'", + "path": "" + } + ], + "message": "The request body is invalid. See error object `details` property for more info.", + "name": "UnprocessableEntityError", + "statusCode": 422 + } } ``` + ---- - -### Set Reboot Time +### Batch Detail -:::note -This feature is only enabled for production devices. -::: +`BatchDetail` +A Batch Detail allows the user to retrieve information about a specific batch (including a list of transacctions) included in the batch for a specific payment terminal. -`POST /devices/{deviceType}/{serialNumber}/set-reboot-time` +`POST /batch/detail` is used to retrieve information about a specific batch (including a list of transacctions) included in the batch for a specific payment terminal, identified by its +`deviceType`, `serialNumber`, `batchNumber` and `RRN`. -Sets the daily reboot time for the device. The actual reboot will occur at a random minute within the specified hour. +#### Parameters -**Request Body Parameters** +| Parameter | Notes | +| --------- | ----- | +| `Header: ApiKeyCloud` Required | Cloud API key used to authenticate the merchant. | +| `Request Body: BatchDetailRequest` Required | [BatchDetailRequest](restobjects#batchDetailRequest) object containing `deviceType`, `serialNumber`, `batchNumber` and `rrn`. | -| Parameter | Type | Description | -|-----------|---------|-----------------------------------------------------| -| `hour` | integer | Hour of the day (0-23) when device should reboot | +Typical fields in the request body (see [BatchDetailRequest](restobjects#batchDetailRequest) for full details): -:::tip -If hour is set to 22, the device will reboot at a random time between 22:01 and 22:59. -::: +- `deviceType` Required – Terminal model identifier (for example, `"PAXA920MAX"`). +- `serialNumber` Required – Serial number of the payment terminal (for example, `"2740013262"`). +- `batchNumber` Required – Identifier of the batch whose summary is being requested (for example, `"1"`). +- `rrn` Optional – Retrieval Reference Number, unique number assigned by the acquirer (for example, `"123"`). -**Response Codes** +#### Returns -| Code | Description | -|------|---------------------------------------------| -| 202 | The request is accepted and will be executed| -| 403 | Authentication failed | -| 422 | Invalid request | -| 400 | Value is outside the valid range | +| Result | Notes | +| ------ | ----- | +| `200` | Batch detail successfully retrieved. The response body is a [BatchDetailResponse](restobjects#batchDetailResponse) with batch status and optional details fields. | +| `400` | Business / lookup error (for example, the batch cannot be summarised for the given terminal). Returned as `BadRequestError`, with `error.code`, `error.message` and optional `error.details`. | +| `422` | Payload validation error (`VALIDATION_FAILED`) when required fields are missing or do not match the schema (for example, missing `batchNumber`). | +| `5xx` | Internal error or service unavailability. The batch itself is not modified; the integrator may retry later or reconcile through other means. | -**Example Request** +**Code Example** **Requests** - + ```shell curl -X POST \ + -H "Content-Type: application/json" \ -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ + -d '{ + "deviceType": "PAXA920MAX", + "serialNumber": "2740013262", + "batchNumber": "1" + }' \ + "https://cloud.handpoint.io/batch/detail" +``` + + + + +```shell +curl -X POST \ -H "Content-Type: application/json" \ + -H "ApiKeyCloud: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX" \ -d '{ - "hour": 22 + "deviceType": "PAXA920MAX", + "serialNumber": "2740013262" }' \ - "https://cloud.handpoint.io/devices/PAXIM30/0000000000/set-reboot-time" + "https://cloud.handpoint.io/batch/detail" ``` @@ -2561,24 +2540,67 @@ curl -X POST \ **Responses** - + -No response body is returned. +```json +{ + "httpStatus": "200", + "batchNumber": "1", + "closedAt": "20260213135114884", + "issuerResponseCode": "00", + "issuerResponseText": "Batch detail retrieved", + "details": [ + { + "transactionType": "SALE", + "amount": "100.00", + "batchDetailElementGuid": "2fac8676-396a-4cf1-a5ab-650f3f79e923" + }, + { + "transactionType": "SALE", + "retrievalReferenceNumber": "RRN08236", + "amount": "50.00", + "batchDetailElementGuid": "dcb718ef-59f0-4de8-b414-41048782aff9" + }, + { + "transactionType": "REFUND", + "retrievalReferenceNumber": "RRN08237", + "amount": "25.00", + "batchDetailElementGuid": "d1a7ef06-c429-4a57-a07b-b461482bcafa" + } + ], + "batchDetailGuid": "10360390-08e4-11f1-8bbe-a982e87fcbf2", + "batchStatus": "CLOSED" +} +``` + +Key fields: + +* `batchStatus` – Current status of the batch (for example, `"CLOSED"`). +* `details.entry` – Optional list with transaction info (for example, `transactionType`, `amount`, `retrievalReferenceNumber`, `batchDetailElementGuid`). - + ```json { - "error": { - "statusCode": 400, - "name": "BadRequestError", - "message": { - "error": 1002, - "message": "No device listening at the other end of the secure channel" - } - } + "error": { + "code": "VALIDATION_FAILED", + "details": [ + { + "code": "required", + "info": { + "missingProperty": "batchNumber" + }, + "message": "must have required property 'batchNumber'", + "path": "" + } + ], + "message": "The request body is invalid. See error object `details` property for more info.", + "name": "UnprocessableEntityError", + "statusCode": 422 + } } ``` + - + \ No newline at end of file