# ioTec Pay Payment Gateway

Storify integrates [ioTec Pay](https://pay.iotec.io) for Ugandan **mobile
money** (MTN MoMo / Airtel Money) and **card** collections, with server-side
status verification and an idempotent finalizer shared by polling and webhooks.

Amounts are charged in **UGX**, which is a zero-decimal currency — whole
shillings only, minimum **500**. Set the store default currency to UGX in
Admin > Settings > General; checkout rejects an ioTec payment in any other
currency rather than mis-denominating the charge.

## Configuration

Configure ioTec Pay from Admin > Settings > Payments, or use environment
variables:

```env
IOTEC_MODE=sandbox
IOTEC_CLIENT_ID=your-iotec-client-id
IOTEC_CLIENT_SECRET=your-iotec-client-secret
IOTEC_WALLET_ID=your-iotec-wallet-uuid
```

Database values take precedence over environment values. The client secret is
server-only and is removed from admin and public API responses. The wallet ID
is found in the ioTec Pay portal under your wallet settings.

Use **Test connection** in Admin > Settings > Payments to confirm the selected
environment authenticates (it requests an OAuth token from
`https://id.iotec.io/connect/token`).

## Authentication

ioTec uses the OAuth2 **client-credentials** grant. Tokens are short-lived
(~300s) and cached for their advertised lifetime, so a collection is one round
trip rather than two; a rejected token is refreshed and the call retried once.
All API requests send `Authorization: Bearer <token>`.

## Payment Lifecycle

### Mobile money (default)

1. The customer selects **ioTec Pay** at checkout, picks the **Mobile money**
   channel, and enters their MTN/Airtel number. The number is normalized to
   MSISDN format (e.g. `0772…` → `256772…`).
2. Checkout stores the local order as **pending** with an `externalId` used for
   reconciliation, *then* calls `POST /api/collections/collect` and writes the
   returned transaction ID back onto the order. The order is persisted first on
   purpose: the collection puts a PIN prompt on the payer's phone immediately
   and ioTec has no refund API, so a failure after the charge must never leave
   a payment with no order behind it. If the collection never starts, the order
   is cancelled.
3. A PIN prompt appears on the customer's phone.
4. There is **no redirect** — the customer is sent to the checkout success
   page, which polls `/api/payments/iotec/verify` until the transaction is
   `Success`, `Failed`, or still pending. Polling runs ~30 times at 3s
   intervals (~90s) because the payer must enter a PIN.
5. On `Success`, the finalizer marks the order paid, records the payment
   transaction, applies coupon usage, decrements inventory or reserves the
   preorder, clears the cart, sends notifications, and emails a confirmation —
   without processing duplicate events twice.

### Card

1. Selecting the **Card** channel at checkout calls
   `POST /api/collections/collect/card`, which returns a `cardRedirectUrl`.
2. The customer is redirected to ioTec's hosted card page, then back to the
   checkout success page (`?iotec_external_id=…`), where the same verify
   polling confirms the payment.

## Webhook (optional)

ioTec can notify Storify server-to-server when a transaction resolves,
eliminating the wait for polling. This is configured in the **ioTec Pay
portal**, not via the API:

1. In the portal, open your wallet's **Callback URLs** section.
2. Add a **Collection** callback pointing to
   `https://your-domain.com/api/payments/iotec/callback`.
3. Optionally set security headers in the portal.

The callback endpoint accepts both GET and POST. It never trusts the callback
body — it always re-fetches the authoritative transaction status from ioTec
before updating an order, and shares the same idempotent finalizer as the
verify route. References that match no order are rejected before that round
trip is spent; the finalizer falls back to the `externalId` so a callback that
arrives before the transaction ID lands on the order still resolves.

## Refunds

ioTec Pay collections do **not** support programmatic refunds. Refunds for
ioTec orders must be issued manually from the ioTec Pay portal; Storify records
the refund transaction but does not call the gateway.

## Go Live

1. Complete ioTec merchant production onboarding and obtain live credentials
   and a live wallet ID.
2. Set `IOTEC_MODE=live` (or switch the mode to Live in Admin > Settings >
   Payments) and enter the live Client ID, Client Secret, and Wallet ID.
3. Confirm the store default currency is UGX.
4. Use **Test connection** to verify the live environment.
5. (Optional) Register the production callback URL in the ioTec portal.

## Files

- `lib/iotec.ts` — API client (token, collect, card collect, status, state
  mapping, MSISDN normalization).
- `lib/iotec-orders.ts` — `finalizeIotecOrder`, the idempotent order finalizer.
- `app/api/payments/checkout/route.ts` — the `iotec` checkout branch.
- `app/api/payments/iotec/verify/route.ts` — client-polled verification.
- `app/api/payments/iotec/callback/route.ts` — server-to-server webhook.
