# Funding Adapter Contract Specification

**Version:** 1.0
**Date:** 2026-03-26
**Phase:** 11 Sprint A
**Task:** P11-SA-A01

---

## 1. Purpose

This contract defines the unified interface for card funding operations across four providers:
- **Lean Technologies**
- **Checkout.com**
- **AANI Pay**
- **AFEX**

All funding adapters implement the `WalletIntegrations.ProviderAdapter` behavior with standardized request/response schemas for card funding operations.

---

## 2. Design Principles

1. **Provider Agnostic**: Domain apps use normalized schemas only
2. **Versioned Schemas**: All schemas versioned and published
3. **Error Normalization**: All provider errors mapped to ADR 0005 envelopes
4. **Metadata Isolation**: Provider-specific fields isolated in metadata
5. **Observable**: All operations emit telemetry and audit events

---

## 3. Supported Operations

### 3.1 Initiate Funding

Submit a card funding request to the provider.

**Input:** `FundingRequest.t()`
```elixir
%FundingRequest{
  payment_id: String.t(),        # Internal idempotency key
  card_token: String.t(),         # Tokenized card identifier
  card_last4: String.t() | nil,  # Last 4 digits (for display)
  card_brand: String.t() | nil,  # Card brand (visa, mastercard, etc.)
  amount: pos_integer(),          # Amount in minor currency units
  currency: String.t(),           # ISO 4217 currency code
  provider: :lean | :checkout | :aani | :afex,
  customer_id: String.t() | nil, # Customer identifier
  correlation_id: String.t() | nil,
  metadata: map()                 # Provider-specific fields
}
```

**Output:** `{:ok, FundingResponse.t()} | {:error, FundingResponse.t()}`

### 3.2 Get Funding Status

Query current status of a funding transaction.

**Input:** `provider_reference :: String.t()`

**Output:** `{:ok, FundingResponse.t()} | {:error, FundingResponse.t()}`

### 3.3 Cancel Funding

Cancel a pending funding transaction (when supported by provider).

**Input:** `provider_reference :: String.t()`

**Output:** `{:ok, FundingResponse.t()} | {:error, FundingResponse.t()}`

### 3.4 Refund Funding

Reverse a completed funding transaction (when supported by provider).

**Input:** `provider_reference :: String.t(), amount :: pos_integer()`

**Output:** `{:ok, FundingResponse.t()} | {:error, FundingResponse.t()}`

---

## 4. Response Schema

### 4.1 FundingResponse

```elixir
%FundingResponse{
  status: :accepted | :pending | :completed | :failed | :unknown,
  provider: :lean | :checkout | :aani | :afex,
  provider_reference: String.t() | nil,
  provider_status_code: String.t() | nil,
  retryable: boolean(),
  occurred_at: DateTime.t(),
  metadata: map()
}
```

### 4.2 Status Values

- **`:accepted`** — Funding request accepted, pending settlement
- **`:pending`** — Funding in progress (3DS, authorization in flight)
- **`:completed`** — Funding completed and settled
- **`:failed`** — Funding failed permanently
- **`:unknown`** — Provider status unclear, requires reconciliation

---

## 5. Error Mapping

All provider errors are mapped to ADR 0005 ErrorEnvelope format.

### 5.1 Error Categories

- **`validation`** — Invalid request parameters
- **`business`** — Business rule violation (card declined, insufficient funds)
- **`auth`** — Authentication or authorization failure
- **`rate_limit`** — Request rate exceeded
- **`dependency`** — Upstream/external service failure
- **`internal`** — Unexpected internal error

### 5.2 Normalized Error Codes

| Normalized Code | Category | Retryable | Description |
|---|---|---|---|
| `FUNDING_INSUFFICIENT_FUNDS` | business | false | Insufficient funds on card |
| `FUNDING_CARD_DECLINED` | business | false | Card declined by issuer |
| `FUNDING_INVALID_CARD` | validation | false | Invalid card number or token |
| `FUNDING_CARD_EXPIRED` | validation | false | Card expired |
| `FUNDING_CVC_DECLINED` | validation | false | CVC check failed |
| `FUNDING_3DS_REQUIRED` | business | false | 3DS authentication required |
| `FUNDING_3DS_FAILED` | business | false | 3DS authentication failed |
| `FUNDING_CARD_BLOCKED` | business | false | Card blocked |
| `FUNDING_LIMIT_EXCEEDED` | business | false | Transaction limit exceeded |
| `FUNDING_DO_NOT_HONOR` | business | false | Do not honor |
| `FUNDING_SUSPECTED_FRAUD` | business | false | Suspected fraud |
| `FUNDING_TIMEOUT` | dependency | true | Request timeout |
| `FUNDING_RATE_LIMITED` | rate_limit | true | Rate limit exceeded |
| `FUNDING_PROVIDER_UNAVAILABLE` | dependency | true | Provider service unavailable |
| `FUNDING_PROCESSING_ERROR` | dependency | true | Processing error |
| `FUNDING_SYSTEM_ERROR` | dependency | true | System error |
| `FUNDING_UNKNOWN_ERROR` | internal | false | Unknown error |

### 5.3 ErrorEnvelope Structure

```json
{
  "error": {
    "code": "FUNDING_CARD_DECLINED",
    "message": "Card declined by issuer",
    "category": "business",
    "retryable": false,
    "details": {
      "provider": "lean",
      "provider_code": "card_declined",
      "card_last4": "1234"
    }
  },
  "meta": {
    "request_id": "req_...",
    "correlation_id": "corr_...",
    "idempotency_key": "idem_...",
    "timestamp": "2026-03-26T10:30:00Z"
  }
}
```

---

## 6. Provider-Specific Mappings

### 6.1 Lean Technologies

| Lean Code | Normalized Code | Category |
|---|---|---|
| `insufficient_funds` | `FUNDING_INSUFFICIENT_FUNDS` | business |
| `card_declined` | `FUNDING_CARD_DECLINED` | business |
| `invalid_card` | `FUNDING_INVALID_CARD` | validation |
| `expired_card` | `FUNDING_CARD_EXPIRED` | validation |
| `authentication_required` | `FUNDING_3DS_REQUIRED` | business |
| `authentication_failed` | `FUNDING_3DS_FAILED` | business |
| `timeout` | `FUNDING_TIMEOUT` | dependency |
| `rate_limit_exceeded` | `FUNDING_RATE_LIMITED` | rate_limit |
| `provider_unavailable` | `FUNDING_PROVIDER_UNAVAILABLE` | dependency |

### 6.2 Checkout.com

| Checkout Code | Normalized Code | Category |
|---|---|---|
| `card_declined` | `FUNDING_CARD_DECLINED` | business |
| `insufficient_funds` | `FUNDING_INSUFFICIENT_FUNDS` | business |
| `invalid_number` | `FUNDING_INVALID_CARD` | validation |
| `expired_card` | `FUNDING_CARD_EXPIRED` | validation |
| `cvc_declined` | `FUNDING_CVC_DECLINED` | validation |
| `processing_error` | `FUNDING_PROCESSING_ERROR` | dependency |
| `Gateway Timeout` | `FUNDING_TIMEOUT` | dependency |
| `rate_limit` | `FUNDING_RATE_LIMITED` | rate_limit |

### 6.3 AANI Pay

| AANI Code | Normalized Code | Category |
|---|---|---|
| `DECLINED` | `FUNDING_CARD_DECLINED` | business |
| `INSUFFICIENT_FUNDS` | `FUNDING_INSUFFICIENT_FUNDS` | business |
| `INVALID_CARD` | `FUNDING_INVALID_CARD` | validation |
| `EXPIRED_CARD` | `FUNDING_CARD_EXPIRED` | validation |
| `DO_NOT_HONOR` | `FUNDING_DO_NOT_HONOR` | business |
| `SUSPECTED_FRAUD` | `FUNDING_SUSPECTED_FRAUD` | business |
| `TIMEOUT` | `FUNDING_TIMEOUT` | dependency |
| `SYSTEM_ERROR` | `FUNDING_SYSTEM_ERROR` | dependency |

### 6.4 AFEX

| AFEX Code | Normalized Code | Category |
|---|---|---|
| `card_declined` | `FUNDING_CARD_DECLINED` | business |
| `insufficient_balance` | `FUNDING_INSUFFICIENT_FUNDS` | business |
| `invalid_account` | `FUNDING_INVALID_CARD` | validation |
| `card_blocked` | `FUNDING_CARD_BLOCKED` | business |
| `transaction_limit_exceeded` | `FUNDING_LIMIT_EXCEEDED` | business |
| `service_timeout` | `FUNDING_TIMEOUT` | dependency |
| `service_unavailable` | `FUNDING_PROVIDER_UNAVAILABLE` | dependency |

---

## 7. Implementation Modules

| Module | Purpose |
|---|---|
| `WalletIntegrations.FundingAdapter` | Main contract facade |
| `WalletIntegrations.Funding.FundingRequest` | Normalized request schema |
| `WalletIntegrations.Funding.FundingResponse` | Normalized response schema |
| `WalletIntegrations.Funding.ErrorMapper` | Error normalization engine |
| `WalletIntegrations.Funding.Schemas.LeanSchema` | Lean-specific schema docs |
| `WalletIntegrations.Funding.Schemas.CheckoutSchema` | Checkout-specific schema docs |
| `WalletIntegrations.Funding.Schemas.AaniSchema` | AANI-specific schema docs |
| `WalletIntegrations.Funding.Schemas.AfexSchema` | AFEX-specific schema docs |

---

## 8. Testing Requirements

### 8.1 Contract Tests

- Request schema validation
- Response schema validation
- Status value normalization
- Error code mapping for all providers
- Metadata isolation

### 8.2 Integration Tests (per adapter)

- Initiate funding success
- Get status success
- Cancel funding (when supported)
- Refund funding (when supported)
- Handle timeout scenarios
- Handle rate limit scenarios
- Handle provider unavailable scenarios

### 8.3 Security Tests

- Sensitive data redaction in logs
- Provider credential isolation
- Callback signature validation

---

## 9. Observability

### 9.1 Telemetry Events

```elixir
[:wallet_integrations, :funding, :initiate]
[:wallet_integrations, :funding, :status]
[:wallet_integrations, :funding, :cancel]
[:wallet_integrations, :funding, :refund]
```

### 9.2 Metrics

- Funding request count by provider and status
- Error rate by provider and error code
- Latency p50/p95/p99 by provider and operation
- Retry count by provider

### 9.3 Audit Events

All funding operations emit audit events with:
- `correlation_id`
- `payment_id`
- `provider`
- `amount`
- `currency`
- `outcome` (success/failure)

---

## 10. Acceptance Criteria

✅ **AC1**: Unified contract for `initiate`, `status`, `cancel`, `refund` operations
✅ **AC2**: Request/response schemas versioned and published
✅ **AC3**: Error mapping follows ADR 0005 envelope
✅ **AC4**: Contract document created
✅ **AC5**: Contract schema tests written and passing

---

## 11. References

- ADR 0005: API Error Envelope and Idempotency Response Contract
- ADR 0008: Integration Adapter Contract and Failure Policy
- Phase 11 Sprint Task Breakdown: `docs/phase-11-sprint-task-breakdown.md`
