# LintasPay — AI / integrator guide

Machine-oriented overview of the LintasPay payment API. Prefer `/docs/` for full field tables and `/swagger` / `/openapi.json` for schemas. Public brand is **LintasPay** only.

---

## 1. What LintasPay is

LintasPay is an Indonesia **IDR** payment API for merchants:

1. **Pay-in** — collect from customers via virtual account (VA), QRIS, DANA, or convenience store
2. **Payout** — disburse to bank accounts, virtual accounts, or e-wallets
3. **Supporting tools** — verify bank / VA / e-wallet account name, query merchant balance, list supported banks & wallets

All traffic is **HTTPS `POST` JSON**. Every merchant request is signed with **RSA SHA256** (`LINTASPAY-SHA256withRSA`). Every response and callback is signed by LintasPay; merchants verify with the **LintasPay platform public key** (`/keys/platform-public-key.pem`).

---

## 2. Transport & envelope

| Topic | Rule |
|-------|------|
| Currency | `IDR` only |
| Amounts | Integer **strings**, e.g. `"10000"` (min pay-in **10000**) |
| Timezone | GMT+7 (e.g. `2026-10-14T10:35:12+07:00`) |
| Method | `POST` only |
| Path style | No `/v1` prefix (e.g. `/pay-in/create`) |
| Content-Type | `application/json` |
| Headers | `Authorization`, `lintaspay-request-id` (unique per request, `[A-Za-z0-9_-]`, max 64) |
| Envelope | `{"code":0,"message":"…","data":{…}}` — success when `code === 0` |
| Auth algorithm | `LINTASPAY-SHA256withRSA` |
| Body limit | 64 KB |
| HTTP notes | `200` = processed (still check `code`); `401` auth fail; `403` IP allowlist; `413` body too large; `429` rate limit |

---

## 3. Authentication / signing (`LINTASPAY-SHA256withRSA`)

### Checklist

1. Build `authString = app_id=…,nonce=…,timestamp=…`  
   - `timestamp` in **milliseconds**  
   - `nonce` unique within ~5 minutes
2. Sign exactly this content (four lines; trailing newline on each):

```text
<authString>
POST
<path+query without domain>
<raw body bytes>
```

3. SHA256withRSA (PKCS#1 v1.5) with the **merchant private key** → Base64 (no line breaks)
4. Header: `Authorization: LINTASPAY-SHA256withRSA <authString>,sign=<base64>`
5. Send a unique `lintaspay-request-id` per request

### Verify platform responses & callbacks

- Response / callback header includes a platform signature (`lintaspay-signature`)
- Verify with LintasPay public key: `/keys/platform-public-key.pem`
- Details: `/docs/signing` · `/docs/verifying` · `/docs/quick-start`

---

## 4. Callable endpoints

All are **POST**, JSON body, signed. Response envelope: `code` / `message` / `data`.

### 4.1 `POST /pay-in/create`

**Purpose:** Create a collection order; returns what the payer uses (`payCode`).

**Request (key fields)**

| Field | Req | Notes |
|-------|-----|-------|
| `productCode` | yes | See product codes table (e.g. `11` BNI VA, `31` QRIS, `41` DANA) |
| `currency` | yes | Must be `IDR` |
| `amount` | yes | IDR integer string; **minimum 10000** |
| `mchOrderNo` | yes | Your unique order no, max 64; reusable only if prior create was rejected |
| `remark` | yes | English, max 255 |
| `userName` | yes | Payer name; **for DANA (41) must be the payer's phone number** |
| `notifyUrl` | no | Callback URL, max 255; else merchant default |
| `expireTime` | no | Seconds; QRIS max 1800; VA default 3600; convenience max 86400 |
| `userPhone` | no | Digits only, max 20 |
| `userEmail` | no | Payer email |
| `payReturnUrl` | no | After-pay redirect (DANA), max 255 |

**Response `data`**

| Field | Notes |
|-------|-------|
| `merchantId` | Your merchant id |
| `merchantOrderNo` | Echo of `mchOrderNo` |
| `orderNo` | LintasPay order number |
| `payCode` | VA number, QRIS string, or DANA payment URL |
| `amount` | Order amount |
| `orderFee` | Fee (2 decimals) |
| `expireAt` | Expiry (GMT+7) |

---

### 4.2 `POST /pay-in/inquiry`

**Purpose:** Query pay-in by LintasPay or merchant order number.

**Request**

| Field | Req | Notes |
|-------|-----|-------|
| `nonceStr` | yes | Random string, max 64 |
| `orderNo` | one of | LintasPay order no (preferred) |
| `mchOrderNo` | one of | Your order no |

Provide **`orderNo` or `mchOrderNo`** (prefer `orderNo`).

**Response `data`**

| Field | Notes |
|-------|-------|
| `merchantId` | |
| `mchOrderNo` | |
| `orderNo` | |
| `amount` | |
| `fee` | Fee (2 decimals) |
| `completionTime` | Final-state time (GMT+7); empty while pending |
| `orderStatus` | `1` paying · `2` success (final) · `3` failed (final) |
| `payCode` | |
| `productCode` | |

---

### 4.3 `POST /pay-out/create`

**Purpose:** Create a disbursement. **Retry with the same `mchOrderNo`** — never a second payout for one intent.

**Request**

| Field | Req | Notes |
|-------|-----|-------|
| `accountName` | yes | Beneficiary name, 1–64: letters, digits, spaces, `.,'-_` |
| `accountNo` | yes | Account / VA / e-wallet phone (`08` / `8` / `628` / `6208`) |
| `accountType` | yes | `1` bank · `2` VA · `3` e-wallet · `4` bank online |
| `amount` | yes | IDR integer string |
| `bankId` | yes | From `/data/bank-list` |
| `currency` | yes | `IDR` |
| `mchOrderNo` | yes | Unique max 64; **reuse on retry** |
| `remark` | yes | Letters, digits, spaces; max 40 |
| `notifyUrl` | no | Callback URL |
| `beneficiaryPhone` | no | |
| `beneficiaryEmail` | no | |

**Response `data`**

| Field | Notes |
|-------|-------|
| `merchantId` | |
| `merchantOrderNo` | |
| `orderNo` | |
| `amount` | |
| `orderFee` | |
| `orderStatus` | `1` created · `2` routing · `3` processing · **`4` success / `5` failed (final only)** |

`code = 0` with `orderStatus = 1` means accepted; outcome may still be pending — wait for callback or inquiry until **4 or 5**.

---

### 4.4 `POST /pay-out/inquiry`

**Purpose:** Query payout status.

**Request:** same shape as pay-in inquiry — `nonceStr` (required) + `orderNo` and/or `mchOrderNo`.

**Response `data`**

| Field | Notes |
|-------|-------|
| `merchantId` | |
| `mchOrderNo` | |
| `orderNo` | |
| `amount` | |
| `fee` | |
| `orderStatus` | 1–5 (only **4 / 5** final) |
| `completionTime` | GMT+7 |
| `failedReason` | Set when `orderStatus = 5` |

---

### 4.5 `POST /pay-out/verify-bank-account`

**Purpose:** Name-check destination before payout. **No callback** — result is in the response.

**Request**

| Field | Req | Notes |
|-------|-----|-------|
| `mchOrderNo` | yes | Unique max 64; **never reusable**, any outcome |
| `accountNo` | yes | Bank 1–30 digits; VA 12–28; e-wallet phone form |
| `bankId` | yes | From `/data/bank-list` |
| `accountType` | no | `1` bank (default) · `2` VA · `3` e-wallet |

**Response `data`**

| Field | Notes |
|-------|-------|
| `merchantId` | |
| `merchantOrderNo` | |
| `orderNo` | |
| `amount` | Always `"0"` |
| `orderFee` | Verification fee (often billed monthly) |
| `orderStatus` | `2` VERIFIED (real holder in `accountName`) · `3` UNVERIFIED (`accountName` empty) |
| `accountNo` | |
| `accountName` | Holder name when verified |

---

### 4.6 `POST /finance/query-balance`

**Purpose:** Merchant balance. May be **disabled** per merchant (`10011430`).

**Request:** empty object `{}`.

**Response `data`**

| Field | Notes |
|-------|-------|
| `merchantId` | |
| `availableBalance` | |
| `frozenBalance` | |
| `pendingSettlementAmount` | |
| `totalBalance` | |
| `currency` | `IDR` |

---

### 4.7 `POST /data/bank-list`

**Purpose:** Supported banks / wallets for `bankId`.

**Request**

| Field | Req | Notes |
|-------|-----|-------|
| `type` | yes | `1` banks & VA · `2` e-wallets |

**Response `data`**

| Field | Notes |
|-------|-------|
| `total` | Count |
| `list[]` | `{ bank_id, bank_name, type }` — use `bank_id` as `bankId` |

---

## 5. Callbacks (not client-callable)

Callbacks are **inbound to the merchant**. They are **not** a third create/query API.

| Topic | Rule |
|-------|------|
| When | Order reaches a **final** state |
| How | LintasPay `POST`s a signed body to the order `notifyUrl` (or merchant default) |
| ACK | HTTP 200 within ~5s with body exactly `{"status":"SUCCESS"}` |
| Verify | Check platform signature before trusting the body |
| Idempotency | Handler must tolerate duplicates |
| Retries | On missing/invalid ACK: **15 s, 1 min, 5 min, 15 min, 30 min, 1 h, 2 h, 6 h**, then stop |
| URL rules | Production `https`; public IP; max 255 chars |

**Final statuses that trigger notify**

| Product | Final `orderStatus` |
|---------|---------------------|
| Pay-in | **2** success · **3** failed |
| Payout | **4** success · **5** failed only |
| Verify | No callback |

Docs: `/docs/callbacks` · `/docs/payin-notify` · `/docs/payout-notify` · `/docs/payout-final-state`

Use **inquiry** for reconciliation; callbacks are the fast path, not the only source of truth.

---

## 6. Status & enum tables

### Pay-in `orderStatus`

| Value | Meaning | Final |
|------:|---------|:-----:|
| 1 | Paying (waiting for payer) | No |
| 2 | Success | **Yes** |
| 3 | Failed / expired | **Yes** |

### Payout `orderStatus`

| Value | Meaning | Final |
|------:|---------|:-----:|
| 1 | Created | No |
| 2 | Routing | No |
| 3 | Processing | No |
| 4 | Success | **Yes** |
| 5 | Failed | **Yes** |

### Verify `orderStatus`

| Value | Meaning |
|------:|---------|
| 2 | VERIFIED — `accountName` is the real holder |
| 3 | UNVERIFIED — `accountName` empty |

### Payout `accountType`

| Value | Meaning |
|------:|---------|
| 1 | Bank account |
| 2 | Virtual account |
| 3 | E-wallet |
| 4 | Bank online (create payout only) |

### Bank list `type`

| Value | Meaning |
|------:|---------|
| 1 | Banks & virtual accounts |
| 2 | E-wallets |

---

## 7. Pay-in product codes

Use as `productCode` in `/pay-in/create`. Minimum amount **10000 IDR**.

| Code | Product | Type | Notes |
|-----:|---------|------|-------|
| 11 | BNI VA | Virtual account | Default expiry ~1 h |
| 12 | CIMB VA | Virtual account | |
| 13 | Mandiri VA | Virtual account | |
| 14 | BRI VA | Virtual account | |
| 15 | BSS VA | Virtual account | |
| 31 | QRIS | QR code | `payCode` = QRIS string; max expiry 30 min |
| 41 | DANA | E-wallet (redirect) | `userName` = payer phone; `payCode` = payment URL |
| 61 | Alfamart | Convenience store | Max expiry 1 day |
| 62 | Pegadaian | Convenience store | |
| 63 | Pos Indonesia | Convenience store | |
| 64 | Alfamidi | Convenience store | |
| 65 | Dandan | Convenience store | |

---

## 8. Recommended agent workflow

1. Read **`/ai.md`** (this file) for orientation
2. Open `/docs/quick-start` and `/docs/signing`
3. Fetch platform key: `/keys/platform-public-key.pem`
4. Use `/swagger` only if a human pastes sandbox credentials (do not exfiltrate keys)
5. Implement: create → show `payCode` or disburse → handle callbacks → inquiry for reconciliation
6. Do not invent polling-only designs that ignore callbacks
7. Do not invent endpoints, metrics, or upstream provider names

### Links on this host

| URL | Content |
|-----|---------|
| `/` | Marketing homepage |
| `/docs/` | Human API docs (guides, endpoints, CB notifications) |
| `/swagger` | OpenAPI try-it UI (no trailing slash) |
| `/ai.md` | This AI / integrator guide (single AI doc) |
| `/keys/platform-public-key.pem` | LintasPay public key for verifying responses & callbacks |
| `/openapi.json` | OpenAPI specification |

---

## Out of scope

Do not mention upstream providers, invent live metrics, or invent endpoints not listed here or under `/docs/`.
