HEX
Server: Apache/2.4.37 (CentOS Stream) OpenSSL/1.1.1k
System: Linux ysnet.com.tw 4.18.0-553.5.1.el8.x86_64 #1 SMP Tue May 21 05:46:01 UTC 2024 x86_64
User: test (521)
PHP: 7.4.33
Disabled: NONE
Upload Files
File: //var/www/test/Installationlist/submit.php
<?php
// ============================================================
// submit.php
// ============================================================
ob_start();
ini_set('display_errors', 0);
error_reporting(E_ALL);
session_start();
header('Content-Type: application/json; charset=utf-8');

register_shutdown_function(function() {
    $err = error_get_last();
    if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        while(ob_get_level()) ob_end_clean();
        header('HTTP/1.1 200 OK');
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['success'=>false,'message'=>'PHP Fatal: '.$err['message'].' (line '.$err['line'].')'], JSON_UNESCAPED_UNICODE);
    }
});

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    if (in_array($errno, [E_ERROR, E_WARNING, E_PARSE])) {
        while(ob_get_level()) ob_end_clean();
        http_response_code(200);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['success'=>false,'message'=>"PHP[$errno]: $errstr (line $errline)"], JSON_UNESCAPED_UNICODE);
        exit;
    }
    return false;
});

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    ob_clean(); echo json_encode(['success'=>false,'message'=>'不合法的請求']); exit;
}

$d = $_SESSION['preview'] ?? [];
if (empty($d)) {
    ob_clean(); echo json_encode(['success'=>false,'message'=>'Session 已過期,請重新填寫']); exit;
}

// ── 讀取欄位 ─────────────────────────
$userId      = $_POST['user_id']     ?? $d['user_id'] ?? '';
$sigType     = $_POST['sig_type']    ?? '本人';
$proxyId     = $_POST['proxy_id']    ?? '';
$dlSpeed     = (float)($_POST['dl_speed'] ?? 0);
$ulSpeed     = (float)($_POST['ul_speed'] ?? 0);
$installDate = $_POST['install_date'] ?? date('Y-m-d');
$enname      = $d['enname']  ?? '';
$enphone     = $d['enphone'] ?? '';

$applicantName  = $d['applicant_name']       ?? '';
$applicantEmail = $d['applicant_email']      ?? '';
$applicantId    = $d['applicant_usernumber'] ?? '';
$applicantPhone = $d['applicant_phone']      ?? '';
$applicantHome  = $d['applicant_homenumber'] ?? '';
$member         = $d['member']               ?? '';
$addr           = ($d['memberaddress'] ?? '') . ($d['useraddress'] ?? '');
$speed          = $d['speed']      ?? '';
$basenumber     = $d['basenumber'] ?? '';
$pppoeid        = $d['pppoeid']    ?? '';
$pppoepw        = $d['pppoepw']    ?? '';
$ipaddress      = $d['ipaddress']  ?? '';
$fistcount      = $d['fistcount']  ?? '';
$totalFee       = (int)($d['total_fee']     ?? 0);
$deposit        = (int)($d['deposit_total'] ?? 0);
$feeAmt         = (int)($d['fee_amount']    ?? 0);
$feeType        = $d['fee_type'] ?? '';

$infoList   = json_decode($d['info_devices']   ?? '[]', true) ?: [];
$routerList = json_decode($d['router_devices'] ?? '[]', true) ?: [];
$useList    = json_decode($d['use_devices']    ?? '[]', true) ?: [];
$extraList  = json_decode($d['extra_fees']     ?? '[]', true) ?: [];
$giftList   = json_decode($d['gift_months']    ?? '[]', true) ?: [];

$feeLabelMap = ['season'=>'季繳','halfyear'=>'半年繳','year'=>'一年繳','twoyear'=>'兩年繳','subscription'=>'定期定額月扣'];
$feeLabel = $feeLabelMap[$feeType] ?? $feeType;

function fmtD($s) {
    if(!$s) return '—';
    $dt = DateTime::createFromFormat('Y-m-d', $s);
    return $dt ? $dt->format('Y年m月d日') : $s;
}
function devChips($list, $type='info') {
    if(empty($list)) return '<span style="color:#999">—</span>';
    $out = '';
    foreach($list as $item) {
        $dev  = htmlspecialchars($item['device'] ?? '');
        $qty  = htmlspecialchars($item['qty']    ?? '1');
        $skip = ($type==='router' && $dev==='單機') || $dev==='無' || $dev==='UY';
        $out .= '<span style="display:inline-block;background:#E1F5EE;color:#085041;border-radius:20px;padding:2px 10px;font-size:12px;margin:2px">'
              . $dev . ($skip ? '' : ' ×'.$qty) . '</span>';
    }
    return $out;
}

// 純文字版(給 FileMaker 文字欄位用,不含 HTML,每項自動換行)
function devListToText($list, $type='info') {
    if(empty($list)) return '';
    $parts = [];
    foreach($list as $item) {
        $dev = $item['device'] ?? '';
        if ($dev === '') continue;
        if ($type === 'info') {
            // 資訊箱內設備:不寫入數量,僅設備名稱
            $parts[] = $dev;
        } else {
            $qty  = $item['qty'] ?? '1';
            $skip = ($type==='router' && $dev==='單機') || $dev==='無' || $dev==='UY';
            $parts[] = $skip ? $dev : ($dev.' ×'.$qty);
        }
    }
    return implode("\n", $parts);
}

// 贈送月份 → 純文字(寫入 FileMaker「介紹用戶資訊」欄位,所有項目合併同欄位、每項自動換行)
function giftListToText($giftList) {
    if (empty($giftList)) return '';
    $parts = [];
    foreach ($giftList as $g) {
        $reason = $g['reason'] ?? '';
        $months = $g['months'] ?? '';
        if ($reason === '') continue;
        $extra = '';
        if ($reason === '住戶介紹' && !empty($g['intro'])) {
            $extra = '(介紹:'.$g['intro'].')';
        } elseif ($reason === '他網轉入' && !empty($g['network'])) {
            $extra = '('.$g['network'].')';
        }
        $parts[] = $reason.$extra.' '.$months.'個月';
    }
    return implode("\n", $parts);
}

// ── 目錄 ─────────────────────────────
$baseUpload = '/home/pdf';
if (!is_dir($baseUpload)) @mkdir($baseUpload, 0755, true);
if (!is_writable($baseUpload)) {
    ob_clean();
    echo json_encode(['success'=>false,'message'=>'/home/pdf 目錄無寫入權限,請執行:chown apache:apache /home/pdf']);
    exit;
}
$safeId = preg_replace('/[^a-zA-Z0-9_-]/','_', $userId);

// ── 圖片 base64(不寫磁碟)──────────
function uploadToB64($key) {
    if (!isset($_FILES[$key]) || $_FILES[$key]['error'] !== 0) return '';
    $tmp  = $_FILES[$key]['tmp_name'];
    $mime = mime_content_type($tmp) ?: 'image/jpeg';
    return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($tmp));
}
$sigB64 = uploadToB64('signature');
$ph     = [uploadToB64('photo_0'), uploadToB64('photo_1'), uploadToB64('photo_2')];

// ── 建構 PDF ─────────────────────────
// 使用設備(含用途、料號、押金/據點)
$useRows = '';
$purposeMap = ['gift'=>'贈品','borrow'=>'借用','node'=>'據點'];
foreach($useList as $u) {
    $purpose = $purposeMap[$u['purpose']??''] ?? '';
    $sn      = $u['sn'] ?? '';
    $dp      = (int)($u['dp'] ?? 0);
    $nodePos = $u['nodePos'] ?? '';
    $sub = [];
    if($sn) $sub[] = '料號:'.htmlspecialchars($sn);
    if(($u['purpose']??'')==='borrow' && $dp > 0) $sub[] = '押金:$'.number_format($dp);
    if(($u['purpose']??'')==='node'   && $nodePos !== '') $sub[] = '據點編號:'.htmlspecialchars($nodePos);
    $subStr = $sub ? ' '.implode(' ', $sub) : '';
    $useRows .= '<div style="margin-bottom:5px">'
              . '<span style="background:#FAEEDA;color:#633806;border-radius:10px;padding:1px 7px;font-size:11px;margin-right:3px">'.$purpose.'</span>'
              . '<span style="background:#f0f0f0;border-radius:10px;padding:1px 8px;font-size:11px">'.htmlspecialchars($u['device']??'').'</span>'
              . '<div style="font-size:10.5px;color:#555;margin-top:2px;padding-left:2px">'.$subStr.'</div>'
              . '</div>';
}

$extraRows = '';
foreach($extraList as $ex) {
    $sn = ($ex['type']==='周邊設備' && !empty($ex['sn'])) ? '料號:'.htmlspecialchars($ex['sn']) : '';
    $extraRows .= '<tr><td>'.htmlspecialchars($ex['type']).'</td><td>'.$sn.'</td>'
                . '<td style="text-align:right">$'.number_format((int)($ex['amt']??0)).'</td></tr>';
}

// 贈送月份(含住戶介紹的介紹用戶資訊 / 他網轉入的他網名稱)
$giftChips = '';
foreach($giftList as $g) {
    $reason = $g['reason'] ?? '';
    $extra  = '';
    if ($reason === '住戶介紹' && !empty($g['intro'])) {
        $extra = '(介紹:'.htmlspecialchars($g['intro']).')';
    } elseif ($reason === '他網轉入' && !empty($g['network'])) {
        $extra = '('.htmlspecialchars($g['network']).')';
    }
    $giftChips .= '<span style="background:#FAEEDA;border-radius:10px;padding:2px 8px;font-size:11px;margin-right:4px;display:inline-block;margin-bottom:2px">'
               . htmlspecialchars($reason).$extra.' '.htmlspecialchars($g['months']).'月</span>';
}

// 頁首
$pf  = '<div style="font-size:9px;text-align:center;color:#666;padding:1.5mm 0 1.5mm">公司地址:桃園市桃園區大興西路二段61號16樓 電話:03-358-5867 傳真:03-301-2115 官方網址:www.ysnet.com.tw 營業時間:每日10:00~20:00</div>';
// 標題:LOGO 左下,標題絕對置中,與公司資訊有間距
$ph2 = '<div style="position:relative;border-bottom:1.5px solid #000;padding-bottom:4px;margin-bottom:6px;min-height:28px">'
     . '<div style="padding-left:4px;line-height:1.4"><strong style="font-size:14px">YSNET</strong><br><span style="font-size:8.5px;color:#555">亞訊股份有限公司</span></div>'
     . '<div style="position:absolute;top:3px;left:0;right:0;text-align:center;font-size:20px;font-weight:bold;letter-spacing:2px;pointer-events:none">網際網路接取服務申請書</div>'
     . '</div>';

// PPPoE 或靜態 IP
$netRow = '';
if (!empty($ipaddress)) {
    $netRow = '<tr><td class="lbl">IP 位址</td><td colspan="3">'.htmlspecialchars($ipaddress).'</td></tr>';
} else {
    $netRow = '<tr><td class="lbl">PPPoE 帳號</td><td>'.htmlspecialchars($pppoeid).'</td>'
            . '<td class="lbl">PPPoE 密碼</td><td>'.htmlspecialchars($pppoepw).'</td></tr>';
}

// 施工工程師&收款人合併為一欄
$ennameCell = htmlspecialchars($enname);
if ($enphone) $ennameCell .= ' '.htmlspecialchars($enphone);

// 代簽列
$proxyCell = ($sigType==='代簽' && $proxyId)
    ? '<span style="font-size:9.5px;color:#555">(代簽人身分證:'.htmlspecialchars($proxyId).')</span>'
    : '';

// 簽名欄 HTML(用 heredoc 避免引號衝突)
$sigCell = '';
if ($sigB64) {
    $sigCell = '<img src="' . $sigB64 . '" style="max-width:100%;max-height:40mm;border:1px solid #ccc;display:block">';
} else {
    $sigCell = '<div style="color:red;font-size:10px">未提供簽名</div>';
}

$photoHtml = '';
$photoLabels = ['身分證正面','身分證反面','第二證件正面'];
foreach ($photoLabels as $i => $lbl) {
    $imgTag = $ph[$i]
        ? '<img src="'.$ph[$i].'" style="max-width:100%;max-height:38mm;border:1px solid #ccc">'
        : '<div style="color:red;font-size:10px">未上傳</div>';
    $photoHtml .= '<div style="text-align:center;margin-bottom:8px">'
               . '<div style="font-weight:bold;font-size:10.5px;margin-bottom:3px">'.$lbl.'</div>'
               . $imgTag
               . '</div>';
}

// 條款
$termsHtml = ''
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第一條</span> Internet係開放性網路,各種資源之正確性與完整性,本公司無法保證,倘客戶因截取使用網路資料而導致損失時,本公司亦不負損害賠償責任。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第二條</span> 用戶租用本業務應繳各項費用及收費標準如價目表;資費如有調整時,按新費率計收,用戶如不同意繳付新費率,可隨時終止契約,並辦理退費。申請加值服務者,以網頁上公告費用計收各項費用。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第三條</span> 本公司因技術上需要,得將用戶號碼、用戶識別碼、IP ADDRESS、連接方式、傳輸型態等予以變更,用戶不得異議或提出其他要求。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第四條</span> 本公司使用網路位址轉換技術共享真實IP,少數需要真實IP的連線服務會受此限制,用戶應確認有無此需求。</p>'
. '<p style="margin:0 0 4px;background:#FFFBEA;padding:3px 6px;border-left:3px solid #E6A817;border-radius:2px"><span style="font-weight:bold;color:#8B5000">第五條</span> 本公司基於保障消費者權益,給予您一個月滿意鑑賞期,期間若不滿意,本公司保證全額退費。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第六條</span> 本公司配發每一個用戶一組帳號密碼,每組帳號密碼可供一台電腦使用,若家中為兩台以上電腦上網,則需請用戶使用IP分享器。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第七條</span> 本公司以戶為單位,用戶不得私自接線提供他戶使用。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第八條</span> 用戶端如有無線網路路由設備,應設加密,以防止第三人竊取頻寬或是進行違法情形。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第九條</span> 本公司應維持本業務系統設備之正常運作,遇有障礙應盡速修復。但用戶自備設備者,應自行檢修,如因本公司系統設備障礙,以致發生錯誤、遲滯、中斷或不能傳遞而造成損害時,除按第十條規定補償外,本公司不負損害賠責任。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第十條</span> 用戶租用本業務,因不可歸責於用戶之原因,致系統障礙不能通信時:本公司應扣減網路租費。連續阻斷二十四小時以上者,每二十四小時扣減全月租費三十分一。由於天然災害之不可抗力致不能通信者,自連續阻日起至修復日止,不收租費。</p>'
. '<div style="border:2px solid #C00;border-radius:3px;background:#FFF0F0;padding:5px 8px;margin:4px 0 6px">'
. '<p style="margin:0 0 3px"><span style="font-weight:bold;color:#C00">第十一條(退租說明)</span> 辦理退租時,須攜帶有效身份證明文件正本至桃園市桃園區大興西路二段61號16樓辦理,注意事項:</p>'
. '<ol style="margin:0;padding-left:18px;font-size:9.5px;line-height:1.6">'
. '<li>使用登記人為退費權益人,需攜帶雙證件正本;委託代理人辦理者,代理人亦需攜帶雙方雙證件正本。</li>'
. '<li>填妥退租申請書,完成折讓程序並經身份確認後,方可退費。</li>'
. '<li>使用月份不足一個月者,依使用天數等比例結算,贈送月份不計入退費。</li>'
. '<li>倘有加購優惠,先依標準資費計算已使用月份,之後月份再依加購資費計算。</li>'
. '<li>特定資費方案有聲明不得退費者,不享有退費措施。</li>'
. '<li>申裝優惠方案未滿優惠期限退費,喪失優惠資格,需補繳裝機費500元,贈品需補繳費用。</li>'
. '<li>光纖安裝用戶若不續用,需歸還光纖設備(光纖盒、變壓器及光纖線),則無息退還押金。</li>'
. '<li>影音方案須綁約乙年,期間無法退租退費。</li>'
. '</ol>'
. '</div>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第十二條</span> 用戶應在通知期限內繳清費用,逾期未繳者,本公司得暫停或終止服務,積欠費用仍需繳納。有異議並申訴者,在未查明責任歸屬前,本公司暫緩催費。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第十三條</span> 禁止行為:竊取他人資訊、擅自複製轉售、寄發垃圾郵件、散播病毒、言論違背公序良俗、擷取未授權資源、不當使用續傳軟體及其他危害通信品質之情事。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第十四條</span> P2P軟體(如BT、迅雷等)下載上傳均無問題;為維護社區連線品質,對影響其他用戶上網品質之情形,得限制其頻寬,大量P2P傳輸需求者請謹慎評估。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第十五條</span> 申請電信業務所需相關證件,依主管機關通訊傳播委員會相關要求辦理;個人資料收集及告知義務,依個人資料保護法相關細則實行。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第十六條</span> 本契約條款未規定事項,用戶同意遵守相關法令規定及本公司各項業務營業規章之規定。</p>'
. '<p style="margin:0 0 4px"><span style="font-weight:bold">第十七條</span> 因本契約涉訟時,雙方合意以桃園地方法院為第一審管轄法院。</p>';

// ── 第一、二頁共用內容(PDF 用 <head>+style+page1+page2,第三頁條款視 $includeTerms 決定是否輸出)──
function buildPdfHtml($includeTerms, $pf, $ph2, $userId, $member, $applicantName, $applicantHome,
    $applicantId, $applicantPhone, $addr, $applicantEmail, $speed, $basenumber, $netRow, $fistcount,
    $infoList, $routerList, $useRows, $feeType, $feeLabel, $feeAmt, $extraRows, $totalFee, $deposit,
    $enname, $enphone, $installDate, $d, $giftList, $giftChips, $dlSpeed, $ulSpeed,
    $proxyCell, $sigB64, $ph, $termsHtml) {

ob_start();
?>
<!DOCTYPE html><html lang="zh-Hant"><head><meta charset="UTF-8">
<style>
*{box-sizing:border-box}
body{font-family:"Microsoft JhengHei","WenQuanYi Micro Hei",sans-serif;font-size:11px;color:#111;margin:0;padding:0}
.page{width:100%;padding:2mm 0}
table{border-collapse:collapse;width:100%;margin-bottom:7px;table-layout:fixed}
td{border:1px solid #000;padding:4px 6px;vertical-align:top;font-size:10.5px;word-break:break-word;overflow-wrap:break-word}
.sec{background:#f0f0f0;font-weight:bold;font-size:12px;text-align:center}
.lbl{font-weight:bold;background:#f7f7f7;width:22%}
.pb{page-break-before:always}
.terms-td{border:none;vertical-align:top}
.terms-td p{margin:0 0 4px;font-size:10px;line-height:1.65}
</style></head><body>

<!-- 第一頁 -->
<div class="page">
<?php echo $pf.$ph2; ?>
<table>
<tr><td colspan="4" class="sec">申請人基本資料</td></tr>
<tr><td class="lbl">用戶編號</td><td style="width:28%"><?= htmlspecialchars($userId) ?></td><td class="lbl">社區名稱</td><td><?= htmlspecialchars($member) ?></td></tr>
<tr><td class="lbl">申請人姓名</td><td><?= htmlspecialchars($applicantName) ?></td><td class="lbl">住宅電話</td><td><?= htmlspecialchars($applicantHome) ?></td></tr>
<tr><td class="lbl">身分證/統編</td><td><?= htmlspecialchars($applicantId) ?></td><td class="lbl">行動電話</td><td><?= htmlspecialchars($applicantPhone) ?></td></tr>
<tr><td class="lbl">裝機地址</td><td colspan="3"><?= htmlspecialchars($addr) ?></td></tr>
<tr><td class="lbl">電子信箱</td><td colspan="3"><?= htmlspecialchars($applicantEmail) ?></td></tr>
<tr><td class="lbl">申請速率</td><td><?= htmlspecialchars($speed) ?> Mbps</td><td class="lbl">連接據點</td><td><?= htmlspecialchars($basenumber) ?></td></tr>
<?php echo $netRow; ?>
<?php if($fistcount): ?>
<tr><td class="lbl">首裝優惠</td><td colspan="3" style="font-size:10px;white-space:pre-wrap"><?= htmlspecialchars($fistcount) ?></td></tr>
<?php endif; ?>
</table>

<table>
<tr><td colspan="3" class="sec">設備資訊</td></tr>
<tr><td style="font-weight:bold;background:#f7f7f7;width:33%">室內資訊箱設備</td><td style="font-weight:bold;background:#f7f7f7;width:33%">室內網路設備</td><td style="font-weight:bold;background:#f7f7f7">使用設備</td></tr>
<tr><td><?= devChips($infoList,'info') ?></td><td><?= devChips($routerList,'router') ?></td><td><?= $useRows ?: '<span style="color:#999">—</span>' ?></td></tr>
</table>

<table>
<tr><td colspan="3" class="sec">收費項目明細</td></tr>
<tr><td style="font-weight:bold;background:#f7f7f7;width:30%">項目</td><td style="font-weight:bold;background:#f7f7f7">品項</td><td style="font-weight:bold;background:#f7f7f7;width:22%">金額</td></tr>
<?php if($feeType==='subscription'): ?>
<tr><td>網路費用</td><td><?= htmlspecialchars($feeLabel) ?></td><td style="text-align:right;color:#0F6E56;font-size:9.5px">信用卡定期定額繳費</td></tr>
<?php else: ?>
<tr><td>網路費用</td><td><?= htmlspecialchars($feeLabel) ?></td><td style="text-align:right">$<?= number_format($feeAmt) ?></td></tr>
<?php endif; ?>
<?php echo $extraRows; ?>
<?php if($feeType==='subscription' && empty($extraRows)): ?>
<tr><td colspan="2" style="text-align:right;font-weight:bold">應繳費用總計</td><td style="text-align:right;font-weight:bold;color:#999;font-size:14px">—</td></tr>
<?php else: ?>
<tr><td colspan="2" style="text-align:right;font-weight:bold">應繳費用總計</td><td style="text-align:right;font-weight:bold;color:#C00">$<?= number_format($totalFee) ?></td></tr>
<?php endif; ?>
<?php if($deposit>0): ?><tr><td colspan="2" style="text-align:right">借用設備押金</td><td style="text-align:right;color:#B07000">$<?= number_format($deposit) ?></td></tr><?php endif; ?>
</table>

<!-- 施工工程師:4格一行 -->
<table>
<tr>
  <td style="font-weight:bold;background:#f7f7f7;width:25%">施工工程師&收款人</td>
  <td style="width:25%"><?= htmlspecialchars($enname) ?></td>
  <td style="font-weight:bold;background:#f7f7f7;width:20%">聯絡電話</td>
  <td style="width:30%"><?= htmlspecialchars($enphone) ?></td>
</tr>
</table>

<!-- 日期資訊(左)+ 完工測速(右)並排 -->
<table>
<tr>
<!-- 左:日期資訊(垂直排列) -->
<td style="border:none;padding:0;width:55%;vertical-align:top">
<table style="margin-bottom:0">
<tr><td colspan="2" class="sec">日期資訊</td></tr>
<tr><td class="lbl" style="width:40%">裝機日</td><td><?= fmtD($installDate) ?></td></tr>
<tr><td class="lbl">起租日</td><td><?= fmtD($d['rental_start_date']??'') ?></td></tr>
<tr><td class="lbl">到期日</td><td><?= fmtD($d['rental_end_date']??'') ?></td></tr>
<?php if(!empty($giftList)): ?>
<tr><td class="lbl">贈送月份</td><td><?= $giftChips ?></td></tr>
<tr><td class="lbl">贈送後到期</td><td><?= fmtD($d['real_end_date']??'') ?></td></tr>
<?php endif; ?>
</table>
</td>
<!-- 間距 -->
<td style="border:none;padding:0;width:3%"></td>
<!-- 右:完工測速 -->
<td style="border:none;padding:0;width:42%;vertical-align:top">
<table style="margin-bottom:0">
<tr><td colspan="2" class="sec">完工有線測速</td></tr>
<tr><td class="lbl" style="width:50%">下載(Mbps)</td><td><?= $dlSpeed ?></td></tr>
<tr><td class="lbl">上傳(Mbps)</td><td><?= $ulSpeed ?></td></tr>
</table>
</td>
</tr>
</table>
</div>

<!-- 第二頁:申請人簽名與證件影本 -->
<div class="page pb">
<?php echo $pf.$ph2; ?>

<!-- 標題 -->
<div style="text-align:center;font-size:14px;font-weight:bold;border:1px solid #000;background:#f0f0f0;padding:5px;margin-bottom:8px">申請人簽名與證件影本</div>

<!-- 申請人簽名(上方,全寬,大) -->
<div style="margin-bottom:8px">
  <div style="font-weight:bold;font-size:11px;padding:4px 8px;background:#f7f7f7;border:1px solid #000;border-bottom:none">
    申請人簽名 <?= $proxyCell ?>
  </div>
  <div style="border:1px solid #000;height:65mm;display:flex;align-items:center;justify-content:center;background:#fafafa">
    <?= $ph[0] === '' && $ph[1] === '' && $ph[2] === '' && !$sigB64
        ? '<div style="color:red;font-size:10px">未提供簽名</div>'
        : ($sigB64 ? '<img src="'.$sigB64.'" style="max-width:90%;max-height:60mm;display:block;margin:0 auto">' : '<div style="color:red;font-size:10px">未提供簽名</div>') ?>
  </div>
</div>

<!-- 證件影本(下方,2×2 格局) -->
<div style="font-weight:bold;font-size:11px;padding:4px 8px;background:#f7f7f7;border:1px solid #000;border-bottom:none">證件影本</div>
<table style="margin-bottom:0;border-top:none;table-layout:fixed">
<tr>
  <!-- 左上:身分證正面 -->
  <td style="width:50%;vertical-align:top;padding:6px;border-right:1px solid #ccc;border-bottom:1px solid #ccc">
    <div style="text-align:center;font-weight:bold;font-size:10.5px;margin-bottom:4px">身分證正面</div>
    <div style="text-align:center;border:1px solid #ddd;background:#fafafa;height:48mm;display:flex;align-items:center;justify-content:center">
      <?= $ph[0] ? '<img src="'.$ph[0].'" style="max-width:100%;max-height:46mm">' : '<div style="color:red;font-size:10px">未上傳</div>' ?>
    </div>
  </td>
  <!-- 右上:第二證件正面 -->
  <td style="width:50%;vertical-align:top;padding:6px;border-bottom:1px solid #ccc">
    <div style="text-align:center;font-weight:bold;font-size:10.5px;margin-bottom:4px">第二證件正面</div>
    <div style="text-align:center;border:1px solid #ddd;background:#fafafa;height:48mm;display:flex;align-items:center;justify-content:center">
      <?= $ph[2] ? '<img src="'.$ph[2].'" style="max-width:100%;max-height:46mm">' : '<div style="color:red;font-size:10px">未上傳</div>' ?>
    </div>
  </td>
</tr>
<tr>
  <!-- 左下:身分證反面 -->
  <td style="width:50%;vertical-align:top;padding:6px;border-right:1px solid #ccc">
    <div style="text-align:center;font-weight:bold;font-size:10.5px;margin-bottom:4px">身分證反面</div>
    <div style="text-align:center;border:1px solid #ddd;background:#fafafa;height:48mm;display:flex;align-items:center;justify-content:center">
      <?= $ph[1] ? '<img src="'.$ph[1].'" style="max-width:100%;max-height:46mm">' : '<div style="color:red;font-size:10px">未上傳</div>' ?>
    </div>
  </td>
  <!-- 右下:空白 -->
  <td style="width:50%;vertical-align:top;padding:6px">
    <div style="height:48mm"></div>
  </td>
</tr>
</table>
</div>

<?php if ($includeTerms): ?>
<!-- 第三頁:服務條款 -->
<div class="page pb">
<?php echo $pf.$ph2; ?>
<div style="text-align:center;font-size:14px;font-weight:bold;border-bottom:2px solid #1D9E75;padding-bottom:4px;margin-bottom:6px;color:#085041">網際網路接取服務契約條款</div>
<div style="font-size:10px;line-height:1.7;color:#111">
<?php echo $termsHtml; ?>
</div>
</div>
<?php endif; ?>

</body></html>
<?php
return ob_get_clean();
}

// 三頁版(給 Email 附件、留檔 /home/pdf 用)
$pdfHtml = buildPdfHtml(true, $pf, $ph2, $userId, $member, $applicantName, $applicantHome,
    $applicantId, $applicantPhone, $addr, $applicantEmail, $speed, $basenumber, $netRow, $fistcount,
    $infoList, $routerList, $useRows, $feeType, $feeLabel, $feeAmt, $extraRows, $totalFee, $deposit,
    $enname, $enphone, $installDate, $d, $giftList, $giftChips, $dlSpeed, $ulSpeed,
    $proxyCell, $sigB64, $ph, $termsHtml);

// 兩頁版(不含條款,給 FileMaker「裝機單掃描」容器用)
$pdfHtmlTwoPage = buildPdfHtml(false, $pf, $ph2, $userId, $member, $applicantName, $applicantHome,
    $applicantId, $applicantPhone, $addr, $applicantEmail, $speed, $basenumber, $netRow, $fistcount,
    $infoList, $routerList, $useRows, $feeType, $feeLabel, $feeAmt, $extraRows, $totalFee, $deposit,
    $enname, $enphone, $installDate, $d, $giftList, $giftChips, $dlSpeed, $ulSpeed,
    $proxyCell, $sigB64, $ph, $termsHtml);

// ── wkhtmltopdf 共用呼叫函式 ─────────
$wkPaths = ['/usr/local/bin/wkhtmltopdf', '/usr/bin/wkhtmltopdf'];
$wkBin   = '';
$disabled = array_map('trim', explode(',', ini_get('disable_functions')));
foreach($wkPaths as $p) {
    if(file_exists($p) && is_executable($p)) {
        if(!in_array('exec', $disabled)){
            $ver=[]; exec('HOME=/tmp '.$p.' --version 2>&1', $ver, $vr);
            if($vr===0){ $wkBin=$p; break; }
        } else { $wkBin=$p; break; }
    }
}

function renderPdf($wkBin, $disabled, $html, $outPath) {
    $wkError = '';
    $generated = false;
    if (!$wkBin) {
        return ['ok'=>false, 'error'=>'wkhtmltopdf 未找到'];
    }
    $htmlTmp = '/tmp/ys_render_'.bin2hex(random_bytes(6)).'.html';
    if (file_put_contents($htmlTmp, $html) === false) {
        return ['ok'=>false, 'error'=>'無法寫入暫存 HTML:'.$htmlTmp];
    }
    if (!in_array('exec', $disabled)) {
        $cmd = 'HOME=/tmp DISPLAY=:0 XAUTHORITY=/dev/null '.$wkBin
             . ' --encoding utf-8 --page-size A4'
             . ' --margin-top 8mm --margin-bottom 8mm --margin-left 10mm --margin-right 10mm'
             . ' --disable-smart-shrinking --load-error-handling ignore --quiet'
             . ' '.escapeshellarg($htmlTmp).' '.escapeshellarg($outPath).' 2>&1';
        $out=[]; exec($cmd, $out, $ret);
        $generated = ($ret===0 && file_exists($outPath) && filesize($outPath)>500);
        if (!$generated) $wkError = 'exec ret='.$ret.': '.implode('|',$out);
    } elseif (!in_array('proc_open', $disabled)) {
        $cmd = $wkBin.' --encoding utf-8 --page-size A4'
             . ' --margin-top 8mm --margin-bottom 8mm --margin-left 10mm --margin-right 10mm'
             . ' --disable-smart-shrinking --load-error-handling ignore --quiet'
             . ' '.escapeshellarg($htmlTmp).' '.escapeshellarg($outPath);
        $proc = proc_open($cmd,[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],$pipes,null,
            ['HOME'=>'/tmp','DISPLAY'=>':0','XAUTHORITY'=>'/dev/null','PATH'=>'/usr/local/bin:/usr/bin:/bin']);
        if(is_resource($proc)){
            fclose($pipes[0]);
            $se=stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]);
            $ret=proc_close($proc);
            $generated=($ret===0 && file_exists($outPath) && filesize($outPath)>500);
            if(!$generated) $wkError='proc ret='.$ret.': '.$se;
        } else { $wkError='proc_open失敗'; }
    } else {
        $wkError = 'exec/proc_open 均被禁用';
    }
    @unlink($htmlTmp);
    return ['ok'=>$generated, 'error'=>$wkError];
}

$pdfFilename  = $safeId.'.pdf';
$pdfPath      = $baseUpload.'/'.$pdfFilename;
$pdfTwoPagePath = $baseUpload.'/'.$safeId.'_2page.pdf';

$r1 = renderPdf($wkBin, $disabled, $pdfHtml, $pdfPath);
$pdfGenerated = $r1['ok'];
$wkError      = $r1['error'];

$r2 = renderPdf($wkBin, $disabled, $pdfHtmlTwoPage, $pdfTwoPagePath);
$pdfTwoPageGenerated = $r2['ok'];
$wkErrorTwoPage       = $r2['error'];

if(!$pdfGenerated) error_log('[submit.php] PDF(3頁)失敗: '.$wkError);
if(!$pdfTwoPageGenerated) error_log('[submit.php] PDF(2頁)失敗: '.$wkErrorTwoPage);

// ── Email ────────────────────────────
$emailSent = false;
if (!empty($applicantEmail)) {
    $from     = 'service@ysnet.com.tw';
    $subject  = '=?UTF-8?B?'.base64_encode('亞訊寬頻網路申請書確認 - '.$applicantName.'(用戶編號:'.$userId.')').'?=';
    $boundary = md5(uniqid('ys_'));
    $bodyHtml = '<!DOCTYPE html><html><body style="font-family:Microsoft JhengHei,sans-serif;color:#111">'
              . '<p>親愛的 <strong>'.htmlspecialchars($applicantName).'</strong> 您好,</p>'
              . '<p>感謝您申請亞訊寬頻網路服務,'.($pdfGenerated?'完整申請書 PDF 已附於此封信件,請妥善保存。':'您的申請已送出,我們將儘速處理。').'</p>'
              . '<table style="border-collapse:collapse;max-width:480px;width:100%">'
              . '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd;width:35%">用戶編號</td><td style="padding:6px;border:1px solid #ddd">'.htmlspecialchars($userId).'</td></tr>'
              . '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd">申請人姓名</td><td style="padding:6px;border:1px solid #ddd">'.htmlspecialchars($applicantName).'</td></tr>'
              . '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd">裝機地址</td><td style="padding:6px;border:1px solid #ddd">'.htmlspecialchars($addr).'</td></tr>'
              . '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd">申請速率</td><td style="padding:6px;border:1px solid #ddd">'.htmlspecialchars($speed).' Mbps</td></tr>'
              . '</table>'
              . '<p style="margin-top:16px">如有任何問題,歡迎來電 <strong>03-358-5867</strong></p>'
              . '<p style="color:#666;font-size:12px">亞訊寬頻 客服中心 | service@ysnet.com.tw | www.ysnet.com.tw</p>'
              . '</body></html>';
    $htmlPart = "--{$boundary}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\n"
              . chunk_split(base64_encode($bodyHtml))."\r\n";
    if ($pdfGenerated && file_exists($pdfPath)) {
        $headers = "From:{$from}\r\nReply-To:{$from}\r\nMIME-Version:1.0\r\nContent-Type:multipart/mixed;boundary=\"{$boundary}\"\r\n";
        $body    = $htmlPart
                 . "--{$boundary}\r\nContent-Type:application/pdf;name=\"{$pdfFilename}\"\r\n"
                 . "Content-Transfer-Encoding:base64\r\nContent-Disposition:attachment;filename=\"{$pdfFilename}\"\r\n\r\n"
                 . chunk_split(base64_encode(file_get_contents($pdfPath)))."\r\n--{$boundary}--";
    } else {
        $headers = "From:{$from}\r\nReply-To:{$from}\r\nMIME-Version:1.0\r\nContent-Type:text/html;charset=UTF-8\r\nContent-Transfer-Encoding:base64\r\n";
        $body    = base64_encode($bodyHtml);
    }
    $emailSent = mail($applicantEmail, $subject, $body, $headers);
    if(!$emailSent) error_log('[submit.php] mail() failed for: '.$applicantEmail);
}

// ============================================================
// ── FileMaker Data API 寫入(v2 套用 fm_test.php 已驗證邏輯)
// ============================================================
//
// 設定:請視實際環境調整網址、資料庫、帳密與布局名稱。
// 若伺服器的 FileMaker Server 使用自簽憑證,下方 cURL 設定了
// 忽略 SSL 驗證;正式環境建議改用正式憑證並移除這兩行 CURLOPT。
//
// 注意:FM_HOST 請與 fm_test.php 驗證通過的位址一致,否則可能因
// DNS / NAT / port 路徑不同造成 submit.php 連得到但 fm_test 不行
// 或反之的狀況。
//
define('FM_HOST',     'https://61.216.173.217:8443');
define('FM_DB',       'YSNET_MIS');
define('FM_LAYOUT',   'api-申請人資料寫入');
define('FM_LAYOUT2',  'api-繳費紀錄'); // 新增:繳費紀錄布局(直接新增紀錄,不搜尋)
define('FM_LAYOUT3',  'api-消料設備'); // 新增:消料設備布局(直接新增紀錄,不搜尋)
define('FM_USER',     'webapi');
define('FM_PASS',     '@Andrejiao9527');

/**
 * 共用 cURL 呼叫(v2 修正版)
 *   - multipart 不再手動設 Content-Type,讓 cURL 自動產生帶 boundary 的 header
 *   - CURLFile 使用三參數版本(指定 MIME 與檔名)
 *   - timeout 提升到 60 秒(PDF 上傳較耗時)
 */
function fmCurl($method, $url, $token = null, $bodyArr = null, $isMultipart = false, $multipartFile = null, $multipartFieldName = 'upload') {
    $ch = curl_init($url);
    $headers = [];
    if ($token) $headers[] = 'Authorization: Bearer '.$token;

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60); // [v2 修正] 30 → 60,避免大 PDF 上傳逾時
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

    if ($isMultipart) {
        // [v2 修正] 重要:不要手動設 Content-Type,讓 cURL 自動產生帶 boundary 的 header
        // CURLFile 第二參數 = MIME type,第三參數 = 顯示檔名
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
            $multipartFieldName => new CURLFile($multipartFile, 'application/pdf', basename($multipartFile))
        ]);
    } elseif ($bodyArr !== null) {
        $headers[] = 'Content-Type: application/json';
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($bodyArr, JSON_UNESCAPED_UNICODE));
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    $resp = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlErr  = curl_error($ch);
    curl_close($ch);

    if ($resp === false) {
        return ['ok'=>false, 'http'=>0, 'error'=>'cURL錯誤: '.$curlErr, 'data'=>null];
    }
    $json = json_decode($resp, true);
    $fmCode = $json['messages'][0]['code'] ?? null;
    $fmMsg  = $json['messages'][0]['message'] ?? '';
    $ok = ($httpCode >= 200 && $httpCode < 300) && ($fmCode === '0');
    return ['ok'=>$ok, 'http'=>$httpCode, 'fmCode'=>$fmCode, 'fmMsg'=>$fmMsg, 'data'=>$json, 'raw'=>$resp];
}

/**
 * 將 Y-m-d 格式日期轉為 FileMaker Data API 預期的 m/d/Y 格式。
 * (FileMaker Data API 寫入日期欄位預設格式為 m/d/Y,與檔案本身的地區設定無關)
 */
function fmDate($isoDate) {
    if (empty($isoDate)) return '';
    $dt = DateTime::createFromFormat('Y-m-d', $isoDate);
    if (!$dt) return $isoDate;
    return $dt->format('m/d/Y');
}

/**
 * 在「api-繳費紀錄」布局中,依序新建繳費相關紀錄(user_id 僅作為寫入內容,
 * 不透過搜尋比對,每一筆都直接新增)。
 * 寫入順序固定為:
 *   1) 網路費(起租日/終止日/網路費=維護費/收款人=ENname)
 *   2) 贈送月份(起租日=到期日隔日/終止日=含贈送後到期/收款人固定寫「贈送」)
 *   3) 其他收費項目:配線費用/裝機費用/周邊設備(一筆一筆新增,收款人=ENname)
 *   4) 押金(收款人=ENname)
 * 回傳每筆寫入結果的陣列,方便上層組裝錯誤訊息。
 */
function createPaymentRecords($token, $baseUrl2, $userId, $installDate, $rentalStart, $rentalEnd,
    $realEndDate, $feeType, $feeAmt, $giftList, $extraList, $deposit, $enname) {

    $results = [];

    // web 端加收項目類型 → FileMaker「api-繳費紀錄」欄位對應
    $extraFieldMap = [
        '裝機費用' => '接線費',
        '配線費用' => '室內配線費',
        '周邊設備' => '電腦週邊',
    ];

    // ── 1) 網路費紀錄 ──────────────────────────────────
    // 定期定額(信用卡月扣)沒有固定金額可寫入,跳過;未選方案或金額為 0 亦跳過。
    if ($feeType !== '' && $feeType !== 'subscription' && $feeAmt > 0) {
        $fd = [
            '用戶編號' => $userId,
            '入帳日期' => fmDate($installDate),
            '起租日'   => fmDate($rentalStart),
            '終止日'   => fmDate($rentalEnd),
            '維護費'   => $feeAmt,
            '收款人'   => $enname,
        ];
        $r = fmCurl('POST', $baseUrl2.'/records', $token, ['fieldData' => $fd]);
        $results[] = ['type' => '網路費', 'ok' => $r['ok'], 'msg' => ($r['fmMsg'] ?? '')];
    }

    // ── 2) 贈送月份紀錄 ────────────────────────────────
    // 起租日=到期日隔日,終止日=含贈送後到期,收款人固定寫「贈送」。
    if (!empty($giftList) && !empty($rentalEnd)) {
        $dayAfterEnd = '';
        $dtEnd = DateTime::createFromFormat('Y-m-d', $rentalEnd);
        if ($dtEnd) {
            $dtEnd->modify('+1 day');
            $dayAfterEnd = $dtEnd->format('Y-m-d');
        }
        $fd = [
            '用戶編號' => $userId,
            '入帳日期' => fmDate($installDate),
            '起租日'   => fmDate($dayAfterEnd),
            '終止日'   => fmDate($realEndDate ?: $rentalEnd),
            '收款人'   => '贈送',
        ];
        $r = fmCurl('POST', $baseUrl2.'/records', $token, ['fieldData' => $fd]);
        $results[] = ['type' => '贈送月份', 'ok' => $r['ok'], 'msg' => ($r['fmMsg'] ?? '')];
    }

    // ── 3) 其他收費項目(配線費用/裝機費用/周邊設備,一筆一筆新增) ──
    foreach ($extraList as $ex) {
        $type = $ex['type'] ?? '';
        $amt  = (int)($ex['amt'] ?? 0);
        if ($amt <= 0 || !isset($extraFieldMap[$type])) continue;
        $field = $extraFieldMap[$type];
        $fd = [
            '用戶編號' => $userId,
            '入帳日期' => fmDate($installDate),
            $field     => $amt,
            '收款人'   => $enname,
        ];
        $r = fmCurl('POST', $baseUrl2.'/records', $token, ['fieldData' => $fd]);
        $results[] = ['type' => $type, 'ok' => $r['ok'], 'msg' => ($r['fmMsg'] ?? '')];
    }

    // ── 4) 押金紀錄 ────────────────────────────────────
    if ($deposit > 0) {
        $fd = [
            '用戶編號' => $userId,
            '入帳日期' => fmDate($installDate),
            '押金'     => $deposit,
            '收款人'   => $enname,
        ];
        $r = fmCurl('POST', $baseUrl2.'/records', $token, ['fieldData' => $fd]);
        $results[] = ['type' => '押金', 'ok' => $r['ok'], 'msg' => ($r['fmMsg'] ?? '')];
    }

    return $results;
}

/**
 * 在「api-消料設備」布局中,依序新建設備料號紀錄(不搜尋,直接新增)。
 * 寫入範圍:
 *   - 使用設備(贈品/借用):有填料號者寫入;用途為「據點」者不寫入
 *   - 加收項目中的「周邊設備」:有填料號者寫入
 * 每一個有料號的設備各自新建一筆紀錄,欄位順序固定先客戶編號,再設備編號。
 */
function createMaterialRecords($token, $baseUrl3, $userId, $useList, $extraList) {
    $results = [];

    // ── 使用設備:用途為「據點」者不寫入料號,其餘(贈品/借用)只要有料號就寫入 ──
    foreach ($useList as $u) {
        $purpose = $u['purpose'] ?? '';
        $sn      = trim($u['sn'] ?? '');
        if ($purpose === 'node') continue; // 據點設備不寫入料號
        if ($sn === '') continue;
        $fd = [
            '客戶編號' => $userId,
            '設備編號' => $sn,
        ];
        $r = fmCurl('POST', $baseUrl3.'/records', $token, ['fieldData' => $fd]);
        $results[] = ['type' => '使用設備('.($u['device'] ?? '').')', 'ok' => $r['ok'], 'msg' => ($r['fmMsg'] ?? '')];
    }

    // ── 加收項目:周邊設備的料號 ──
    foreach ($extraList as $ex) {
        if (($ex['type'] ?? '') !== '周邊設備') continue;
        $sn = trim($ex['sn'] ?? '');
        if ($sn === '') continue;
        $fd = [
            '客戶編號' => $userId,
            '設備編號' => $sn,
        ];
        $r = fmCurl('POST', $baseUrl3.'/records', $token, ['fieldData' => $fd]);
        $results[] = ['type' => '周邊設備', 'ok' => $r['ok'], 'msg' => ($r['fmMsg'] ?? '')];
    }

    return $results;
}

/**
 * 將申請資料寫入 FileMaker,並上傳兩頁版 PDF 到容器欄位。
 * 回傳 ['ok'=>bool, 'message'=>string]
 */
function syncToFileMaker($userId, $applicantName, $applicantId, $applicantHome, $applicantPhone,
    $applicantEmail, $infoText, $routerText, $giftText, $pdfTwoPagePath, $pdfTwoPageGenerated,
    $installDate, $rentalStart, $rentalEnd, $realEndDate, $feeType, $feeAmt, $giftList,
    $extraList, $deposit, $enname, $useList) {

    if (empty($userId)) {
        return ['ok'=>false, 'message'=>'缺少用戶編號,無法寫入 FileMaker'];
    }

    // ── 1) 登入取得 token(v2 修正版) ──────────────────
    // [v2 修正] 改用手動 Basic Auth header,避開 CURLOPT_USERPWD 在某些情境下
    //           處理特殊字元(如 @)的隱性問題
    $loginUrl  = FM_HOST.'/fmi/data/v1/databases/'.rawurlencode(FM_DB).'/sessions';
    $basicAuth = 'Basic ' . base64_encode(FM_USER.':'.FM_PASS);

    $ch = curl_init($loginUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode((object)[]));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Authorization: '.$basicAuth,
    ]);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $resp = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlErr  = curl_error($ch);
    curl_close($ch);

    if ($resp === false) {
        return ['ok'=>false, 'message'=>'FileMaker 登入失敗(連線錯誤): '.$curlErr];
    }
    $loginJson = json_decode($resp, true);
    $token = $loginJson['response']['token'] ?? null;
    if (!$token) {
        $msg = $loginJson['messages'][0]['message'] ?? '未知錯誤';
        $fmCode = $loginJson['messages'][0]['code'] ?? '';
        return ['ok'=>false, 'message'=>'FileMaker 登入失敗: '.$msg.'(HTTP '.$httpCode.', FM code '.$fmCode.')'];
    }

    $baseUrl = FM_HOST.'/fmi/data/v1/databases/'.rawurlencode(FM_DB).'/layouts/'.rawurlencode(FM_LAYOUT);

    // ── 2) 用「客戶編號」查找是否已有紀錄 ────────────────
    $findResult = fmCurl('POST', $baseUrl.'/_find', $token, [
        'query' => [[ '客戶編號' => '=='.$userId ]]
    ]);

    $recordId = null;
    $existingFieldData = [];
    if ($findResult['ok']) {
        $records = $findResult['data']['response']['data'] ?? [];
        if (!empty($records)) {
            $recordId = $records[0]['recordId'];
            $existingFieldData = $records[0]['fieldData'] ?? [];
        }
    } elseif ($findResult['fmCode'] !== '401') {
        // FM code 401 = 找不到符合的紀錄(正常情況),其他錯誤才算異常
        fmCurl('DELETE', FM_HOST.'/fmi/data/v1/databases/'.rawurlencode(FM_DB).'/sessions/'.$token);
        return ['ok'=>false, 'message'=>'FileMaker 查詢失敗: '.$findResult['fmMsg'].'(HTTP '.$findResult['http'].')'];
    }

    // web 端帶來的候選欄位值(不含客戶編號,客戶編號只作查找依據,不參與比對覆蓋)
    $webFieldData = [
        '用戶名稱'     => $applicantName,
        '身分證字號'   => $applicantId,
        '住家電話'     => $applicantHome,
        '行動電話'     => $applicantPhone,
        '外部郵址'     => $applicantEmail,
        '資訊箱內設備' => $infoText,
        '家中設備情況' => $routerText,
        '介紹用戶資訊' => $giftText,
    ];

    // ── 3) 依比對規則組出實際要寫入的欄位 ────────────────
    //    - web 端為空值 → 跳過,保留 FileMaker 原值(不覆蓋為空)
    //    - FileMaker 尚無此筆紀錄(新增情境)→ web 有值的欄位直接寫入
    //    - 兩邊都有值且「嚴格比對(含空白)」相同 → 不異動
    //    - 兩邊都有值但不同 → 以 web 為準覆蓋寫入
    $fieldData = ['客戶編號' => $userId]; // 新增時需要客戶編號;更新時多送這個值不影響(與原值相同)
    foreach ($webFieldData as $key => $webVal) {
        if ($webVal === '' || $webVal === null) {
            continue; // web 空值視為「沒有要異動」,不送出此欄位
        }
        if ($recordId) {
            $existingVal = $existingFieldData[$key] ?? '';
            if ((string)$existingVal === (string)$webVal) {
                continue; // 嚴格比對(含空白)相同,不需要異動
            }
        }
        $fieldData[$key] = $webVal;
    }

    // 若是更新情境,且除了客戶編號外沒有任何欄位需要異動,就不發送 PATCH(沒有變化)
    $hasChanges = count($fieldData) > 1; // 1 = 只剩客戶編號
    if ($recordId && !$hasChanges) {
        $writeResult = ['ok' => true]; // 視為成功(無需更動)
    } elseif ($recordId) {
        $writeResult = fmCurl('PATCH', $baseUrl.'/records/'.$recordId, $token, [
            'fieldData' => $fieldData
        ]);
    } else {
        $writeResult = fmCurl('POST', $baseUrl.'/records', $token, [
            'fieldData' => $fieldData
        ]);
        if ($writeResult['ok']) {
            $recordId = $writeResult['data']['response']['recordId'] ?? null;
        }
    }

    if (!$writeResult['ok']) {
        fmCurl('DELETE', FM_HOST.'/fmi/data/v1/databases/'.rawurlencode(FM_DB).'/sessions/'.$token);
        return ['ok'=>false, 'message'=>'FileMaker 寫入欄位失敗: '.($writeResult['fmMsg'] ?? '').'(HTTP '.($writeResult['http'] ?? '').')'];
    }

    // ── 4) 上傳兩頁版 PDF 到「裝機單掃描」容器欄位(v2 修正版) ──
    // [v2 修正] 容器欄位名稱「裝機單掃描」含中文,URL 必須做 rawurlencode
    $containerMsg = '';
    if ($pdfTwoPageGenerated && file_exists($pdfTwoPagePath) && $recordId) {
        $containerUrl = $baseUrl.'/records/'.$recordId.'/containers/'.rawurlencode('裝機單掃描').'/1';
        $uploadResult = fmCurl('POST', $containerUrl, $token, null, true, $pdfTwoPagePath, 'upload');
        if (!$uploadResult['ok']) {
            $errDetail = ($uploadResult['fmMsg'] ?? '');
            if ($errDetail === '' && isset($uploadResult['error'])) {
                $errDetail = $uploadResult['error'];
            }
            $containerMsg = ';容器欄位上傳失敗: '.$errDetail.'(HTTP '.($uploadResult['http'] ?? '0').')';
            // 寫入 error_log 方便事後排查(含 raw response 前 500 字)
            error_log('[submit.php] FM 容器上傳失敗 url='.$containerUrl.' resp='.substr($uploadResult['raw'] ?? '', 0, 500));
        }
    } elseif (!$pdfTwoPageGenerated) {
        $containerMsg = ';PDF(2頁版)未產生,跳過容器上傳';
    }

    // ── 4.5) 在「api-繳費紀錄」布局新增繳費相關紀錄(不搜尋,直接新增) ──
    $baseUrl2 = FM_HOST.'/fmi/data/v1/databases/'.rawurlencode(FM_DB).'/layouts/'.rawurlencode(FM_LAYOUT2);
    $paymentResults = createPaymentRecords($token, $baseUrl2, $userId, $installDate, $rentalStart,
        $rentalEnd, $realEndDate, $feeType, $feeAmt, $giftList, $extraList, $deposit, $enname);

    $paymentFailMsgs = [];
    foreach ($paymentResults as $pr) {
        if (!$pr['ok']) {
            $paymentFailMsgs[] = $pr['type'].':'.$pr['msg'];
        }
    }

    // ── 4.6) 在「api-消料設備」布局新增設備料號紀錄(不搜尋,直接新增) ──
    $baseUrl3 = FM_HOST.'/fmi/data/v1/databases/'.rawurlencode(FM_DB).'/layouts/'.rawurlencode(FM_LAYOUT3);
    $materialResults = createMaterialRecords($token, $baseUrl3, $userId, $useList, $extraList);

    $materialFailMsgs = [];
    foreach ($materialResults as $mr) {
        if (!$mr['ok']) {
            $materialFailMsgs[] = $mr['type'].':'.$mr['msg'];
        }
    }

    // ── 5) 登出 ─────────────────────────────────────────
    fmCurl('DELETE', FM_HOST.'/fmi/data/v1/databases/'.rawurlencode(FM_DB).'/sessions/'.$token);

    if ($containerMsg !== '') {
        $finalMsg = 'FileMaker 主紀錄寫入成功,但'.ltrim($containerMsg, ';');
        if (!empty($paymentFailMsgs)) {
            $finalMsg .= ';繳費紀錄部分失敗: '.implode(';', $paymentFailMsgs);
        }
        if (!empty($materialFailMsgs)) {
            $finalMsg .= ';消料設備部分失敗: '.implode(';', $materialFailMsgs);
        }
        return ['ok'=>false, 'message'=>$finalMsg, 'payment_records'=>$paymentResults, 'material_records'=>$materialResults];
    }
    if (!empty($paymentFailMsgs) || !empty($materialFailMsgs)) {
        $failParts = [];
        if (!empty($paymentFailMsgs))  $failParts[] = '繳費紀錄: '.implode(';', $paymentFailMsgs);
        if (!empty($materialFailMsgs)) $failParts[] = '消料設備: '.implode(';', $materialFailMsgs);
        return ['ok'=>false, 'message'=>'FileMaker 主紀錄寫入成功,但'.implode(';', $failParts).'部分失敗',
            'payment_records'=>$paymentResults, 'material_records'=>$materialResults];
    }
    return ['ok'=>true, 'message'=>'FileMaker 寫入成功', 'payment_records'=>$paymentResults, 'material_records'=>$materialResults];
}

$infoText   = devListToText($infoList, 'info');
$routerText = devListToText($routerList, 'router');
$giftText   = giftListToText($giftList);

$rentalStart = $d['rental_start_date'] ?? '';
$rentalEnd   = $d['rental_end_date']   ?? '';
$realEndDate = $d['real_end_date']     ?? '';

$fmResult = syncToFileMaker(
    $userId, $applicantName, $applicantId, $applicantHome, $applicantPhone,
    $applicantEmail, $infoText, $routerText, $giftText, $pdfTwoPagePath, $pdfTwoPageGenerated,
    $installDate, $rentalStart, $rentalEnd, $realEndDate, $feeType, $feeAmt, $giftList,
    $extraList, $deposit, $enname, $useList
);

if (!$fmResult['ok']) {
    error_log('[submit.php] FileMaker同步失敗: '.$fmResult['message']);
}

// 兩頁版 PDF 僅用於上傳 FileMaker,上傳後即可清除暫存(正式留檔以三頁版 $pdfPath 為準)
@unlink($pdfTwoPagePath);

// ── Session ──────────────────────────
if ($pdfGenerated && file_exists($pdfPath)) {
    $pdfToken = bin2hex(random_bytes(16));
    $_SESSION['done_pdf_token'] = $pdfToken;
    $_SESSION['done_pdf_path']  = $pdfPath;
} else {
    $pdfToken = '';
    $_SESSION['done_pdf_token'] = '';
    $_SESSION['done_pdf_path']  = '';
}
$_SESSION['done_submitted'] = true;
unset($_SESSION['preview'],$_SESSION['otp_verified'],$_SESSION['otp_code'],
      $_SESSION['otp_email'],$_SESSION['otp_expires'],$_SESSION['otp_sent_at'],
      $_SESSION['form_enname'],$_SESSION['form_user_id']);

// ── 回傳 ─────────────────────────────
while(ob_get_level() > 1) ob_end_clean();
ob_clean();
echo json_encode([
    'success'             => true,
    'pdf_generated'       => $pdfGenerated,
    'pdf_error'           => $wkError,
    'email_sent'          => $emailSent,
    'pdf_token'           => $pdfToken,
    'fm_synced'           => $fmResult['ok'],
    'fm_message'          => $fmResult['message'],
    'fm_payment_records'  => $fmResult['payment_records']  ?? [],
    'fm_material_records' => $fmResult['material_records'] ?? [],
    'message'       => $emailSent
        ? '申請書已送出,PDF 已寄至 '.$applicantEmail.($fmResult['ok'] ? '' : '(注意:FileMaker同步失敗,請通知客服處理)')
        : ($pdfGenerated ? '送出成功,但 Email 發送失敗'.($fmResult['ok'] ? '' : ';FileMaker同步亦失敗,請通知客服處理')
                         : '送出成功,但 PDF 產生失敗:'.$wkError),
], JSON_UNESCAPED_UNICODE);