<?php
// ===== 码支付 - PHP版入口（URL路由） =====
// 所有请求通过此文件，配合Nginx URL重写

error_reporting(E_ALL);
ini_set('display_errors', 0);
date_default_timezone_set('Asia/Shanghai');

// 加载配置
$config = require __DIR__ . '/config.php';

// ===== 统一解析 JSON body（前端 fetch 用 JSON 提交） =====
$_BODY = $_POST;
if (empty($_BODY)) {
    $raw = file_get_contents('php://input');
    if ($raw) {
        $json = json_decode($raw, true);
        if ($json) {
            $_BODY = $json;
            $_POST = array_merge($_POST, $json);
        }
    }
}

// ===== URL路由 =====
$uri = $_SERVER['REQUEST_URI'];
$path = parse_url($uri, PHP_URL_PATH);
$path = rtrim($path, '/') ?: '/';

// ===== 目录黑名单：禁止直接访问模板源码 / 库源码（与 .htaccess 对齐，兼容 Nginx） =====
// templates/pay/*.php 仅供内部渲染读取，直访只返回 403；lib/ 目录同理。
if (preg_match('#^/(templates|lib)(/|$)#', $path)) {
    http_response_code(403);
    header('Content-Type: text/plain; charset=utf-8');
    exit('403 Forbidden');
}

// ===== 静态资源（复用public目录） =====
$publicDir = __DIR__ . '/public';
$staticExtensions = ['html', 'htm', 'css', 'js', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'svg', 'webp', 'woff', 'woff2', 'ttf', 'json', 'xml'];
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));

// ===== Lottie 登录动效资源（无扩展名路由，规避宝塔 js|css 静态 location 404） =====
// 宝塔默认 Nginx 有 location ~ .*\.(js|css)?$ 且无 try_files 回退：
// public/ 下的 .js 直访一律 404（v2.6.2 同款坑），请求根本到不了本入口。
// 故登录页改用 /lottie/xxx 无扩展名 URL，经伪静态回退到此处，再按白名单映射到真实文件。
if (preg_match('#^/lottie/([A-Za-z0-9._-]+)$#', $path, $lm)) {
    $lottieMap = [
        'cat-login'   => ['cat-login.js',  'application/javascript'],
        'lottie.min'  => ['lottie.min.js', 'application/javascript'],
        'cat.json'    => ['cat.json',      'application/json'],
        'status.json' => ['status.json',   'application/json'],
    ];
    $lkey = $lm[1];
    if (isset($lottieMap[$lkey])) {
        $lfile = $publicDir . '/assets/lottie/' . $lottieMap[$lkey][0];
        if (is_file($lfile)) {
            header('Content-Type: ' . $lottieMap[$lkey][1] . '; charset=utf-8');
            header('Cache-Control: no-cache');
            readfile($lfile);
            exit;
        }
    }
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    exit('404 Not Found');
}

// 直接检查public目录下的静态文件
if (in_array($ext, $staticExtensions) || $ext === '') {
    // 尝试匹配public目录下的文件
    $staticPath = $publicDir . $path;
    if (is_file($staticPath)) {
        $mimeTypes = [
            'html' => 'text/html; charset=utf-8', 'css' => 'text/css', 'js' => 'application/javascript',
            'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif',
            'ico' => 'image/x-icon', 'svg' => 'image/svg+xml', 'webp' => 'image/webp',
            'woff' => 'font/woff', 'woff2' => 'font/woff2', 'json' => 'application/json',
        ];
        if (isset($mimeTypes[$ext])) header('Content-Type: ' . $mimeTypes[$ext]);
        // HTML 不缓存：后台/监控页更新后，避免浏览器还拿旧版 HTML（表现为「部署了新功能但页面没变化」）
        if ($ext === 'html' || $ext === 'htm') {
            header('Cache-Control: no-cache, no-store, must-revalidate');
            header('Pragma: no-cache');
            header('Expires: 0');
        }
        readfile($staticPath);
        exit;
    }
    // 尝试无扩展名路径（如 /login → login.html）
    if ($path !== '/' && $ext === '') {
        foreach (['.html', '.htm'] as $suffix) {
            $tryPath = $publicDir . $path . $suffix;
            if (is_file($tryPath)) {
                header('Content-Type: text/html; charset=utf-8');
                header('Cache-Control: no-cache, no-store, must-revalidate');
                readfile($tryPath);
                exit;
            }
        }
    }
}

// ===== /data 目录的静态文件（二维码图片等） =====
if (strpos($path, '/data/') === 0) {
    // 安全修复：原逻辑对 /data/ 下任意文件无条件 readfile，
    // 导致 data/session_*.json（登录态）可被读取，且 ../ 拼接可越出 data 目录读到 config.php（含数据库口令）。
    // 现仅放行图片扩展名，并显式拒绝路径穿越。
    $mimeTypes = ['png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', 'webp' => 'image/webp'];
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    if (isset($mimeTypes[$ext]) && strpos($path, '..') === false && strpos($path, "\0") === false) {
        $filePath = realpath(__DIR__ . $path);
        $dataRoot = realpath(__DIR__ . '/data');
        // realpath 归一化后必须仍位于 data 目录内，否则拒绝
        if ($filePath !== false && $dataRoot !== false && strpos($filePath, $dataRoot . DIRECTORY_SEPARATOR) === 0 && is_file($filePath)) {
            header('Content-Type: ' . $mimeTypes[$ext]);
            header('X-Content-Type-Options: nosniff');
            readfile($filePath);
            exit;
        }
    }
    http_response_code(404);
    exit;
}

// ===== 点文件拦截：.env 等敏感文件禁止直访（宝塔 Nginx 下 .htaccess 不生效，应用层兜底） =====
if (preg_match('#^/\.env#', $path) || preg_match('#^/(\.htaccess|\.git|\.svn)(/|$)#', $path)) {
    http_response_code(403);
    header('Content-Type: text/plain; charset=utf-8');
    exit('403 Forbidden');
}

// ===== 安装向导路由（v2.8.0）：未安装时所有页面请求自动跳转向导 =====
if (!file_exists(__DIR__ . '/data/installed.lock')) {
    $isStaticReq = strpos($path, '/api/') === 0 || preg_match('/\.(css|js|png|jpg|gif|ico|svg|woff2?)$/i', $path);
    if ($path === '/install') {
        require_once __DIR__ . '/lib/installer.php';
        codepay_handle_install($config);
        exit;
    }
    if (!$isStaticReq) {
        // 首次部署：打开域名自动进入安装向导
        header('Location: /install');
        exit;
    }
    // 未安装期间的 API/静态请求放行（安装完成后才有数据，避免误伤安装页资源）
} else {
    // 已安装：向导永久关闭，防止重装攻击
    if ($path === '/install') {
        header('Location: /');
        exit;
    }
}

// ===== API路由 =====

// 0. 易支付兼容入口（submit 形式）：/submit.php / /mapi.php / /api.php 及子目录下的同名文件
// 站点根目录已有同名物理文件（宝塔 Nginx 对 *.php 直接交给 php-fpm，不会走 index.php），
// 此处路由用于 try_files 全量转发到 index.php 的部署形态，两条路行为一致。
if (preg_match('#(^|/)(submit|mapi|epay_submit)\.php$#', $path) || $path === '/api.php') {
    require __DIR__ . '/api/gateway.php';
    exit;
}
// 网关地址填到 /api/pay 时，部分系统会在其后拼接 submit.php
if (preg_match('#^/api/pay/(submit|mapi)\.php$#', $path)) {
    require __DIR__ . '/api/gateway.php';
    exit;
}

// 1. 支付网关（v19：GET 也可下单，兼容表单直接提交；响应规则维持原样不影响已对接商户）
if ($path === '/api/pay') {
    require __DIR__ . '/api/pay.php';
    exit;
}

// 3. 订单查询
if ($path === '/api/query' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    require __DIR__ . '/api/query.php';
    exit;
}

// 3.5 退款网关（易支付风格签名，供发卡网等对接系统调用）
if ($path === '/api/refund' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    require __DIR__ . '/api/refund.php';
    exit;
}

// 4. 商户信息
if ($path === '/api/info' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    require __DIR__ . '/api/info.php';
    exit;
}

// 5. 二维码图片
if (preg_match('#^/api/qrcode/image/([^/]+)$#', $path, $m)) {
    $_GET['id'] = $m[1];
    require __DIR__ . '/api/qrcode.php';
    exit;
}

// 6. 本地二维码生成
// 修复：原实现把二维码内容放在路径里（/api/qr/{urlencoded}），内容形如 weixin://wxpay/bizpayurl?pr=xxx
// 时，其中的 %2F 会被 Nginx 解码成真实斜杠，Nginx 找不到对应文件便直接返回 404，请求根本进不到 PHP，
// 表现为收银台二维码破图。现改为查询参数 ?text= 传内容（路径方式保留兼容）。
if ($path === '/api/qr' || strpos($path, '/api/qr/') === 0) {
    if (isset($_GET['text'])) {
        $text = (string)$_GET['text'];          // 查询参数已由 PHP 解码，不可再次 urldecode
    } else {
        $text = urldecode(substr($path, strlen('/api/qr/')));   // 兼容旧路径方式
    }
    $text = trim($text);
    if ($text === '') { http_response_code(400); exit('缺少二维码内容'); }

    // 使用phpqrcode库（需要 GD 支持）
    $qrLib = __DIR__ . '/lib/phpqrcode.php';
    if (is_file($qrLib) && function_exists('imagecreate')) {
        header('Content-Type: image/png');
        header('Cache-Control: private, max-age=300');
        require_once $qrLib;
        QRcode::png($text, false, QR_ECLEVEL_M, 6, 1);
        exit;
    }
    http_response_code(500);
    header('Content-Type: text/plain; charset=utf-8');
    exit('二维码组件不可用（缺少 lib/phpqrcode.php 或 GD 扩展）');
}

// 7. 管理后台API
if (strpos($path, '/api/admin/') === 0) {
    $action = substr($path, strlen('/api/admin/'));
    $action = str_replace('/', '_', $action);
    $_GET['action'] = $action;
    require __DIR__ . '/api/admin.php';
    exit;
}

// 8. 商户API
if (strpos($path, '/api/merchant/') === 0) {
    $action = substr($path, strlen('/api/merchant/'));
    $action = str_replace('/', '_', $action);
    $_GET['action'] = $action;
    require __DIR__ . '/api/merchant.php';
    exit;
}

// 9. 监控API
if (strpos($path, '/api/monitor/') === 0) {
    $action = substr($path, strlen('/api/monitor/'));
    $_GET['action'] = $action;
    require __DIR__ . '/api/monitor.php';
    exit;
}

// 9.5 计划任务（回调重试队列 + 过期订单清理），需携带正确的 cron_token
if ($path === '/api/cron' || strpos($path, '/api/cron/') === 0) {
    $action = substr($path, strlen('/api/cron/'));
    if ($action) $_GET['action'] = $action;
    require __DIR__ . '/api/cron.php';
    exit;
}

// 9.6 外部对账推送（v10）：需携带 settings.reconcile_token
if ($path === '/api/reconcile' || strpos($path, '/api/reconcile/') === 0) {
    require __DIR__ . '/api/reconcile.php';
    exit;
}

// 9.7 接入文档 Demo 下载（v10）：以附件形式下发 PHP 示例源码（存为 .php.txt，避免被当 PHP 执行）
if ($path === '/docs/demo.php') {
    $f = __DIR__ . '/public/demo/epay-demo.php.txt';
    if (!is_file($f)) { http_response_code(404); exit('demo 文件不存在'); }
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="codepay-demo.php"');
    readfile($f);
    exit;
}

// 10. 官方支付
if (strpos($path, '/api/pay/official/') === 0 || strpos($path, '/api/pay/alipay_wap_redirect/') === 0) {
    require __DIR__ . '/api/official.php';
    exit;
}
if ($path === '/api/official/oauth') {
    $_GET['action'] = 'oauth';
    require __DIR__ . '/api/official.php';
    exit;
}
if ($path === '/api/official/notify') {
    $_GET['action'] = 'notify';
    require __DIR__ . '/api/official.php';
    exit;
}

// 11. 回调接口
if ($path === '/api/callback/sms' || $path === '/api/callback/report' || $path === '/api/callback/heartbeat') {
    $action = substr($path, strlen('/api/callback/'));
    if ($action === 'sms' || $action === 'report') {
        $_GET['action'] = $action;
        require __DIR__ . '/api/monitor.php';
    } elseif ($action === 'heartbeat') {
        header('Content-Type: application/json');
        echo json_encode(['ok' => true, 'time' => date('c')]);
        exit;
    }
    exit;
}

// 12. 订单状态
if (preg_match('#^/api/order_status/([^/]+)$#', $path, $m)) {
    require_once __DIR__ . '/lib/db.php';
    $trade_no = $m[1];
    require_once __DIR__ . '/lib/payapi.php';
    PayAPI::expireOvertime();
    $order = DB::get('SELECT * FROM orders WHERE trade_no=?', [$trade_no]);

    // v18：支付宝收款码订单在「收银台展示二维码期间」触发增量账单扫描。
    // 页面每 1.5s 轮询一次本接口，引擎内部按凭证做 3 秒节流，
    // 且只查订单创建之后的账务明细，不做全量拉取，避免被支付宝限流。
    // 前置条件（引擎内校验）：商户开启账单回调 + 本单收款码账号已绑定启用中的凭证。
    // v2.4.0：支付宝免输金额免挂机转账订单（alipay_transfer）同样依赖账单回调确认到账，
    // 只是匹配的账务明细类型为「转账」而非「收入」，引擎内按订单类型分流处理。
    if ($order && (string)$order['status'] === 'pending'
        && ((string)$order['type'] === 'alipay' || PayAPI::isTransfer((string)$order['type']))) {
        try {
            require_once __DIR__ . '/lib/alipaybill.php';
            AlipayBill::scanForOrder($order);
        } catch (Exception $e) {
            error_log('[codepay] alipay bill scan 异常: ' . $e->getMessage());
        }
        $fresh = DB::get('SELECT * FROM orders WHERE trade_no=?', [$trade_no]);
        if ($fresh) $order = $fresh;
    }

    header('Content-Type: application/json');
    if ($order) {
        // 安全加固（M6）：redirect_url（含平台签名）仅在订单已支付时下发，
        // pending/过期状态一律返回空串，防止未授权方凭 trade_no 领取带签名的跳转地址；
        // money/status 字段为收银台轮询所必需（买家本人可见），单号可枚举性已由
        // v2.6.0 加长随机段（6 位）缓解。
        $redirect_url = '';
    if ($order['status'] === 'paid' && !empty($order['return_url'])) {
        $_mer = DB::get('SELECT * FROM merchants WHERE id=?', [$order['pid']]);
        if ($_mer && !empty(merchantKey($_mer))) {
            $_ps = [];
            foreach (['pid','trade_no','type','money','trade_status'] as $_k) $_ps[$_k] = $order[$_k];
            if (!empty($order['out_trade_no'])) $_ps['out_trade_no'] = $order['out_trade_no'];
            if (!empty($order['name'])) $_ps['name'] = $order['name'];
            $_ps['trade_status'] = 'TRADE_SUCCESS';
            $_ps['sign'] = PayAPI::sign($_ps, merchantKey($_mer));
            $_ps['sign_type'] = 'MD5';
            $redirect_url = $order['return_url'] . (strpos($order['return_url'], '?') !== false ? '&' : '?') . http_build_query($_ps);
        }
    }
    echo json_encode(['status' => $order['status'], 'money' => $order['money'], 'paid_at' => $order['paid_at'], 'redirect_url' => $redirect_url]);
    } else {
        echo json_encode(['status' => 'not_found']);
    }
    exit;
}

// 13. 支付页面（收银台，模板化：templates/pay/*.php + public/pay_templates/pay.js）
if (preg_match('#^/pay/([^/]+)$#', $path, $m)) {
    require_once __DIR__ . '/lib/db.php';
    require_once __DIR__ . '/lib/payapi.php';
    require_once __DIR__ . '/lib/paypage.php';
    $trade_no = $m[1];
    $order = DB::get('SELECT * FROM orders WHERE trade_no=?', [$trade_no]);
    if (!$order) { http_response_code(404); exit('<h2 style="font-family:sans-serif;text-align:center;padding:60px">订单不存在</h2>'); }

    // 先把超时未付款的订单标记为过期，再取最新状态渲染
    PayAPI::expireOvertime();
    $fresh = DB::get('SELECT * FROM orders WHERE trade_no=?', [$trade_no]);

    $merchant = DB::get('SELECT * FROM merchants WHERE id=?', [$fresh['pid']]);

    // 收款码：优先取订单绑定的收款账号（多账号收款），兼容历史订单回退 qr_code_id
    $qr = null;
    if (!empty($fresh['account_id'])) {
        $qr = DB::get('SELECT * FROM channel_accounts WHERE id=?', [$fresh['account_id']]);
    }
    if (!$qr && !empty($fresh['qr_code_id'])) {
        $qr = DB::get('SELECT * FROM channel_accounts WHERE id=?', [$fresh['qr_code_id']]);
        if (!$qr) $qr = DB::get('SELECT * FROM qrcodes WHERE id=?', [$fresh['qr_code_id']]);
    }
    // 无绑定账号时按通道轮换策略临时选号，仅用于展示收款码
    if (!$qr) {
        // 直接取订单自身通道（wxpay/alipay/alipay_face 等各自独立账号池），
        // 不按支付类型归并——官方通道（含当面付）此码不参与渲染（走 official_qr_code），仅兜底
        $targetType = in_array((string)$fresh['type'], array_keys(PayAPI::CHANNEL_META), true) ? (string)$fresh['type'] : 'wxpay';
        $qr = PayAPI::pickAccount($fresh['pid'], $targetType, floatval($fresh['money']));
    }
    $qrImageUrl = ($qr && !empty($qr['image'])) ? (string)$qr['image'] : '';

    $isOfficial = in_array($fresh['type'], PayAPI::OFFICIAL_KEYS);

    // v12/v13 微信面额码：功能开启 且 本单命中面额码 → 展示该面额码（扫码金额自动填好）；
    // 未开启 / 未命中 / 面额码已删除或停用 / 归属或金额对不上 → 一律展示原普通收款码，
    // 行为与此前完全一致。支付宝通道与官方通道不参与面额码。
    // v13 口径：按【订单最终金额】（含金额浮动后的实付金额）严格匹配 —— 若开启浮动导致
    // 实付为 50.03 而面额码为 50.00，则不展示面额码，回退普通收款码。
    $faceHit = 0;
    if (!$isOfficial && $qr && ($qr['channel'] ?? '') === 'wxpay' && PayAPI::faceEnabled($fresh['pid'])) {
        $_face = PayAPI::faceForOrder($fresh['face_id'] ?? 0, $fresh['pid'], $qr['id'], floatval($fresh['money']));
        // 兼容 v26 历史订单（未记录 face_id）：按账号 + 金额取首张面额码
        if (!$_face) $_face = PayAPI::findFace($qr['id'], floatval($fresh['money']));
        if ($_face && trim((string)$_face['image']) !== '') {
            $faceHit = 1;
            $qrImageUrl = (string)$_face['image'];
        }
    }

    // ===== 支付宝免输金额免挂机转账接口（alipay_transfer，v2.4.0）=====
    // 动态转账码：金额 / 备注 / 收款方按订单实时生成支付宝转账表单页，买家扫码后
    // 无需手动输入金额，确认即可转账；到账由支付宝账单回调（转账流水）确认。
    // 二维码图片复用 /api/qr?text= 服务端生成（内容为转账表单页 URL，页面零改动）。
    // 仅待支付订单需要生成转账码；已支付 / 已过期订单按原逻辑渲染（无码状态）。
    if (PayAPI::isTransfer((string)$fresh['type']) && $fresh['status'] === 'pending') {
        // 订单绑定账号异常（绑到了其他通道账号）时，按转账通道重新选号兜底
        if ($qr && ($qr['channel'] ?? '') !== 'alipay_transfer') {
            $qr = PayAPI::pickAccount($fresh['pid'], 'alipay_transfer', floatval($fresh['money']));
        }
        $idInfo = $qr ? PayAPI::transferIdentityOf($qr) : ['identity' => '', 'identity_type' => 'ALIPAY_LOGON_ID'];
        if ($idInfo['identity'] === '') {
            // 账号缺失或未配置收款方支付宝账号：明确错误页（仿「商户预存款不足」样式），
            // 避免渲染出空白/错误二维码让买家困惑
            $_orderNo = $fresh['out_trade_no'] ?: $fresh['trade_no'];
            $_money = number_format((float)$fresh['money'], 2, '.', '');
            $_mName = ($merchant['name'] ?? '') ?: '码支付';
            header('Content-Type: text/html; charset=utf-8');
            echo '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">'
               . '<meta name="viewport" content="width=device-width,initial-scale=1.0">'
               . '<title>无法收款 - ' . PayPage::esc($_mName) . '</title>'
               . '<style>body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#f5f6fa;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}'
               . '.card{background:#fff;border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,.08);padding:40px 36px;max-width:420px;width:100%;text-align:center}'
               . 'h1{font-size:19px;color:#2c3e50;margin:0 0 18px;font-weight:600}.ico{font-size:54px;margin-bottom:6px}'
               . '.tip{background:#fff3cd;color:#856404;padding:14px 16px;border-radius:10px;font-size:14px;font-weight:600;line-height:1.6;margin:10px 0 18px}'
               . '.meta{font-size:12px;color:#999;margin-top:14px;line-height:1.7}</style></head><body>'
               . '<div class="card"><div class="ico">💳</div><h1>支付宝转账通道未配置</h1>'
               . '<div class="tip">当前收款账号尚未配置收款方支付宝账号（手机号/邮箱或 user_id），暂时无法生成转账码。<br>请联系商户在「通道管理 - 免输金额免挂机」中补全收款账号后重试。</div>'
               . '<div class="meta">' . PayPage::esc($_mName) . ' · 由码支付提供收款服务<br>订单号：' . PayPage::esc($_orderNo) . ' · 金额：¥' . PayPage::esc($_money) . '</div>'
               . '</div></body></html>';
            exit;
        }
        // 备注固定使用平台订单号 trade_no：与支付宝账单 trans_memo 对齐，可精确对账、防串单。
        // out_trade_no 多为外部商户号或为 'AUTO_'.time() 默认值（同秒会碰撞），不适合做转账码备注，故不使用。
        $transferRemark = (string)($fresh['trade_no'] ?: $fresh['out_trade_no']);
        $transferPageUrl = PayAPI::transferPageUrl(
            $fresh['money'],
            $transferRemark,
            $idInfo['identity'],
            $idInfo['identity_type']
        );
        $qrImageUrl = '/api/qr?text=' . rawurlencode($transferPageUrl);
    }

    // v14 增强：收银台「商户预存款不足」兜底拦截
    // 费率余款 ≤ 0 且本单通道有费率，且该订单未成功扣费（fee_amount=0 的旧版小数额订单）→ 收银台直接展示提示，禁止付款。
    // 正常下单已在 PayAPI::chargeFee 前置拦截（余额 ≤ 0 直接拒单），此处防止历史未扣费订单被继续付款。
    if ($fresh['status'] === 'pending') {
        $_chRate = PayAPI::merchantFeeRate($merchant, $fresh['type']);
        if ($_chRate > 0 && floatval($merchant['fee_balance'] ?? 0) <= 0 && floatval($fresh['fee_amount'] ?? 0) <= 0) {
            $_orderNo = $fresh['out_trade_no'] ?: $fresh['trade_no'];
            $_money = number_format((float)$fresh['money'], 2, '.', '');
            $_mName = ($merchant['name'] ?? '') ?: '码支付';
            header('Content-Type: text/html; charset=utf-8');
            echo '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">'
               . '<meta name="viewport" content="width=device-width,initial-scale=1.0">'
               . '<title>无法支付 - ' . PayPage::esc($_mName) . '</title>'
               . '<style>body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#f5f6fa;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}'
               . '.card{background:#fff;border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,.08);padding:40px 36px;max-width:420px;width:100%;text-align:center}'
               . 'h1{font-size:19px;color:#2c3e50;margin:0 0 18px;font-weight:600}.ico{font-size:54px;margin-bottom:6px}'
               . '.tip{background:#fff3cd;color:#856404;padding:14px 16px;border-radius:10px;font-size:14px;font-weight:600;line-height:1.6;margin:10px 0 18px}'
               . '.meta{font-size:12px;color:#999;margin-top:14px;line-height:1.7}</style></head><body>'
               . '<div class="card"><div class="ico">💴</div><h1>商户预存款不足</h1>'
               . '<div class="tip">当前商户费率预存款余额已耗尽（≤ ¥0.00），无法继续收款。<br>请联系总商户（平台方）充值后重试。</div>'
               . '<div class="meta">' . PayPage::esc($_mName) . ' · 由码支付提供收款服务<br>订单号：' . PayPage::esc($_orderNo) . ' · 金额：¥' . PayPage::esc($_money) . '</div>'
               . '</div></body></html>';
            exit;
        }
    }

    // 构造带签名参数的 return_url（支付成功后回跳商户）
    $_rturl = '';
    if (!empty($fresh['return_url'])) {
        $_mer = $merchant;
        if ($_mer && !empty(merchantKey($_mer))) {
            $_ps = [];
            foreach (['pid', 'trade_no', 'type', 'money', 'trade_status'] as $_k) $_ps[$_k] = $fresh[$_k];
            if (!empty($fresh['out_trade_no'])) $_ps['out_trade_no'] = $fresh['out_trade_no'];
            if (!empty($fresh['name'])) $_ps['name'] = $fresh['name'];
            $_ps['trade_status'] = 'TRADE_SUCCESS';
            $_ps['sign'] = PayAPI::sign($_ps, merchantKey($_mer));
            $_ps['sign_type'] = 'MD5';
            $_rturl = $fresh['return_url'] . (strpos($fresh['return_url'], '?') !== false ? '&' : '?') . http_build_query($_ps);
        }
    }

    // 模板优先级：?tpl= 覆盖（用于预览）> 商户自定义 > 全局默认
    $tplId = PayPage::resolveId($merchant, isset($_GET['tpl']) ? (string)$_GET['tpl'] : '');

    echo PayPage::render([
        'tplId'     => $tplId,
        'order'     => $fresh,
        'merchant'  => $merchant,
        'qrImage'   => $qrImageUrl,
        'returnUrl' => $_rturl,
        'openid'    => isset($_GET['openid']) ? (string)$_GET['openid'] : '',
        'isOfficial'=> $isOfficial,
        'faceHit'   => $faceHit,
    ]);
    exit;
}

// 13.5 收银台模板预览（假数据，不查真实订单，仅供后台选择模板时对比样式）
// 仅允许已注册的模板 id；不涉及任何真实订单与商户数据
if (preg_match('#^/paydemo/([a-z0-9_]+)/?$#', $path, $m)) {
    require_once __DIR__ . '/lib/db.php';
    require_once __DIR__ . '/lib/paypage.php';
    $tplId = $m[1];
    if (!PayPage::isValid($tplId)) { http_response_code(404); exit('模板不存在'); }

    // 预览二维码用站点自身接口生成一个占位码（内容为演示串，不指向真实收款）
    $demoQr = '/api/qr?text=' . rawurlencode('CODEPAY-DEMO-' . $tplId);

    echo PayPage::render([
        'tplId'  => $tplId,
        'order'  => [
            // ↓ 以下均为演示假数据，不含任何真实订单号 / 商户名 / 收款信息
            'trade_no'     => 'DEMO202601010000000001',
            'out_trade_no' => 'DEMO2026010100001',
            'pid'          => '100001',
            'type'         => 'wxpay',
            'name'         => '示例商品 · 自动发货',
            'money'        => '68.80',
            'status'       => 'pending',
            'created_at'   => date('Y-m-d H:i:s', time() - 60),
        ],
        'merchant' => ['id' => '100001', 'name' => '示例商户', 'channels' => 'wxpay,alipay'],
        'qrImage'  => $demoQr,
        'returnUrl'=> '',
        'openid'   => '',
        'isOfficial'=> false,
        'demo'     => true,
    ]);
    exit;
}

// 14. 首页：自动跳转至商户后台登录页
if ($path === '/') {
    // 域名首页直接 302 跳转商户后台登录页；总后台仍可通过 /admin/login.html 访问
    header('Location: /merchant/login.html', true, 302);
    exit;
}

// 15. 404
http_response_code(404);
echo '<h2>404 Not Found</h2>';
