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.
Shortcuts: Space play/pause · ←/→ step · 1–3 flow · O mode · T theme · F fullscreen · R reset layout · Drag nodes to reposition
addon-control?addon_* di ipl_platform, sehingga tetap 2 DB.workspace_deployments tetap dipakai.source_type, splits, dan refund.ipl_app).internal/application/addon di platform-control, dengan catatan harus dipecah nanti. Perlu keputusan tim (lihat §05).EventSink/Deliver, dan konsumen mencatat event_inbox.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
}
erDiagram
addon_products ||--o{ addon_orders : "dibeli"
addon_orders ||--o{ addon_order_events : "timeline"
addon_orders ||--o{ addon_provider_transactions : "attempt ke provider"
addon_orders ||--o| addon_fulfillment_tasks : "manual vendor"
addon_orders ||--o{ addon_ledger_entries : "4 baris per order"
addon_commission_periods ||--o{ addon_ledger_entries : "periode"
addon_providers ||--o{ addon_provider_settlements : "kewajiban"
addon_orders {
uuid id PK
varchar order_no UK "ORD-260928-0091"
uuid workspace_id
uuid unit_id "nullable (pembeli operator)"
varchar buyer_type "resident|operator"
uuid buyer_id
uuid product_id FK
varchar customer_ref "no meter / ID pelanggan"
int quantity
numeric base_price "snapshot"
numeric platform_fee "snapshot"
numeric operator_fee "snapshot"
numeric total_amount
varchar status "lihat State: Order"
uuid payment_order_id "ref payment-control"
jsonb fulfillment_result "token/SN, terenkripsi"
jsonb delivery_snapshot
varchar idempotency_key UK
text failed_reason
timestamptz paid_at
timestamptz fulfilled_at
}
addon_order_events {
bigint id PK
uuid order_id FK
varchar from_status
varchar to_status
varchar actor_type "resident|operator|superadmin|system|provider"
uuid actor_id
jsonb payload
timestamptz created_at
}
addon_provider_transactions {
uuid id PK
uuid order_id FK
uuid provider_id FK
varchar ref_id "= order_no"
int attempt
varchar status "success|pending|failed|timeout"
jsonb request
jsonb response "tanpa data sensitif"
int latency_ms
}
addon_fulfillment_tasks {
uuid id PK
uuid order_id FK "UK"
uuid provider_id FK
varchar status "waiting_forward|forwarded|done|cancelled"
timestamptz sla_due_at
uuid forwarded_by
timestamptz forwarded_at
text vendor_note
text proof_object_key
uuid completed_by
timestamptz completed_at
}
addon_ledger_entries {
bigint id PK
varchar period "2026-08"
uuid workspace_id
uuid provider_id
varchar source_type "order|subscription_charge|refund"
uuid source_id
varchar entry_type "sale|nuratech_commission|operator_commission|provider_payable|refund"
numeric amount "negatif utk reversal"
timestamptz created_at
}
addon_commission_periods {
varchar period PK "2026-08"
varchar status "open|closed"
jsonb totals_snapshot
timestamptz closed_at
}
addon_provider_settlements {
uuid id PK
uuid provider_id FK
varchar period
numeric amount
varchar method "deposit_auto|manual_transfer"
varchar status "unpaid|paid"
text reference
timestamptz paid_at
}
erDiagram
addon_offerings ||--o{ addon_subscriptions : "dilanggan"
addon_subscriptions ||--o{ addon_subscription_units : "unit (selektif)"
addon_subscriptions ||--o{ addon_subscription_charges : "tagihan per periode"
addon_offerings ||--o{ addon_usage_events : "pemakaian"
addon_subscriptions {
uuid id PK
uuid workspace_id
uuid offering_id FK
uuid product_id FK
varchar scope "tenant_wide|selective"
varchar billing_target "operator_invoice|resident_bill"
numeric unit_price
varchar quantity_basis "unit_count|area|fixed"
varchar start_period "2026-09"
varchar end_period "nullable"
varchar status "pending_activation|active|stopped|cancelled"
uuid agreed_by
timestamptz agreed_at
text stop_reason
}
addon_subscription_units {
uuid id PK
uuid subscription_id FK "UK(sub, unit)"
uuid unit_id
varchar unit_code_snapshot
varchar start_period
varchar end_period
}
addon_subscription_charges {
uuid id PK
uuid subscription_id FK
varchar period "UK(sub, period, unit)"
uuid unit_id "null utk tenant-wide"
numeric quantity
numeric amount
varchar target "operator_invoice|resident_bill"
varchar external_ref "invoice_item_id / bill_item_id"
varchar status "pending|posted|failed|carried_over"
}
addon_usage_events {
uuid id PK
uuid workspace_id
uuid unit_id
uuid offering_id FK
varchar action "gate_open|guest_qr|cctv_view"
varchar source_ref UK
timestamptz created_at
}
addon_outbox {
bigint id PK
varchar event_type
uuid aggregate_id
jsonb payload "envelope"
int attempts
text last_error
timestamptz created_at
timestamptz published_at "null = belum terkirim"
}
event_inbox {
varchar consumer PK
uuid event_id PK
timestamptz processed_at
}
erDiagram
payment_orders ||--o{ payment_refunds : "refund"
resident_bills ||--o{ resident_bill_items : "item"
platform_invoices ||--o{ platform_invoice_items : "item"
payment_orders {
uuid id PK
varchar source_type "BARU: ipl_bill|addon_order"
varchar source_id "BARU"
jsonb splits "BARU: bagian SAC operator"
}
payment_refunds {
uuid id PK "BARU (payment-control)"
uuid payment_order_id FK
numeric amount
varchar reason
varchar status "pending|processing|completed|failed|manual"
jsonb doku_response
}
payment_outbox {
bigint id PK "BARU (payment-control)"
varchar event_type "payment.order.paid dst"
jsonb payload
timestamptz published_at
}
resident_bill_items {
uuid id PK
uuid bill_id FK
varchar name
numeric amount
varchar source_type "BARU: component|addon_charge"
varchar source_ref "BARU, UK(source_type, source_ref)"
}
platform_invoice_items {
uuid id PK
uuid invoice_id FK
text description
numeric amount
varchar source_ref "BARU: addon charge id, UK"
}
resident_bills {
uuid id PK
numeric total_amount "direcalc saat item addon ditempel"
}
platform_invoices {
uuid id PK
uuid workspace_id
varchar status "draft|sent|paid|void"
}
stateDiagram-v2 [*] --> pending_payment : CreateOrder pending_payment --> expired : checkout lewat 60 mnt pending_payment --> paid : payment.order.paid paid --> fulfilling : PPOB dipanggil / task vendor dibuat fulfilling --> fulfilled : provider SUCCESS / ops Tandai Selesai fulfilling --> fulfilling : provider PENDING (polling 5 mnt) fulfilling --> failed : provider FAILED final / vendor batal failed --> refund_required : otomatis refund_required --> refunded : payment.refund.completed fulfilled --> [*] expired --> [*] refunded --> [*]
stateDiagram-v2 [*] --> pending_activation : operator konfirmasi pending_activation --> active : vendor/ISP konfirmasi pending_activation --> cancelled : Batalkan active --> active : tambah/kurangi unit (selektif) active --> stopped : Hentikan (berlaku akhir periode) stopped --> [*] cancelled --> [*]
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.>).
| Event | Producer | Consumer | Efek |
|---|---|---|---|
addon.product.published / .price_changed / .archived | addon | operator (cache), user (cache) | Katalog tenant & harga mobile ter-refresh |
addon.activation.changed | addon | user | Invalidasi mobile:addons:{ws} |
payment.order.paid | payment | addon (filter source_type=addon_order), user/operator (IPL, existing) | Order → fulfilling |
payment.order.expired | payment | addon | Order → expired |
addon.order.fulfilled | addon | user | Notifikasi + push ke pembeli |
addon.order.refund_required | addon | payment, (Telegram ops) | Buat payment_refunds |
payment.refund.completed | payment | addon, user | Order → refunded, ledger reversal, push |
addon.subscription.activated / .units_changed / .stopped | addon | user | Entitlement unit (Layanan Aktif, Gate/CCTV) |
addon.charge.resident_bill_requested | addon | operator | Tempel resident_bill_items |
operator.bill_item.attached | operator | addon | Charge → posted (+ external_ref) |
ipl-v2-protobuff/ipl/v1)addon.proto: AddonAdminService (dipanggil platform), AddonWorkspaceService (operator), AddonResidentService (user).events.proto: EventEnvelope + EventSink/Deliver (Fase 1).payment.proto: CreatePaymentRequest + source_type, source_id, splits; RPC CreateRefund.platform.proto: RPC UpsertAddonInvoiceLines.addon.pb.go ke addon, platform, operator, dan user; payment.pb.go ke payment, user, operator, dan addon./api/v1/admin/addons/…: providers, products, orders, fulfillment, commission-report./api/v1/addons/…: catalog, {key}/retail, {key}/subscriptions, {key}/assign-units, orders. Resource RBAC baru addons (read, update, subscribe) di workspace_role_permissions./api/v1/me/addons/…: config, inquiry, orders, access, gate/open, cctv stream./webhooks/ppob/{provider}: callback status PPOB (verifikasi signature).addon-control.paid/void saat charge selektif datang: pindahkan ke periode berikutnya (usulan) atau buat tagihan susulan?addon-control, migrasi tabel addon_*, addon.proto/events.proto, outbox, relay, dan inbox.source_type + split SAC, fulfillment, push, riwayat.