IPL V2 · Rancangan Fitur

Add-on Marketplace Architecture & Flow

Marketplace layanan pihak ketiga yang dikurasi Nuratech: PPOB (pulsa, token, BPJS, PDAM), vendor fisik (gas/galon/jasa), langganan tenant-wide & selektif per unit, serta connected service (gate & CCTV). Klik tab flow lalu tekan Play; toggle mode untuk melihat event bus Fase 1 (outbox) vs Fase 2 (NATS JetStream). Rancangan database, katalog event, dan state machine ada di bawah diagram.

Pengguna (FE) Service existing Service BARU Database Pihak ketiga Event bus / Scheduler

Shortcuts: Space play/pause · ←/→ step · 1–3 flow · O mode · T theme · F fullscreen · R reset layout · Drag nodes to reposition

Flow · Kurasi Katalog
0 / 0
Superadmin Nuratech
Portal Global Control
6 layar Add-ons
·
Operator Tenant
Portal Operator
6 layar Marketplace
·
Scheduler
goroutine di addon-control
tgl 1 · tiap 5 mnt
·
Warga (App Mobile)
Flutter · menu Layanan
9 layar
·
platform-control
BFF superadmin · invoice
:8083 · gRPC 9091
·
operator-control
BFF operator · tagihan IPL
:8080 · gRPC 9092
·
ipl_app
resident_bill_items · inbox
PostgreSQL
·
user-control
BFF mobile · push FCM
:8081 · gRPC 9093
·
ipl_platform
addon_* (BARU) · outbox
PostgreSQL
·
addon-control (BARU)
katalog · order · langganan · komisi
:8084 · gRPC 9095
·
payment-control
DOKU checkout · split SAC
:8082 · gRPC 9092
·
Outbox Relay
tabel outbox → gRPC EventSink
poll 1 dtk
·
Vendor & Connected Svc
gas/galon · ISP · SmartGate · CCTV
WA manual / REST
·
Aggregator PPOB
Konekta · Nusantara Pulsa
REST + callback
·
DOKU
VA · QRIS · e-wallet
checkout + webhook
·
01 · Keputusan arsitektur

Kenapa service baru addon-control?

Batas tanggung jawab

  • addon-control (BARU) menangani provider, katalog, kurasi, harga, aktivasi, order, integrasi PPOB/vendor, langganan, ledger, dan komisi. Database: tabel addon_* di ipl_platform, sehingga tetap 2 DB.
  • platform / operator / user-control hanya menjadi BFF untuk FE masing-masing, jadi FE tidak butuh base URL baru, dan auth, RBAC, serta routing workspace_deployments tetap dipakai.
  • payment-control dipakai ulang untuk DOKU. Kontraknya ditambah source_type, splits, dan refund.
  • operator-control tetap satu-satunya penulis tagihan IPL (ipl_app).

Alasan dipisah (bukan ditumpuk di platform)

  • Integrasi pihak ketiga (PPOB, vendor, gate) punya timeout, retry, dan kredensial sendiri. Jika dipisah, gangguan provider tidak ikut menjatuhkan login gateway di platform.
  • Beban transaksi retail berbeda pola dengan admin platform, sehingga bisa di-scale terpisah.
  • Alternatif yang lebih murah: jadikan modul internal/application/addon di platform-control, dengan catatan harus dipecah nanti. Perlu keputusan tim (lihat §05).

Event-driven: 2 fase

  • Fase 1 (tanpa infra baru): transactional outbox di Postgres, relay goroutine mengirim via gRPC EventSink/Deliver, dan konsumen mencatat event_inbox.
  • Fase 2: relay publish ke NATS JetStream (sejalan dengan riset panic button). Kode handler konsumen tidak berubah; yang diganti hanya transport-nya.
02 · Database diagram

Tabel baru & perubahan tabel existing

Semua tabel BARU dimiliki addon-control di ipl_platform. Referensi lintas DB (workspace_id, unit_id, resident_user_id) disimpan sebagai UUID tanpa foreign key dan divalidasi lewat gRPC, mengikuti pola split DB yang sudah berjalan. Nilai uang memakai numeric(14,2) dan string desimal di proto.

erDiagram
  addon_providers ||--o{ addon_products : "menyediakan"
  addon_providers ||--o{ addon_product_sync_runs : "log sync"
  addon_categories ||--o{ addon_offerings : "mengelompokkan"
  addon_offerings ||--o{ addon_products : "berisi SKU"
  addon_offerings ||--o{ addon_workspace_activations : "diaktifkan tenant"
  addon_providers {
    uuid id PK
    varchar code UK "konekta-ppob"
    varchar name
    varchar type "ppob_aggregator|physical_vendor|connected_service|isp"
    varchar credential_ref "nama secret, BUKAN kredensial"
    varchar contact_name
    varchar contact_phone
    varchar status "draft|active|inactive"
    timestamptz last_synced_at
    timestamptz deleted_at
  }
  addon_product_sync_runs {
    uuid id PK
    uuid provider_id FK
    varchar status "running|success|partial|failed"
    int total_items
    int failed_items
    jsonb errors
    timestamptz started_at
    timestamptz finished_at
  }
  addon_categories {
    smallint id PK
    varchar code UK "PULSA|TOKEN_LISTRIK|PPOB|INTERNET|KEAMANAN|JASA_FISIK"
    varchar name
    int sort_order
  }
  addon_offerings {
    uuid id PK
    varchar key UK "PULSA_DATA, CCTV_CERDAS"
    smallint category_id FK
    varchar name
    text description
    text icon_url
    varchar_array allowed_modes "retail|tenant_wide|selective"
    varchar billing_unit "per_trx|per_unit_month|per_area_month|per_card|per_visit"
    varchar fulfillment_type "ppob_api|manual_vendor|connected_service|provisioning"
    numeric max_operator_fee_pct "batas atas, nullable"
    jsonb public_config "aman untuk mobile"
    varchar status "draft|published|archived"
  }
  addon_products {
    uuid id PK
    uuid offering_id FK
    uuid provider_id FK
    varchar sku UK "PLN-TOK-050"
    varchar provider_sku "UK(provider_id, provider_sku)"
    varchar name
    varchar price_type "one_off|recurring_monthly|postpaid_inquiry"
    numeric base_price "null utk postpaid (BPJS/PDAM)"
    varchar platform_fee_type "flat|percent"
    numeric platform_fee_value
    varchar status "draft|active|archived"
    text curation_note "internal"
    uuid approved_by
    timestamptz approved_at
    timestamptz synced_at
  }
  addon_workspace_activations {
    uuid id PK
    uuid workspace_id "UK(ws, offering, mode)"
    uuid offering_id FK
    varchar mode "retail|tenant_wide|selective"
    varchar state "active|paused|stopped"
    varchar operator_fee_type "flat|percent"
    numeric operator_fee_value
    boolean show_in_mobile
    uuid activated_by
    timestamptz activated_at
    timestamptz updated_at
  }
03 · Katalog event

Event yang diterbitkan & siapa yang mendengar

Envelope standar: { id (UUIDv7), type, source, occurred_at, workspace_id, trace_id, data }. Pengiriman bersifat at-least-once, jadi setiap konsumen wajib idempoten lewat event_inbox(consumer, event_id). Fase 2 memakai subject yang sama dengan type, dengan stream ADDON (addon.>) dan PAYMENT (payment.>).

EventProducerConsumerEfek
addon.product.published / .price_changed / .archivedaddonoperator (cache), user (cache)Katalog tenant & harga mobile ter-refresh
addon.activation.changedaddonuserInvalidasi mobile:addons:{ws}
payment.order.paidpaymentaddon (filter source_type=addon_order), user/operator (IPL, existing)Order → fulfilling
payment.order.expiredpaymentaddonOrder → expired
addon.order.fulfilledaddonuserNotifikasi + push ke pembeli
addon.order.refund_requiredaddonpayment, (Telegram ops)Buat payment_refunds
payment.refund.completedpaymentaddon, userOrder → refunded, ledger reversal, push
addon.subscription.activated / .units_changed / .stoppedaddonuserEntitlement unit (Layanan Aktif, Gate/CCTV)
addon.charge.resident_bill_requestedaddonoperatorTempel resident_bill_items
operator.bill_item.attachedoperatoraddonCharge → posted (+ external_ref)
04 · Kontrak & endpoint

Perubahan protobuf & API

Protobuf (ipl-v2-protobuff/ipl/v1)

  • BARU addon.proto: AddonAdminService (dipanggil platform), AddonWorkspaceService (operator), AddonResidentService (user).
  • BARU events.proto: EventEnvelope + EventSink/Deliver (Fase 1).
  • UBAH payment.proto: CreatePaymentRequest + source_type, source_id, splits; RPC CreateRefund.
  • UBAH platform.proto: RPC UpsertAddonInvoiceLines.
  • Sesuai AGENTS.md, stub hanya disalin ke service pemakai: addon.pb.go ke addon, platform, operator, dan user; payment.pb.go ke payment, user, operator, dan addon.

Endpoint HTTP BFF (Swagger wajib di-regenerate)

  • platform /api/v1/admin/addons/…: providers, products, orders, fulfillment, commission-report.
  • operator /api/v1/addons/…: catalog, {key}/retail, {key}/subscriptions, {key}/assign-units, orders. Resource RBAC baru addons (read, update, subscribe) di workspace_role_permissions.
  • user /api/v1/me/addons/…: config, inquiry, orders, access, gate/open, cctv stream.
  • addon /webhooks/ppob/{provider}: callback status PPOB (verifikasi signature).
05 · Butuh keputusan PM

Pertanyaan terbuka

  1. Service baru atau modul di platform? Rekomendasi: service baru addon-control.
  2. Siapa menanggung MDR DOKU (VA/QRIS/e-wallet) untuk transaksi add-on: warga, operator, atau Nuratech? Ini memengaruhi rumus "biaya layanan".
  3. Refund otomatis atau via approval ops? VA tidak bisa direfund otomatis lewat API.
  4. Add-on selektif ditagih ke warga lewat tagihan IPL. Apakah Nuratech menagih biaya pokoknya ke operator lewat invoice bulanan (operator menanggung risiko warga tidak bayar)?
  5. Tagihan IPL yang sudah paid/void saat charge selektif datang: pindahkan ke periode berikutnya (usulan) atau buat tagihan susulan?
  6. Batas maksimum Operator Fee per offering: perlu atau bebas?
  7. Operator sebagai pembeli retail (terlihat di Monitoring Order: "Pak Dedi Operator") dibayar dari kas tenant atau pribadi? Apakah butuh alur approval?
  8. Rating layanan (ada di dokumen desain lama) masuk MVP atau fase berikutnya?
06 · Rencana rilis

Usulan urutan pengerjaan

  1. Fondasi: scaffold addon-control, migrasi tabel addon_*, addon.proto/events.proto, outbox, relay, dan inbox.
  2. Katalog: provider, sync PPOB (1 aggregator dulu), kurasi, dan layar platform 01–03.
  3. Retail PPOB end-to-end: aktivasi operator, mobile config, inquiry, order, payment source_type + split SAC, fulfillment, push, riwayat.
  4. Monitoring & vendor fisik: Monitoring Order, antrian fulfillment manual, refund.
  5. Langganan: tenant-wide (invoice platform) dan selektif (bill items), plus scheduler bulanan.
  6. Komisi & dashboard: ledger, laporan komisi, dan widget operator.
  7. Connected service (gate/CCTV) setelah kontrak vendor siap, lalu Fase 2 NATS bersamaan dengan panic button.