RimsClient.php

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,
        ];
    }
}