@affix-io/affixiomerchant

Offline payments are mandatory and essential — accept card payments without internet

Global regulations and business continuity make offline payment acceptance mandatory and essential for merchants. Accept card payments without internet: EU DCR, PSD2, and EMV compliant. Double-spend protection, agentic payments, < 90 byte proofs. Zero runtime dependencies — Node 18+, browser, Deno.

Patent Pending — AffixIO's ZKP-based proof system for offline and agentic payments. All rights reserved.

Why offline payments are mandatory and essential globally

Offline payment acceptance is no longer optional. Regulators, card schemes, and consumers expect merchants to accept payments when the internet fails — at retail checkouts, transport gates, events, and in rural or disaster-affected areas. Offline payments are mandatory for continuity under EU DCR (Digital Operational Resilience), PSD2 expectations, and EMV offline approval; they are essential for retail, hospitality, transport, and any merchant with a terminal.

AffixIO Merchant SDK gives you offline-first payment acceptance: generate proofs locally, block double-spend in under 1 ms, queue transactions when offline, and auto-sync when connectivity returns. No raw card data stored or transmitted. Agentic AI payments are supported — AI agents can call pay() directly. Contact us for an API key and start accepting mandatory offline payments today.

Global compliance: offline payments and regulations

Offline payment capability supports compliance worldwide:

  • EU DCR / DORA — Digital Operational Resilience requires payment service providers to ensure continuity; offline acceptance supports operational resilience.
  • PSD2 — Payment Services Directive expectations around availability and continuity favour offline-capable terminals.
  • EMV offline — Chip and contactless standards support offline approval; our SDK works with tokenised card data and fits EMV-offline flows.
  • Retail and transport — Stores, stadiums, trains, and ferries need to accept payments without reliable internet; offline payments are essential.

Use the same SDK for offline and online; when connectivity returns, queued transactions sync automatically. No extra compliance burden — proofs are cryptographically bound and double-spend is prevented locally and, when configured, server-side.

Features

  • Agentic payments — AI agents can call pay() directly; idempotency, double-spend, and proof are all handled
  • Offline-first — generates proofs locally, queues transactions, syncs on reconnect
  • Zero runtime dependencies — pure TypeScript, ships < 25 KB compiled JS
  • < 90 byte proofs — compact HMAC-SHA256 commitment bound to card token + amount
  • Double-spend protection — nullifier checked locally in < 1 ms before any network call
  • All payment methods — chip, contactless, NFC, QR, magstripe, Apple Pay, Google Pay, Samsung Pay
  • Aggressive reconnect — exponential backoff from 1 ms → 5 s, instant revival on reconnect
  • Any runtime — Node 18+, browser, Deno (uses Web Crypto API, no native modules)
  • Pluggable storage — swap in Redis, SQLite, or any custom backend

Install

npm install @affix-io/affixiomerchant

No peer dependencies required. API key required to use after download — get one from the contact page or hello@affix-io.com.

Quick start

import { AffixioMerchant } from '@affix-io/affixiomerchant';

const merchant = new AffixioMerchant({
  apiKey: 'affix_your_key_here',
});

await merchant.setup();

const result = await merchant.pay({
  cardToken: 'tok_chip_abc123',
  amount: 24.99,
  currency: 'GBP',
  paymentMethod: 'chip',
});

console.log(result.accepted);    // true
console.log(result.proofBytes);  // always <= 90
console.log(result.synced);     // true if online, false if queued offline

await merchant.destroy();

Configuration

const merchant = new AffixioMerchant({
  apiKey:         'affix_your_key_here',       // required
  terminalId:     'TERM-001',                  // optional — enables server-side validation
  terminalSecret: 'term_secret_here',          // optional — required when terminalId is set
  baseUrl:        'https://api.affix-io.com',  // optional — default shown
  storageDir:     '.affixio',                  // optional — persistent state directory
  memoryOnly:     false,                       // optional — in-memory only (no disk writes)
});
OptionTypeDefaultDescription
apiKeystringrequiredAPI key from the AffixIO dashboard.
terminalIdstringTerminal ID registered with AffixIO. Enables server-side double-spend check.
terminalSecretstringTerminal secret. Required when terminalId is set.
baseUrlstringhttps://api.affix-io.comOverride the API base URL.
storageDirstring.affixioDirectory for persistent nullifiers and queue. Created automatically.
memoryOnlybooleanfalseUse in-memory storage only. Useful for testing.

Payment input

const result = await merchant.pay({
  cardToken:     'tok_chip_abc123',    // required — pre-tokenised from POS hardware
  amount:         24.99,               // required — major currency units (e.g. £24.99)
  currency:      'GBP',               // optional — ISO 4217, default GBP
  customerId:    'cus_12345',          // optional — for reconciliation
  paymentMethod: 'chip',              // optional — see payment methods below
  transactionId: 'txn_my_id',         // optional — auto-generated if omitted
  metadata:      { orderId: '789' },  // optional — passed through to the API
});

Supported payment methods: chip · contactless · nfc · qr · magstripe · manual · applepay · googlepay · samsungpay

Payment result:

{
  accepted:      boolean,  // false if double-spend or server rejection
  transactionId: string,   // reference this in your systems
  proofBytes:    number,   // always <= 90
  synced:        boolean,  // true → submitted to API immediately
  queued:        boolean,  // true → saved offline, will sync on reconnect
  error?:        string,   // only present when accepted = false
}

Agentic payments

Patent Pending AffixIO's agentic payment architecture is patent pending.

AI agents can call pay() directly. The SDK handles idempotency, double-spend prevention, and cryptographic proof — no special agent integration required.

import { AffixioMerchant } from '@affix-io/affixiomerchant';

// Initialise once at agent startup — memoryOnly keeps state ephemeral
const merchant = new AffixioMerchant({
  apiKey:     process.env.AFFIX_API_KEY!,
  memoryOnly: true,   // no disk writes; safe for serverless / short-lived agents
});

await merchant.setup();

// Agent calls pay() autonomously — no human in the loop
async function agentCheckout(cardToken: string, amount: number) {
  const result = await merchant.pay({
    cardToken,
    amount,
    currency:      'GBP',
    transactionId: `agent-${Date.now()}`,  // deterministic ID prevents double-charge on retry
    metadata:      { agentId: 'checkout-bot-v2', sessionId: crypto.randomUUID() },
  });

  if (!result.accepted) {
    throw new Error(`Payment rejected: ${result.error}`);
  }

  return result.transactionId;  // pass to fulfilment pipeline
}

Why it works for agents:

  • transactionId supplied by the agent → idempotent; retrying with the same ID cannot double-charge
  • Nullifier check is local and synchronous — no round-trip blocking the agent
  • memoryOnly: true — no filesystem access; safe inside Lambda, Deno Deploy, or Docker
  • queued: true result means the charge is recorded even if the network drops mid-task
  • Proof is cryptographically bound to cardToken + amount — unforgeable, non-replayable

Offline operation

pay() works with zero internet. Flow:

  1. Proof generated locally using Web Crypto (no network).
  2. Nullifier checked against local store — double-spend blocked in < 1 ms.
  3. If online with a terminal token → API validates server-side.
  4. Online → transaction submitted immediately → { synced: true }.
  5. Offline → transaction saved to local queue → { queued: true }.
  6. When connectivity returns → queue flushed silently in the background.

The terminal can accept payments indefinitely offline. Proofs are cryptographically bound to card token and amount — unforgeable and non-replayable.

Sync

// Automatic — triggered in the background when connectivity returns.

// Manual flush:
const result = await merchant.syncNow();
// { uploaded: 3, failed: 0, errors: [] }

// Queue depth:
const pending = await merchant.queueSize();

Transactions upload in batches of 50. Failed ones stay in the queue and retry on next sync.

Connectivity monitor

  • online → ping every ~500ms
  • offline → 1ms → 1ms → 5ms → 50ms → 250ms → 1s → 2s → 5s (holds)
  • reconnect → reset to fast interval + auto-sync
  • merchant.isOnline // boolean

Pluggable storage

Default: nullifiers and queue persisted to <storageDir>/affixio-state.json.

Implement these interfaces to use Redis, SQLite, or any custom store:

import type { NullifierStorage, QueueStorage } from '@affix-io/affixiomerchant';

interface NullifierStorage {
  has(nullifier: string): boolean | Promise<boolean>;
  add(nullifier: string): void | Promise<void>;
  delete(nullifier: string): void | Promise<void>;
  size(): number | Promise<number>;
}

interface QueueStorage {
  enqueue(tx: QueuedTransaction): void | Promise<void>;
  dequeueAll(): QueuedTransaction[] | Promise<QueuedTransaction[]>;
  remove(transactionId: string): void | Promise<void>;
  size(): number | Promise<number>;
}

Proof format

nullifier = SHA-256("nullifier:" + cardToken)[:18] → base64url  (24 chars)
mac       = HMAC-SHA256(key=cardToken, msg=amount|nonce|merchantId|ts)[:18] → base64url  (24 chars)
proof_str = mac + "." + nullifier  →  49 chars total  (under 90 bytes)

No raw card data is ever stored, transmitted, or logged.

Full API reference

import {
  AffixioMerchant,          // main class
  ApiClient,                // HTTP client (low-level)
  DoubleSpendGuard,         // nullifier guard
  SyncEngine,               // queue + upload engine
  PingMonitor,              // connectivity monitor
  FileStorage,              // file-backed storage
  InMemoryNullifierStorage, // in-memory nullifier store
  InMemoryQueueStorage,     // in-memory queue
  generateProof,            // proof generation
  serialiseProof,           // proof → string
  proofByteLength,          // proof string byte length
  parseConfig,              // config validation
  parsePayment,             // payment input validation
} from '@affix-io/affixiomerchant';

import type {
  MerchantConfig, PaymentInput, PaymentResult, SyncResult,
  CompactProof, QueuedTransaction, NullifierStorage, QueueStorage, CardToken,
} from '@affix-io/affixiomerchant';

Endpoints used

MethodPathPurpose
GET/healthConnectivity ping
POST/api/merchant/terminals/authenticateTerminal token
POST/api/merchant/payments/validateServer-side double-spend check
POST/api/merchant/transactions/syncUpload offline queue (batches of 50)

All requests use Authorization: Bearer <token>.

Requirements

  • Node.js 18+ — uses fetch, crypto.subtle, node:fs/promises
  • Browser — any modern browser with Web Crypto API
  • Deno — fully supported
  • Zero runtime dependencies

Offline payments use cases worldwide

Where offline payments are mandatory or essential: retail checkouts and pop-up stores, public transport (trains, buses, ferries, metro), events and stadiums, hospitality and restaurants, fuel forecourts, rural and low-connectivity areas, disaster recovery and business continuity, and any POS or terminal where internet is unreliable. The AffixIO Merchant SDK runs on Node 18+, browser, and Deno — same code for kiosks, terminals, and agentic payment servers. See Agentic AI payments for autonomous payment flows.

Frequently asked questions about offline payments and the Merchant SDK

Why are offline payments mandatory for merchants?

Regulations and consumer expectations make offline payment acceptance mandatory: EU DCR and PSD2 require continuity of payment services; EMV supports offline approval; retail, transport, and events need to accept payments when internet fails. Offline payments are essential for business continuity globally.

What is the best offline payment SDK for merchant terminals?

AffixIO Merchant SDK (@affix-io/affixiomerchant) is built for terminals: offline-first, double-spend protection, under 90 byte proofs, zero runtime dependencies. Works on Node 18+, browser, Deno. Supports chip, contactless, NFC, QR, Apple Pay, Google Pay. Download free on npm.

How do I accept card payments without internet?

Use an offline-first payment SDK like AffixIO Merchant SDK. It generates cryptographic proofs locally, checks double-spend in under 1 ms, queues transactions when offline, and auto-syncs when connectivity returns. No internet required at point of sale. Get an API key to start.

Is offline payment compliance required in the EU?

Yes. EU Digital Operational Resilience (DORA) and Payment Services Directive (PSD2) expect payment service providers to ensure continuity. Offline payment capability supports resilience and is increasingly essential for merchant terminals across Europe and globally.

Does the AffixIO Merchant SDK support agentic or AI payments?

Yes. AI agents can call pay() directly. The SDK handles idempotency, double-spend prevention, and proof. Use memoryOnly: true for serverless or short-lived agents. No special agent integration required. Read more on agentic payments.

How does double-spend prevention work offline?

A local nullifier store records each payment bound to card token and amount. Before accepting, the SDK checks the nullifier in under 1 ms. Duplicate attempts are rejected. Optional server-side validation when online with a terminal token.

Get the offline payments SDK — mandatory and essential for your terminal

Offline payments are mandatory and essential for merchants globally. Download the SDK from npm; request an API key for production. Join merchants who accept card payments without internet.

Download on npm Request API key

AffixIO home · Agentic payments · Identify via API · Why offline is mandatory · FAQ · Contact · Privacy