---
updatedAt: 2026-08-24T07:59:02.000Z
agentTools:
  projectIndex: https://developer.eftcorp.com/llms.txt
---

# VAS Developer Guide

Everything a till/POS integrator needs to build the eSocket.POS VAS interface: message flow, field rules, response parsing, receipt printing, and error handling

This guide covers Value-Added Services (VAS) transactions over the eSocket.POS interface — non-card purchases of utility tokens, telephone prepay, and bill payments, sent using the `Esp:Merchandise` element (not `Esp:Transaction`, which handles card PURCHASE). The worked example throughout is a ZESA prepaid electricity token purchase — the only VAS product implemented in this release. The same message flow and field rules are documented for telephone prepay, water, and bill payment products for completeness, but see the release scope note below before building against any of them.

> 📘 Scope note
>
> This guide does not repeat material already covered in the core eSocket.POS XML Interface Quick Reference Guide (message framing, `Esp:Admin` INIT/CLOSE, `Esp:Error`, general event/callback handling). Read that guide first if you have not integrated a card PURCHASE transaction before.

> 📘 Release scope
>
> Only `ProductType=ELECTRICITY` (ZESA) is implemented and supported in this release of the integration. WATER, TELEPHONE PREPAY, and BILL\_PAYMENT are documented below for completeness and future extensibility, but must not be built against in this release — their field mappings (for example, the open question on carrying an account/subscriber reference in [The MeterNumber omission rule](#the-meternumber-omission-rule)) have not been confirmed for production use.

## Supported VAS product types

Selecting a product category fully determines `ExtendedTransactionType` (defaulted, not hardcoded — see the field reference below), `MerchantName`, and `ProductName` — the latter two are not retailer-editable fields.

| Product category (till selection) | ExtendedTransactionType | Default MerchantName | Default ProductName | Typical use case                        |
| --------------------------------- | ----------------------- | -------------------- | ------------------- | --------------------------------------- |
| `ELECTRICITY`                     | 9000                    | ZETDC                | ZETDC\_PREPAID      | ZESA prepaid electricity token purchase |
| `WATER`                           | 9000                    | HARARE               | HARARE              | Municipal prepaid water token purchase  |
| `TELEPHONE PREPAY`                | 9100                    | ECONET               | ECONET\_AIRTIME     | Mobile network prepaid airtime top-up   |
| `BILL_PAYMENT`                    | 9000                    | DSTV                 | DSTV                | Subscription / bill account payment     |

The eSocket.POS developer guide documents exactly three `ProductType` values: `TELEPHONE PREPAY`, `ELECTRICITY`, and `OTHER`. Electricity and Telephone Prepay send that same value as `ProductType`. Water and Bill Payment have no `ProductType` value of their own — the reference implementation sends `ProductType="OTHER"` for both, and relies on `ProductId`, `MerchantName`, and `ProductName` to tell them apart. `ExtendedTransactionType` defaults to 9100 for Telephone Prepay and 9000 for everything else, but remains a configurable field (see the field reference below) — it is not hardcoded.

## Prerequisites

* eSocket.POS installed, configured, and reachable from the till — the reference implementation connects to `127.0.0.1:23001` over TCP. Confirm the actual host/port for your site before going live.
* An initialised terminal (`Esp:Admin` INIT) — see the core XML Interface Quick Reference Guide, section 8. VAS transactions use the same initialised TCP session as card transactions; there is no separate VAS initialisation.
* A Terminal ID provisioned in the eSocket.POS/Postilion database and enabled for the VAS product(s) you intend to sell.
* Valid `ProductId` values for each VAS product you will offer, issued by EFT Corp or the upstream VAS aggregator during onboarding (see the field reference below) — `ProductId` is not a value you invent.
* Access to the eSocket.POS developer's guide and the Postilion Merchandise interface specification for the authoritative upstream field definitions. This guide documents the subset exercised by the reference test harness.

## Message overview

A VAS purchase is always a three-phase exchange, each phase a separate request/response pair over the same TCP session used for INIT:

1. **REQUEST** — validates the meter/account and amount, and returns customer/account details for the cashier to confirm before money moves.
2. **PROCURE** — commits the purchase. On success (`ResponseCode` `00`) this is where the token (or a narrative result, for non-token products) is returned.
3. **REVERSE** — reverses a PROCURE that must be backed out (for example, a printer failure after purchase, or a POS timeout with no confirmed outcome). Only ever sent against a purchase that was successfully procured.

> 📘 Terminology
>
> eSocket.POS documents two valid `Esp:Merchandise` flows for a purchase: a single-step Procure flow (`Type="PROCURE"`, no separate confirmation message) and a two-step legacy Request/Confirm flow (`Type="REQUEST"`, then `Type="CONFIRM"`). This guide and the reference implementation use the single-step Request/Procure flow throughout — treat any Request/Confirm-based documentation or examples you come across as describing that other, legacy flow, not this one.
>
> Whichever flow is used, the reversal phase is `Type="REVERSE"` — this matches eSocket.POS's own `Type` definition and its worked reversal examples. Some copies of the core XML Interface Quick Reference Guide's timeout-handling table instead show "REVERSAL" for this row; that conflicts with the documented `Type` values and doesn't match any real example message, so treat it as an error in that table, not an alternative literal to send.
>
> For ZETDC specifically: the interface itself allows sending a REVERSE here — it isn't rejected — but ZESA discourages sending reversals for a completed token purchase and doesn't action them. See [VAS REVERSE](#vas-reverse) and [Error handling and edge cases](#error-handling-and-edge-cases) for the manual-intervention procedure to use instead.

**Sequence for a successful purchase:**

1. POS sends `Esp:Merchandise Type="REQUEST"` to eSocket.POS, which forwards it upstream to Postilion.
2. Postilion returns the customer name and billing currency; the cashier confirms these with the customer.
3. POS sends `Esp:Merchandise Type="PROCURE"` with the same RRN.
4. Postilion returns the token (or narrative) with `ActionCode="APPROVE"` and `ResponseCode="00"`.
5. The till prints the receipt/token.
6. Only if the PROCURE must be backed out: POS sends `Esp:Merchandise Type="REVERSE"` with the same RRN, amount, and product/meter/user values as the PROCURE.

### Framing

VAS messages use the same TCP length-prefixed framing as every other eSocket.POS message: a 2-byte big-endian length header for messages under 65,535 bytes, or `0xFF 0xFF` followed by a 4-byte length for larger messages. See `EspFrame.Encode` in the reference implementation, and section 7 of the core XML Interface Quick Reference Guide.

## Field reference — hardcoded, configurable, and dynamic fields

Every attribute in a VAS message falls into one of three categories. Getting this classification right is the single most important part of a correct integration:

* **Hardcoded** — a literal constant your integration should never make user-editable. Wrong values here typically cause silent misrouting upstream, not a clean decline.
* **Configurable** — set once per deployment (per terminal/till), not per transaction. Store in your POS configuration, not in transaction-entry code.
* **Dynamic** — must be supplied fresh by your till/POS integration for every transaction, either from cashier/customer input or generated at send-time.

| Attribute / Element                  | Category                                     | Example value                                          | Notes                                                                                                                                                                                                                                            |
| ------------------------------------ | -------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Esp:Interface` `Version`            | Hardcoded                                    | `1.0`                                                  | Fixed protocol version.                                                                                                                                                                                                                          |
| `xmlns:Esp` namespace                | Hardcoded                                    | `http://www.mosaicsoftware.com/Postilion/eSocket.POS/` | Fixed.                                                                                                                                                                                                                                           |
| `Type` (REQUEST / PROCURE / REVERSE) | Hardcoded                                    | `REQUEST`                                              | One literal per phase; never user input.                                                                                                                                                                                                         |
| `ExtendedTransactionType`            | Configurable, defaulted per product category | `9000` / `9100`                                        | Pre-filled per product category (see the product types table above) but left as a plain editable field — not hardcoded into the outgoing message.                                                                                                |
| `ApiProcessingCode` (PROCURE only)   | Hardcoded                                    | `U50000`                                               | Same literal for every PROCURE regardless of product type.                                                                                                                                                                                       |
| `MerchantName` (PROCURE only)        | Hardcoded per ProductType                    | `ZETDC`                                                | Fixed lookup, not user-editable in the reference UI.                                                                                                                                                                                             |
| `ProductName` (PROCURE only)         | Hardcoded per ProductType                    | `ZETDC_PREPAID`                                        | Fixed lookup, paired with `MerchantName`.                                                                                                                                                                                                        |
| `TerminalId`                         | Configurable                                 | `TEST0001`                                             | Loaded once from eSocket.POS config, or entered once per session; same value the till already uses for card PURCHASE.                                                                                                                            |
| `ProductId`                          | Configurable (per product)                   | `80000129`                                             | Assigned by EFT Corp/upstream aggregator per VAS product offered at onboarding — not invented by the retailer. Pre-fill per `ProductType` in your POS config; do not free-type in production.                                                    |
| `ProductType`                        | Configurable (selected)                      | `ELECTRICITY`                                          | Chosen from the till's product category list above. eSocket.POS itself only recognises `TELEPHONE PREPAY`, `ELECTRICITY`, and `OTHER` as `ProductType` values; Water and Bill Payment categories are sent as `ProductType="OTHER"`.              |
| `CurrencyCode`                       | Configurable / dynamic (PROCURE only)        | `840`                                                  | ISO 4217 numeric, from the till's active currency — see the currency table below. Sent as a `PosStructuredData` element on PROCURE, not a top-level attribute — see [CurrencyCode on the PROCURE message](#currencycode-on-the-procure-message). |
| `TransactionAmount`                  | Dynamic                                      | `100`                                                  | Minor denomination units (cents). Sale amount entered by the cashier.                                                                                                                                                                            |
| `UserId`                             | Dynamic                                      | `263773176741`                                         | Subscriber/customer reference — e.g. mobile number for telephone prepay, account holder reference for bills.                                                                                                                                     |
| `MeterNumber` (`PosStructuredData`)  | Dynamic                                      | `14316800680`                                          | ONLY transmitted when `ProductType` = `ELECTRICITY` (case-insensitive) — see below.                                                                                                                                                              |
| `TransactionId` (STAN)               | Dynamic, generated                           | `260755`                                               | Generated per transaction by the POS — see the STAN/RRN section below for the uniqueness caveat.                                                                                                                                                 |
| `RetrievalRefNr` (RRN)               | Dynamic, generated                           | `260807143205`                                         | Generated once at REQUEST and threaded unchanged through PROCURE and REVERSE for the same purchase attempt.                                                                                                                                      |
| `DateTime`                           | Dynamic, generated                           | `0807143205`                                           | System clock, `MMddHHmmss`, regenerated fresh on every message — not user input, but not static either.                                                                                                                                          |

### The MeterNumber omission rule

> 📘 Important
>
> The reference implementation only emits the `<Esp:PosStructuredData Name="MeterNumber">` element when `ProductType` is `ELECTRICITY`. For `WATER`, `TELEPHONE PREPAY`, and `BILL_PAYMENT`, the meter/account number field is still required as UI input (so the cashier cannot submit a blank), but it is silently dropped and never sent in the XML. If your integration needs an account/meter/subscriber reference transmitted for those other product categories, use the `UserId` attribute instead: eSocket.POS documents `UserId` as identifying the user in a merchandise request generally, and specifically as the MSISDN (phone number) for telephone prepay. This is documented for completeness only — per the release scope above, `WATER`, `TELEPHONE PREPAY`, and `BILL_PAYMENT` are not implemented in this release, and no real example message populating `UserId` for a voucher/water/bill-payment purchase has been found to corroborate it. Confirm with EFT Corp before relying on it in a future release.

### STAN and RRN generation — build your own, do not copy the reference logic verbatim

The reference implementation generates the Transaction ID (STAN) as `DateTime.Now.ToString("ymmss")` and the RRN as `DateTime.Now.ToString("yyMMddHHmmss")`, both purely time-based with no persistence or collision check. This is adequate for a manual test tool sending one transaction at a time, but it is **not** a safe pattern for a production POS:

* The core XML Interface Quick Reference Guide requires Transaction IDs to be 6-digit numbers that do not begin with zero, and to be unique within the eSocket.POS transaction retention period. A single `"y"` format specifier does not guarantee a fixed 2-digit year component in all years, and a purely clock-derived value can collide if two transactions are sent within the same second.
* Implement a persisted, monotonically increasing (or otherwise collision-free) STAN generator per terminal, respecting the no-leading-zero rule, and re-use the exact original STAN/RRN pair when resending an in-progress VAS message (see [Response code 09](#response-code-09--asynchronous-in-progress) below).
* Generate the RRN once at REQUEST time and carry the same value through PROCURE and REVERSE for that purchase attempt — the upstream host needs this to correlate all three phases as one purchase. The reference implementation does this via a form-level field; your integration must persist this per in-flight transaction, not per UI session.

### Currency codes (ISO 4217 numeric)

| Currency | Code |
| -------- | ---- |
| USD      | 840  |
| ZWG      | 924  |
| ZMW      | 967  |
| ZAR      | 710  |
| MWK      | 454  |
| BWP      | 072  |
| ZIG      | 932  |
| GBP      | 826  |
| EUR      | 978  |

### CurrencyCode on the PROCURE message

`Esp:Merchandise` does not declare a `CurrencyCode` attribute. Adding one directly to the element causes the interface to reject the message outright with a Format Error, not a decline — `ResponseCode` `30`, `MessageReasonCode` `9791` ("Administrative response"), with the validator reporting `Attribute "CurrencyCode" must be declared for element type "Esp:Merchandise"`. Do not add `CurrencyCode` as a top-level attribute on `Esp:Merchandise`.

Currency is instead supplied as a `PosStructuredData` element on the PROCURE message: `<Esp:PosStructuredData Name="CurrencyCode" Value="{CODE}"/>`, using the ISO 4217 numeric code (see the currency table above) of the currency tendered by the customer. This is the mechanism used by the reference implementation — see the worked PROCURE example in [VAS PROCURE](#vas-procure).

This value should match the currency the meter/account is actually billed in, as returned by the host in the REQUEST response's `meterCurrency` structured data (see [Parsing the response](#parsing-the-response)). A mismatch between the tendered currency and the meter's billing currency is expected to be declined by the host — see the worked example below.

The reference implementation does not enforce this match before PROCURE: the tendered currency (selected by the operator) is sent as-is and is not compared against the REQUEST-returned `meterCurrency` before the purchase is allowed to proceed. Decide your own policy for production — lock the currency selector to `meterCurrency` once REQUEST returns it, or validate and block PROCURE on a mismatch before sending it.

`CurrencyCode` is not currently sent on REQUEST or REVERSE. REQUEST does not require it to succeed, and REVERSE is already documented as best-effort only for a merchant like ZETDC (see [VAS REVERSE](#vas-reverse)).

Example — a PROCURE declined for a currency mismatch:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise ActionCode="DECLINE" ResponseCode="05" RetrievalRefNr="260807143205"
    TerminalId="TEST0001" TransactionAmount="100" LocalDate="0807" LocalTime="143209" Type="PROCURE">
    <Esp:StructuredData Name="Narrative" Value="Invalid currency code / Currency mismatch"/>
  </Esp:Merchandise>
</Esp:Interface>
```

Treat a decline with this narrative as a currency-configuration problem to resolve before retrying, not a transient failure — confirm the tendered currency against the meter's `meterCurrency` first. See [Error handling and edge cases](#error-handling-and-edge-cases).

Currency for this integration is controlled entirely through the `CurrencyCode` `PosStructuredData` value described above — do not try to work around a currency-mismatch decline by selecting a different `ProductId` for the same product; `ProductId` is not used as a per-currency selector in this implementation.

## Message definitions

### VAS REQUEST

Sent first, for every VAS product. Validates the meter/account and returns customer details for cashier confirmation. No money moves at this phase.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise DateTime="{DATETIME}" ExtendedTransactionType="{EXT_TX_TYPE}"
    ProductId="{PRODUCT_ID}" ProductType="{PRODUCT_TYPE}"
    RetrievalRefNr="{RRN}" TerminalId="{TERMINAL_ID}"
    TransactionAmount="{AMOUNT_MINOR}" TransactionId="{TRANSACTION_ID}"
    UserId="{USER_ID}" Type="REQUEST">
    <Esp:PosStructuredData Name="MeterNumber" Value="{METER_NUMBER}"/> <!-- ELECTRICITY only -->
  </Esp:Merchandise>
</Esp:Interface>
```

Worked example — ZESA electricity meter, TEST/UAT values:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise DateTime="0807143205" ExtendedTransactionType="9000" ProductId="80000129"
    ProductType="ELECTRICITY" RetrievalRefNr="260807143205" TerminalId="TEST0001"
    TransactionAmount="100" TransactionId="614532" UserId="263773176741" Type="REQUEST">
    <Esp:PosStructuredData Name="MeterNumber" Value="14316800680"/>
  </Esp:Merchandise>
</Esp:Interface>
```

**Response — approved:** returns the account holder's name and billing currency for the cashier to confirm before proceeding to PROCURE.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise ActionCode="APPROVE" ResponseCode="00" RetrievalRefNr="260807143205"
    TerminalId="TEST0001" TransactionAmount="100" Type="REQUEST">
    <Esp:StructuredData Name="customerName" Value="J MOYO"/>
    <Esp:StructuredData Name="meterCurrency" Value="USD"/>
  </Esp:Merchandise>
</Esp:Interface>
```

### VAS PROCURE

Commits the purchase using the same RRN, `TransactionId`, `ProductId`, `ProductType`, `UserId`, `MeterNumber`, and `TransactionAmount` as the REQUEST (a fresh STAN and `DateTime` are still generated for this second message). Adds four `PosStructuredData` elements that are only present at PROCURE.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise DateTime="{DATETIME}" ExtendedTransactionType="{EXT_TX_TYPE}"
    ProductId="{PRODUCT_ID}" ProductType="{PRODUCT_TYPE}"
    RetrievalRefNr="{RRN}" TerminalId="{TERMINAL_ID}"
    TransactionAmount="{AMOUNT_MINOR}" TransactionId="{TRANSACTION_ID}"
    UserId="{USER_ID}" Type="PROCURE">
    <Esp:PosStructuredData Name="MeterNumber" Value="{METER_NUMBER}"/> <!-- ELECTRICITY only -->
    <Esp:PosStructuredData Name="MerchantName" Value="{MERCHANT_NAME}"/> <!-- hardcoded per ProductType -->
    <Esp:PosStructuredData Name="ProductName" Value="{PRODUCT_NAME}"/> <!-- hardcoded per ProductType -->
    <Esp:PosStructuredData Name="ApiProcessingCode" Value="U50000"/> <!-- hardcoded, always -->
    <Esp:PosStructuredData Name="CurrencyCode" Value="{CURRENCY_CODE}"/> <!-- ISO 4217 numeric, tendered currency -->
  </Esp:Merchandise>
</Esp:Interface>
```

Worked example continued:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise DateTime="0807143207" ExtendedTransactionType="9000" ProductId="80000129"
    ProductType="ELECTRICITY" RetrievalRefNr="260807143205" TerminalId="TEST0001"
    TransactionAmount="100" TransactionId="614532" UserId="263773176741" Type="PROCURE">
    <Esp:PosStructuredData Name="MeterNumber" Value="14316800680"/>
    <Esp:PosStructuredData Name="MerchantName" Value="ZETDC"/>
    <Esp:PosStructuredData Name="ProductName" Value="ZETDC_PREPAID"/>
    <Esp:PosStructuredData Name="ApiProcessingCode" Value="U50000"/>
    <Esp:PosStructuredData Name="CurrencyCode" Value="840"/>
  </Esp:Merchandise>
</Esp:Interface>
```

**Response — approved:** the `Token` field is what your POS must decode and print (see [The ZESA / VAS token format](#the-zesa--vas-token-format)).

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise ActionCode="APPROVE" ResponseCode="00" RetrievalRefNr="260807143205"
    TerminalId="TEST0001" TransactionAmount="100" LocalDate="0807" LocalTime="143209" Type="PROCURE">
    <Esp:StructuredData Name="Token" Value="12345678901234567890|5.23|0.1912|123456789|85.00|15.00|0.15"/>
  </Esp:Merchandise>
</Esp:Interface>
```

> 📘 Non-token products
>
> For `TELEPHONE PREPAY`, `WATER`, and `BILL_PAYMENT`, the PROCURE response typically returns `Esp:StructuredData Name="Narrative"` instead of `"Token"` — a free-text confirmation string to print in place of a token block. Always check for `Token` first and fall back to `Narrative` (see [Parsing the response](#parsing-the-response) below).

### VAS REVERSE

Sent only against a purchase that has already been PROCUREd — never send a REVERSE for a REQUEST that was never PROCUREd. Repeats the same attribute set as the original PROCURE, with `Type="REVERSE"` and the same RRN/`TransactionId`/amount/product/meter/user values as that PROCURE.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/">
  <Esp:Merchandise DateTime="0807143301" ExtendedTransactionType="9000" ProductId="80000129"
    ProductType="ELECTRICITY" RetrievalRefNr="260807143205" TerminalId="TEST0001"
    TransactionAmount="100" TransactionId="614532" UserId="263773176741" Type="REVERSE">
    <Esp:PosStructuredData Name="MeterNumber" Value="14316800680"/>
  </Esp:Merchandise>
</Esp:Interface>
```

> 📘 Only reverse a confirmed PROCURE
>
> Retain the RRN, `TransactionId`, amount, `ProductId`, `ProductType`, `UserId`, and `MeterNumber` from a PROCURE only once that PROCURE has come back with `ResponseCode` `00` — do not offer a reversal option for a PROCURE that is still in-flight or that failed outright, since there is nothing upstream yet to reverse.

> 📘 REVERSE does not undo a completed ZETDC token purchase
>
> Once PROCURE succeeds, the token has already been drawn from the vendor's float and is not refunded by a subsequent REVERSE. Sending `Type="REVERSE"` is still a valid message to send — it confirms what the host itself returns — but do not rely on it to claw back a completed purchase; treat it as best-effort only. See [A REQUEST or PROCURE with an uncertain outcome](#a-request-or-procure-with-an-uncertain-outcome) for handling a REQUEST or PROCURE whose outcome is uncertain.

### A REQUEST or PROCURE with an uncertain outcome

`Esp:Merchandise` supports exactly three message types: REQUEST, PROCURE, and REVERSE. There is no message type for querying or resending an earlier REQUEST or PROCURE. If one of these times out, or the connection drops before a response is received, your terminal has no way to independently confirm what happened upstream.

> 📘 Do not retry, and do not rely on REVERSE
>
> Do not retry PROCURE for the same purchase in this situation — a token may already have been issued even though your terminal never received the response, and retrying risks drawing a second one. Do not rely on REVERSE to undo it either (see [VAS REVERSE](#vas-reverse) above). Hold the transaction as unconfirmed and escalate to EFT Corp for support in establishing the real outcome before concluding success, concluding failure, or refunding the customer.

## Parsing the response

### Top-level attributes

| Attribute                 | Meaning                                                                                                                      |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `ActionCode`              | `APPROVE` / `DECLINE` / `RETAIN` / `AUTH` — check this first.                                                                |
| `ResponseCode`            | ISO-style 2-digit response code. `00` = approved. See [Response code 09](#response-code-09--asynchronous-in-progress) below. |
| `Type`                    | Echoes the phase: `REQUEST` / `PROCURE` / `REVERSE`.                                                                         |
| `RetrievalRefNr`          | Echoed RRN — confirm it matches what you sent.                                                                               |
| `TransactionAmount`       | Echoed amount in minor units.                                                                                                |
| `LocalDate` + `LocalTime` | `MMdd` + `HHmmss` — concatenate to get a 10-digit `MMddHHmmss` timestamp for display formatting.                             |

### StructuredData / PosStructuredData — parse by walking every descendant

The fields that actually matter for VAS — the token, the customer name, the currency, the narrative — are not top-level attributes. They arrive as child elements, either `Esp:StructuredData` or `Esp:PosStructuredData`, each carrying a `Name`/`Value` attribute pair. Do not assume a fixed nesting depth or a fixed element name: walk every descendant element of the response and collect any node whose local name is `StructuredData` or `PosStructuredData` into a name→value lookup (case-insensitive names):

```
foreach (node in every descendant element of the Esp:Merchandise response)
  if node.LocalName in ("StructuredData", "PosStructuredData"):
    name = node.Attributes["Name"]
    value = node.Attributes["Value"]
    if name and value:
      result[name] = value  # case-insensitive key
```

| Structured data Name | Present at phase                      | Meaning                                                                                                     |
| -------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `customerName`       | REQUEST                               | Registered account holder name for the meter/account — show to the cashier for verification before PROCURE. |
| `meterCurrency`      | REQUEST                               | Currency the meter/account is billed in.                                                                    |
| `Token`              | PROCURE (success)                     | The ZESA/VAS token payload — see the token format below.                                                    |
| `Narrative`          | PROCURE (success, non-token products) | Free-text result to display/print when there is no token (telephone prepay, water, bill payment).           |

> 📘 Check Token before Narrative
>
> A response can in principle carry either field depending on the upstream product configuration — always check for `Token` first and only fall back to `Narrative` if no `Token` is present, regardless of `ProductType`.

### Response code 09 — asynchronous "in progress"

> 📘 Handle this explicitly
>
> `ResponseCode` `"09"` ("Request in progress") on a VAS REQUEST or PROCURE means the upstream VAS host has not yet returned a final outcome. Do not treat this as a decline and do not treat it as a success — the reference implementation resends the same message (same RRN/`TransactionId`) after 180 seconds. Build this wait-and-resend behaviour into your integration rather than surfacing an immediate error to the cashier.

For all other non-approved outcomes, follow the general `Esp:Merchandise` timeout/error handling rules in the core XML Interface Quick Reference Guide, and see [Error handling and edge cases](#error-handling-and-edge-cases) below for what to do when a REQUEST or PROCURE times out with no response at all.

### The ZESA / VAS token format

The raw value of the `Token` structured-data field is a 7-field, pipe-delimited string:

```
token_number|units|rate|receipt_number|net_amount|tax_amount|tax_rate
```

| Field # | Name             | Example                | Notes                                                                                                                                                                                                                                                                         |
| ------- | ---------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0       | `token_number`   | `12345678901234567890` | A 20-digit ZESA prepaid token in the standard format. Displayed grouped in blocks of 5 digits: `1234 5678 9012 3456 7890`.                                                                                                                                                    |
| 1       | `units`          | `5.23`                 | kWh purchased. Printed exactly as returned — no unit label is added automatically; add "kWh" yourself when formatting for display if the host does not include it.                                                                                                            |
| 2       | `rate`           | `0.1912`               | Tariff rate applied. Decoded, but not printed on the dedicated ZESA receipt (see [Receipt layout](#receipt-layout) below) — the eVending SMS Token Layout spec's mandated receipt lines don't include a standalone rate line. Available if your integration wants to show it. |
| 3       | `receipt_number` | `123456789`            | ZETDC/utility-side receipt reference — print this alongside the token; customers may need it for queries with the utility.                                                                                                                                                    |
| 4       | `net_amount`     | `85.00`                | Net electricity value before tax. Decoded and used — this is the source of the "Energy" line on the dedicated ZESA receipt.                                                                                                                                                   |
| 5       | `tax_amount`     | `15.00`                | Tax portion of the purchase. Decoded and used — this is the source of the "VAT" line on the dedicated ZESA receipt.                                                                                                                                                           |
| 6       | `tax_rate`       | `0.15`                 | Tax rate applied. Decoded, but not printed, for the same reason as field 2 (rate) above.                                                                                                                                                                                      |

> 📘 Multiple token records (Key Change Tokens)
>
> The `Token` field can contain more than one record, separated by `#` (a new Key Change Token is issued whenever the meter's encryption key group has expired). Split on `#` first, then split each record on `|` using the 7-field layout above. The **last** record's sub-fields are used for on-screen/receipt display (units, receipt number, net/tax amounts) — but every token number, in every record, must still be printed and loaded into the meter, in the order returned. When more than one record is present, print an explicit "Please input in the given order" instruction.

> 📘 Rate and tax rate are decoded but not printed
>
> All seven fields are decoded into a token record (token, units, rate, receipt number, net amount, tax amount, tax rate). Net amount and tax amount now feed the "Energy" and "VAT" lines on the dedicated ZESA receipt (see [Receipt layout](#receipt-layout) below). Rate and tax rate are parsed and available but still not printed, because the eVending spec's mandated receipt lines don't include a standalone rate or tax-rate line. If your market's fiscalisation rules require them, add your own line — don't assume this field selection is complete for your jurisdiction.

Reference decode logic:

```
tokenRecords = raw.Split('#')                     // one entry per Key Change Token
  .Select(entry => entry.Split('|'))
  .Where(p => p.Length >= 7)
  .Select(p => new VasTokenRecord(
    Token: p[0].Trim(), Units: p[1].Trim(), Rate: p[2].Trim(),
    ReceiptNumber: p[3].Trim(),
    NetAmountCents: ParseCents(p[4]), TaxAmountCents: ParseCents(p[5]),
    TaxRate: p[6].Trim()))

last = tokenRecords[^1]                            // last record's sub-fields used for display
tokenDisplay = FormatTokenGroups(last.Token)       // groups in 5s if exactly 20 digits, else raw

// Debt / REA totals use the same pipe layout, "#"-separated; sum the amount sub-field:
debtCents = SumPipeAmount(structuredData["Arrears"])       // description|receiptNr|amount|tax|balance
reaCents = SumPipeAmount(structuredData["FixedCharges"])   // description|receiptNr|amount|taxAmount|rate
```

### Arrears (Debt) and FixedCharges (REA Levy) structured data

ZESA electricity purchases can carry two further pipe-delimited structured-data fields alongside `Token`, both using the same `#`-separated multi-record convention as `Token` — sum the amount sub-field across every record for the receipt line:

| Structured data Name | Format                                                        | Notes                                                                                                                                                                                                  |
| -------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Arrears`            | `description\|receiptNumber\|amount\|tax\|outstandingBalance` | Outstanding debt recovered against this purchase, e.g. `Debt Recovery\|898766677\|800\|0\|7700`. Sum the amount sub-field for the receipt's "Debt" line — see [Receipt layout](#receipt-layout) below. |
| `FixedCharges`       | `description\|merchantReceiptNumber\|amount\|taxAmount\|rate` | REA (Rural Electrification Agency) levy, e.g. `RE Levy(6%)\|78\|150\|0\|0.06`. Sum the amount sub-field for the receipt's "REA" line — see [Receipt layout](#receipt-layout) below.                    |

> 📘 Don't assume Arrears/FixedCharges will be populated
>
> These two fields are not always present in the PROCURE response, even on a purchase that did involve a non-zero debt recovery or REA levy. Treat their absence as zero, not as an error, but be aware the printed "Debt"/"REA" receipt lines may under-report the true amount collected when this happens. Confirm the current behaviour of these two fields with EFT Corp before relying on them being consistently present.

## Printing the token / VAS receipt

### Receipt layout

The reference implementation prints a fixed-width, 40-column plain-text receipt suitable for a thermal POS printer. As of this revision, two distinct layouts exist, selected automatically by `ProductType` — see the routing note below.

**Generic VAS receipt** (`WATER`, `TELEPHONE PREPAY`, `BILL_PAYMENT`, and any Token-absent fallback):

| Line         | Content                                    | Source field                                                                                                                        |
| ------------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| Header       | Business name / "eSocket.POS" / date-time  | Local system clock — not from the response.                                                                                         |
| Title        | "VAS RECEIPT"                              | Static.                                                                                                                             |
| Terminal     | Terminal ID                                | Configurable field, see the field reference above.                                                                                  |
| Date/Time    | Transaction date/time                      | `LocalDate` + `LocalTime` from the PROCURE response, formatted `DD/MM/YYYY HH:mm:ss`.                                               |
| RRN          | Retrieval Reference Number                 | `RetrievalRefNr` echoed in the response.                                                                                            |
| Product line | `ProductType` (e.g. ELECTRICITY)           | The value you sent.                                                                                                                 |
| Product      | `ProductId`                                | The value you sent.                                                                                                                 |
| Meter/Acc    | Meter or account number                    | The value you sent (even for products where it was not transmitted on the wire — print what the cashier entered).                   |
| Customer     | Account holder name                        | Structured data `customerName`, if present (typically only returned at REQUEST — carry it forward to print on the PROCURE receipt). |
| Currency     | Billing currency                           | Structured data `meterCurrency`, if present.                                                                                        |
| Amount       | Sale amount, formatted with currency label | `TransactionAmount` ÷ 100, with the currency the till charged in.                                                                   |
| TOKEN block  | 20-digit token, grouped in 5s, centred     | Decoded from structured data `Token`, field 0 — see the token format above.                                                         |
| Units        | kWh purchased                              | Token field 1.                                                                                                                      |
| Receipt      | Utility-side receipt number                | Token field 3.                                                                                                                      |
| (or) Result  | Narrative text                             | Structured data `Narrative`, when there is no `Token` (non-electricity products).                                                   |
| Outcome      | `ActionCode`                               | APPROVE / DECLINE / etc.                                                                                                            |
| Response     | `ResponseCode` + description               | e.g. "00 - Approved or completed successfully".                                                                                     |
| Footer       | Thank-you / website                        | Static.                                                                                                                             |

**ZESA-specific receipt** (`ProductType = ELECTRICITY`):

| Line           | Content                                                     | Source field                                                                                                                                                                                          |
| -------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Header         | Business name / "eSocket.POS" / date-time                   | Local system clock — not from the response.                                                                                                                                                           |
| Title          | "ZESA TOKEN RECEIPT"                                        | Static.                                                                                                                                                                                               |
| Terminal       | Terminal ID                                                 | Configurable field, see the field reference above.                                                                                                                                                    |
| Date/Time      | Transaction date/time, `DD/MM/YY HH:mm`                     | A shorter format than the generic receipt's `DD/MM/YYYY HH:mm:ss`, per the eVending spec.                                                                                                             |
| RRN            | Retrieval Reference Number                                  | `RetrievalRefNr` echoed in the response.                                                                                                                                                              |
| TOKEN(S) block | Every token number, grouped in 5s, one per line, centred    | Every record from `Token`, in order — see the token format above. Followed by "Please input in the given order" when more than one record is present.                                                 |
| Meter          | Meter/account number                                        | The value you sent.                                                                                                                                                                                   |
| KwH            | Units purchased                                             | Last token record's units sub-field.                                                                                                                                                                  |
| Energy         | Net electricity value, in the Settlement Currency (SCC)     | Last token record's net amount.                                                                                                                                                                       |
| Debt           | Outstanding debt recovered, in SCC                          | Sum of `Arrears` amount sub-fields — `0.00` if the field is absent (see [Arrears (Debt) and FixedCharges (REA Levy) structured data](#arrears-debt-and-fixedcharges-rea-levy-structured-data) above). |
| REA            | Rural Electrification Agency levy, in SCC                   | Sum of `FixedCharges` amount sub-fields — `0.00` if the field is absent.                                                                                                                              |
| VAT            | Tax portion, in SCC                                         | Last token record's tax amount.                                                                                                                                                                       |
| Total Amt      | Energy + Debt + REA + VAT, in SCC                           | Computed — not echoed by the host.                                                                                                                                                                    |
| Amt Tend.      | Amount the customer paid, in the Transaction Currency (TCC) | The response's `TransactionAmount` if parseable, else falls back to Total Amt.                                                                                                                        |
| Receipt        | Utility-side receipt number                                 | Last token record's receipt number sub-field.                                                                                                                                                         |
| (or) Result    | Narrative text                                              | Structured data `Narrative`, when there is no `Token` at all (e.g. a declined PROCURE) — same fallback rule as the generic layout.                                                                    |
| Response       | `ResponseCode` + description                                | e.g. "00 - Approved or completed successfully".                                                                                                                                                       |
| Footer         | Thank-you / website                                         | Static.                                                                                                                                                                                               |

> 📘 Which layout prints, and how it's selected
>
> There is no separate "print token" button. The receipt builder inspects the last VAS product type: when it equals `ELECTRICITY` (case-insensitive) it builds the ZESA-specific receipt above; for every other VAS product type it builds the generic receipt. Both are reached through the same "Print Last Receipt" button.

> 📘 Settlement vs Transaction currency, and a CurrencyCode caveat
>
> Energy/Debt/REA/VAT/Total Amt are labelled with the Settlement Currency Code (SCC) — structured data `SettlementCurrencyCode` if present, otherwise the response's echoed `CurrencyCode`, otherwise the currency the operator selected at request time. Amt Tend. is labelled with the Transaction Currency Code (TCC) — the response's echoed `CurrencyCode`, otherwise the request-time currency.
>
> In practice, expect both to fall back to the request-time currency: a live capture confirmed `Esp:Merchandise` responses carry no `CurrencyCode` attribute at all — the response's `CurrencyCode` is always null for VAS responses. Do not rely on parsing a currency out of a VAS response.

### Sample printed output

**Generic VAS receipt sample:**

```
EFTCORP ZIMBABWE
eSocket.POS TEST
07/08/2026 14:32:09
VAS RECEIPT
----------------------------------------
Terminal : TEST0001
Date/Time : 07/08/2026 14:32:09
RRN : 260807143205
----------------------------------------
ELECTRICITY
Product : 80000129
Meter/Acc : 14316800680
Customer : J MOYO
Currency : USD
----------------------------------------
Amount: USD $ 1.00
----------------------------------------
TOKEN:
1234 5678 9012 3456 7890
Units : 5.23
Receipt : 123456789
----------------------------------------
Outcome : APPROVE
Response : 00 - Approved or completed successfully
----------------------------------------
** THANK YOU **
www.eftcorp.com
```

**ZESA-specific receipt sample** (`ProductType = ELECTRICITY`):

```
EFTCORP ZIMBABWE
eSocket.POS TEST
12/08/2026 14:57:29

ZESA TOKEN RECEIPT
----------------------------------------
Terminal : TEST0001
Date/Time : 12/08/26 14:57
RRN : 260807143205
----------------------------------------
TOKEN(S):
1234 5678 9012 3456 7890

Meter : 14316800680
KwH : 5.23
----------------------------------------
Energy : ZWG85.00
Debt : ZWG7.00
REA : ZWG1.50
VAT : ZWG15.00
----------------------------------------
Total Amt: ZWG108.50
Amt Tend.: ZWG108.50
----------------------------------------
Receipt : 123456789
Response : 00 - Approved or completed su
----------------------------------------

** THANK YOU **
www.eftcorp.com
```

### Layout mechanics (for a fixed-width thermal receipt)

* Column width: 40 characters (`W = 40` in the reference implementation) — adjust to your printer's actual character width, but keep it fixed for the whole receipt.
* Centred lines (header, title, token block, footer): pad text with spaces on both sides so it sits in the middle of the line; truncate rather than wrap if the text is longer than the column width.
* Label/value lines (Amount, Outcome, Response, Units, Receipt): left-align the label, right-align the value, padding the middle with spaces so the value's last character lands on the final column.
* Horizontal rules: a full line of `-` characters (40 dashes) used as section separators.
* Long value truncation: label/value formatting truncates — it does not wrap — once "label value" reaches the column width. This is confirmed behaviour, not a hypothetical: a full `ResponseCode` `00` description on the ZESA receipt's "Response :" line is cut to 40 characters (see the sample above). Keep this in mind if you add your own label/value lines with longer values.

### Output destinations

| Destination          | Mechanism                                                                                                                    | Notes                                                                                                                                                                                                       |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Text file            | Write the formatted receipt text to a timestamped `.txt` file, UTF-8 encoded, in a dedicated receipts folder.                | Useful for logging/archival and for environments without an attached printer (e.g. UAT). Reference path pattern: `receipt_yyyyMMdd_HHmmss.txt`.                                                             |
| Windows/GDI+ printer | Send the same text through a `PrintDocument` to any installed Windows printer by name (or the system default if left blank). | Reference implementation uses Courier New at 7.5pt with a 13px line height (≈ 60 lines per A4 page). For a narrow thermal roll, set the correct paper width in the printer driver, not in application code. |

> 📘 Fiscalisation / duplicate-print controls
>
> If your market requires fiscalised receipts or controls on reprinting financial documents, apply those controls at the point you call your print routine (e.g. mark the transaction as "printed", require a supervisor override to reprint) — the eSocket.POS interface itself has no concept of a receipt and will not prevent you from printing the same token twice if you call your print function twice.

## Error handling and edge cases

| Condition                                                                                                   | Required handling                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| REQUEST or PROCURE times out with no response                                                               | Do not send a REVERSE, and do not retry PROCURE for the same purchase — a token may already have been drawn down even though your terminal never received the response, and `Esp:Merchandise` has no message type to independently confirm what happened upstream. Hold the transaction as unconfirmed and escalate to EFT Corp for support in establishing the real outcome before concluding failure or refunding the customer. |
| `ResponseCode` 09 on REQUEST or PROCURE                                                                     | Not a decline. Wait and resend the identical message (same RRN/`TransactionId`) after 180 seconds.                                                                                                                                                                                                                                                                                                                                |
| PROCURE approved but the printer fails before the token is printed                                          | Do not attempt a second PROCURE for the same purchase — recover the token from your stored response and reprint. Do not send a REVERSE either: the token has already been drawn down and, for a merchant like ZETDC, cannot actually be reversed — a REVERSE here would not undo it. If the token truly cannot be recovered, follow your organisation's manual token-recovery/void procedure with EFT Corp instead.               |
| Attempting a REVERSE with no prior successful PROCURE                                                       | Disallow at the UI level — there is nothing upstream to reverse. Note this is about REQUEST-only lines specifically; even against a successfully procured line, REVERSE itself is best-effort only for a merchant like ZETDC.                                                                                                                                                                                                     |
| `MeterNumber` left blank for a non-ELECTRICITY product                                                      | The reference implementation still requires a non-blank entry even though it is not transmitted — decide whether your integration keeps this validation or relaxes it per product.                                                                                                                                                                                                                                                |
| Duplicate `TransactionId`/RRN reused too soon                                                               | Will be rejected or misrouted upstream. Implement a robust, collision-free generator — do not reuse the reference implementation's pure clock-based approach unmodified in production.                                                                                                                                                                                                                                            |
| PROCURE declined with `ResponseCode` 05, narrative containing "Invalid currency code" / "Currency mismatch" | Currency-configuration problem, not a transient failure — do not retry unchanged. Correct the `CurrencyCode` value sent at PROCURE (see [CurrencyCode on the PROCURE message](#currencycode-on-the-procure-message)) so it matches the REQUEST-returned `meterCurrency`, then re-attempt REQUEST. Do not try a different `ProductId` as a workaround — `ProductId` does not select currency in this implementation.               |

## Compliance and security considerations

VAS/Merchandise transactions carry no cardholder data (no PAN, track data, or PIN), so they do not themselves extend PCI-DSS cardholder data environment (CDE) scope. However, the surrounding integration still touches sensitive data and must be built to the same security bar as the card PURCHASE flow.

### Data minimisation and PII

* `customerName` and `UserId` (typically a phone number/subscriber ID) returned in VAS responses are personal data. Do not log them in plaintext to shared/long-retention logs; mask or omit them from any log destined for a wider audience, and apply your organisation's data retention policy to stored receipts.
* Do not embed real customer meter numbers, phone numbers, or names in test scripts, sample code, or support tickets shared outside the immediate integration team — use TEST/UAT values for all development and demonstration purposes.
* The decoded token itself is effectively a bearer instrument (whoever holds the 20-digit number can redeem the electricity units) — treat printed and stored tokens with the same handling discipline as a gift voucher or cash equivalent, not as routine transaction metadata.

### Transport and network security

eSocket.POS always runs on the same physical machine as the retail POS/till application it serves — this is a fixed deployment constraint, not an assumption. The reference implementation reflects this by connecting to `127.0.0.1:23001`, i.e. loopback only. Because this traffic never leaves the machine, it never traverses a network segment, so TLS is not applicable to this hop and is not required for it.

> 📘 Loopback-only by design — keep it that way
>
> Do not bind or expose the eSocket.POS TCP listener on a network-reachable interface, and do not reconfigure a till to reach eSocket.POS over the LAN "for convenience" (e.g. a shared eSocket.POS instance for multiple lanes). The entire security model here depends on this channel staying local-machine-only — any deployment that breaks that constraint reintroduces an unencrypted-transport gap (SOC 2 Confidentiality / ISO/IEC 27001) that this architecture is otherwise not exposed to. Treat "same machine, always" as a control to be enforced (e.g. firewall the port to loopback), not just a fact about today's topology.

* Restrict any other process or account on that same machine from reaching the loopback port unnecessarily (principle of least privilege) — co-location narrows the network attack surface to nil, but does not by itself limit local access.
* The terminal configuration files read by the POS (`configagent_properties.txt`, `properties.txt`) should be protected with file-system permissions restricting write access to the eSocket.POS service account/administrators only.

### Audit logging (PCI-DSS Requirement 10 / ISO 27001 alignment)

* Log, at minimum: `TerminalId`, `TransactionId` (STAN), `RetrievalRefNr`, `ProductType`, `ProductId`, `DateTime`, `ActionCode`, and `ResponseCode` for every REQUEST/PROCURE/REVERSE — this gives a complete, time-synchronised audit trail without needing to log the token or customer PII.
* Do not log the raw `Token` value or `customerName`/`UserId` into general application logs; if you need them for support/reconciliation, store them in a separate, access-controlled record with defined retention, not in free-text debug logs.
* Ensure the POS till's system clock is time-synchronised (e.g. NTP) with eSocket.POS — the `DateTime`/`LocalDate`/`LocalTime` fields in this interface depend on it, and audit trail integrity requires consistent timestamps across systems.

### Secure coding

* Validate and sanitise all cashier-entered dynamic fields (amount, meter number, user ID, product ID) before building the XML — the message builders in the reference implementation perform no XML-escaping of these values; a meter number or narrative value containing XML special characters (`<`, `&`, `"`) could malform the outgoing message. Escape or reject invalid input at the point of entry.
* Never hardcode a production `TerminalId`, host, or port into a compiled binary shared across sites — load them from a per-deployment configuration file or secure configuration store, consistent with the existing pattern of reading `TerminalId` from eSocket.POS's own configuration.
* Handle every network and parse failure gracefully (connection refused, timeout, malformed XML) without crashing the till application or leaking stack traces/internal paths to the cashier UI.

## Appendix A — key VAS response codes

This is the subset most relevant to VAS. Refer to the core XML Interface Quick Reference Guide for the complete list.

| Code | Meaning                                        |
| ---- | ---------------------------------------------- |
| 00   | Approved or completed successfully             |
| 05   | Do not honour                                  |
| 09   | Request in progress — resend after 180 seconds |
| 12   | Invalid transaction                            |
| 13   | Invalid amount                                 |
| 30   | Format error                                   |
| 51   | Not sufficient funds                           |
| 91   | Issuer or switch inoperative                   |
| 94   | Duplicate transaction                          |
| 96   | System malfunction                             |

## Appendix B — glossary

| Term                               | Meaning                                                                                                                                                                                                                                        |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| VAS                                | Value-Added Services — non-card purchases (electricity, water, telephone prepay, bill payment) processed via `Esp:Merchandise`.                                                                                                                |
| ZESA                               | The Zimbabwe Electricity Supply Authority; "ZESA token" is the common name for a prepaid electricity token, issued in this interface under `ProductType` `ELECTRICITY` via ZETDC (Zimbabwe Electricity Transmission and Distribution Company). |
| STAN                               | System Trace Audit Number — the `TransactionId` attribute; must be unique per terminal within the retention period.                                                                                                                            |
| RRN                                | Retrieval Reference Number — correlates REQUEST, PROCURE, and REVERSE as one purchase attempt.                                                                                                                                                 |
| PosStructuredData / StructuredData | Generic Name/Value child elements used to carry fields that do not have a dedicated XML attribute (meter number, merchant/product name, token, narrative, customer name, currency).                                                            |
| Esp:Merchandise                    | The XML element used for all VAS transactions, distinct from `Esp:Transaction` (used for card PURCHASE/DEPOSIT).                                                                                                                               |