# Dime POS Android Intent Integration Guide

**Android Intent Contract & Flow Specification for Integrated Card Payments**

## 1. Overview

This document defines the Android intent contract for integrating card payment processing via the **Dime App**.

Your app (the **third-party app**) owns the checkout experience. When it's time to collect a card payment, your app launches the **Dime App** via an explicit Android intent, passing all required transaction details. Dime handles the card capture, authorization, and processing, then returns the result to your app using `setResult()` / Activity Result APIs.

This integration supports all card types — credit, debit, local debit, and any other card accepted by Dime.

**Participants:**

| App | Role |
|  --- | --- |
| **Your App (Third-Party)** | Primary app — owns the checkout experience and initiates the payment |
| **Dime App** | Payment processor — handles card capture, authorization, and returns the result |


## 2. Payment Flow

```
┌──────────────────┐                          ┌──────────────┐
│  Your App        │                          │   Dime App   │
│  (Third-Party)   │                          │              │
└────────┬─────────┘                          └──────┬───────┘
         │                                           │
         │  1. User initiates payment in your app    │
         │  2. Your app prepares transaction details  │
         │                                           │
         │  ──── startActivityForResult ────►        │
         │       (Intent with transaction data)      │
         │                                           │
         │                              3. Dime displays the
         │                                 card capture screen
         │                              4. User taps/inserts/swipes
         │                                 their card
         │                              5. Dime processes the card
         │                                 and authorizes payment
         │                                           │
         │  ◄──── setResult + finish ────────        │
         │       (Result with outcome data)          │
         │                                           │
         │  6. Your app shows receipt or error        │
         │  7. Your app updates its backend           │
         ▼                                           ▼
```

### Step-by-Step

1. **Checkout in Your App** — The user initiates a payment in your app. Your app computes the total amount and prepares a payment request.
2. **Launch Dime** — Your app constructs an explicit intent targeting Dime's payment activity and starts it with `startActivityForResult` (or the Activity Result API).
3. **Card Capture & Authorization** — Dime displays a screen prompting the user to tap, insert, or swipe their card. Dime processes the card through its payment rails/acquirer.
4. **Dime Returns Result** — Dime calls `setResult(resultCode, resultIntent)` and finishes. Your app receives the `resultCode` and a `resultIntent` containing the transaction outcome.
5. **Post-Processing** — Your app interprets the result:
  - **Approved** — show a receipt / confirmation screen.
  - **Declined / Error** — show a failure message with the option to retry or select an alternate payment method.


## 3. Intent Contract

### Intent Metadata

| Property | Value |
|  --- | --- |
| **Action** | `net.dime.paxapp.ACTION_PROCESS_CARD_PAYMENT` |
| **Target Activity** | `net.dime.paxapp.ui.CardPaymentActivity` |
| **Launch Type** | Explicit intent (recommended to avoid conflicts) |
| **Data URI** *(optional)* | `dime://payments/card` |
| **MIME Type** *(optional)* | `application/vnd.dime.cardpayment+json` |


### Debug vs Release Builds

The Dime App uses different package names for debug and release builds. You must target the correct package depending on which build of Dime is installed on the device.

| Build | Target Package |
|  --- | --- |
| **Debug** | `net.dime.paxapp.debug` |
| **Release** | `net.dime.paxapp` |


**Debug builds** — target package `net.dime.paxapp.debug`:

```kotlin
val intent = Intent().apply {
    setClassName("net.dime.paxapp.debug", "net.dime.paxapp.ui.CardPaymentActivity")
}
```

**Release builds** — target package `net.dime.paxapp`:

```kotlin
val intent = Intent().apply {
    setClassName("net.dime.paxapp", "net.dime.paxapp.ui.CardPaymentActivity")
}
```

A recommended pattern is to define the target package as a build config constant so your code adapts automatically:

```kotlin
// In your build.gradle
buildTypes {
    debug {
        buildConfigField("String", "DIME_PACKAGE", "\"net.dime.paxapp.debug\"")
    }
    release {
        buildConfigField("String", "DIME_PACKAGE", "\"net.dime.paxapp\"")
    }
}

// In your code
val intent = Intent().apply {
    setClassName(BuildConfig.DIME_PACKAGE, "net.dime.paxapp.ui.CardPaymentActivity")
}
```

> **Important:** Your app should gracefully handle the case where the Dime App is not installed (catch `ActivityNotFoundException`). This is especially relevant when switching between debug and release — ensure the matching Dime build variant is installed on the device.


## 4. Request Intent (Your App → Dime)

All fields are sent as Intent extras. Key names are stable contract keys.

### Request Properties

| Key | Type | Required | Description |
|  --- | --- | --- | --- |
| `transactionId` | String | Yes | Unique ID generated by your app to represent this transaction (for reconciliation) |
| `amount` | Double | Yes | Amount in dollars (e.g. `10.00`) |
| `currency` | String | Yes | ISO 4217 currency code (e.g. `JMD`) |
| `merchantId` | String | Yes | Identifier of the merchant in Dime's system |
| `merchantName` | String | Optional | Display name of the merchant to show on the Dime UI |
| `terminalId` | String | Optional | Logical terminal ID for card processing (if used by Dime/acquirer) |
| `orderId` | String | Optional | POS/order reference from your app — echoed in the response |
| `customerReference` | String | Optional | Customer reference (invoice number, customer ID, etc.) |
| `description` | String | Optional | Short description of the purchase / basket summary |
| `requestTimeoutSeconds` | Int | Optional | Soft timeout hint for Dime to complete the transaction (e.g. `120`) |
| `isTestMode` | Boolean | Optional | Indicates if this transaction is in test/sandbox mode |
| `callbackMeta` | String | Optional | Opaque JSON string or token that your app wants returned unchanged (for correlation) |


### Example: Launching Dime from Your App (Kotlin)

```kotlin
private const val REQ_CODE_CARD_PAYMENT = 1001

fun startCardPayment(context: Activity, request: CardPaymentRequest) {
    val intent = Intent().apply {
        setClassName(
            BuildConfig.DIME_PACKAGE, // net.dime.paxapp.debug or net.dime.paxapp
            "net.dime.paxapp.ui.CardPaymentActivity"
        )
        action = "net.dime.paxapp.ACTION_PROCESS_CARD_PAYMENT"

        putExtra("transactionId", request.transactionId)
        putExtra("amount", request.amount)
        putExtra("currencyCode", request.currencyCode)
        putExtra("merchantId", request.merchantId)
        putExtra("merchantName", request.merchantName)
        putExtra("terminalId", request.terminalId)
        putExtra("orderId", request.orderId)
        putExtra("customerReference", request.customerReference)
        putExtra("description", request.description)
        putExtra("requestTimeoutSeconds", request.requestTimeoutSeconds)
        putExtra("isTestMode", request.isTestMode)
        putExtra("callbackMeta", request.callbackMeta)
    }
    context.startActivityForResult(intent, REQ_CODE_CARD_PAYMENT)
}
```

> You can replace `startActivityForResult` with the Activity Result API in newer codebases.


## 5. Response Intent (Dime → Your App)

Dime returns a result via `setResult(resultCode, intent)`.

### 5.1 Result Codes

| Result Code | Meaning |
|  --- | --- |
| `RESULT_OK` | Transaction completed (approved or declined) — response payload is provided |
| `RESULT_CANCELED` | User canceled or Dime could not start/complete processing; payload may include error information |


### 5.2 Transaction Status Values

The response extras include a `status` field with one of these values:

| Status | Description |
|  --- | --- |
| `APPROVED` | Transaction authorized and captured/approved |
| `DECLINED` | Transaction processed but declined |
| `CANCELLED` | User cancelled before authorization |
| `TIMEOUT` | Processing timed out |
| `ERROR` | Internal error or unexpected failure |


### 5.3 Response Properties

| Key | Type | Present When | Description |
|  --- | --- | --- | --- |
| `transactionId` | String | Always | Echo of the original `transactionId` from the request |
| `orderId` | String | Optional | Echo of the original `orderId` |
| `status` | String | Always | One of: `APPROVED`, `DECLINED`, `CANCELLED`, `TIMEOUT`, `ERROR` |
| `transactionIdHost` | String | APPROVED / DECLINED | Host/acquirer transaction reference |
| `authCode` | String | APPROVED | Authorization code from issuer/acquirer |
| `responseCode` | String | APPROVED / DECLINED | ISO-like or gateway-specific response code |
| `responseMessage` | String | Always | Human-readable description of the outcome |
| `amount` | Double | APPROVED / DECLINED | Final authorized amount in dollars (may differ from requested if surcharges/rounding apply) |
| `currencyCode` | String | APPROVED / DECLINED | Currency used in authorization |
| `cardMaskedPan` | String | APPROVED | Masked PAN (e.g. `411111******1111`) |
| `cardBrand` | String | APPROVED | Card brand/network (e.g. `VISA`, `MASTERCARD`) |
| `cardHolderName` | String | APPROVED | Cardholder name (if available) |
| `cardExpiry` | String | APPROVED | Expiry in `MMYY` or `MM/YYYY` format |
| `signatureRequired` | Boolean | Optional | Indicates if a signature is required |
| `receiptDataMerchantCopy` | String | Optional | Formatted text or JSON for the merchant receipt |
| `receiptDataCustomerCopy` | String | Optional | Formatted text or JSON for the customer receipt |
| `errorCode` | String | ERROR / TIMEOUT / CANCELLED | Machine-readable error/cancel code |
| `errorDescription` | String | ERROR / TIMEOUT / CANCELLED | Human-readable error description |
| `callbackMeta` | String | Optional | Echo of the request's `callbackMeta` (if provided) |


### Example: Dime Returning an Approved Result (Kotlin)

```kotlin
private fun returnApprovedResult() {
    val data = Intent().apply {
        putExtra("transactionId", request.transactionId)
        putExtra("orderId", request.orderId)
        putExtra("status", "APPROVED")
        putExtra("transactionIdHost", hostTxnId)
        putExtra("authCode", authCode)
        putExtra("responseCode", "00")
        putExtra("responseMessage", "Approved")
        putExtra("amount", approvedAmount)
        putExtra("currencyCode", request.currencyCode)
        putExtra("cardMaskedPan", maskedPan)
        putExtra("cardBrand", cardBrand)
        putExtra("cardHolderName", cardHolderName)
        putExtra("cardExpiry", cardExpiry)
        putExtra("signatureRequired", false)
        putExtra("receiptDataMerchantCopy", merchantReceiptText)
        putExtra("receiptDataCustomerCopy", customerReceiptText)
        putExtra("callbackMeta", request.callbackMeta)
    }
    setResult(Activity.RESULT_OK, data)
    finish()
}
```

## 6. Handling the Result in Your App

When Dime finishes processing, your app receives the result in `onActivityResult`:

```kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == REQ_CODE_CARD_PAYMENT) {
        if (data == null) {
            handlePaymentError("NO_DATA", "No result data from Dime")
            return
        }

        val status = data.getStringExtra("status") ?: "ERROR"
        val txnId = data.getStringExtra("transactionId")

        when {
            resultCode == Activity.RESULT_OK && status == "APPROVED" -> {
                val hostTxnId = data.getStringExtra("transactionIdHost")
                val authCode = data.getStringExtra("authCode")
                val amount = data.getDoubleExtra("amount", 0.0)
                val currency = data.getStringExtra("currencyCode")
                handlePaymentApproved(txnId, hostTxnId, authCode, amount, currency, data.extras)
            }
            resultCode == Activity.RESULT_OK && status == "DECLINED" -> {
                val message = data.getStringExtra("responseMessage") ?: "Transaction declined"
                handlePaymentDeclined(txnId, message, data.extras)
            }
            else -> {
                val errorCode = data.getStringExtra("errorCode") ?: "UNKNOWN"
                val errorDesc = data.getStringExtra("errorDescription") ?: "Transaction cancelled or failed"
                handlePaymentError(errorCode, errorDesc)
            }
        }
    }
}
```

## 7. Refund Intent

Your app can initiate a refund for a previously approved transaction by launching Dime with a refund intent. Dime uses the `transactionIdHost` to process the refund on the host/acquirer.

### Refund Intent Metadata

| Property | Value |
|  --- | --- |
| **Action** | `net.dime.paxapp.ACTION_PROCESS_REFUND` |
| **Target Activity** | `net.dime.paxapp.ui.RefundActivity` |
| **Launch Type** | Explicit intent |


### Refund Request Properties (Your App → Dime)

| Key | Type | Required | Description |
|  --- | --- | --- | --- |
| `orderId` | String | Yes | Original order reference from the payment transaction |
| `transactionIdHost` | String | Yes | Dime transaction ID returned from the original approved payment — used to initiate the refund on the host |
| `amount` | Double | No | Amount to display on the refund screen |
| `currencyCode` | String | No | Currency code for display (falls back to terminal config if not provided) |
| `description` | String | No | Description to show on the refund screen (defaults to "Refund") |


### Example: Launching a Refund (Kotlin)

```kotlin
private const val REQ_CODE_REFUND = 1002

fun startRefund(context: Activity, orderId: String, transactionIdHost: String, amount: Double? = null, currencyCode: String? = null, description: String? = null) {
    val intent = Intent().apply {
        setClassName(
            BuildConfig.DIME_PACKAGE,
            "net.dime.paxapp.ui.RefundActivity"
        )
        action = "net.dime.paxapp.ACTION_PROCESS_REFUND"

        putExtra("orderId", orderId)
        putExtra("transactionIdHost", transactionIdHost)
        amount?.let { putExtra("amount", it) }
        currencyCode?.let { putExtra("currencyCode", it) }
        description?.let { putExtra("description", it) }
    }
    context.startActivityForResult(intent, REQ_CODE_REFUND)
}
```

### Refund Response Properties (Dime → Your App)

Dime returns a result via `setResult(resultCode, intent)` using the same result code and status conventions as the payment flow.

| Key | Type | Present When | Description |
|  --- | --- | --- | --- |
| `orderId` | String | Always | Echo of the original `orderId` |
| `transactionIdHost` | String | Always | Echo of the original `transactionIdHost` |
| `status` | String | Always | One of: `APPROVED`, `DECLINED`, `CANCELLED`, `TIMEOUT`, `ERROR` |
| `responseCode` | String | APPROVED / DECLINED | ISO-like or gateway-specific response code |
| `responseMessage` | String | Always | Human-readable description of the outcome |
| `errorCode` | String | ERROR / TIMEOUT / CANCELLED | Machine-readable error/cancel code |
| `errorDescription` | String | ERROR / TIMEOUT / CANCELLED | Human-readable error description |


### Example: Handling the Refund Result (Kotlin)

```kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == REQ_CODE_REFUND) {
        if (data == null) {
            handleRefundError("NO_DATA", "No result data from Dime")
            return
        }

        val status = data.getStringExtra("status") ?: "ERROR"
        val orderId = data.getStringExtra("orderId")

        when {
            resultCode == Activity.RESULT_OK && status == "APPROVED" -> {
                handleRefundApproved(orderId, data.extras)
            }
            resultCode == Activity.RESULT_OK && status == "DECLINED" -> {
                val message = data.getStringExtra("responseMessage") ?: "Refund declined"
                handleRefundDeclined(orderId, message, data.extras)
            }
            else -> {
                val errorCode = data.getStringExtra("errorCode") ?: "UNKNOWN"
                val errorDesc = data.getStringExtra("errorDescription") ?: "Refund cancelled or failed"
                handleRefundError(errorCode, errorDesc)
            }
        }
    }
}
```

## 8. Error & Edge Case Handling

### Dime App Not Installed

Your app should catch `ActivityNotFoundException` and show an appropriate error (e.g. *"Dime payment app is not available. Please install the Dime app to process card payments."*).

### User Cancels in Dime

Dime returns:

- `resultCode` = `RESULT_CANCELED`
- `status` = `CANCELLED`
- `errorCode` = `USER_CANCELLED`
- `errorDescription` = `"User cancelled before card was processed"`


Your app should show a non-error message and allow the user to retry or select an alternate payment method.

### Timeout / Network Failure

Dime returns:

- `resultCode` = `RESULT_OK`
- `status` = `TIMEOUT` or `ERROR`
- `errorCode` and `errorDescription` populated appropriately


Your app should decide whether to retry or mark the transaction as pending based on your reconciliation rules.

### Partial or Invalid Payload

Your app should validate required fields on the response (`transactionId`, `status`). If invalid, treat it as an error and escalate accordingly (log, notify your backend).

## 9. Pre-Authorization & Capture Flow

This section describes the two-step payment flow for B2B use cases where funds must be held before a service is delivered, and charged only after it is completed.

### 9.1 Overview

A standard payment authorizes and captures funds in a single step. The pre-authorization flow splits this into two distinct steps:

| Step | Description |
|  --- | --- |
| **Authorization (Hold)** | Freezes the funds on the customer's card. The customer sees a pending charge, but no money moves. |
| **Capture (Charge)** | Settles the frozen funds once the service is complete. |
| **Void (Release)** | Releases the hold if the service is cancelled before capture. Faster and cheaper than a refund. |


**Why use this for B2B?**

- Verify the customer has a valid card and sufficient balance before delivering the service.
- Cancel easily with a void if the service does not proceed.
- Capture the exact final amount when the cost is confirmed (e.g. hourly usage, variable fees).


### 9.2 Authorization (Hold)

Launch Dime with `autoCapture` set to `false`. Dime will authorize the card and hold the funds without capturing.

#### Request Properties (Additional)

All standard request properties from Section 4 apply. The following additional property controls pre-authorization:

| Key | Type | Required | Description |
|  --- | --- | --- | --- |
| `autoCapture` | Boolean | No | Defaults to `true`. Set to `false` to authorize only (hold funds, do not capture). |


#### Example: Launching a Pre-Authorization (Kotlin)

```kotlin
fun startPreAuth(context: Activity, request: CardPaymentRequest) {
    val intent = Intent().apply {
        setClassName(
            BuildConfig.DIME_PACKAGE,
            "net.dime.paxapp.ui.CardPaymentActivity"
        )
        action = "net.dime.paxapp.ACTION_PROCESS_CARD_PAYMENT"

        putExtra("transactionId", request.transactionId)
        putExtra("amount", request.amount)
        putExtra("currencyCode", request.currencyCode)
        putExtra("merchantId", request.merchantId)
        putExtra("autoCapture", false) // Hold funds, do not charge
    }
    context.startActivityForResult(intent, REQ_CODE_CARD_PAYMENT)
}
```

#### Response

A successful pre-authorization returns `RESULT_OK` with `status = "AUTHORIZED"`. The response includes two identifiers that you must store:

| Key | Type | Description |
|  --- | --- | --- |
| `status` | String | `AUTHORIZED` — funds are held, not captured |
| `transactionIdHost` | String | Dime transaction ID. Required for void and backend API capture. |
| `transactionIdentifier` | String | mPOS transaction identifier. Required for on-device capture via `CaptureActivity`. |
| `autoCapture` | Boolean | `false` — confirms this was a pre-auth |
| `authCode` | String | Authorization code from the issuer |
| `amount` | Double | Authorized amount |
| `currencyCode` | String | Currency used |
| `cardMaskedPan` | String | Masked card number |
| `cardBrand` | String | Card brand (e.g. `VISA`, `MASTERCARD`) |
| `responseCode` | String | `"00"` for successful authorization |
| `responseMessage` | String | `"Authorized - pending capture"` |


> **Important — Two IDs:** A pre-auth response returns both `transactionIdHost` (the Dime backend ID) and `transactionIdentifier` (the mPOS SDK ID). You need `transactionIdHost` for voiding and backend API capture, and `transactionIdentifier` for on-device capture. Store both.


#### Example: Handling the Pre-Auth Response (Kotlin)

```kotlin
resultCode == Activity.RESULT_OK && status == "AUTHORIZED" -> {
    val transactionIdHost = data.getStringExtra("transactionIdHost")         // For void & backend capture
    val transactionIdentifier = data.getStringExtra("transactionIdentifier") // For on-device capture
    val amount = data.getDoubleExtra("amount", 0.0)
    val authCode = data.getStringExtra("authCode")

    // Store both IDs for later capture or void
    savePreAuth(transactionIdHost, transactionIdentifier, amount, authCode)
}
```

#### Backend Behaviour

The Dime App handles two backend calls automatically as part of the pre-authorization flow:

**1. Create Transaction** — called before launching the card capture screen:

```
POST /api/v1/pax-terminal-transactions
{ ..., "auto_capture": false }
```

When `auto_capture` is `false`, the backend registers the transaction as an authorization-only operation (`transaction_type: AUTHORIZATION`) and stores the flag in the transaction metadata. The transaction is created with status `PENDING`.

**2. Update Transaction** — called after the terminal returns a successful authorization:

```
PUT /api/v1/pax-terminal-transactions/:id
{ "status": "AUTHORISED", "external_transaction_id": "<acquirer_reference>" }
```

The transaction is set to `AUTHORISED` on the backend. The associated order remains `PENDING` until capture.

> **Authorization Window:** Holds expire after **7 days**. Capture or void before this window closes.


### 9.3 Capture (Charge)

Once the service is complete, trigger a capture to settle the held funds.

There are two ways to capture:

| Method | When to Use |
|  --- | --- |
| **On-device capture** (via `CaptureActivity` intent) | When the capture is triggered from an app on the same device as Dime |
| **Backend API capture** (via `POST /capture`) | When the capture is triggered from a server, dashboard, or different device |


#### 9.3.1 On-Device Capture (via Intent)

Launch Dime's `CaptureActivity` with the `transactionIdentifier` from the pre-auth response.

##### Capture Intent Metadata

| Property | Value |
|  --- | --- |
| **Action** | `net.dime.paxapp.ACTION_CAPTURE_TRANSACTION` |
| **Target Activity** | `net.dime.paxapp.ui.CaptureActivity` |
| **Launch Type** | Explicit intent |


##### Capture Request Properties

| Key | Type | Required | Description |
|  --- | --- | --- | --- |
| `transactionIdentifier` | String | Yes | mPOS transaction identifier from the pre-auth response |
| `amount` | String | No | Capture amount as a string (e.g. `"95.00"`). If omitted, the full authorized amount is captured. Must be ≤ authorized amount. |
| `currencyCode` | String | No | ISO 4217 currency code (e.g. `TTD`). Required if `amount` is provided. |


##### Example: Launching a Capture (Kotlin)

```kotlin
private const val REQ_CODE_CAPTURE = 1003

fun startCapture(context: Activity, transactionIdentifier: String, amount: String? = null, currencyCode: String? = null) {
    val intent = Intent().apply {
        setClassName(
            BuildConfig.DIME_PACKAGE,
            "net.dime.paxapp.ui.CaptureActivity"
        )
        action = "net.dime.paxapp.ACTION_CAPTURE_TRANSACTION"

        putExtra("transactionIdentifier", transactionIdentifier)
        amount?.let { putExtra("amount", it) }
        currencyCode?.let { putExtra("currencyCode", it) }
    }
    context.startActivityForResult(intent, REQ_CODE_CAPTURE)
}
```

##### Capture Response Properties

| Key | Type | Present When | Description |
|  --- | --- | --- | --- |
| `transactionIdentifier` | String | Always | Echo of the mPOS transaction identifier |
| `status` | String | Always | `CAPTURED` on success, `ERROR` on failure |
| `responseCode` | String | CAPTURED | `"00"` for successful capture |
| `responseMessage` | String | Always | Human-readable description |
| `amount` | String | CAPTURED | Captured amount (if partial capture was requested) |
| `currencyCode` | String | CAPTURED | Currency used |
| `errorCode` | String | ERROR | Machine-readable error code (e.g. `CAPTURE_FAILED`, `PROCESSING_ERROR`, `INVALID_REQUEST`) |
| `errorDescription` | String | ERROR | Human-readable error description |


##### Example: Handling the Capture Result (Kotlin)

```kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == REQ_CODE_CAPTURE) {
        if (data == null) {
            handleCaptureError("NO_DATA", "No result data from Dime")
            return
        }

        val status = data.getStringExtra("status") ?: "ERROR"

        when {
            resultCode == Activity.RESULT_OK && status == "CAPTURED" -> {
                val amount = data.getStringExtra("amount")
                handleCaptureSuccess(amount)
            }
            else -> {
                val errorCode = data.getStringExtra("errorCode") ?: "UNKNOWN"
                val errorDesc = data.getStringExtra("errorDescription") ?: "Capture failed"
                handleCaptureError(errorCode, errorDesc)
            }
        }
    }
}
```

#### 9.3.2 Backend API Capture

If the capture is triggered from a server or dashboard (not from an app on the same device), call the Dime backend API directly.

##### Endpoint

```
POST /api/v1/pax-terminal-transactions/capture
```

##### Headers

Standard Dime request headers apply: `Authorization`, `dime-id-x`, `timestamp`, `x-client-type`. The payload must be JWT-encoded in the standard `{ "dq": ... }` format.

##### Request Payload

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `transaction_id` | String | Yes | The Dime transaction ID (`transactionIdHost` from the pre-auth response) |
| `amount` | String | No | Capture amount. If omitted, the full authorized amount is captured. Must be ≤ authorized amount. |


##### Example Request

```json
{
  "transaction_id": "txn_abc123",
  "amount": "95.00"
}
```

##### Success Response

```json
{
  "status": true,
  "message": "Transaction captured successfully",
  "data": {
    "transaction_id": "uuid",
    "status": "COMPLETE",
    "captured_amount": 95.00,
    "currency": "TTD"
  }
}
```

##### Partial Capture

If the final cost is less than the authorized amount, provide the lower `amount` in the capture request. The remaining hold is released automatically. The captured amount must be greater than zero.

##### Error Responses

| Reason | Message |
|  --- | --- |
| Transaction not found | `Transaction not found` |
| Not in `AUTHORISED` status | `Transaction cannot be captured. Current status: <status>` |
| Authorization expired | `Authorization has expired. Transaction was authorized on <date>` |
| Capture amount exceeds authorized | `Capture amount (<n>) exceeds authorized amount (<n>)` |
| Capture amount is zero or negative | `Capture amount must be greater than zero` |


### 9.4 Void (Release Hold)

If the service is cancelled before capture, void the authorization to release the hold immediately. A void is preferred over a refund because no funds were ever moved.

There are two ways to void:

| Method | When to Use |
|  --- | --- |
| **On-device void** (via `VoidAuthActivity` intent) | When the void is triggered from an app on the same device as Dime |
| **Backend API void** (via `POST /void`) | When the void is triggered from a server, dashboard, or different device |


#### 9.4.1 On-Device Void (via Intent)

##### Void Intent Metadata

| Property | Value |
|  --- | --- |
| **Action** | `net.dime.paxapp.ACTION_VOID_AUTHORIZATION` |
| **Target Activity** | `net.dime.paxapp.ui.VoidAuthActivity` |
| **Launch Type** | Explicit intent |


##### Void Request Properties (Your App → Dime)

| Key | Type | Required | Description |
|  --- | --- | --- | --- |
| `transactionId` | String | Yes | The Dime transaction ID (`transactionIdHost` from the pre-auth response) |
| `orderId` | String | No | Original order reference (echoed in the response) |
| `amount` | Double | No | Original authorized amount (for display only) |
| `currencyCode` | String | No | Currency code (for display only) |


##### Example: Launching a Void (Kotlin)

```kotlin
private const val REQ_CODE_VOID = 1004

fun startVoid(context: Activity, transactionIdHost: String, orderId: String? = null, amount: Double? = null, currencyCode: String? = null) {
    val intent = Intent().apply {
        setClassName(
            BuildConfig.DIME_PACKAGE,
            "net.dime.paxapp.ui.VoidAuthActivity"
        )
        action = "net.dime.paxapp.ACTION_VOID_AUTHORIZATION"

        putExtra("transactionId", transactionIdHost)
        orderId?.let { putExtra("orderId", it) }
        amount?.let { putExtra("amount", it) }
        currencyCode?.let { putExtra("currencyCode", it) }
    }
    context.startActivityForResult(intent, REQ_CODE_VOID)
}
```

##### Void Response Properties (Dime → Your App)

| Key | Type | Present When | Description |
|  --- | --- | --- | --- |
| `transactionId` | String | Always | Echo of the transaction ID |
| `orderId` | String | Always | Echo of the order ID |
| `status` | String | Always | `VOID` on success, `DECLINED` on failure, `ERROR` on error, `CANCELLED` if user backs out |
| `responseCode` | String | VOID / DECLINED | `"00"` for success, `"05"` for declined |
| `responseMessage` | String | Always | Human-readable description — includes the backend failure reason on decline (e.g. `"Authorization has expired"`) |
| `errorCode` | String | ERROR / CANCELLED | Machine-readable error code |
| `errorDescription` | String | ERROR / CANCELLED | Human-readable error description |


##### Example: Handling the Void Result (Kotlin)

```kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == REQ_CODE_VOID) {
        if (data == null) {
            handleVoidError("NO_DATA", "No result data from Dime")
            return
        }

        val status = data.getStringExtra("status") ?: "ERROR"

        when {
            resultCode == Activity.RESULT_OK && status == "VOID" -> {
                handleVoidSuccess(data.extras)
            }
            resultCode == Activity.RESULT_OK && status == "DECLINED" -> {
                val message = data.getStringExtra("responseMessage") ?: "Void declined"
                handleVoidDeclined(message)
            }
            else -> {
                val errorCode = data.getStringExtra("errorCode") ?: "UNKNOWN"
                val errorDesc = data.getStringExtra("errorDescription") ?: "Void cancelled or failed"
                handleVoidError(errorCode, errorDesc)
            }
        }
    }
}
```

#### 9.4.2 Backend API Void

##### Endpoint

```
POST /api/v1/pax-terminal-transactions/void
```

##### Headers

Standard Dime request headers apply: `Authorization`, `dime-id-x`, `timestamp`, `x-client-type`. The payload must be JWT-encoded in the standard `{ "dq": ... }` format.

##### Request Payload

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `transaction_id` | String | Yes | The Dime transaction ID (`transactionIdHost` from the pre-auth response) |


##### Example Request

```json
{
  "transaction_id": "txn_abc123"
}
```

##### Success Response

```json
{
  "status": true,
  "message": "Authorization voided successfully",
  "data": {
    "transaction_id": "uuid",
    "status": "VOID",
    "currency": "TTD"
  }
}
```

##### Error Responses

| Reason | Message |
|  --- | --- |
| Transaction not found | `Transaction not found` |
| Not in `AUTHORISED` status | `Transaction cannot be voided. Current status: <status>` |
| Authorization expired (>7 days) | `Authorization has expired. Transaction was authorized on <date>` |


> **Note:** A void is not a refund. No funds were moved during authorization, so no refund flow is needed. If the transaction has already been captured (`COMPLETE`), use the refund flow in Section 7 instead.


### 9.5 Transaction Status Flow

```
PENDING → AUTHORISED → COMPLETE   (capture succeeded)
                     → VOID       (voided before capture)
                     → FAILED     (capture failed)
```

### 9.6 Key Identifiers Reference

A pre-authorization response returns two distinct identifiers. Understanding which to use for each operation is critical:

| Identifier | Source | Use For |
|  --- | --- | --- |
| `transactionIdHost` | Dime backend transaction ID | Void (via `VoidAuthActivity` or backend API), Backend API capture |
| `transactionIdentifier` | mPOS SDK transaction identifier | On-device capture (via `CaptureActivity`) |


Both are returned in the pre-auth response. Store both when `status == "AUTHORIZED"`.