Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions data/acquirers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions docs/acquirers/tsys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

</TabItem>
<TabItem value="android-pax" label="Android (PAX)">

Expand All @@ -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.

</TabItem>
<TabItem value="android-hilite" label="Android (HiLite)">

Expand Down
91 changes: 91 additions & 0 deletions docs/features/avs-for-moto.mdx
Original file line number Diff line number Diff line change
@@ -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. |
1 change: 1 addition & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
12 changes: 12 additions & 0 deletions scripts/generate-acquirer-pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
11 changes: 11 additions & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ const sidebars = {
},
],

featuresSidebar: [
{
type: 'category',
label: 'Features',
collapsible: false,
items: [
'features/avs-for-moto',
],
},
],

referenceSidebar: [
{
type: 'category',
Expand Down
4 changes: 4 additions & 0 deletions src/data/acquirerDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/partials/functions/moto.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

<Tabs groupId="integration-path">
Expand Down
2 changes: 1 addition & 1 deletion static/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down