# API Specification

This document defines the initial API shape for Open Solar Payments. Endpoint names are intentionally simple and can be adapted to a merchant application's routing conventions.

## Conventions

Base path:

```text
/api/open-solar-payments/v1
```

Response format:

```json
{
  "data": {},
  "error": null
}
```

Error format:

```json
{
  "data": null,
  "error": {
    "code": "invoice_expired",
    "message": "The invoice has expired."
  }
}
```

Timestamps use ISO 8601 UTC strings.

Amounts should store Bitcoin values in sats. Fiat values may be stored for display and reconciliation, but Bitcoin/Lightning is the payment rail.

## Status Values

Payment request statuses:

```text
draft
pending
paid
settled
expired
failed
cancelled
refunded
```

Invoice statuses:

```text
created
pending
paid
expired
failed
cancelled
```

Settlement statuses:

```text
not_required
pending
confirmed
reconciled
paid_out
disputed
cancelled
```

## Create Payment Request

```text
POST /payment-requests
```

Creates a payment request for a solar quote, product checkout, workorder deposit, or workorder balance.

Request:

```json
{
  "sourceType": "solar_quote",
  "sourceId": "quote_456",
  "customerRef": "customer_789",
  "merchantRef": "merchant_suntecorb",
  "description": "Solar quote deposit for 5kVA inverter system",
  "amountSats": 185000,
  "displayAmount": {
    "amount": 250000,
    "currency": "NGN"
  },
  "expiresInSeconds": 1800,
  "metadata": {
    "market": "Nigeria",
    "orderType": "deposit"
  }
}
```

Response:

```json
{
  "data": {
    "id": "payreq_123",
    "status": "pending",
    "sourceType": "solar_quote",
    "sourceId": "quote_456",
    "amountSats": 185000,
    "displayAmount": {
      "amount": 250000,
      "currency": "NGN"
    },
    "expiresAt": "2026-07-01T12:00:00Z",
    "createdAt": "2026-07-01T11:30:00Z"
  },
  "error": null
}
```

## Get Payment Request

```text
GET /payment-requests/:paymentRequestId
```

Response:

```json
{
  "data": {
    "id": "payreq_123",
    "status": "paid",
    "sourceType": "solar_quote",
    "sourceId": "quote_456",
    "invoiceId": "inv_123",
    "receiptId": "receipt_123",
    "settlementId": "settlement_123",
    "amountSats": 185000,
    "paidAt": "2026-07-01T11:42:00Z"
  },
  "error": null
}
```

## Create Lightning Invoice

```text
POST /payment-requests/:paymentRequestId/invoices
```

Creates a Lightning invoice through the configured backend adapter.

Request:

```json
{
  "provider": "btcpay",
  "memo": "Suntecorb solar payment payreq_123"
}
```

Response:

```json
{
  "data": {
    "id": "inv_123",
    "paymentRequestId": "payreq_123",
    "provider": "btcpay",
    "status": "pending",
    "amountSats": 185000,
    "bolt11": "lnbc1850u1p...",
    "checkoutUrl": "https://pay.example.com/i/abc123",
    "expiresAt": "2026-07-01T12:00:00Z",
    "createdAt": "2026-07-01T11:31:00Z"
  },
  "error": null
}
```

## Get Invoice Status

```text
GET /invoices/:invoiceId/status
```

Response:

```json
{
  "data": {
    "id": "inv_123",
    "paymentRequestId": "payreq_123",
    "status": "paid",
    "amountSats": 185000,
    "paidAt": "2026-07-01T11:42:00Z",
    "confirmations": 1
  },
  "error": null
}
```

## Payment Confirmation Webhook

```text
POST /webhooks/:provider/payment-confirmed
```

Receives a payment event from the configured Lightning backend.

Example normalized event:

```json
{
  "provider": "btcpay",
  "providerEventId": "evt_123",
  "providerInvoiceId": "btcpay_invoice_456",
  "status": "paid",
  "amountSats": 185000,
  "paidAt": "2026-07-01T11:42:00Z",
  "metadata": {
    "paymentRequestId": "payreq_123"
  }
}
```

Expected behavior:

- Verify webhook authenticity.
- Match event to invoice and payment request.
- Ignore duplicate events safely.
- Mark invoice as paid.
- Mark payment request as paid.
- Generate receipt if one does not exist.
- Update or create settlement record if the source is a workorder or installation payment.

Response:

```json
{
  "data": {
    "accepted": true,
    "paymentRequestId": "payreq_123",
    "invoiceId": "inv_123",
    "receiptId": "receipt_123"
  },
  "error": null
}
```

## Get Receipt

```text
GET /receipts/:receiptId
```

Response:

```json
{
  "data": {
    "id": "receipt_123",
    "paymentRequestId": "payreq_123",
    "sourceType": "solar_quote",
    "sourceId": "quote_456",
    "amountSats": 185000,
    "displayAmount": {
      "amount": 250000,
      "currency": "NGN"
    },
    "paidAt": "2026-07-01T11:42:00Z",
    "receiptNumber": "OSP-2026-000001"
  },
  "error": null
}
```

## Create Settlement Record

```text
POST /settlements
```

Request:

```json
{
  "workorderId": "workorder_456",
  "installerRef": "installer_789",
  "paymentRequestId": "payreq_123",
  "amountSats": 185000,
  "settlementType": "installation_deposit"
}
```

Response:

```json
{
  "data": {
    "id": "settlement_123",
    "workorderId": "workorder_456",
    "installerRef": "installer_789",
    "paymentRequestId": "payreq_123",
    "status": "confirmed",
    "amountSats": 185000,
    "createdAt": "2026-07-01T11:43:00Z"
  },
  "error": null
}
```

## Get Workorder Settlement

```text
GET /workorders/:workorderId/settlement
```

Response:

```json
{
  "data": {
    "workorderId": "workorder_456",
    "settlements": [
      {
        "id": "settlement_123",
        "status": "confirmed",
        "settlementType": "installation_deposit",
        "amountSats": 185000,
        "receiptId": "receipt_123"
      }
    ]
  },
  "error": null
}
```

## Idempotency

Write endpoints should support an `Idempotency-Key` header. This is especially important for invoice creation and webhook processing.

```text
Idempotency-Key: quote_456-deposit-v1
```

## Authentication

The open-source module should support whichever authentication model the merchant application already uses for customer and admin calls. Provider webhooks should use provider-specific verification.

Minimum recommendation:

- Customer reads require ownership of the related order or payment request.
- Admin reads require merchant/admin permission.
- Webhook calls require provider signature verification or configured secret validation.
