Library file. In your own project, load it with require __DIR__ . '/RimsClient.php'; and instantiate RimsClient from your entry script.
Two POST shapes for KPRA /rims-integration: Public documentation often uses total_amount, sales_tax, and date — use sendLiveInvoice(). Some server deployments expect the same fields as the Windows utility (amount, tax_amount, date_time) — use sendRimsIntegration(). Use the one that matches your environment.
For the local middleware only: sendToMiddleware() posts JSON to /api/invoice (default base http://localhost:3000).
<?php
/**
* When this file is opened directly in a browser (documentation URL), show the source.
* When the file is required from another script, only the class is loaded.
*/
if (PHP_SAPI !== 'cli' && !empty($_SERVER['SCRIPT_FILENAME'])) {
$thisFile = realpath(__FILE__);
$script = realpath($_SERVER['SCRIPT_FILENAME']);
if ($thisFile !== false && $script !== false && $thisFile === $script) {
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if ($method === 'HEAD') {
header('Content-Type: text/html; charset=UTF-8');
exit;
}
if ($method !== 'GET') {
http_response_code(405);
header('Allow: GET, HEAD');
header('Content-Type: text/plain; charset=UTF-8');
echo 'Method Not Allowed';
exit;
}
header('Content-Type: text/html; charset=UTF-8');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RimsClient.php — KPRA RIMS sample client</title>
<style>
:root { color-scheme: light dark; }
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 0; padding: 1rem 1.25rem 2rem; line-height: 1.55; background: #f6f8fa; color: #1f2328; }
@media (prefers-color-scheme: dark) {
body { background: #0d1117; color: #e6edf3; }
.note { background: #161b22; border-color: #30363d; }
}
.note { max-width: 52rem; margin: 0 auto 1rem; padding: 1rem 1.1rem; background: #fff; border: 1px solid #d0d7de; border-radius: 8px; font-size: 0.95rem; }
.note code { font-size: 0.9em; }
h1 { font-size: 1.15rem; margin: 0 0 0.5rem; }
.src { max-width: 52rem; margin: 0 auto; border: 1px solid #d0d7de; border-radius: 8px; overflow: auto; background: #fff; }
@media (prefers-color-scheme: dark) {
.src { background: #161b22; border-color: #30363d; }
}
.src pre { margin: 0; padding: 1rem; font-size: 13px; line-height: 1.45; tab-size: 4; white-space: pre; overflow-x: auto; }
</style>
</head>
<body>
<div class="note">
<h1>RimsClient.php</h1>
<p><strong>Library file.</strong> In your own project, load it with <code>require __DIR__ . '/RimsClient.php';</code> and instantiate <code>RimsClient</code> from your entry script.</p>
<p><strong>Two POST shapes for KPRA <code>/rims-integration</code>:</strong> Public documentation often uses <code>total_amount</code>, <code>sales_tax</code>, and <code>date</code> — use <code>sendLiveInvoice()</code>. Some server deployments expect the same fields as the Windows utility (<code>amount</code>, <code>tax_amount</code>, <code>date_time</code>) — use <code>sendRimsIntegration()</code>. Use the one that matches your environment.</p>
<p>For the local middleware only: <code>sendToMiddleware()</code> posts JSON to <code>/api/invoice</code> (default base <code>http://localhost:3000</code>).</p>
</div>
<div class="src"><?php
if (function_exists('highlight_file')) {
highlight_file(__FILE__);
} else {
echo '<pre>' . htmlspecialchars((string) file_get_contents(__FILE__), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</pre>';
}
?></div>
</body>
</html>
<?php
exit;
}
}
class RimsClient
{
/** @var string */
private $baseUrl;
/** @var string */
private $ntn;
/** @var string */
private $posId;
/** @var string */
private $key;
public function __construct($baseUrl, $ntn, $posId, $key)
{
$this->baseUrl = rtrim((string) $baseUrl, '/');
$this->ntn = (string) $ntn;
$this->posId = (string) $posId;
$this->key = (string) $key;
}
/**
* KPRA live API — JSON shape from public docs / OpenAPI (sales_tax, date).
* POST {baseUrl}/rims-integration
*
* @param array $invoiceData
* @return array
*/
public function sendLiveInvoice(array $invoiceData)
{
$payload = [
'ntn' => $this->ntn,
'pos_id' => $this->posId,
'key' => $this->key,
'invoice_no' => $invoiceData['invoice_no'],
'total_amount' => $invoiceData['total_amount'],
'sales_tax' => $invoiceData['sales_tax'],
'tax_rate' => $invoiceData['tax_rate'],
'date' => $invoiceData['date'],
];
return $this->postRequest('/rims-integration', $payload);
}
/**
* Same endpoint as sendLiveInvoice but utility-shaped body (amount, tax_amount, date_time).
* Matches many PHP integration handlers deployed alongside legacy POS formats.
*
* @param array $invoiceData
* @return array
*/
public function sendRimsIntegration(array $invoiceData)
{
$payload = [
'ntn' => $this->ntn,
'pos_id' => $this->posId,
'key' => $this->key,
'invoice_no' => $invoiceData['invoice_no'],
'amount' => $invoiceData['amount'],
'tax_rate' => $invoiceData['tax_rate'],
'tax_amount' => $invoiceData['tax_amount'],
'total_amount' => $invoiceData['total_amount'],
'date_time' => $invoiceData['date_time'],
];
return $this->postRequest('/rims-integration', $payload);
}
/**
* Local KPRA RIMS Windows utility / Node middleware.
* POST {baseUrl}/api/invoice
*
* @param array $invoiceData
* @return array
*/
public function sendToMiddleware(array $invoiceData)
{
$payload = [
'ntn' => $this->ntn,
'pos_id' => $this->posId,
'key' => $this->key,
'invoice_no' => $invoiceData['invoice_no'],
'amount' => $invoiceData['amount'],
'tax_rate' => $invoiceData['tax_rate'],
'tax_amount' => $invoiceData['tax_amount'],
'total_amount' => $invoiceData['total_amount'],
'date_time' => $invoiceData['date_time'],
];
if (isset($invoiceData['payment_mode'])) {
$payload['payment_mode'] = (int) $invoiceData['payment_mode'];
}
return $this->postRequest('/api/invoice', $payload);
}
/**
* @param string $endpoint
* @param array $data
* @return array
*/
private function postRequest($endpoint, array $data)
{
$url = $this->baseUrl . $endpoint;
$ch = curl_init($url);
$json = json_encode($data);
if ($json === false) {
return [
'status' => 500,
'error' => 'JSON encode failed: ' . json_last_error_msg(),
];
}
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => $json,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 15,
]);
$responseBody = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$err = curl_error($ch);
curl_close($ch);
return [
'status' => 500,
'error' => $err,
];
}
curl_close($ch);
$decoded = json_decode((string) $responseBody, true);
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
return [
'status' => $httpCode,
'response' => null,
'raw' => $responseBody,
];
}
return [
'status' => $httpCode,
'response' => $decoded,
];
}
}