@aroha-sdk/settlement
@alpha

AP2 Payment Settlement

Connect Aroha spending mandates to the Agent Payments Protocol (AP2) — Google's open standard for cryptographically verifiable agent-initiated payments. Aroha handles delegation authority; AP2 handles payment execution.

Two complementary layers

1

Aroha mandate chain (Ed25519)

Who delegated what authority. Human → Orchestrator → Agent, narrowing at every hop. Verifiable without trusting any intermediary.

2

AP2 Payment Mandate (ES256 / ECDSA P-256)

How that authority is presented to merchants and financial institutions. An SD-JWT credential the payment processor can verify independently.

The aroha_mandate_ref field in every AP2 mandate carries the Aroha saga ID forward — so any AP2 payment receipt can be traced all the way back to the human who originally issued authority.

About the AP2 Protocol

AP2 (Agent Payments Protocol) is an open standard developed by Google as an extension of the A2A and Universal Commerce protocols. It defines cryptographically signed Verifiable Digital Credentials (VDCs) — Checkout Mandates (shared with merchants) and Payment Mandates (shared with financial institutions) — that prove an agent had genuine user authorisation before initiating a transaction. Spec: ap2-protocol.org

Alpha — AP2 processor ecosystem is still emerging

AP2Settlement generates spec-compliant SD-JWTs. However, no widely-available AP2 processors exist yet. For production card payments today, use StripeSettlement. Use AP2Settlement for early integration, evaluation, and building towards AP2 processor availability.

01

Install

AP2Settlement ships inside @aroha-sdk/settlement — no extra package needed.

npm install @aroha-sdk/settlement @aroha-sdk/orchestrator
02

Generate and persist an ECDSA P-256 key pair

AP2 mandates must be signed with ES256 (ECDSA P-256), not Ed25519. This is a separate key from your Aroha mandate chain keys — store it in a secrets manager alongside them.

import { AP2Settlement } from "@aroha-sdk/settlement";

// One-time: generate and export
const keyPair = await AP2Settlement.generateKeyPair();
const { privateJwk, publicJwk } = await AP2Settlement.exportKeyPair(keyPair);

// Persist privateJwk in your secrets manager (Vault, AWS Secrets Manager, etc.)
// publicJwk can be shared — it's embedded in every Open Payment Mandate

// On each restart: re-hydrate from stored JWKs
const keyPair = await AP2Settlement.importKeyPair(
  JSON.parse(process.env.AP2_PRIVATE_JWK!),
  JSON.parse(process.env.AP2_PUBLIC_JWK!),
);

Why ECDSA and not Ed25519? AP2 requires non-deterministic signatures to prevent observers from correlating two mandates signed for the same payload — a privacy property for payment credentials.

03

Create AP2Settlement and inject into SagaEngine

Provide a resolveCharge function that maps a capability name to payment details. Return null for free capabilities.

import { AP2Settlement } from "@aroha-sdk/settlement";
import { SagaEngine } from "@aroha-sdk/orchestrator";

const settlement = new AP2Settlement({
  keyPair,   // from step 02

  resolveCharge: async (ctx) => {
    if (ctx.capability === "book-flight") {
      return {
        amountMinorUnits: 29900,   // $299.00 in cents
        currency: "USD",
        payee: {
          id:      "did:aroha:provider:demo-airline",
          name:    "Demo Airline",
          website: "https://demo-airline.example",
        },
        paymentInstrument: {
          id:          "card-4242",
          type:        "card",
          description: "Card ••••4242",
        },
      };
    }
    return null;  // free capability — no mandate generated
  },

  // Optional: POST mandates to an AP2 processor endpoint
  processorEndpoint: process.env.AP2_PROCESSOR_URL,
  processorApiKey:   process.env.AP2_PROCESSOR_KEY,

  acknowledgeAlpha: true,
});

const engine = new SagaEngine({
  client,
  orchestratorDID: identity.did,
  privateKey:      identity.privateKey,
  settlement,   // ← AP2Settlement fulfils IArohaSettlement
});
04

Lifecycle: what gets generated at each saga step

The SagaEngine calls settlement hooks automatically — you don't invoke them directly.

// onReserve  → Open Payment Mandate (constraints-based, pre-auth)
//   vct: "mandate.payment.open.1"
//   Signed with typ "example+sd-jwt"
//   Contains: budget, amount_range, allowed_payees,
//             allowed_payment_instruments, aroha_mandate_ref
//
// onCommit   → Closed Payment Mandate (single transaction, key-bound)
//   vct: "mandate.payment.1"
//   Signed with typ "kb+sd-jwt"
//   Contains: transaction_id, payee, payment_amount, payment_instrument
//
// onCancel   → Best-effort cancellation POST (errors swallowed)
// onSagaComplete → no-op (reservations already cleaned up)

// If processorEndpoint is not set, retrieve mandates for manual submission:
const openToken   = settlement.getOpenMandateToken(reservationToken);
const closedToken = settlement.getClosedMandateToken(reservationToken);
05

What the generated SD-JWTs look like

Decoded payloads of the two mandate types, for reference.

Open Payment Mandate (onReserve)

{
  "vct": "mandate.payment.open.1",
  "iat": 1751000000,
  "exp": 1751003600,
  "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." } },
  "budget":       { "amount": 29900, "currency": "USD" },
  "amount_range": {
    "min": { "amount": 0,     "currency": "USD" },
    "max": { "amount": 29900, "currency": "USD" }
  },
  "allowed_payees": [
    { "id": "did:aroha:provider:demo-airline",
      "name": "Demo Airline",
      "website": "https://demo-airline.example" }
  ],
  "allowed_payment_instruments": [
    { "id": "card-4242", "type": "card", "description": "Card ••••4242" }
  ],
  "aroha_mandate_ref": "aroha:saga:saga-uuid:correlation-uuid"
}

Closed Payment Mandate (onCommit)

{
  "vct": "mandate.payment.1",
  "transaction_id": "base64url-sha256-of-reservation-token",
  "payee": {
    "id": "did:aroha:provider:demo-airline",
    "name": "Demo Airline",
    "website": "https://demo-airline.example"
  },
  "payment_amount":     { "amount": 29900, "currency": "USD" },
  "payment_instrument": { "id": "card-4242", "type": "card", "description": "Card ••••4242" },
  "iat": 1751000180,
  "exp": 1751003600
}

End-to-end accountability chain

The aroha_mandate_ref field ties the AP2 payment credential back to the Aroha saga. A compliance auditor, regulator, or AP2 processor can follow the chain:

AP2 Closed Mandate
  └─ aroha_mandate_ref: "aroha:saga:saga-uuid:correlation-uuid"
       └─ SagaEngine log → ArohaSpendingMandateBody
            └─ mandateId → parentMandateId → ... → root IntentMandate
                 └─ grantor: "did:aroha:human:alice"   ← human issuer

Every AP2 payment is traceable to the human who originally granted authority — satisfying the accountability requirement that AP2 was designed to address.