# Pay-by-link and bank feeds

> Create payment links for invoices, receive gateway confirmations, and reconcile bank transactions with automatic match suggestions.

Source: /docs/guides/payments-and-bank-feeds

## Payment gateways

Gateways are configured in the app under **Settings → Payments** (owners and admins): Stripe, Telr, PayTabs, Tap, or the built-in **mock** gateway for development. `GET /payment-gateways` lists them without credentials and returns the supported providers with their credential fields.

Each gateway has `mode` = `test` or `live`. Test documents can only use test-mode or mock gateways.

## Create a payment link

```bash
curl -X POST "$BASE/invoices/$INVOICE_ID/payment-link" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
  -d '{ "gatewayId": null, "expiresInDays": 30 }'
```

Rules enforced by the database function behind it:

- the invoice must be issued (not `draft`), not a credit note, and have an outstanding amount;
- `gatewayId` defaults to the organization's default active gateway;
- a pending, unexpired link for the same invoice is returned instead of creating a second one.

The response contains `url` (`{APP_URL}/pay/{token}`), `amount`, `currency`, `status`, `expiresAt`. Share the URL or let Alya Books add it: while a pending link exists, `POST /invoices/{id}/send` adds a **Pay now** button to the email.

`GET /invoices/{id}/payment-links` lists an invoice's links; `GET /payment-links?status=pending` pages all of them; `POST /payment-links/{id}/cancel` cancels a pending link.

## Settlement

The public pay page calls the `pay-link` function, which asks the gateway for a checkout session and redirects the buyer. Confirmation arrives on `payment-webhook?provider=<provider>&org=<organizationId>`, verified with the gateway's signature (Stripe `Stripe-Signature`, Telr and Tap HMAC secrets, PayTabs server key). The `settle_payment_link` function then:

1. marks the link `paid` with the provider reference (idempotent: a repeated notification is ignored);
2. inserts a payment on the invoice with `method: online`, `source: gateway`;
3. updates `paidAmount` / `paymentStatus` and emits `payment_link.paid` (and `invoice.paid` when fully settled).

If the buyer returns before the webhook fired, the pay page reconciles by fetching the status from the gateway. The mock gateway settles through **Simulate payment** on the page.

## Bank feeds

Transactions arrive from a live connection (Lean Technologies, Tarabut, or the mock feed; synced twice a day and on demand) or from statement rows you post yourself:

```bash
curl -X POST "$BASE/bank/import" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{
  "currency": "AED",
  "rows": [
    { "Date": "2027-02-03", "Description": "TRF INV-2027-00012 ACME TRADING", "Credit": "5250.00", "Debit": "", "Reference": "FT27034ABC" }
  ]
}'
```

Rows are objects keyed by the bank's own column names; Alya Books recognises date, amount or debit/credit, description, reference, counterparty name and IBAN in the usual spellings. A row id is derived from date, amount and text, so re-importing overlapping statements never duplicates. The response counts `imported`, `duplicates`, `suggested`, `autoMatched`.

`POST /bank/sync` pulls the live connections (`connectionId` to restrict) and, with `rematch: true`, rescores everything still open.

## Matching

For every transaction the matcher scores open documents in the same direction (credits against issued invoices, debits against accepted incoming invoices):

| Signal | Score |
| --- | --- |
| Invoice number found in description/reference | +0.6 |
| Amount equals the outstanding balance | +0.3 (invoice total +0.2, partial +0.1) |
| Counterparty name overlaps the customer/supplier name | up to +0.2 |
| TRN in the text | +0.2 |
| Booked within 60 days after the issue date | +0.05 |

Candidates ≥ 0.5 make the transaction `suggested` (top five kept in `suggestions`); ≥ 0.9 with a clear lead over the runner-up is `matched` automatically when the organization's `bankAutoMatch` switch is on. `GET /bank/transactions?status=suggested` pages transactions with their candidates.

Confirm, change or undo:

```bash
curl -X POST "$BASE/bank/transactions/$TXN_ID/match" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{ "invoiceId": "…" }'
curl -X POST "$BASE/bank/transactions/$TXN_ID/unmatch" -H "X-API-KEY: $KEY"
curl -X POST "$BASE/bank/transactions/$TXN_ID/ignore" -H "X-API-KEY: $KEY"
```

A confirmed match inserts a payment (`source: bank_feed`, the bank reference, the transaction amount unless `amount` is given) and emits `bank.transaction_matched`; unmatching deletes that payment and returns the transaction to `unmatched`.

## MCP and CLI

MCP: `create_payment_link`, `list_payment_links`, `cancel_payment_link`, `list_bank_transactions`, `match_bank_transaction`, `ignore_bank_transaction`, `import_bank_statement`, `sync_bank_feeds`. CLI: `alyabooks pay-link create <invoiceId>`, `alyabooks pay-link list --status pending`, `alyabooks bank transactions --status suggested`, `alyabooks bank match <txnId> --invoice-id <id>`, `alyabooks bank import -f rows.json`, `alyabooks bank sync --rematch`.
