Search by

PBB tax bill lookup for the Nawasara superapp framework — proxies the Ponorogo sipandaunik system, parses its HTML into JSON, and serves it to logged-in residents.

Package info

github.com/nawasara/pbb

pkg:composer/nawasara/pbb

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-09-14 02:17 UTC

This package is auto-updated.

Last update: 2026-09-14 02:29:09 UTC


README

PBB (property tax) bill lookup for the Nawasara superapp framework. It sits in front of Ponorogo's tax system, sipandaunik: it calls the NOP search endpoint, parses the HTML response into clean JSON, hides the taxpayer's identity, and serves the result to the citizen app (SuperApps Flutter) behind citizen login.

Status v0.1.0

Feature Status
Look up a bill by NOP (POST /pbb/check) ready
Parse the year history (paid/unpaid, principal, penalty, total) ready
Mask the name (initial) and drop the address ready
Behind citizen login (api.citizen) with throttle ready
Per-citizen search history not built yet
Panel or menu in Nawasara intentionally none (see below)

The package stores nothing. There are no migrations, models, sync jobs, or Livewire pages. Every request goes straight through to sipandaunik. That is why there is no sidebar workspace and no PermissionSeeder: access is gated by the citizen realm JWT, not a Spatie permission.

Setup

No migrations or seeders. Register it in the root composer.json (path repo plus require), then:

composer update nawasara/pbb

Optional configuration through .env:

NAWASARA_PBB_BASE_URL=https://sipandaunik.ponorogo.go.id
NAWASARA_PBB_HTTP_TIMEOUT=20

There is no @source line in app.css. The package ships no Blade views, so there are no Tailwind classes to compile.

Endpoint

POST {api-prefix}/pbb/check, behind ['api', 'api.citizen', 'throttle:nawasara-citizen'].

Request body:

{ "nop": "35.02.020.022.004-0206.0" }

Response (200):

{
  "data": {
    "name_masked": "T••••••",
    "bills": [
      { "year": 2026, "status": "LUNAS", "principal": 13133, "penalty": 0, "amount": 13133 }
    ],
    "total_due": 0,
    "has_arrears": false
  }
}

Other codes: 404 when the NOP is not found, 422 when the NOP is not 18 digits, 502 when sipandaunik cannot be reached.

Design notes

We use POST rather than GET even though the request only reads data. A NOP points to a specific plot of land, and putting it in a query string records it in access logs and proxy history. A POST body does not.

The name is shortened to its first letter and the address is removed completely. sipandaunik returns the full nama_wp, alamat_wp, and alamat_op to anyone who knows a NOP, with no session check at all. We tested this on 12 September 2026: the request returns the same full data with no cookie and with a garbage cookie. Because a neighbour's NOP is easy to guess (they run in sequence within a block), showing the full name would leak who owns what. An initial is enough for a citizen to confirm "this is mine". The property address never leaves the system, because it is the field that points to the physical location.

Citizen login is required even though the source demands nothing. Since sipandaunik protects nothing itself, we moved the gate here, so that whoever asks about a bill is at least a known citizen rather than an anonymous bulk scraper.

Filtering lives in the Resource, not the client. PbbBillResource is written as an allow list, so if SipandaunikClient ever returns a new field it is not forwarded by accident. Masking in the client would leave paths that forget to filter. In the Resource there is one exit for everything.

The NOP comes from the request body, not the token. A single citizen commonly manages PBB for several plots (a house, a rice field, an inheritance), and there is no fixed link between a citizen's sub and any given NOP, so locking it to the token identity would be wrong.

A 502 is kept separate from a 404. sipandaunik being down and a NOP not existing are two different things, and treating them the same would make a citizen think their tax is paid when the system is actually unreachable.

Roadmap

Per-citizen search history, if it turns out to be useful (for example "NOPs I have checked before"). It would need one table keyed on keycloak_sub. It is deliberately left out for now, because storing the NOPs a citizen searches means storing personal data we do not currently need.

Author

Pringgo J. Saputro, Kominfo Ponorogo. MIT License.