# Panduan API Merchant E-Sign — OTT

Panduan ini untuk tim merchant yang membangun aplikasi B2C/B2B sendiri. Semua request publik lewat API Gateway. Jangan pernah memanggil database atau service internal Tandara.

**Metode integrasi resmi:** OTT. OAuth client credentials dan HMAC bukan flow merchant pada panduan ini.

## 1. Yang Disediakan Platform

Superadmin menyiapkan satu application merchant dengan metode `OTT`, quota, dan bila diperlukan webhook. Merchant menerima nilai satu-kali:

- `client_id`
- `key_id`

Seluruh API merchant berikut memakai header:

```http
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}
```

Jangan tambahkan `Authorization: Bearer`, OAuth secret, atau signature HMAC pada flow OTT.

## 2. Konvensi Wajib

| Hal | Aturan |
| --- | --- |
| Base URL | `{{gateway_base_url}}` |
| Content type | `application/json`, kecuali upload PDF |
| Idempotency | Wajib untuk create document, create signer, send, placement, dan signature asset |
| Retry | Pakai `Idempotency-Key` yang sama hanya untuk retry request bisnis yang sama |
| Data rahasia | Jangan log credential, OTP, `signer_token`, signing URL, presigned URL, atau webhook secret |
| PDF | Hanya `application/pdf`; maksimum 25 MiB |
| Waktu | Semua waktu diperlakukan UTC/RFC3339 |

Contoh header command:

```http
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}
Idempotency-Key: {{uuid_baru}}
Content-Type: application/json
```

## 3. Flow Merchant End-to-End

```mermaid
sequenceDiagram
    participant M as Aplikasi Merchant
    participant G as API Gateway
    participant S as Signer / B2C UI

    M->>G: Validasi OTT
    M->>G: Buat document
    M->>G: Upload PDF ke presigned URL
    M->>G: Konfirmasi upload sampai READY
    M->>G: Buat signer dan policy OTP
    M->>G: Kirim invitation
    G-->>S: Signing URL
    S->>G: Open session, preview, placement, OTP, consent, sign
    M->>G: Poll signing status atau terima webhook
```

## 4. Validasi Credential

```http
GET /api/v1/context
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}
```

Gunakan endpoint ini saat bootstrapping integrasi atau pemeriksaan koneksi. Respons berisi konteks merchant/application. Jangan mengambil `merchant_id` dari input pengguna untuk menentukan akses data.

## 5. Buat Dokumen

```http
POST /api/v1/documents
Idempotency-Key: document-create-{{uuid}}
Content-Type: application/json
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}

{
  "external_reference": "ORDER-2026-0001",
  "title": "Perjanjian Kerja Sama",
  "document_type": "AGREEMENT",
  "original_filename": "perjanjian.pdf",
  "content_type": "application/pdf",
  "size_bytes": 123456,
  "expires_in_hours": 72
}
```

Simpan `document_id`, `upload.url`, `upload.required_headers`, dan `upload.expires_at`. `external_reference` harus dapat ditelusuri dari sistem merchant sendiri.

Status awal: `UPLOAD_PENDING`.

## 6. Upload PDF dan Validasi

Upload langsung ke `upload.url` yang dikembalikan create document. Request upload **tidak** memakai header OTT.

```http
PUT {{upload.url}}
Content-Type: application/pdf

<binary PDF>
```

Lalu konfirmasi:

```http
POST /api/v1/documents/{{document_id}}/upload-complete
Idempotency-Key: document-upload-complete-{{uuid}}
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}
```

Lanjut ke signer hanya setelah status dokumen `READY`. Jika `409`, ambil ulang detail atau status dokumen; jangan membuat document baru hanya karena request retry.

Endpoint baca:

| Endpoint | Kegunaan |
| --- | --- |
| `GET /api/v1/documents/{document_id}` | Metadata dokumen dan state saat ini |
| `GET /api/v1/documents/{document_id}/status` | Status upload/dokumen |
| `POST /api/v1/documents/{document_id}/cancel` | Batalkan sebelum flow selesai |

## 7. Buat Signer dan Kebijakan OTP

```http
POST /api/v1/documents/{{document_id}}/signers
Idempotency-Key: signers-create-{{uuid}}
Content-Type: application/json
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}

{
  "ordered_flow": true,
  "otp_policy": {
    "primary_channel": "EMAIL",
    "fallback_channels": []
  },
  "signers": [
    {
      "signer_id": "customer-001",
      "display_name": "Nama Signer",
      "email": "signer@example.com",
      "order": 1
    }
  ]
}
```

- `ordered_flow: true`: signer mengikuti nilai `order`.
- `ordered_flow: false`: signer dapat berjalan paralel.
- `primary_channel`: `EMAIL`, `SMS`, atau `WHATSAPP` sesuai channel yang platform aktifkan.
- `fallback_channels`: dipakai bila channel utama gagal.
- `signer_id`: ID stabil dari sistem merchant; jangan gunakan email sebagai ID.

### Placement sebelum invitation

Merchant dapat mengirim `fields` dalam setiap signer bila placement sudah diketahui:

```json
"fields": [{
  "type": "SIGNATURE",
  "page": 1,
  "x": 70,
  "y": 650,
  "width": 180,
  "height": 60
}]
```

Koordinat hanya ditampilkan di UI merchant; jangan tampilkan koordinat mentah ke signer bila tidak diperlukan.

## 8. Kirim Invitation

```http
POST /api/v1/documents/{{document_id}}/send
Idempotency-Key: signing-send-{{uuid}}
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}
```

Respons mengandung signer session/signing URL bila tersedia. Signing URL adalah kredensial akses sementara: hanya kirim ke signer yang berhak dan jangan masukkan ke log analytics.

Jangan resend otomatis. Cek `signing-status` dan delivery log dashboard. Resend hanya ketika delivery gagal atau link benar-benar expired.

## 9. Implementasi UI Signer B2C

Aplikasi merchant boleh membuat UI sendiri. Semua endpoint di bawah memakai `signer_token` dari signing URL, **bukan** header OTT.

| Tahap | Endpoint | Catatan |
| --- | --- | --- |
| Buka sesi | `GET /sign/v1/sessions/{signer_token}` | Menampilkan status, signer, channel OTP ter-mask, consent, expiry |
| Preview PDF | `GET /sign/v1/sessions/{signer_token}/document-preview` | Render PDF di UI merchant |
| Satu placement | `PUT /sign/v1/sessions/{signer_token}/field-placement` | Wajib `Idempotency-Key` |
| Banyak placement | `PUT /sign/v1/sessions/{signer_token}/field-placements` | Maksimum 12 placement per request |
| Minta OTP | `POST /sign/v1/sessions/{signer_token}/otp/request` | Jangan tampilkan tujuan OTP lengkap |
| Verifikasi OTP | `POST /sign/v1/sessions/{signer_token}/otp/verify` | Body `{ "otp": "123456" }` |
| Upload signature asset | `POST /sign/v1/sessions/{signer_token}/signature-assets` | Wajib `Idempotency-Key` |
| Setuju consent | `POST /sign/v1/sessions/{signer_token}/consent` | Kirim `consent_version` dari sesi |
| Tanda tangan | `POST /sign/v1/sessions/{signer_token}/sign` | Kirim `signature_asset_id` |
| Tolak | `POST /sign/v1/sessions/{signer_token}/decline` | Kirim alasan penolakan |

Contoh multi-placement:

```http
PUT /sign/v1/sessions/{{signer_token}}/field-placements
Idempotency-Key: placement-{{uuid}}
Content-Type: application/json

{
  "placements": [{
    "field_id": "signature-1",
    "type": "SIGNATURE",
    "page": 1,
    "x": 80,
    "y": 600,
    "width": 180,
    "height": 60
  }]
}
```

Urutan UI signer:

1. Open session dan cek `expires_at`.
2. Render preview PDF.
3. Minta/set placement bila belum ditentukan merchant.
4. Minta OTP.
5. Verifikasi OTP.
6. Tampilkan dan terima consent bila `consent_required`.
7. Register signature asset.
8. Submit signature.
9. Tampilkan hasil `accepted`; backend memfinalisasi secara asynchronous.

## 10. Pantau Status, Final PDF, dan Cancel

```http
GET /api/v1/documents/{{document_id}}/signing-status
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}
```

Alternatif lebih rinci:

```http
GET /api/v1/signing-requests/{{signing_request_id}}
```

Status terminal yang harus ditangani UI merchant: `COMPLETED`, `FAILED`, `CANCELLED`, `EXPIRED`, atau `DECLINED` bila dikembalikan oleh status workflow.

> **Batasan API saat ini:** endpoint final-PDF download publik OTT belum diekspos di API Gateway. Endpoint `final-download` saat ini hanya dipakai jalur admin. Merchant harus memakai data/status yang tersedia sambil endpoint publik final download ditambahkan. Jangan mencoba memanggil endpoint internal/admin dengan credential OTT.

Pembatalan signing request tersedia:

```http
POST /api/v1/signing-requests/{{signing_request_id}}/cancel
X-Client-Id: {{ott_client_id}}
X-Key-Id: {{ott_key_id}}
```

## 11. Webhook

Daftarkan webhook melalui dashboard merchant/superadmin untuk event:

- `signing.completed.v1`
- `signing.failed.v1`
- `signing.cancelled.v1`
- `signing.expired.v1`
- `signer.declined.v1`

Aturan consumer webhook:

1. Verifikasi signature menggunakan secret webhook.
2. Simpan event ID untuk deduplikasi.
3. Balas HTTP `2xx` hanya setelah event tersimpan/masuk queue internal merchant.
4. Jangan mengasumsikan urutan event.
5. Bila status lokal berbeda, ambil ulang `signing-status`.

## 12. Error dan Retry

| Status | Arti | Tindakan |
| --- | --- | --- |
| `400` | Payload/header tidak valid | Perbaiki payload; jangan retry otomatis |
| `401` | Credential OTT tidak valid | Periksa `client_id` dan `key_id`; jangan kirim secret ke log |
| `402` | Quota tidak cukup | Hentikan flow; hubungi admin merchant/superadmin |
| `404` | Document/signer session tidak ada atau expired | Tampilkan state aman; jangan buat ulang tanpa cek `external_reference` |
| `409` | Konflik state/idempotency | Ambil status terbaru; retry hanya dengan idempotency key yang sama bila request sebelumnya tidak pasti |
| `410` | Upload/link kedaluwarsa | Buat upload baru sesuai state dokumen |
| `5xx` | Gangguan sementara | Retry eksponensial dengan jitter dan idempotency key yang sama |

## 13. Checklist Produksi

- [ ] Credential OTT disimpan di secret manager.
- [ ] Semua command memakai `Idempotency-Key`.
- [ ] PDF upload memakai actual `size_bytes`.
- [ ] Polling berhenti pada status terminal.
- [ ] Webhook diverifikasi dan idempotent.
- [ ] `signer_token` tidak berada di URL analytics, log, screenshot, atau local storage persisten.
- [ ] OTP tidak pernah diteruskan ke backend merchant selain request verify langsung ke gateway.
- [ ] Error UI tidak menampilkan secret atau raw response upstream.

## Postman dan Referensi

- Collection: `docs/postman/esign-b2b-complete-ott.postman_collection.json`
- Environment contoh: `docs/postman/esign-b2b-local-sandbox.postman_environment.json`
- OpenAPI Gateway: `services/api-gateway/docs/openapi/openapi.yaml`
- Ringkasan singkat: `docs/integration/merchant-ott.md`
