kayya / api-sdk
Integrasi server tenant dengan marketplace Kayya API: verifikasi panggilan gateway, tanda tangan webhook & sinkronisasi.
Requires
- php: >=8.1
Requires (Dev)
- nyholm/psr7: ^1.8
- orchestra/testbench: ^10.0 || ^11.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5 || ^11.5 || ^12.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/http-server-middleware: ^1.0
- psr/log: ^1.1 || ^2.0 || ^3.0
- slim/psr7: ^1.7
- slim/slim: ^4.14
- squizlabs/php_codesniffer: ^3.10
Suggests
- psr/http-server-middleware: Untuk Kayya\Api\Psr15\KayyaMiddleware (Slim, Mezzio, dan framework PSR-15 lain)
- psr/log: Untuk mencatat panggilan yang ditolak lewat logger PSR-3
Provides
None
Conflicts
None
Replaces
None
README
Menghubungkan server API kamu dengan marketplace Kayya API (kontrak integrasi tenant v2). PHP 8.1+, tanpa dependency wajib.
Yang wajib hanya satu: memastikan setiap panggilan benar datang dari gateway Kayya API. Tanpa itu, base URL kamu adalah API terbuka bagi siapa pun yang menemukannya. Sisanya opsional:
| Fungsi | Wajib? | Untuk |
|---|---|---|
| Verifikasi panggilan gateway | Ya | Menolak panggilan yang tidak lewat gateway (401), menyediakan identitas buyer |
| Penerima webhook | Tidak | Diberi tahu saat entitlement dibeli, diperpanjang, atau dicabut |
| Penyimpan lokal | Tidak | Daftar pelanggan di database kamu sendiri |
| Sinkronisasi | Tidak | Jaring pengaman webhook; membangun ulang data lokal dari nol |
kayya-api doctor | — | Membuktikan integrasi siap sebelum go-live |
Panduan lengkap kontraknya ada di halaman Integrasi untuk developer di situs
Kayya API (/developer/integrasi). SDK ini tidak wajib — yang diuji marketplace
adalah perilaku server kamu — tetapi ia menjadikan jalur yang aman sekaligus
jalur yang paling mudah.
Instalasi
composer require kayya/api-sdk
Konfigurasi
| Variabel lingkungan | Isi | Dibutuhkan untuk |
|---|---|---|
KAYYA_API_SECRET | Secret integrasi — dashboard tenant → Integrasi. Pakai apa adanya, termasuk awalan whsec_ | Semua |
KAYYA_API_TENANT_ID | Tenant ID dari halaman yang sama | Sinkronisasi |
KAYYA_API_BASE_URL | Alamat API marketplace yang diberikan saat onboarding (dengan atau tanpa /api/v1) | Sinkronisasi |
Setiap nilai juga bisa diberikan sebagai argumen, yang selalu menang atas
variabel lingkungan. Secret yang tidak diisi membuat middleware melempar
KonfigurasiSalah saat dibuat — lebih aman daripada diam-diam menerima setiap
panggilan.
Mulai cepat
Middleware dipasang di depan seluruh route, bukan per route atau per grup.
Uji integrasi marketplace mengetuk satu path acak dengan tanda tangan palsu dan
menuntut 401; verifikasi yang hanya dipasang di sebagian route gagal di sana.
Laravel 12+
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->prepend(\Kayya\Api\Laravel\KayyaMiddleware::class);
})
// routes/api.php — identitas buyer disuntikkan container
use Kayya\Api\Pemanggil;
Route::get('/v1/cuaca', fn (Pemanggil $p) => ['untuk' => $p->entitlementId]);
Isi KAYYA_API_SECRET di .env. Opsi lain di config/kayya.php:
php artisan vendor:publish --tag=kayya-config
prepend, bukan append dan bukan grup api: middleware grup tidak pernah
berjalan untuk path yang tidak punya route. Health check bawaan Laravel (/up)
ikut diverifikasi — kalau load balancer kamu memakainya, masukkan ke
kecualikan.
Slim 4 & framework PSR-15 lain
use Kayya\Api\Gerbang;
use Kayya\Api\Psr15\KayyaMiddleware;
// Slim menjalankan middleware dari yang terakhir ditambahkan: tambahkan PALING AKHIR.
$app->add(new KayyaMiddleware($app->getResponseFactory(), new Gerbang(kecualikan: ['/healthz'])));
$app->get('/v1/cuaca', function (Request $request, Response $response) {
$p = KayyaMiddleware::pemanggil($request);
// ...
});
Butuh psr/http-server-middleware (sudah terpasang bersama Slim, Mezzio, dan
framework PSR-15 lain). Pasang sebagai middleware terluar — sebelum routing,
dan sebelum middleware yang mengganti method dari _method: gateway
menandatangani method di baris permintaan.
PHP polos, CodeIgniter, & lainnya
require __DIR__ . '/vendor/autoload.php';
// Paling awal, sebelum routing.
$pemanggil = \Kayya\Api\Polos::lindungi();
Panggilan yang tidak sah dijawab 401 dan skrip berhenti di situ. Opsi:
Polos::lindungi(new Gerbang(kecualikan: ['/healthz'])).
Identitas pemanggil
| Properti | Isi |
|---|---|
buyerId | UUID buyer di marketplace |
entitlementId | UUID entitlement — kunci yang sama dengan webhook & sinkronisasi |
produk | Slug produk yang dipanggil |
requestId | Sertakan saat menghubungi dukungan — ditelusuri di sisi marketplace juga |
expiresAt | DateTimeImmutable (UTC) akhir periode berjalan — informasi saja; null kalau tak terurai |
ujiIntegrasi | true untuk panggilan uji integrasi marketplace — jangan dicatat sebagai pemakaian |
Jangan menolak panggilan berdasarkan expiresAt. Keputusan akses sepenuhnya
milik gateway: ada keadaan di mana akses tetap sah meski tanggalnya lewat, dan
panggilan yang sampai ke server kamu sudah diputuskan sah.
Kamu tidak pernah menerima API key buyer, dan tidak membutuhkannya.
Opsi
new Gerbang(...) untuk PSR-15 & PHP polos; kunci config/kayya.php untuk Laravel.
Gerbang | config/kayya.php | Bawaan | Isi |
|---|---|---|---|
secret | secret | KAYYA_API_SECRET | Secret integrasi |
prefixYangDibuang | prefix_yang_dibuang | '' | Awalan path yang dibuang reverse proxy di depan server kamu (proxy menerima /v1/cuaca, meneruskan /cuaca → isi '/v1') |
kecualikan | kecualikan | [] | Path yang tidak diverifikasi, dibandingkan persis (tanpa query) — mis. ['/healthz']. Path di sini terbuka untuk siapa pun; jangan pernah memasukkan route API |
periksaPath | periksa_path | true | Matikan hanya kalau proxy menulis ulang path dengan cara yang tidak bisa dijelaskan satu awalan |
webhook | webhook | null / false | Terima webhook di path webhook (lihat Webhook) |
pathWebhook | path_webhook | '/kayya/webhook' | |
logger | — (logger Laravel) | null | Logger PSR-3 untuk mencatat panggilan yang ditolak |
Jawaban yang ditolak
401 dengan body JSON:
{"alasan": "timestamp", "keterangan": "X-Marketplace-Timestamp 412 detik di belakang jam server ini ..."}
alasan | Penyebab paling mungkin |
|---|---|
header_kurang | Bukan panggilan gateway (pemindai internet, health check) |
timestamp | Jam server kamu meleset lebih dari 5 menit — nyalakan NTP |
tanda_tangan | Secret berbeda: awalan whsec_ terpotong, atau secret baru belum dipasang setelah diganti |
path | Reverse proxy membuang awalan path — isi prefixYangDibuang |
Dashboard tenant membaca alasan untuk menampilkan penyebab saat uji integrasi
gagal. Penolakan dicatat lewat logger (warning; header_kurang di debug
supaya pemindai internet tidak memenuhi log).
Webhook
Laravel — nyalakan di config/kayya.php, lalu dengarkan event-nya:
'webhook' => true,
'simpan_entitlement' => true, // lihat "Penyimpan lokal"
use Kayya\Api\Laravel\EntitlementBerubah;
Event::listen(function (EntitlementBerubah $e) {
if ($e->perubahan->keputusan?->value === 'baru') {
// email sambutan untuk $e->perubahan->entitlement->buyerId
}
});
PSR-15 & PHP polos — pasang penerimanya di Gerbang:
use Kayya\Api\{Gerbang, PenerimaWebhook, Perubahan};
$gerbang = new Gerbang(webhook: new PenerimaWebhook(
penyimpan: $penyimpan,
saatPerubahan: function (Perubahan $p): void { /* ... */ },
));
Isi URL webhook di dashboard tenant → Integrasi dengan https://<server kamu>/kayya/webhook.
- Kiriman webhook dijawab middleware sebelum routing, CSRF, dan body-parser — tanda tangan dihitung dari raw body.
pingdan event yang belum dikenal versi SDK ini dijawab2xx.- Dengan penyimpan,
entitlement.updatedditerapkan dengan aturanupdated_atdan callback/event hanya dipanggil kalau data lokal berubah —keputusansalah satubaru,perpanjangan,dicabut,diperbarui. Duplikat dan retry lama tidak memanggilnya lagi. - Tanpa penyimpan, callback dipanggil untuk setiap kiriman sah (
keputusan=null), dan urutannya urusanmu. - Callback berjalan setelah data tersimpan, paling banyak sekali per
perubahan. Galat di dalamnya dicatat, tidak menggagalkan kiriman: jawaban
5xxmembuat marketplace mengulang, dan enam kegagalan berturut menonaktifkan webhook kamu. Pekerjaan berat atau yang tidak boleh hilang: listenerShouldQueue(Laravel) atau antrean yang tahan lama. - Marketplace menunggu paling lama 10 detik.
Webhook adalah pemberitahuan, bukan penjaga akses: entitlement yang dicabut sudah ditolak gateway detik itu juga.
Penyimpan lokal
| Adapter | Pemasangan |
|---|---|
Kayya\Api\Penyimpan\PenyimpanMemori | Test & percobaan — hilang di akhir request |
Kayya\Api\Penyimpan\PenyimpanPdo($pdo) | SQLite, PostgreSQL, MySQL/MariaDB; buatTabel: true, atau jalankan DDL dari PenyimpanPdo::skema($driver) di migrasi kamu |
Kayya\Api\Laravel\PenyimpanLaravel | php artisan vendor:publish --tag=kayya-migrations && php artisan migrate, lalu 'simpan_entitlement' => true |
Semua adapter memakai tabel yang sama — kayya_entitlement (kunci
entitlement_id) dan kayya_penanda_sync — dengan waktu UTC berpresisi
mikrodetik (DATETIME(6) di MySQL/MariaDB). Satu aturan berlaku untuk webhook
maupun sinkronisasi: per entitlement_id, hanya kalau updated_at yang datang
lebih baru. Perpanjangan memajukan expiresAt; retry lama yang tiba terlambat
tidak pernah menimpa data baru, juga saat webhook dan sinkronisasi menulis baris
yang sama bersamaan.
Entitlement::masihBerlaku() menghitung dari status + expiresAt data lokal
— untuk ditampilkan, bukan untuk memutuskan akses. limitHit disimpan tapi
tidak ditegakkan; kuota ditegakkan gateway.
Adapter sendiri cukup mengimplementasikan lima metode Kayya\Api\Penyimpan\Penyimpan
(Laravel: ikat ke container dengan $this->app->bind(Penyimpan::class, ...)).
Sinkronisasi
Laravel — isi KAYYA_API_TENANT_ID & KAYYA_API_BASE_URL, lalu jadwalkan:
// routes/console.php
Schedule::command('kayya:sinkron')->hourly()->withoutOverlapping();
php artisan kayya:sinkron menjalankan satu putaran; --terus menjalankannya
sebagai proses tersendiri (sekarang, lalu tiap jam). Setiap perubahan juga
disiarkan sebagai event EntitlementBerubah.
PHP polos — satu putaran dari cron tiap jam:
use Kayya\Api\Penyinkron;
use Kayya\Api\Penyimpan\PenyimpanPdo;
(new Penyinkron(new PenyimpanPdo(new PDO($dsn, $pengguna, $sandi))))->jalankanSekali();
atau proses tersendiri: (new PenjadwalSync(new Penyinkron($penyimpan)))->jalankan().
- Halaman pertama
?since=, berikutnya?kursor=apa adanya; penanda (sampai) disimpan hanya setelah halaman terakhir. Putaran yang gagal di tengah diulang dari penanda lama. - Kegagalan (jaringan,
401,5xx) dilempar sebagaiGalatSyncdengansaranpenyebabnya;PenjadwalSyncmencatatnya lalu mengulang bertahap 1, 5, 15, 30 menit — buyer tetap dilayani tanpa data lokal sama sekali. - Jalankan di satu proses. Beberapa proses yang masing-masing menyinkron menarik data yang sama berkali-kali.
- Marketplace membatasi 60 permintaan per menit per tenant. Lewat dari itu
dijawab
429(GalatSync->status === 429); penjadwal mundur 1 menit lalu mengulang putaran dari penanda lama. - Pengalihan tidak diikuti — tanda tangan melekat pada alamat.
kayya-api doctor
vendor/bin/kayya-api doctor # konfigurasi, test vector, sinkronisasi, jam
vendor/bin/kayya-api doctor --url https://api.contoh.id --path /v1/ping
vendor/bin/kayya-api doctor --webhook https://api.contoh.id/kayya/webhook
php artisan kayya:doctor --url https://api.contoh.id # Laravel: konfigurasi dari config/kayya.php
Memeriksa konfigurasi, menjalankan seluruh test vector resmi terhadap SDK yang
terpasang, mengirim satu permintaan sinkronisasi (membuktikan tenant ID &
secret), menghitung selisih jam dengan marketplace, lalu — kalau --url
diberikan — mengetuk server kamu seperti uji integrasi: tanda tangan sah harus
dilayani, tanda tangan palsu ke akar dan ke path acak harus 401. Keluar
dengan status 1 kalau ada yang gagal. Secret tidak pernah dicetak.
Menguji aplikasimu
Kayya\Api\Uji membuat panggilan gateway dan kiriman webhook bertanda tangan
dengan secret uji, tanpa marketplace. Dengan KAYYA_API_SECRET=whsec_uji di
lingkungan test (mis. <env> di phpunit.xml):
use Kayya\Api\Uji;
public function test_cuaca(): void
{
$this->get('/v1/cuaca', Uji::headerGateway('/v1/cuaca', secret: 'whsec_uji'))->assertOk();
}
public function test_palsu(): void
{
$this->get('/v1/cuaca', Uji::headerGateway('/v1/cuaca', secret: 'whsec_lain'))->assertUnauthorized();
}
Uji::kirimanWebhook($payload, secret: ...) mengembalikan [header, rawBody]
untuk menguji penerima webhook.
Kompatibilitas
| PHP | 8.1 – 8.5 |
| Kontrak integrasi | v2 |
| Laravel | 12 & 13 (Laravel 10 & 11 sudah EOL) |
| PSR-15 | psr/http-server-middleware 1.x; diuji dengan Slim 4 |
| Database | SQLite, PostgreSQL, MySQL/MariaDB (PDO & Laravel) |
Setiap perubahan diuji terhadap test vector resmi, penyimpan diuji di PostgreSQL & MySQL sungguhan, dan aplikasi contoh (PHP polos, Slim, Laravel) diuji di server sungguhan terhadap simulator marketplace.
Menemukan celah keamanan? Lihat SECURITY.md — jangan lewat issue publik.