Lewati ke konten utama

Multi Staff

Endpoint untuk mengundang, menampilkan, dan mengelola staf merchant. Cocok untuk Badan Usaha yang memerlukan akses bersama (admin, kasir, viewer). Perseorangan dibatasi 1 user (owner) — lihat Quota & Batasan.

Base URL:

EnvironmentHost
Productionhttps://api-merchant.kesles.com
Staginghttps://api-merchant-staging.kesles.com

1. Otorisasi

Seluruh endpoint butuh Bearer token JWT user. RBAC di-enforce oleh middleware withMerchantRole. Lihat merchant-rbac-permission-matrix.md untuk matriks lengkap.

EndpointMethodRole yang diizinkan
/merchant/staff/invitationsPOSTowner
/merchant/staff/invitationsGETowner
/merchant/staff/invitations/{id}DELETEowner
/merchant/staff/invitations/verifyPOST— (deprecated, return 410 Gone)
/merchant/staffGETowner, admin, viewer
/merchant/staff/{user_id}PATCHowner (PIN gate)
/merchant/staff/{user_id}DELETEowner (PIN gate)
/staff/invite/{id}GETpublik (no JWT)
/merchant/staff/invitations/{id}/acceptPOSTauthenticated user (Bearer)
/merchant/staff/invitations/{id}/rejectPOSTauthenticated user (Bearer)

2. Role yang Tersedia

5 role enum di merchant_users.membership_role:

RoleDeskripsi singkat
ownerPemilik merchant. 1 per merchant, auto-assigned saat registrasi. Tidak bisa di-invite atau di-edit-ke.
adminOperasional penuh kecuali transfer ownership.
staffKasir / operasional harian.
viewerRead-only (audit eksternal, reconciliation).
memberAkses minimum (notif only).

3. Quota & Batasan

Limit jumlah user per merchant berdasarkan business_entity_type:

Bentuk UsahaDefault limit (owner + staf)
Perseorangan1 (owner only — tidak bisa invite)
Badan Usaha3 (owner + max 2 staf)

Override limit tersedia via admin Kesles. Pre-check quota dilakukan saat invite (cegah kelebihan undangan) dan saat verify (final guard sebelum row dimasukkan ke merchant_users).

4. Endpoint

4.1 Kirim Undangan

POST /merchant/staff/invitations

Owner mengundang staf via WhatsApp atau Email. OTP 6 digit di-generate server dan dikirim ke channel target. TTL default 10 menit.

Request body

{
"phone_e164": "+628114169868",
"role": "admin",
"channel": "email",
"email": "head@example.com"
}
FieldTipeWajibCatatan
phone_e164stringyaFormat +62… (otomatis di-normalize server-side).
rolestringyaSalah satu: admin, staff, viewer, member. owner ditolak.
channelstringyawhatsapp atau email.
emailstringbersyaratWajib kalau channel = email.

Response 202 Accepted

{
"invitation_id": "0c7d4d12-…",
"expires_at": "2026-05-27T18:28:00+07:00",
"channel": "email"
}

Error responses

HTTPCodePenyebab
400bad_requestphone_e164 kosong / format invalid.
403forbiddenCaller bukan owner.
422user_limit_reachedSlot quota habis. Hubungi admin Kesles untuk upgrade.
409already_memberPhone tsb sudah jadi member merchant.
409invitation_activeSudah ada invitation aktif untuk phone tsb.
400invalid_roleRole tidak ada di enum allowed.
429rate_limitedHourly (10) atau daily (50) limit lampaui.

4.2 Daftar Undangan Tertunda

GET /merchant/staff/invitations

List undangan yang masih tertunda untuk merchant caller. Status "tertunda" = consumed_at IS NULL AND revoked_at IS NULL AND expires_at > now().

Response 200 OK

{
"items": [
{
"id": "0c7d4d12-…",
"phone_e164": "+628114169868",
"email": "head@example.com",
"role": "admin",
"channel": "email",
"expires_at": "2026-05-27T18:28:00+07:00",
"created_at": "2026-05-26T18:28:00+07:00"
}
],
"summary": { "total": 1 }
}

Field email di-return untuk channel email. Untuk channel whatsapp field email umumnya kosong ("").

ORDER BY created_at DESC — undangan terbaru muncul duluan.

Error responses

HTTPCodePenyebab
403forbiddenCaller bukan owner (admin/viewer dst).

4.3 Batalkan Undangan

DELETE /merchant/staff/invitations/{id}

Owner membatalkan undangan tertunda. Setelah revoke, OTP yang sudah terlanjur dikirim ke channel target otomatis tidak valid. Owner bebas mengirim undangan ulang via POST /merchant/staff/invitations.

Path parameter

  • id — UUID invitation.

Response 204 No Content — sukses revoke.

Error responses

HTTPCodePenyebab
400bad_requestPath param id kosong / format invalid.
403forbiddenCaller bukan owner.
404not_foundInvitation tidak ditemukan / sudah consumed / sudah revoked.

4.4 Verifikasi OTP — Deprecated

POST /merchant/staff/invitations/verify

Deprecated

Endpoint ini mengembalikan 410 Gone untuk semua request.

Gunakan flow accept/reject di bawah (§5.1):

  • GET /staff/invite/{id} — info publik tanpa auth
  • POST /merchant/staff/invitations/{id}/accept — terima undangan (Bearer JWT)
  • POST /merchant/staff/invitations/{id}/reject — tolak undangan (Bearer JWT)

4.5 List Staf Aktif

GET /merchant/staff

Daftar member aktif merchant. Dapat diakses oleh role owner, admin, atau viewer.

Response 200 OK

{
"items": [
{
"user_id": "…",
"phone": "+628114169868",
"name": "Budi Suwandi",
"full_name": "Budi Suwandi",
"display_name": "Budi",
"membership_role": "owner",
"is_owner": true,
"joined_at": "2025-08-12T10:00:00+07:00"
}
],
"summary": {
"total": 1,
"by_role": { "owner": 1 }
},
"quota": {
"business_entity_type": "Badan Usaha",
"effective_limit": 3,
"active_member_count": 1,
"pending_unique_invitations": 0,
"can_invite_more": true
}
}

Field name adalah nama tampilan ter-resolve dengan urutan display_namefull_name''. Field full_name dan display_name juga di-return apa adanya untuk kebutuhan label berbeda.

4.6 Ubah Role Staf

PATCH /merchant/staff/{user_id}

Owner ubah role staf non-owner. Memerlukan PIN gate.

Request body

{ "role": "admin" }

Response 204 No Content.

4.7 Hapus Staf

DELETE /merchant/staff/{user_id}

Owner remove staf non-owner. Memerlukan PIN gate. Tolak target owner (409 cannot_remove_owner).

Response 204 No Content.

5. Lifecycle Undangan

[Owner] POST /invitations


[server] generate OTP, kirim via channel


status: pending

├─ [Owner cancel] DELETE /invitations/{id} ────► revoked

├─ [TTL 10 menit habis] ────► expired

└─ [Invitee] GET /staff/invite/{id}


[accept/reject]

├─ accept ──► INSERT merchant_users
└─ reject ──► revoked

Endpoint GET /invitations hanya mengembalikan baris yang masih status pending. Begitu transition ke revoked, expired, atau consumed, baris keluar dari hasil list.

5.1 Endpoint Accept/Reject

GET /staff/invite/{id} — Info Publik (no auth)

Dipakai app untuk menampilkan detail undangan sebelum user login. Tidak mengembalikan data sensitif.

Response 200:

{
"invitation_id": "uuid",
"merchant_name": "Warung Bu Susi",
"inviter_name": "Bu Susi",
"role": "staff",
"role_label": "Staf",
"expires_at": "2026-05-28T10:00:00+07:00"
}

410 Gone — invitation expired atau sudah consumed/revoked.

POST /merchant/staff/invitations/{id}/accept — Terima Undangan

Auth: Bearer JWT (authenticated user, bukan merchant member context).

Otorisasi: target_user_id == authenticated_user.id ATAU phone_e164 == authenticated_user.primary_phone.

Response 200:

{
"merchant_id": "uuid",
"merchant_name": "Warung Bu Susi",
"role": "staff",
"role_label": "Staf",
"joined_at": "2026-05-27T10:30:00+07:00"
}
ErrorHTTPCode
Invitation tidak ditemukan404invitation_not_found
Sudah expired410invitation_expired
Sudah consumed/revoked409invitation_already_used
Bukan untuk user ini403invitation_not_for_you
Quota merchant penuh422user_limit_reached

Idempotent: accept ulang saat sudah member → return 200 existing data.

POST /merchant/staff/invitations/{id}/reject — Tolak Undangan

Auth: Bearer JWT. Otorisasi: sama dengan accept.

Request body (opsional): { "reason": "salah orang" }

Response 200:

{
"invitation_id": "uuid",
"rejected_at": "2026-05-27T10:30:00+07:00"
}

6. Audit Events

Setiap aksi staff/invitation menulis ke log audit.

Event typePemicu
staff_invitation_sentPOST /invitations sukses.
staff_invitation_revokedDELETE /invitations/{id} sukses.
staff_joinedInvitee selesai accept dan bergabung.
staff_role_changedPATCH /staff/{user_id} sukses.
staff_removedDELETE /staff/{user_id} sukses.
staff_invitation_rejectedPOST /invitations/{id}/reject sukses.