xmlEscape($invoice['buyer_name'] ?: 'عميل نقدي'); $buyerId = $invoice['buyer_tin'] ?: $invoice['buyer_national_id'] ?: '000000000'; $payMethod = $invoice['payment_method_code'] ?: '013'; $supplierName = $this->xmlEscape($company['name']); $supplierAddress = $this->xmlEscape($company['address'] ?? ''); $xml = << 2.1 urn:www.cen.eu:en16931:2017#compliant#urn:www.josefotara.jo:trns:ubl:3.0 reporting:1.0 {$invoice['invoice_number']} {$issueDate} {$issueTime} {$typeCode} JOD JOD {$supplierName} {$supplierAddress} JO {$company['tax_identification_number']} VAT {$supplierName} {$buyerName} {$buyerId} VAT {$payMethod} {$this->fmt($invoice['tax_amount'])} {$this->fmt($invoice['subtotal'] - $invoice['discount_total'])} {$this->fmt($invoice['tax_amount'])} S 16.000 VAT {$this->fmt($invoice['subtotal'])} {$this->fmt($invoice['subtotal'] - $invoice['discount_total'])} {$this->fmt($invoice['grand_total'])} {$this->fmt($invoice['discount_total'])} {$this->fmt($invoice['grand_total'])} {$this->buildInvoiceLines($invoice['items'])} XML; return $xml; } private function buildInvoiceLines(array $items): string { $result = ''; foreach ($items as $item) { $taxAmount = round($item['line_total'] * $item['tax_rate'], 3); $taxCategory = $item['tax_rate'] > 0 ? 'S' : 'Z'; $result .= << {$item['line_number']} {$this->fmt($item['quantity'])} {$this->fmt($item['line_total'])} {$this->fmt($taxAmount)} {$this->fmt($item['line_total'])} {$this->fmt($taxAmount)} {$taxCategory} {$this->fmt($item['tax_rate'] * 100)} VAT {$this->xmlEscape($item['description'])} {$this->fmt($item['unit_price'])} XML; } return $result; } private function fmt(float $val): string { return number_format($val, 3, '.', ''); } private function xmlEscape(string $str): string { return htmlspecialchars($str, ENT_XML1, 'UTF-8'); } /** * 2. Generate Base64 TLV QR Code (Local Fallback) */ public function generateQRCode(array $invoiceData): string { $sellerName = $invoiceData['supplier_name'] ?? ''; $taxNumber = $invoiceData['supplier_tin'] ?? ''; $timestamp = date('Y-m-d\TH:i:s\Z', strtotime($invoiceData['invoice_date'] ?? 'now')); $total = number_format($invoiceData['grand_total'] ?? 0, 3, '.', ''); $vat = number_format($invoiceData['tax_amount'] ?? 0, 3, '.', ''); $tlv = $this->toTLV(1, $sellerName) . $this->toTLV(2, $taxNumber) . $this->toTLV(3, $timestamp) . $this->toTLV(4, $total) . $this->toTLV(5, $vat); return base64_encode($tlv); } private function toTLV(int $tag, string $value): string { return chr($tag) . chr(strlen($value)) . $value; } /** * 3. Submit Invoice to JoFotara API */ public function submitInvoice(string $xmlContent, string $clientId, string $secretKey): array { // For production, we must encode XML in Base64 and wrap in JSON $payload = json_encode([ 'invoice' => base64_encode($xmlContent) ]); $ch = curl_init($this->baseUrl); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', "ClientId: $clientId", "SecretKey: $secretKey" ], CURLOPT_TIMEOUT => 30 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $decoded = json_decode($response, true) ?? []; $decoded['_http_code'] = $httpCode; if ($httpCode === 200) { return [ 'success' => true, 'uuid' => $decoded['invoiceUUID'] ?? $decoded['uuid'] ?? 'mock-' . uniqid(), 'qrCode' => $decoded['qrCode'] ?? $decoded['QRCode'] ?? null, 'raw' => $decoded ]; } return [ 'success' => false, 'error' => $decoded['errorMessage'] ?? 'API Connection Failed', 'raw' => $decoded ]; } }