omnix-sopiga/README.md
2026-08-07 12:36:57 +07:00

125 lines
5.0 KiB
Markdown

# omnix-sopiga
Collection broadcast worker: mengirim invoice via WhatsApp (Sopiga Collar API) ke nasabah Gadai Mulia.
> Repo standalone (bukan bagian monorepo). Dependency internal ada di
> [`go-dw-framework`](../go-dw-framework) — lihat `go.mod` untuk detail
> `replace` directive yang dipakai selama development lokal.
## Architecture
```
Gadai Collection Service
→ INSERT broadcast_staging (message_payload JSONB, status=pending)
→ BroadcastWorker (poll every 30s)
→ Query template_variable_mapping
→ Build message dynamically
→ POST Sopiga Collar API
→ status=dispatched (sopiga_recipient_detail_id disimpan)
→ DeliverySyncWorker (poll every 5 min)
→ GET recipient detail dari Sopiga
→ status=delivered (delivered_at diisi) atau status=failed
```
Tiga komponen jalan bersamaan (lihat `main.go`):
- **BroadcastWorker** (`worker/broadcast_worker.go`) — poll `pending`, dispatch ke Sopiga.
- **Webhook receiver** (`handler/webhook.go`, `POST /webhooks/sopiga/delivery-status`) —
jalur utama update status delivery. Begitu Omnix push callback, status langsung
diupdate tanpa perlu polling balik ke Sopiga sama sekali.
- **DeliverySyncWorker** (`worker/delivery_sync_worker.go`) — polling **fallback/backstop**
untuk record `dispatched` yang tidak kunjung dapat webhook (mis. delivery gagal
terkirim/network hiccup di sisi Omnix). Karena webhook jadi jalur utama, interval ini
bisa diperlonggar jauh lebih besar dari 5 menit tanpa menambah beban signifikan ke Omnix.
> ⚠️ **Catatan:** payload webhook di `dto/webhook.go` dan skema signature (`X-Sopiga-Signature`,
> HMAC-SHA256) masih **asumsi kita sendiri** — belum dikonfirmasi tim Omnix, karena endpoint
> registrasi callback tidak ditemukan di dokumentasi resmi (`/docs?api-docs.yaml`) saat
> implementasi ini dibuat. Sebelum pakai di production: (1) konfirmasi ke tim Omnix apakah
> mereka support outgoing webhook untuk status collar recipient, (2) minta format payload &
> skema signature asli mereka, (3) sesuaikan `dto/webhook.go` dan `handler/webhook.go`,
> (4) daftarkan URL `/webhooks/sopiga/delivery-status` ke mereka.
Template baru cukup didaftarkan lewat database (`sopiga_template_config` +
`template_variable_mapping` + `sopiga_collar_config`) — tanpa redeploy kode.
Lihat [docs/implementasi_guide.md](docs/implementasi_guide.md) dan
[docs/quick_start.md](docs/quick_start.md) untuk panduan lengkap. Detail
permintaan integrasi webhook ke tim Omnix ada di
[docs/webhook_integration_request.md](docs/webhook_integration_request.md).
## Layers
| Layer | Responsibility |
|---|---|
| `worker/` | Poller loop untuk dispatch + delivery sync fallback, fan-out ke concurrent processing |
| `handler/` | `WebhookHandler` — HTTP receiver untuk callback status delivery dari Omnix |
| `transformer/` | Entity ↔ Domain mapping, `BuildDynamicMessage` interpolasi template |
| `service/` | Business logic: fetch pending/dispatched, validate, dispatch, sync delivery (polling & webhook), retry, mark failed |
| `repository/` | Query `broadcast_staging` & `template_variable_mapping`, update status, lookup by `recipient_detail_id` |
| `client/` | HTTP client ke Sopiga Collar API (add-recipient + get recipient detail) |
| `domain/` | `Broadcast`, `TemplateVariable` |
| `entity/` | DB row mapping |
| `dto/` | Sopiga API request/response |
| `validator/` | Validasi payload sebelum dispatch |
| `migrations/` | Schema `collection_broadcasts` (tables, views, functions) |
## Configuration (env vars)
| Var | Default |
|---|---|
| `DATABASE_DSN` | — |
| `DB_MAX_CONNS` | 25 |
| `DB_MIN_CONNS` | 5 |
| `SOPIGA_BASE_URL` | `https://omnix.promas.site` |
| `SOPIGA_TOKEN` | — |
| `WORKER_CHECK_INTERVAL_SEC` | 30 |
| `WORKER_BATCH_SIZE` | 100 |
| `WORKER_MAX_RETRIES` | 3 |
| `WORKER_SYNC_INTERVAL_SEC` | 300 (fallback saja — perlonggar kalau webhook sudah aktif) |
| `WORKER_SYNC_BATCH_SIZE` | 100 |
| `WEBHOOK_PORT` | 8081 |
| `WEBHOOK_SECRET` | — (HMAC-SHA256 shared secret; kosongkan untuk skip verifikasi saat dev) |
## Development setup
Clone this repo and `go-dw-framework` as **siblings** (same parent folder) —
`go.mod` uses a local `replace` directive during pre-release development:
```
some-folder/
├── go-dw-framework/
└── omnix-sopiga/ ← you are here
```
```bash
git clone <go-dw-framework-remote> ../go-dw-framework
go build ./...
```
Once `go-dw-framework` is tagged (e.g. `v0.1.0`) and pushed to its remote,
switch `go.mod` to a plain `require repository.promas.id/prana/go-dw-framework v0.1.0`
(drop the `replace` line) and `go mod tidy`.
## Run
```bash
psql -d gadai_mulia -f migrations/001_create_collection_broadcasts.up.sql
export DATABASE_DSN="postgres://user:pass@localhost:5432/gadai_mulia"
export SOPIGA_TOKEN="your_api_token"
go run .
```
## Docker build
```bash
# from the parent folder containing both omnix-sopiga/ and go-dw-framework/
docker build -f omnix-sopiga/Dockerfile -t omnix-sopiga ..
```
## Test
```bash
go test ./tests/... -v
```