> ## Documentation Index
> Fetch the complete documentation index at: https://docs.manthan.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment Approval

> Govern payment authorization decisions with signed attestation

## Scenario

A payment processing service must authorize every transaction before funds move. Decisions approve, reject, or hold for review — must be cryptographically recorded. The payment system cannot execute a transfer without a valid attestation from the governance runtime.

Requirements:

* Every payment authorization is signed and verifiable
* High-value or high-risk transactions require manual hold
* No replay: each transaction ID executes exactly once
* The authorization record must be presentable to card networks and regulators

***

## Policy

Create `policies/payment-approval/1.0.0/policy.json`:

```json theme={null}
{
  "policyId": "payment-approval",
  "policyVersion": "1.0.0",
  "schemaVersion": "1.0.0",
  "signalsSchema": {
    "amount":              { "type": "number" },
    "currency":            { "type": "string" },
    "merchantCategory":    { "type": "string" },
    "accountAge":          { "type": "integer" },
    "fraudScore":          { "type": "number" },
    "velocityCount24h":    { "type": "integer" },
    "countryRisk":         { "type": "string" }
  },
  "rules": [
    {
      "id": "block-high-fraud",
      "condition": { "signal": "fraudScore", "greater_than": 0.8 },
      "outcome": {
        "action": "reject",
        "requires_override": false,
        "reason": "Transaction blocked: fraud score exceeds maximum threshold."
      }
    },
    {
      "id": "block-high-velocity",
      "condition": { "signal": "velocityCount24h", "greater_than": 20 },
      "outcome": {
        "action": "reject",
        "requires_override": false,
        "reason": "Transaction blocked: velocity limit exceeded."
      }
    },
    {
      "id": "hold-large-transaction",
      "condition": { "signal": "amount", "greater_than": 10000 },
      "outcome": {
        "action": "hold",
        "requires_override": true,
        "reason": "Transaction held for manual review: amount exceeds auto-approval limit."
      }
    },
    {
      "id": "hold-high-risk-country",
      "condition": { "signal": "countryRisk", "equals": "HIGH" },
      "outcome": {
        "action": "hold",
        "requires_override": true,
        "reason": "Transaction held: high-risk country of origin."
      }
    },
    {
      "id": "approve-standard",
      "condition": { "all": [] },
      "outcome": {
        "action": "approve",
        "requires_override": false,
        "reason": "Transaction approved: within standard parameters."
      }
    }
  ]
}
```

***

## Complete TypeScript example

```typescript theme={null}
import { ParmanaClient, ParmanaApiError } from "@parmanasystems/sdk-client";
import type { ExecutionAttestation } from "@parmanasystems/sdk-client";

const client = new ParmanaClient({
  baseUrl: process.env.PARMANA_URL ?? "http://localhost:3000",
  apiKey: process.env.PARMANA_API_KEY,
});

interface PaymentSignals {
  amount: number;
  currency: string;
  merchantCategory: string;
  accountAge: number;      // days
  fraudScore: number;      // 0.0–1.0
  velocityCount24h: number;
  countryRisk: "LOW" | "MEDIUM" | "HIGH";
}

interface PaymentAuthResult {
  transactionId: string;
  authorized: boolean;
  action: string;
  reason: string;
  requiresHold: boolean;
  attestationSignature: string;
}

async function authorizePayment(
  transactionId: string,
  signals: PaymentSignals
): Promise<PaymentAuthResult> {
  const attestation = await client.execute({
    executionId: transactionId,  // transaction ID IS the executionId — must be globally unique
    policyId: "payment-approval",
    policyVersion: "1.0.0",
    signals,
  });

  // Always verify before authorizing — fail closed
  const verification = await client.verify(attestation);
  if (!verification.valid) {
    return {
      transactionId,
      authorized: false,
      action: "reject",
      reason: "Governance attestation could not be verified. Payment blocked.",
      requiresHold: false,
      attestationSignature: "",
    };
  }

  const authorized =
    attestation.execution_state === "completed" &&
    attestation.decision.action === "approve";

  return {
    transactionId,
    authorized,
    action: attestation.decision.action,
    reason: attestation.decision.reason,
    requiresHold: attestation.execution_state === "pending_override",
    attestationSignature: attestation.signature,
  };
}

// Post-execution confirmation — prove the payment matched the authorization
async function confirmPaymentExecuted(
  transactionId: string,
  attestation: ExecutionAttestation,
  paymentDetails: {
    amount: number;
    currency: string;
    recipientAccountId: string;
  }
) {
  const proof = await client.confirmExecution({
    attestation,
    executedAction: {
      actionType: "payment_transfer",
      actionId: transactionId,
      actionTimestamp: new Date().toISOString(),
      actionDetails: paymentDetails,
    },
    timeWindowSeconds: 60,  // action must occur within 60 seconds of authorization
  });

  return proof;
}

// Example usage
async function main() {
  // Standard approval
  const approved = await authorizePayment("TXN-20240115-0001", {
    amount: 250,
    currency: "USD",
    merchantCategory: "retail",
    accountAge: 730,
    fraudScore: 0.02,
    velocityCount24h: 3,
    countryRisk: "LOW",
  });
  console.log(approved.action);     // "approve"
  console.log(approved.authorized); // true

  // Rejected: fraud score
  const blocked = await authorizePayment("TXN-20240115-0002", {
    amount: 500,
    currency: "USD",
    merchantCategory: "electronics",
    accountAge: 90,
    fraudScore: 0.92,
    velocityCount24h: 15,
    countryRisk: "LOW",
  });
  console.log(blocked.action);     // "reject"
  console.log(blocked.authorized); // false

  // Hold: large transaction
  const held = await authorizePayment("TXN-20240115-0003", {
    amount: 15000,
    currency: "USD",
    merchantCategory: "wire_transfer",
    accountAge: 1200,
    fraudScore: 0.05,
    velocityCount24h: 1,
    countryRisk: "LOW",
  });
  console.log(held.action);       // "hold"
  console.log(held.requiresHold); // true
}

main().catch(console.error);
```

***

## Handling held transactions

When `requiresHold` is `true`, route the transaction to a compliance officer:

```typescript theme={null}
const resolution = await client._request<{
  status: string;
  overrideId: string;
}>("/override", {
  method: "POST",
  body: JSON.stringify({
    executionId: "TXN-20240115-0003",
    approved: true,
    approvedBy: "compliance-officer-james-wu",
    approverRole: "compliance_officer",
    reason: "Wire transfer verified: known business account, documented purpose.",
  }),
});

console.log(resolution.status);     // "approved"
console.log(resolution.overrideId); // ID of the override record
```

***

## Expected results

| Signals                         | Expected `action` | `requires_override` |
| ------------------------------- | ----------------- | ------------------- |
| `fraudScore: 0.02, amount: 250` | `approve`         | `false`             |
| `fraudScore: 0.92`              | `reject`          | `false`             |
| `velocityCount24h: 25`          | `reject`          | `false`             |
| `amount: 15000`                 | `hold`            | `true`              |
| `countryRisk: "HIGH"`           | `hold`            | `true`              |

***

## Troubleshooting

**`[INV-013@replay] Replay detected`** Each `transactionId` is used as the `executionId`. Once a transaction is authorized, it cannot be re-authorized with the same ID. Generate a new transaction ID for retries.

**`execution_state: "pending_override"` but `action: "approve"`** A policy rule can set `action: "approve"` with `requires_override: true`. The convention is to use a distinct action name (e.g., `"hold"`) when override is required, but it is not enforced. Always check both `execution_state` and `decision.action`.

**Payment executed without attestation verification** Always call `client.verify()` before authorizing a transfer. If verification fails, block the payment and investigate.
