ivanwilliammd/satusehat-integration

Build SATUSEHAT FHIR Object in Easy Way

Maintainers

Package info

github.com/ivanwilliammd/satusehat-integration

pkg:composer/ivanwilliammd/satusehat-integration

Transparency log

Fund package maintenance!

ivanwilliammd

Statistics

Installs: 4 663

Dependents: 0

Suggesters: 0

Stars: 78

Open Issues: 0

v4.3.3 2026-08-18 01:02 UTC

README

Build SATUSEHAT FHIR R4 objects with ease — open source Laravel PHP library.

PHP Laravel FHIR R4 License GitHub Actions Latest Version

Overview

satusehat-integration is an open-source Laravel PHP library for integrating with SATUSEHAT — Indonesia's national health data platform powered by FHIR R4.

Built on the official SATUSEHAT Platform Guidelines, it provides:

  • OAuth2 authentication with SATUSEHAT IAM
  • 31 composable DataType classes (Coding, CodeableConcept, Identifier, Reference, etc.)
  • 16 PayloadBuilder classes for FHIR R4 resources (Patient, Encounter, Observation, Condition, Procedure, etc.)
  • SSRequest / SSResponse — typed HTTP client with auto token-refresh, retry logic, and structured response handling
  • Bundle operations for multi-resource transactions
  • Master data: ICD-10, Kode Wilayah Indonesia, KFA v2

Quick Install

composer require ivanwilliammd/satusehat-integration
# .env
SATUSEHAT_ENV=DEV          # DEV | STG | PROD
SATUSEHAT_BASE_URL_DEV=https://api-satusehat-dev.dto.kemkes.go.id
CLIENTID_DEV=your_client_id
CLIENTSECRET_DEV=your_client_secret
ORGID_DEV=your_org_id

Architecture

DataType Classes (src/DataType/)

Atomic FHIR R4 value objects. All extend DataType which provides a recursive toArray() method — nested DataType instances serialize to clean FHIR JSON automatically.

Category Classes
Core Coding, CodeableConcept, Identifier, Period, ContactPoint, Address, HumanName, Reference
Quantity Quantity, SimpleQuantity, Range, Ratio, Age, Count, Distance, Duration, Money
Structured Attachment, Narrative, Annotation, Timing, TimingRepeat, Dosage, DosageDoseAndRate
Utility Extension, Signature, RelatedArtifact, Expression, TriggerDefinition, DataRequirement, ParameterDefinition

Example — HumanName:

use Satusehat\Integration\DataType\HumanName;

$name = new HumanName(
    family: 'Doe',
    given: ['John', 'Michael'],
    use: 'official'
);
// $name->toArray() → ['family' => 'Doe', 'given' => ['John', 'Michael'], 'use' => 'official']

PayloadBuilder Pattern (src/Builder/)

Fluent builder for each FHIR resource. Each builder accepts DataType instances and exposes a build() method returning a clean FHIR JSON payload.

$patient = (new PayloadBuilderPatient)
    ->setId('12345678-1234-1234-1234-123456789012')
    ->addIdentifier($identifier)
    ->addName($name)
    ->setGender('male')
    ->setBirthDate('1990-01-15')
    ->addAddress($address)
    ->addTelecom($phone)
    ->build();

SSRequest / SSResponse

  • SSRequest — HTTP client with get(), post(), put(), delete() methods. Handles OAuth2 bearer tokens, auto-refresh on HTTP 401, retry with exponential backoff on 429/5xx, configurable timeout.
  • SSResponse — Structured response wrapper: isSuccess() / isError(), getErrorMessages(), getResourceId().
use Satusehat\Integration\SSRequest\SSRequest;
use Satusehat\Integration\OAuth2Client;

$oauth2 = new OAuth2Client();
$ss = new SSRequest($oauth2);

$resp = $ss->post('Patient', $patientPayload);

if ($resp->isSuccess()) {
    $patientId = $resp->getResourceId();
} else {
    foreach ($resp->getErrorMessages() as $msg) {
        // handle error
    }
}

Usage Examples

Patient

use Satusehat\Integration\SSRequest\SSRequest;
use Satusehat\Integration\OAuth2Client;
use Satusehat\Integration\DataType\Identifier;
use Satusehat\Integration\DataType\HumanName;
use Satusehat\Integration\DataType\Address;
use Satusehat\Integration\DataType\ContactPoint;
use Satusehat\Integration\DataType\CodeableConcept;
use Satusehat\Integration\DataType\Period;
use Satusehat\Integration\Builder\PayloadBuilderPatient;

$ss = new SSRequest(new OAuth2Client());

// Compose DataType objects
$identifier = new Identifier(
    system: 'https://fhir.kemkes.go.id/id/NIK',
    value: '3312345678901234'
);

$name = new HumanName(
    family: 'Doe',
    given: ['John'],
    use: 'official'
);

$phone = new ContactPoint(
    system: 'phone',
    value: '081234567890',
    use: 'mobile'
);

$address = new Address(
    use: 'home',
    line: ['Jl. Sudirman No.1'],
    city: 'Jakarta Selatan',
    district: 'Kebayoran Baru',
    state: 'DKI Jakarta',
    postalCode: '12190',
    country: 'ID'
);

// Build Patient resource
$patient = (new PayloadBuilderPatient)
    ->addIdentifier($identifier)
    ->addName($name)
    ->setGender('male')
    ->setBirthDate('1990-01-15')
    ->addTelecom($phone)
    ->addAddress($address)
    ->setActive(true)
    ->build();

$resp = $ss->post('Patient', $patient);

Encounter

use Satusehat\Integration\DataType\Reference;
use Satusehat\Integration\DataType\CodeableConcept;
use Satusehat\Integration\DataType\Period;
use Satusehat\Integration\Builder\PayloadBuilderEncounter;

$subject = new Reference(
    reference: "Patient/{$patientId}",
    display: 'John Doe'
);

$participant = new Reference(
    reference: 'Practitioner/10009880728',
    display: 'Dr. Smith'
);

$class = new CodeableConcept(
    coding: [new \Satusehat\Integration\DataType\Coding(
        system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode',
        code: 'AMB',
        display: 'ambulatory'
    )]
);

$encounter = (new PayloadBuilderEncounter)
    ->setStatus('finished')
    ->setClass($class)
    ->setSubject($subject)
    ->addParticipantIndividual($participant)
    ->setPeriodStart(now()->toIso8601String())
    ->addReasonText('Pemeriksaan umum')
    ->build();

$resp = $ss->post('Encounter', $encounter);

Observation

use Satusehat\Integration\DataType\CodeableConcept;
use Satusehat\Integration\DataType\Coding;
use Satusehat\Integration\DataType\Reference;
use Satusehat\Integration\DataType\Quantity;
use Satusehat\Integration\DataType\Annotation;
use Satusehat\Integration\Builder\PayloadBuilderObservation;

$category = new CodeableConcept(
    coding: [new Coding(
        system: 'http://terminology.hl7.org/CodeSystem/observation-category',
        code: 'vital-signs',
        display: 'Vital Signs'
    )]
);

$code = new CodeableConcept(
    coding: [new Coding(
        system: 'http://loinc.org',
        code: '8867-4',
        display: 'Heart rate'
    )]
);

$value = new Quantity(
    value: 72,
    unit: 'beats/minute',
    system: 'http://unitsofmeasure.org',
    code: '/min'
);

$observation = (new PayloadBuilderObservation)
    ->setStatus('final')
    ->addCategory($category)
    ->setCode($code)
    ->setSubject($subject)
    ->setEncounter($encounterRef)
    ->setEffectiveDateTime(now()->toIso8601String())
    ->setValueQuantity($value)
    ->addReferenceRange(
        low: new Quantity(value: 60, unit: 'bpm', system: 'http://unitsofmeasure.org', code: '/min'),
        high: new Quantity(value: 100, unit: 'bpm', system: 'http://unitsofmeasure.org', code: '/min'),
        text: '60-100 bpm'
    )
    ->build();

$resp = $ss->post('Observation', $observation);

Old Way vs v4 Way

Before v4 (raw array)

use Satusehat\Integration\OAuth2Client;

$client = new OAuth2Client();

$patient = [
    'resourceType' => 'Patient',
    'identifier' => [['system' => '...', 'value' => '...']],
    'name' => [['family' => 'Doe', 'given' => ['John'], 'use' => 'official']],
    // ... manually build every nested structure
];

[$status, $resp] = $client->ss_post('Patient', $patient);

// Check response by inspecting raw array
if ($status >= 200 && $status < 300) {
    $id = $resp['id'] ?? null;
}

v4 (DataType + PayloadBuilder + SSResponse)

use Satusehat\Integration\SSRequest\SSRequest;
use Satusehat\Integration\OAuth2Client;
use Satusehat\Integration\DataType\Identifier;
use Satusehat\Integration\DataType\HumanName;
use Satusehat\Integration\Builder\PayloadBuilderPatient;

$ss = new SSRequest(new OAuth2Client());

$patient = (new PayloadBuilderPatient)
    ->addIdentifier(new Identifier(system: '...', value: '...'))
    ->addName(new HumanName(family: 'Doe', given: ['John'], use: 'official'))
    ->setGender('male')
    ->build();

$resp = $ss->post('Patient', $patient);

if ($resp->isSuccess()) {
    $id = $resp->getResourceId();
} else {
    foreach ($resp->getErrorMessages() as $msg) { /* log */ }
}

Key improvements in v4:

  • DataType classes guarantee valid FHIR structure
  • toArray() handles nested serialization recursively
  • SSResponse gives typed, structured access to responses
  • Automatic token refresh and retry on network failures
  • Fully fluent builder API

Supported FHIR Resources

All 16 resources fully implemented via PayloadBuilder classes:

# Resource GET POST PUT
1 Patient
2 Practitioner
3 PractitionerRole
4 Organization
5 Location
6 Encounter
7 Condition
8 Observation
9 Procedure
10 MedicationRequest
11 Bundle
12 CarePlan
13 Composition
14 ClinicalImpression
15 Goal
16 NutritionOrder

Documentation

Page Description
CHANGELOG Version history and release notes
Wiki Full documentation
Installation composer require, publish config, env setup
Usage OAuth, Patient, Encounter, Condition, Bundle, KFA
Features Full feature matrix
Onboarding SATUSEHAT developer account setup
ROADMAP.md Phased release plan v3.x → v5.0

External Resources

Contributing

Contributions are welcome. See CONTRIBUTING.md for guidelines.

Support

Open an issue for bugs or feature requests.

License

MIT — see LICENSE.