diff --git a/data/acquirers.yaml b/data/acquirers.yaml
index 83b1175..53a8417 100644
--- a/data/acquirers.yaml
+++ b/data/acquirers.yaml
@@ -19,6 +19,14 @@ acquirers:
Batch auto-closes ~11pm EST (host-capture). Partial reversal available for
TSYS US and Canada — uses the MasterCard flag in ISO 8583.
capabilities:
+ avs-for-moto:
+ # Draft feature — TSYS only, Android SDK (PAX) and Cloud API only for now.
+ # Not modeled as a Functionalities capability row on purpose: it has no
+ # entry in CapabilitySummary's CAPABILITY_LABELS, so it stays invisible
+ # in the acquirer table. Consumed only by generate-acquirer-pages.js to
+ # inject the callout in moto.mdx. Full detail lives in docs/features/avs-for-moto.mdx.
+ cloud-api: public
+ android-pax: public
sale:
cloud-api: public
android-pax: public
diff --git a/docs/acquirers/tsys.mdx b/docs/acquirers/tsys.mdx
index 53bf8ce..e556295 100644
--- a/docs/acquirers/tsys.mdx
+++ b/docs/acquirers/tsys.mdx
@@ -947,6 +947,16 @@ Use for call centres, phone order systems, or recurring billing where the cardho
Batch auto-closes ~11pm EST (host-capture). Partial reversal available for TSYS US and Canada — uses the MasterCard flag in ISO 8583.
:::
+### Address Verification Service (AVS)
+
+TSYS supports optional Address Verification (AVS) for MOTO Sale, Pre-Authorization, and Refund — on Android SDK and Cloud API only, for now.
+
+Turning it on for a merchant requires `avsForMoto` (an internal flag Handpoint sets per merchant, default `false`) and `motoEnabled = true`.
+
+:::info
+Setup, code samples for both integration paths, and known limitations are covered in [AVS for MOTO](/features/avs-for-moto).
+:::
+
### Code
@@ -987,6 +997,29 @@ Content-Type: application/json
For back-office MOTO, `amount` is in major currency units (decimal string). `transactionReference` is UUID v4 and used for idempotency.
+**AVS (optional)** — attach a `billing` object to a MOTO on terminal request (TSYS only):
+
+```http
+POST https://cloud.handpoint.com/transactions
+ApiKeyCloud: YOUR_MERCHANT_API_KEY
+Content-Type: application/json
+
+{
+ "operation": "moToSale",
+ "amount": "1000",
+ "currency": "USD",
+ "terminal_type": "PAXA920",
+ "serial_number": "082104578",
+ "transactionReference": "2bfde1fc-23b1-4c67-93d9-1d4a557f4d4f",
+ "billing": {
+ "zipCode": "10001",
+ "address": "123 Main St"
+ }
+}
+```
+
+`zipCode` is required whenever you include `billing`; `address` is optional. See [AVS for MOTO](/features/avs-for-moto) for enablement, the on-device prompt alternative (`enableAvsFields`), edge cases, and what to persist.
+
@@ -1005,6 +1038,26 @@ options.cardToken = "YOUR_STORED_CARD_TOKEN"
hapi.motoSale(BigInteger("1000"), Currency.USD, options)
```
+**AVS (optional)** — attach a `Billing` object on MOTO on terminal (TSYS only):
+
+```kotlin
+val billing = Billing(
+ zipCode = customerZipCode, // e.g. "10001" — required
+ address = customerAddress // e.g. "123 Main St" — optional
+)
+val options = MoToOptions().apply { this.billing = billing }
+hapi.motoSale(BigInteger("1000"), Currency.USD, options)
+```
+
+Or let the terminal ask for it instead of collecting it yourself:
+
+```kotlin
+val options = MoToOptions().apply { enableAvsFields = true }
+hapi.motoSale(BigInteger("1000"), Currency.USD, options)
+```
+
+See [AVS for MOTO](/features/avs-for-moto) for enablement, edge cases, and what to persist.
+
diff --git a/docs/features/avs-for-moto.mdx b/docs/features/avs-for-moto.mdx
new file mode 100644
index 0000000..d7427ea
--- /dev/null
+++ b/docs/features/avs-for-moto.mdx
@@ -0,0 +1,91 @@
+---
+id: avs-for-moto
+title: "AVS for MOTO"
+sidebar_label: "AVS for MOTO"
+description: "Attach the cardholder's billing address to a MOTO transaction so the acquirer can use it during authorization."
+---
+
+# AVS for MOTO
+
+Attach the cardholder's billing address to a MOTO Sale, Pre-Authorization, or Refund, so the acquirer can use it during authorization.
+
+:::info Availability
+Available via **Android SDK** and **Cloud API**. For now, only on **TSYS**.
+:::
+
+## Overview
+
+MOTO transactions carry more fraud risk than card-present ones — there's no chip, no PIN, no physical card to inspect. Address Verification Service (AVS) is the acquirer's way of checking a piece of the cardholder's billing information against what the issuer has on file for that card, alongside the card number and CVV. It's one of the standard risk signals issuers and acquirers use when deciding whether to approve a MOTO transaction, on top of the usual authorization checks.
+
+This feature adds a `Billing` object — a postal code and, optionally, a street address — to a MOTO Sale, Pre-Authorization, or Refund. Handpoint doesn't validate the address or run any lookup: you (or the cardholder, via the on-device prompt) supply the data, and the gateway forwards it to the acquirer as part of the authorization request.
+
+AVS only applies to MOTO — Sale, Pre-Authorization, and Refund. It doesn't apply to card-present transactions, and MOTO must already be enabled for the merchant (see [Prerequisites](#prerequisites)).
+
+## Prerequisites
+
+- Handpoint Android SDK (hapi-android) **7.1014.0 or later** for `Billing`, `zipCode`, and `address` on MOTO.
+- A more recent SDK release for the on-device prompt opt-in (`enableAvsFields` on `MoToOptions`) — this shipped recently; pin to the latest release rather than 7.1014.0 if you need it.
+- MOTO enabled for the merchant (`motoEnabled = true`), regardless of who supplies the billing data.
+- Cloud API integrations target `POST /transactions` with `operation=moToSale`, `moToRefund`, or `moToPreAuthorization`.
+
+## Configuration
+
+AVS for MOTO is enabled per merchant on the backend by Handpoint — there's no self-service toggle for it today.
+
+| Key | Type | Description |
+|---|---|---|
+| `avsForMoto` | boolean | Internal flag Handpoint sets per merchant. Default `false`. Requires `motoEnabled = true`. The native SDK reads the same setting under a different key, `"AVS"` (see [Code](#code)). |
+
+:::note
+You can't set this yourself — it's configured by Handpoint on the backend. Read it (via the SDK key `"AVS"`) to adapt your own UI, for example hiding billing fields when AVS isn't enabled for the merchant. It doesn't control the on-device prompt — that's `enableAvsFields`, set per transaction.
+:::
+
+## Handling cardholder data
+
+:::note
+The billing address is personal data — don't store it in clear text or write it to application logs (mask or truncate if you display it). The postal code isn't subject to the same restriction and can be logged. Apply your own retention policy and don't keep it longer than necessary.
+:::
+
+## Collecting the billing address
+
+Two ways to get `zipCode` / `address`, mutually exclusive on the same `MoToOptions`:
+
+| If you... | Do this |
+|---|---|
+| Have your own billing screen | Collect `zipCode` / `address` yourself and pass them via `Billing`. Don't set `enableAvsFields` — it's ignored once `billing` is already set. |
+| Don't want to build one | Set `enableAvsFields = true` and leave `billing` unset. The native card-entry screen asks for `zipCode` / `address` before sending the transaction. |
+
+This applies to Sale and Pre-Authorization only — Refund never shows the on-device prompt, so set `billing` programmatically if you want it on a refund.
+
+## Code
+
+Code samples live with the rest of the MOTO implementation for each integration path, not here:
+
+- **Android SDK** — see [MOTO on the TSYS page](/acquirers/tsys#moto-mail-order--telephone-order), Android (PAX) tab, for how to attach `Billing` and use `enableAvsFields`.
+- **Cloud API** — see the same [MOTO section](/acquirers/tsys#moto-mail-order--telephone-order), Cloud API tab, for the `billing` payload shape.
+
+## What comes back
+
+The issuer/acquirer returns an AVS result alongside the usual approve/decline outcome for a MOTO transaction with billing data attached, on both the native SDK and Cloud API.
+
+## Edge cases
+
+| Scenario | Behaviour |
+|---|---|
+| `billing` is null / omitted | Transaction proceeds normally, no billing data sent. |
+| `zipCode` omitted, `address` supplied | Not a supported combination — `zipCode` is required whenever you construct a `Billing` object. |
+| `address` omitted, `zipCode` supplied | Valid — `address` is optional. |
+| `billing` supplied, AVS not enabled for the merchant | No special handling — the SDK forwards whatever billing you set regardless of this flag (verified in `MoToRequestFactory`: billing is sent whenever it's present, with no check against `avsForMoto`). The flag only matters if you choose to read it for your own UI logic — see [Code](#code). |
+| MOTO Refund with no billing set | Transaction proceeds without billing data. MOTO Refund has no on-device prompt under any circumstance, regardless of `enableAvsFields` — collect and set `billing` yourself if you need it on a refund. |
+| `enableAvsFields = true` but `billing` already set (native SDK) | The on-device prompt is suppressed — your billing values are used as-is. |
+
+## What to persist after a transaction
+
+Store the following locally. Don't persist the raw billing address in clear text.
+
+| Field to store | Source | Notes |
+|---|---|---|
+| `originalTransactionId` | `TransactionResult.transactionId` / API transaction id | Needed to link any later void or refund. |
+| `billingSent` | Whether you set billing on this transaction | Your own local flag — useful for reconciliation alongside the AVS result. |
+| `billingZipCode` (optional) | Your local variable at transaction time | Store only what your reconciliation process needs. |
+| `billingAddress` | Not stored in clear text | Mask, truncate, or omit per your data retention policy. |
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 4dfdc3d..2d7051c 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -47,6 +47,7 @@ const config = {
{ type: 'custom-GlobalFilters', position: 'left' },
{ type: 'doc', docId: 'get-started/index', label: 'Get Started', position: 'left' },
{ type: 'docSidebar', sidebarId: 'acquirersSidebar', label: 'Functionalities', position: 'left' },
+ { type: 'docSidebar', sidebarId: 'featuresSidebar', label: 'Features', position: 'left' },
{ type: 'docSidebar', sidebarId: 'referenceSidebar', label: 'Reference', position: 'left' },
{ type: 'docSidebar', sidebarId: 'releaseNotesSidebar', label: 'Release Notes', position: 'left' },
{ type: 'custom-VersionToggle', position: 'right' },
diff --git a/scripts/generate-acquirer-pages.js b/scripts/generate-acquirer-pages.js
index a0e012f..d754aa0 100644
--- a/scripts/generate-acquirer-pages.js
+++ b/scripts/generate-acquirer-pages.js
@@ -114,6 +114,18 @@ function buildPage(slug, a) {
? `:::note ${a.name}\n${a.notes.trim()}\n:::\n`
: '';
content = content.replace('{/* ACQUIRER_NOTE_INJECTION_POINT */}', note);
+
+ if (partialName === 'moto') {
+ const avsCaps = caps['avs-for-moto'];
+ let avsNote = '';
+ if (avsCaps && isVisible(avsCaps)) {
+ const supportedPaths = PATHS.filter(p => avsCaps[p] === 'public' || avsCaps[p] === 'coming-soon');
+ const pathLabels = supportedPaths.map(p => PATH_LABELS[p]).join(' and ');
+ avsNote = `### Address Verification Service (AVS)\n\n${a.name} supports optional Address Verification (AVS) for MOTO Sale, Pre-Authorization, and Refund — on ${pathLabels} only, for now.\n\nTurning it on for a merchant requires \`avsForMoto\` (an internal flag Handpoint sets per merchant, default \`false\`) and \`motoEnabled = true\`.\n\n:::info\nSetup, code samples for both integration paths, and known limitations are covered in [AVS for MOTO](/features/avs-for-moto).\n:::\n`;
+ }
+ content = content.replace('{/* AVS_FOR_MOTO_INJECTION_POINT */}', avsNote);
+ }
+
sections.push(content);
}
}
diff --git a/sidebars.js b/sidebars.js
index e4b36fd..ffb29fb 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -50,6 +50,17 @@ const sidebars = {
},
],
+ featuresSidebar: [
+ {
+ type: 'category',
+ label: 'Features',
+ collapsible: false,
+ items: [
+ 'features/avs-for-moto',
+ ],
+ },
+ ],
+
referenceSidebar: [
{
type: 'category',
diff --git a/src/data/acquirerDetails.js b/src/data/acquirerDetails.js
index f0a2e9c..7d179a5 100644
--- a/src/data/acquirerDetails.js
+++ b/src/data/acquirerDetails.js
@@ -15,6 +15,10 @@ export const ACQUIRER_DETAILS = [
"Discover"
],
"capabilities": {
+ "avs-for-moto": {
+ "cloud-api": "public",
+ "android-pax": "public"
+ },
"sale": {
"cloud-api": "public",
"android-pax": "public",
diff --git a/src/partials/functions/moto.mdx b/src/partials/functions/moto.mdx
index fef9ac2..6927a86 100644
--- a/src/partials/functions/moto.mdx
+++ b/src/partials/functions/moto.mdx
@@ -27,6 +27,8 @@ Use for call centres, phone order systems, or recurring billing where the cardho
{/* ACQUIRER_NOTE_INJECTION_POINT */}
+{/* AVS_FOR_MOTO_INJECTION_POINT */}
+
### Code
diff --git a/static/llms.txt b/static/llms.txt
index 2a020c2..d2a88e9 100644
--- a/static/llms.txt
+++ b/static/llms.txt
@@ -3,7 +3,7 @@
# Generated: 2026-06-12T06:05:24.079Z
## Acquirers
-TSYS | geography: US, Canada | sale, refund, reversal, partial-reversal, tip-adjustment, pre-auth, moto, tokenization, batching, pre-auth-capture-reversal | cards: VISA, MC, Discover | url: /acquirers/tsys
+TSYS | geography: US, Canada | avs-for-moto, sale, refund, reversal, partial-reversal, tip-adjustment, pre-auth, moto, tokenization, batching, pre-auth-capture-reversal | cards: VISA, MC, Discover | url: /acquirers/tsys
PAYSAFE + Interac | geography: Canada | sale, refund, reversal, tip-adjustment, pre-auth, moto, tokenization, batching, void | cards: VISA, MC, Discover, Interac | interac: true | url: /acquirers/tsys-tns
TNS (Interac) | geography: Canada | sale, void | cards: Interac | interac: true | url: /acquirers/tns
PAYSAFE | geography: US | sale, refund, reversal | cards: VISA, MC, AMEX, Discover, Interac | interac: true | url: /acquirers/paysafe-tsys