<?php
/**
 * NuriBoard - 플러그인 마켓 결제 (MainPay 연동)
 *
 * 이 파일 하나로 결제/구매내역/다운로드 모두 처리합니다.
 * 문제 생기면 이 파일만 삭제하세요.
 *
 * 의존 파일:
 *   - index.php에 require 1줄
 *   - admin/index.php switch에 case 1개
 *   - theme/default/market/my-purchases.php (구매내역 뷰)
 *   - admin/market-purchases.php (관리자 구매내역)
 *
 * 주의: 이 파일은 마켓 오너 사이트(nuribd.com)에서만 라우트를 등록합니다.
 *       다른 일반 사이트에 설치되어도 라우트는 동작하지 않습니다.
 */

// 마켓 오너 사이트가 아니면 아무것도 하지 않음
if (strpos((string)nb_setting('site_url'), 'nuribd.com') === false) {
    return;
}

// ============================================================
// MainPay 가맹점 설정 (워프스타)
// ============================================================
if (!defined('MAINPAY_MBR_NO'))  define('MAINPAY_MBR_NO',  '117513');
if (!defined('MAINPAY_TID'))     define('MAINPAY_TID',     '1347628540');
if (!defined('MAINPAY_API_KEY')) define('MAINPAY_API_KEY', 'E5EyzV4YZhnftgQV3Sfxle9LofkBqKtbDbH05qCPamnK');
if (!defined('MAINPAY_BASE'))    define('MAINPAY_BASE',    'https://api-std.mainpay.co.kr');

// ============================================================
// 구매 테이블 자동 생성 / 스키마 업그레이드
// ============================================================
try {
    DB::query("SELECT 1 FROM " . DB::getPrefix() . "market_purchases LIMIT 1");
} catch (Exception $e) {
    DB::getInstance()->exec("CREATE TABLE IF NOT EXISTS " . DB::getPrefix() . "market_purchases (
        id INT AUTO_INCREMENT PRIMARY KEY,
        member_id INT DEFAULT 0,
        plugin_id INT NOT NULL,
        amount INT NOT NULL,
        mbr_ref_no VARCHAR(30) NOT NULL,
        aid VARCHAR(60) DEFAULT '',
        auth_token VARCHAR(60) DEFAULT '',
        ref_no VARCHAR(60) DEFAULT '',
        card_no VARCHAR(30) DEFAULT '',
        status VARCHAR(20) DEFAULT 'pending',
        download_count INT DEFAULT 0,
        error_message VARCHAR(255) DEFAULT '',
        site_token VARCHAR(64) DEFAULT '',
        site_url VARCHAR(255) DEFAULT '',
        return_url VARCHAR(500) DEFAULT '',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        paid_at DATETIME DEFAULT NULL,
        UNIQUE KEY uk_ref (mbr_ref_no),
        INDEX idx_member (member_id),
        INDEX idx_plugin (plugin_id),
        INDEX idx_status (status),
        INDEX idx_site_token (site_token)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}
// 기존 테이블 스키마 업그레이드 (없는 컬럼만 추가)
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN site_token VARCHAR(64) DEFAULT ''"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN site_url VARCHAR(255) DEFAULT ''"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN return_url VARCHAR(500) DEFAULT ''"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD INDEX idx_site_token (site_token)"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases MODIFY member_id INT DEFAULT 0"); } catch (Exception $e) {}
// 취소 API용 필드
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN pay_type VARCHAR(4) DEFAULT ''"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN tran_date VARCHAR(8) DEFAULT ''"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN canceled_at DATETIME DEFAULT NULL"); } catch (Exception $e) {}
// 청약철회 동의 기록 필드 (전자상거래법 제17조 대응)
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN consent_ip VARCHAR(45) DEFAULT ''"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN consent_ua VARCHAR(255) DEFAULT ''"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN consent_at DATETIME DEFAULT NULL"); } catch (Exception $e) {}
try { DB::getInstance()->exec("ALTER TABLE " . DB::getPrefix() . "market_purchases ADD COLUMN consent_version VARCHAR(16) DEFAULT ''"); } catch (Exception $e) {}

// 현재 청약철회 고지 버전 (문구 변경 시 숫자 올릴 것 — 분쟁 시 구매자가 어떤 버전에 동의했는지 추적)
if (!defined('MP_CONSENT_VERSION')) define('MP_CONSENT_VERSION', '2026-04-15-v1');

// ============================================================
// MainPay 헬퍼 함수
// ============================================================
if (!function_exists('mp_signature')) {
    function mp_signature(string $mbrRefNo, int $amount, string $timestamp): string {
        $plain = MAINPAY_MBR_NO . '|' . $mbrRefNo . '|' . $amount . '|' . MAINPAY_API_KEY . '|' . $timestamp;
        return hash('sha256', $plain);
    }
}
if (!function_exists('mp_timestamp')) {
    function mp_timestamp(): string {
        list($usec, $sec) = explode(' ', microtime());
        return date('ymdHis', (int)$sec) . str_pad((string)(int)($usec * 1000), 3, '0', STR_PAD_LEFT);
    }
}
if (!function_exists('mp_log')) {
    /**
     * MainPay 전문 로그 저장 (카드사 제출용)
     * 경로: data/logs/mainpay_YYYY-MM-DD.log
     * 민감 필드(apiKey, signature)는 마스킹 처리
     */
    function mp_log(string $type, string $endpoint, $data, array $extra = []): void {
        $logDir = NB_ROOT . '/data/logs';
        if (!is_dir($logDir)) { @mkdir($logDir, 0755, true); }
        $file = $logDir . '/mainpay_' . date('Y-m-d') . '.log';

        $masked = $data;
        if (is_array($masked)) {
            if (isset($masked['signature'])) {
                $masked['signature'] = substr($masked['signature'], 0, 10) . '...(masked)';
            }
        }

        $now = date('Y-m-d H:i:s') . '.' . substr((string)(microtime(true) - floor(microtime(true))), 2, 3);
        $line  = "================================================================\n";
        $line .= "[{$now}] [{$type}] {$endpoint}\n";
        if (!empty($extra)) {
            foreach ($extra as $k => $v) { $line .= "  {$k}: {$v}\n"; }
        }
        $line .= "----------------------------------------------------------------\n";
        $line .= (is_string($masked) ? $masked : json_encode($masked, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . "\n\n";
        @file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
    }
}
if (!function_exists('mp_post')) {
    function mp_post(string $endpoint, array $params): array {
        // 요청 전문 로그
        mp_log('REQUEST', MAINPAY_BASE . $endpoint, $params, [
            'mbrNo'    => $params['mbrNo']    ?? '',
            'mbrRefNo' => $params['mbrRefNo'] ?? '',
            'amount'   => $params['amount']   ?? '',
        ]);

        $startTime = microtime(true);
        $ch = curl_init(MAINPAY_BASE . $endpoint);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => http_build_query($params),
            CURLOPT_TIMEOUT        => 15,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded; charset=utf-8'],
        ]);
        $body     = curl_exec($ch);
        $err      = curl_error($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        $elapsed = round((microtime(true) - $startTime) * 1000) . 'ms';

        if (!$body) {
            mp_log('RESPONSE_ERROR', MAINPAY_BASE . $endpoint, ['curlError' => $err, 'httpCode' => $httpCode], ['elapsed' => $elapsed]);
            return ['resultCode' => 'ERR', 'resultMessage' => $err ?: '응답 없음'];
        }

        // 응답 전문 로그 (raw body + parsed)
        mp_log('RESPONSE', MAINPAY_BASE . $endpoint, $body, ['httpCode' => $httpCode, 'elapsed' => $elapsed]);

        $json = json_decode($body, true);
        return is_array($json) ? $json : ['resultCode' => 'ERR', 'resultMessage' => '응답 파싱 실패'];
    }
}

// ============================================================
// 라우트: 결제 시작
//   GET  /market/buy/{id}  → 청약철회 제한 동의 페이지 (전자상거래법 제17조)
//   POST /market/buy/{id}  → 동의 후 실제 결제창 띄우기
// ============================================================
Router::get('/market/buy/{id}', function ($params) {
    $prefix = DB::getPrefix();
    $pluginId = (int)$params['id'];
    $plugin = DB::fetch("SELECT * FROM {$prefix}market_plugins WHERE id = ?", [$pluginId]);
    if (!$plugin) { die('플러그인을 찾을 수 없습니다.'); }
    if ((int)$plugin['price'] <= 0) { die('무료 플러그인은 구매할 필요가 없습니다.'); }

    $siteToken    = trim($_GET['site_token'] ?? '');
    $returnUrl    = trim($_GET['return_url'] ?? '');
    $buyerSiteUrl = trim($_GET['site_url'] ?? '');
    $isAnonBuy    = !empty($siteToken);

    if (!$isAnonBuy) { Auth::requireLogin(); }
    if ($returnUrl && !preg_match('#^https?://#i', $returnUrl)) { $returnUrl = ''; }

    // 이미 구매한 건 중복 결제 방지
    if ($isAnonBuy) {
        $already = DB::fetch(
            "SELECT id FROM {$prefix}market_purchases WHERE site_token = ? AND plugin_id = ? AND status = 'paid'",
            [$siteToken, $pluginId]
        );
        if ($already) {
            if ($returnUrl) {
                Router::redirect($returnUrl . (strpos($returnUrl,'?')!==false?'&':'?') . 'purchased=1&already=1');
            } else { die('이미 구매한 플러그인입니다.'); }
            return;
        }
    } else {
        $already = DB::fetch(
            "SELECT id FROM {$prefix}market_purchases WHERE member_id = ? AND plugin_id = ? AND status = 'paid'",
            [Auth::id(), $pluginId]
        );
        if ($already) { Router::redirect(nb_url('market/my-purchases')); return; }
    }

    // ===== 청약철회 제한 동의 페이지 렌더 =====
    $csrf      = Auth::csrfToken();
    $postUrl   = nb_url('market/buy/' . $pluginId);
    $pluginNm  = nb_e($plugin['name']);
    $pluginDesc= nb_e(mb_strimwidth($plugin['description'] ?? '', 0, 200, '…'));
    $priceFmt  = number_format((int)$plugin['price']);
    $companyNm = nb_e(nb_setting('biz_company', '(주)누리보드'));
    $csHour    = '평일 10:00~18:00 (주말·공휴일 제외)';
    $siteTokenE= nb_e($siteToken);
    $returnUrlE= nb_e($returnUrl);
    $buyerSiteE= nb_e($buyerSiteUrl);
    $consentV  = MP_CONSENT_VERSION;
    ?><!DOCTYPE html>
<html lang="ko"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>구매 확인 — <?= $pluginNm ?></title>
<style>
*{box-sizing:border-box}
body{font-family:-apple-system,'Malgun Gothic',sans-serif;background:#f8fafc;margin:0;padding:20px;color:#0f172a;line-height:1.6}
.wrap{max-width:640px;margin:0 auto;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:32px;box-shadow:0 1px 3px rgba(0,0,0,.04)}
h1{margin:0 0 4px;font-size:22px;font-weight:700}
.sub{color:#64748b;font-size:13px;margin-bottom:24px}
.plugin-box{background:#f1f5f9;border-radius:8px;padding:16px 20px;margin-bottom:24px}
.plugin-box .pn{font-weight:700;font-size:16px;margin-bottom:4px}
.plugin-box .pd{font-size:13px;color:#64748b;margin-bottom:10px}
.plugin-box .pp{font-size:22px;font-weight:800;color:#dc2626}
.notice{background:#fef3c7;border-left:4px solid #f59e0b;padding:16px 20px;border-radius:6px;margin-bottom:20px}
.notice h3{margin:0 0 8px;font-size:14px;color:#92400e}
.notice p{margin:0;font-size:13px;color:#78350f}
.policy{border:1px solid #e2e8f0;border-radius:8px;padding:18px 22px;margin-bottom:20px;max-height:260px;overflow-y:auto;background:#fafafa}
.policy h3{margin:0 0 10px;font-size:14px;color:#0f172a}
.policy h4{margin:14px 0 6px;font-size:13px;color:#1e293b}
.policy p,.policy li{font-size:12.5px;color:#475569;margin:4px 0}
.policy ul{margin:6px 0;padding-left:20px}
.agree-box{background:#f0f9ff;border:1px solid #7dd3fc;border-radius:8px;padding:16px 20px;margin-bottom:20px}
.agree-box label{display:flex;align-items:flex-start;gap:10px;cursor:pointer;font-size:14px;color:#0c4a6e;font-weight:600}
.agree-box input[type=checkbox]{width:18px;height:18px;margin-top:2px;flex-shrink:0;cursor:pointer}
.btn-row{display:flex;gap:10px}
.btn{flex:1;padding:14px 20px;border-radius:8px;border:0;font-size:15px;font-weight:700;cursor:pointer;text-align:center;text-decoration:none;display:inline-block}
.btn-primary{background:#3b82f6;color:#fff}
.btn-primary:hover{background:#2563eb}
.btn-primary:disabled{background:#cbd5e1;cursor:not-allowed}
.btn-cancel{background:#f1f5f9;color:#475569;border:1px solid #e2e8f0}
.btn-cancel:hover{background:#e2e8f0}
.cs{text-align:center;margin-top:20px;font-size:12px;color:#94a3b8}
</style>
</head><body>
<div class="wrap">
    <h1>구매 확인</h1>
    <p class="sub">결제를 진행하기 전에 아래 내용을 꼭 확인해 주세요.</p>

    <div class="plugin-box">
        <div class="pn"><?= $pluginNm ?></div>
        <?php if ($pluginDesc): ?><div class="pd"><?= $pluginDesc ?></div><?php endif; ?>
        <div class="pp"><?= $priceFmt ?>원</div>
    </div>

    <div class="notice">
        <h3>⚠ 청약철회가 제한되는 상품입니다</h3>
        <p>본 상품은 <strong>디지털 콘텐츠(플러그인 파일)</strong>로, 결제 완료 직후 즉시 다운로드 및 사용이 가능합니다. 「전자상거래 등에서의 소비자보호에 관한 법률」 제17조 제2항 제5호에 따라 <strong>구매 후 청약철회 및 환불이 제한</strong>됩니다.</p>
    </div>

    <div class="policy">
        <h3>환불 · 청약철회 정책</h3>

        <h4>제1조 (청약철회 제한)</h4>
        <p>아래의 경우 청약철회가 불가능합니다:</p>
        <ul>
            <li>구매자가 플러그인 파일을 <strong>1회 이상 다운로드한 경우</strong></li>
            <li>구매한 플러그인을 <strong>본인 사이트에 설치·활성화한 경우</strong></li>
            <li>결제 완료 후 라이선스 키가 발급된 경우</li>
        </ul>

        <h4>제2조 (환불 가능 사유)</h4>
        <p>다음 경우에는 환불이 가능합니다:</p>
        <ul>
            <li>결제 후 <strong>다운로드하지 않은 상태에서 결제 당일 환불 요청</strong>한 경우 (MainPay 당일 취소)</li>
            <li>플러그인 파일이 제공되지 않거나 손상되어 정상적인 설치·실행이 불가능한 경우</li>
            <li>상품 설명과 현저히 다른 기능을 제공하는 경우</li>
            <li>판매자의 귀책으로 서비스가 영구 중단된 경우</li>
        </ul>

        <h4>제3조 (환불 절차)</h4>
        <ul>
            <li>환불 요청: <strong>nuribd.com 고객센터</strong> 또는 <strong>내정보 → 구매내역</strong>에서 문의</li>
            <li>처리 기간: 요청 접수 후 영업일 기준 3~5일 이내 심사 및 환불</li>
            <li>환불 방법: 원결제수단(카드) 취소 처리</li>
        </ul>

        <h4>제4조 (분쟁 처리)</h4>
        <p>본 정책으로 해결되지 않는 분쟁은 <strong>전자거래분쟁조정위원회</strong> 또는 <strong>소비자분쟁조정위원회</strong>를 통해 조정을 신청할 수 있습니다.</p>

        <h4>제5조 (판매자 정보)</h4>
        <p>판매자: <?= $companyNm ?> / 고객센터 운영시간: <?= $csHour ?></p>
    </div>

    <form method="post" action="<?= $postUrl ?>" id="buyForm">
        <input type="hidden" name="_token" value="<?= nb_e($csrf) ?>">
        <input type="hidden" name="site_token" value="<?= $siteTokenE ?>">
        <input type="hidden" name="return_url" value="<?= $returnUrlE ?>">
        <input type="hidden" name="site_url" value="<?= $buyerSiteE ?>">
        <input type="hidden" name="consent_version" value="<?= nb_e($consentV) ?>">

        <div class="agree-box">
            <label>
                <input type="checkbox" name="agree" value="1" id="agreeChk" required>
                <span>위 상품이 <strong>디지털 콘텐츠</strong>로서 다운로드 후 <strong>청약철회가 제한</strong>됨을 확인했으며, 환불 정책에 동의합니다. (필수)</span>
            </label>
        </div>

        <div class="btn-row">
            <a href="javascript:window.close()" class="btn btn-cancel">취소</a>
            <button type="submit" class="btn btn-primary" id="payBtn" disabled><?= $priceFmt ?>원 결제하기</button>
        </div>
    </form>

    <p class="cs">결제 시스템: MainPay (워프스타) · 카드결제<br>문의: <?= nb_e(nb_setting('biz_email', 'contact@nuribd.com')) ?></p>
</div>
<script>
document.getElementById('agreeChk').addEventListener('change', function(){
    document.getElementById('payBtn').disabled = !this.checked;
});
</script>
</body></html><?php
});

// ============================================================
// POST /market/buy/{id} — 동의 제출 후 실제 결제 준비 및 리다이렉트
// ============================================================
Router::post('/market/buy/{id}', function ($params) {
    $prefix = DB::getPrefix();
    $pluginId = (int)$params['id'];
    $plugin = DB::fetch("SELECT * FROM {$prefix}market_plugins WHERE id = ?", [$pluginId]);
    if (!$plugin) { die('플러그인을 찾을 수 없습니다.'); }
    if ((int)$plugin['price'] <= 0) { die('무료 플러그인은 구매할 필요가 없습니다.'); }

    if (!Auth::verifyCsrf()) { die('잘못된 요청입니다. (CSRF)'); }
    if (empty($_POST['agree'])) { die('청약철회 제한 고지에 동의하셔야 결제가 가능합니다.'); }

    $siteToken    = trim($_POST['site_token'] ?? '');
    $returnUrl    = trim($_POST['return_url'] ?? '');
    $buyerSiteUrl = trim($_POST['site_url'] ?? '');
    $isAnonBuy    = !empty($siteToken);

    if (!$isAnonBuy) { Auth::requireLogin(); }
    if ($returnUrl && !preg_match('#^https?://#i', $returnUrl)) { $returnUrl = ''; }

    // 중복 결제 재검사
    if ($isAnonBuy) {
        $already = DB::fetch(
            "SELECT id FROM {$prefix}market_purchases WHERE site_token = ? AND plugin_id = ? AND status = 'paid'",
            [$siteToken, $pluginId]
        );
        if ($already) {
            if ($returnUrl) {
                Router::redirect($returnUrl . (strpos($returnUrl,'?')!==false?'&':'?') . 'purchased=1&already=1');
            } else { die('이미 구매한 플러그인입니다.'); }
            return;
        }
    } else {
        $already = DB::fetch(
            "SELECT id FROM {$prefix}market_purchases WHERE member_id = ? AND plugin_id = ? AND status = 'paid'",
            [Auth::id(), $pluginId]
        );
        if ($already) { Router::redirect(nb_url('market/my-purchases')); return; }
    }

    // 가맹점 주문번호
    $idForRef = $isAnonBuy ? substr(sha1($siteToken), 0, 4) : (string)Auth::id();
    $mbrRefNo = MAINPAY_TID . date('YmdHis') . str_pad($idForRef, 4, '0', STR_PAD_LEFT);
    $timestamp = mp_timestamp();
    $amount = (int)$plugin['price'];
    $signature = mp_signature($mbrRefNo, $amount, $timestamp);
    $siteUrl = rtrim(nb_setting('site_url'), '/');

    $buyerName = '';
    $buyerEmail = '';
    if (!$isAnonBuy) {
        $user = Auth::user();
        $buyerName = mb_strimwidth($user['nickname'] ?? '', 0, 20, '');
        $buyerEmail = $user['email'] ?? '';
    } else {
        $buyerName = '배포사이트구매';
    }

    $resp = mp_post('/v1/payment/ready', [
        'version'       => 'V1',
        'mbrNo'         => MAINPAY_MBR_NO,
        'mbrRefNo'      => $mbrRefNo,
        'paymethod'     => 'CARD',
        'amount'        => $amount,
        'goodsName'     => mb_strimwidth($plugin['name'], 0, 28, ''),
        'customerName'  => $buyerName,
        'customerEmail' => $buyerEmail,
        'approvalUrl'   => $siteUrl . nb_url('market/approval'),
        'closeUrl'      => $siteUrl . nb_url('market/close'),
        'timestamp'     => $timestamp,
        'signature'     => $signature,
    ]);

    if (($resp['resultCode'] ?? '') !== '200') {
        $msg = nb_e($resp['resultMessage'] ?? '알 수 없음');
        die('<div style="font-family:sans-serif;padding:40px;text-align:center"><h2>결제 준비 실패</h2><p>' . $msg . '</p><a href="' . nb_url('/') . '">홈으로</a></div>');
    }

    // 구매 레코드 pending 저장 (청약철회 동의 기록 포함)
    $consentIp = $_SERVER['REMOTE_ADDR'] ?? '';
    $consentUa = mb_strimwidth($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255, '');
    $consentVer = trim($_POST['consent_version'] ?? MP_CONSENT_VERSION);

    DB::insert("{$prefix}market_purchases", [
        'member_id'       => $isAnonBuy ? 0 : Auth::id(),
        'plugin_id'       => $pluginId,
        'amount'          => $amount,
        'mbr_ref_no'      => $mbrRefNo,
        'aid'             => $resp['data']['aid'] ?? '',
        'status'          => 'pending',
        'site_token'      => $siteToken,
        'site_url'        => mb_strimwidth($buyerSiteUrl, 0, 255, ''),
        'return_url'      => mb_strimwidth($returnUrl, 0, 500, ''),
        'consent_ip'      => $consentIp,
        'consent_ua'      => $consentUa,
        'consent_at'      => date('Y-m-d H:i:s'),
        'consent_version' => mb_strimwidth($consentVer, 0, 16, ''),
    ]);

    $nextPc     = $resp['data']['nextPcUrl'] ?? '';
    $nextMobile = $resp['data']['nextMobileUrl'] ?? '';
    $isMobile   = preg_match('/Mobile|Android|iPhone/i', $_SERVER['HTTP_USER_AGENT'] ?? '');

    // 익명 구매(크로스사이트): 이미 새 탭으로 열린 상태이므로 바로 리다이렉트
    if ($isAnonBuy) {
        $target = ($isMobile && $nextMobile) ? $nextMobile : $nextPc;
        Router::redirect($target);
        return;
    }

    if ($isMobile && $nextMobile) {
        Router::redirect($nextMobile);
        return;
    }
    // PC (nuribd.com 내부): 팝업으로 결제창 열기
    $popupUrl = nb_e($nextPc);
    $myPageUrl = nb_e(nb_url('market/my-purchases'));
    ?><!DOCTYPE html>
<html lang="ko"><head><meta charset="utf-8"><title>결제 진행 중...</title>
<style>body{font-family:-apple-system,sans-serif;text-align:center;padding:60px 20px;color:#334155}h2{margin-bottom:12px}a{color:#3b82f6}</style>
</head><body>
<h2>결제창을 준비 중입니다</h2>
<p>팝업이 열리지 않으면 <a href="<?= $popupUrl ?>" target="_blank">여기를 클릭</a>해주세요.</p>
<script>
var pop = window.open('<?= $popupUrl ?>', 'mainpayWindow', 'width=500,height=720,scrollbars=yes');
if (!pop) {
    alert('팝업이 차단되었습니다. 브라우저 팝업 차단을 해제하고 다시 시도해주세요.');
} else {
    var timer = setInterval(function(){
        if (pop.closed) {
            clearInterval(timer);
            location.href = '<?= $myPageUrl ?>';
        }
    }, 700);
}
</script>
</body></html><?php
});

// ============================================================
// 라우트: 결제 승인 콜백 (approvalUrl)
//   /market/approval
// ============================================================
$mp_approval_handler = function () {
    $prefix = DB::getPrefix();
    $aid       = $_REQUEST['aid'] ?? '';
    $authToken = $_REQUEST['authToken'] ?? '';
    $myPageUrl = nb_url('market/my-purchases');

    if (!$aid || !$authToken) {
        ?><!DOCTYPE html><html><head><meta charset="utf-8"><title>오류</title></head><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>잘못된 요청입니다</h2></body></html><?php
        return;
    }

    $purchase = DB::fetch("SELECT * FROM {$prefix}market_purchases WHERE aid = ?", [$aid]);
    if (!$purchase) {
        ?><!DOCTYPE html><html><head><meta charset="utf-8"><title>오류</title></head><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>구매 내역을 찾을 수 없습니다</h2></body></html><?php
        return;
    }

    // 이미 처리됨 (approvalUrl 중복 호출 대비)
    if ($purchase['status'] === 'paid') {
        // 익명 구매였으면 return_url로 이동
        if (!empty($purchase['return_url'])) {
            $ru = $purchase['return_url'];
            $ru .= (strpos($ru,'?')!==false?'&':'?') . 'purchased=1';
            Router::redirect($ru);
            return;
        }
        ?><!DOCTYPE html><html><head><meta charset="utf-8"><title>결제 완료</title></head><body style="font-family:sans-serif;text-align:center;padding:40px">
        <h2 style="color:#059669">이미 결제가 완료되었습니다</h2>
        <script>
        if(window.opener && !window.opener.closed){ window.opener.location.href='<?= nb_e($myPageUrl) ?>'; window.close(); }
        else { setTimeout(function(){location.href='<?= nb_e($myPageUrl) ?>'}, 1000); }
        </script></body></html><?php
        return;
    }

    // 결제 승인 API 호출
    $timestamp = mp_timestamp();
    $signature = mp_signature($purchase['mbr_ref_no'], (int)$purchase['amount'], $timestamp);
    $resp = mp_post('/v1/payment/pay', [
        'version'   => 'V1',
        'mbrNo'     => MAINPAY_MBR_NO,
        'aid'       => $aid,
        'mbrRefNo'  => $purchase['mbr_ref_no'],
        'authToken' => $authToken,
        'paymethod' => 'CARD',
        'amount'    => (int)$purchase['amount'],
        'timestamp' => $timestamp,
        'signature' => $signature,
    ]);

    if (($resp['resultCode'] ?? '') === '200') {
        DB::update("{$prefix}market_purchases", [
            'status'     => 'paid',
            'auth_token' => $authToken,
            'ref_no'     => $resp['data']['refNo'] ?? '',
            'card_no'    => $resp['data']['cardNo'] ?? '',
            'pay_type'   => $resp['data']['payType'] ?? '',
            'tran_date'  => $resp['data']['tranDate'] ?? '',
            'paid_at'    => date('Y-m-d H:i:s'),
        ], 'id = ?', [$purchase['id']]);

        // 플러그인 다운로드 카운트 증가
        DB::query("UPDATE {$prefix}market_plugins SET downloads = downloads + 1 WHERE id = ?", [$purchase['plugin_id']]);

        // 크로스사이트 결제 → 구매자 사이트로 자동 리다이렉트
        if (!empty($purchase['return_url'])) {
            $ru = $purchase['return_url'];
            $ru .= (strpos($ru,'?')!==false?'&':'?') . 'purchased=1';
            $safeRu = nb_e($ru);
            ?><!DOCTYPE html><html lang="ko"><head><meta charset="utf-8"><title>결제 완료</title>
            <style>body{font-family:-apple-system,sans-serif;text-align:center;padding:60px 20px;color:#334155}h2{color:#059669;margin-bottom:8px}</style>
            </head><body>
            <h2>✅ 결제가 완료되었습니다!</h2>
            <p>잠시 후 내 사이트로 돌아갑니다...</p>
            <p style="font-size:12px;color:#94a3b8">자동으로 이동하지 않으면 <a href="<?= $safeRu ?>">여기를 클릭</a></p>
            <script>setTimeout(function(){location.href='<?= $safeRu ?>'}, 1500);</script>
            </body></html><?php
            return;
        }

        ?><!DOCTYPE html><html lang="ko"><head><meta charset="utf-8"><title>결제 완료</title>
        <style>body{font-family:-apple-system,sans-serif;text-align:center;padding:60px 20px;color:#334155}h2{color:#059669;margin-bottom:8px}</style>
        </head><body>
        <h2>✅ 결제가 완료되었습니다!</h2>
        <p>잠시 후 구매 내역으로 이동합니다...</p>
        <script>
        setTimeout(function(){
            if(window.opener && !window.opener.closed){ window.opener.location.href='<?= nb_e($myPageUrl) ?>'; window.close(); }
            else { location.href='<?= nb_e($myPageUrl) ?>'; }
        }, 1500);
        </script></body></html><?php
    } else {
        $errMsg = $resp['resultMessage'] ?? '승인 실패';
        DB::update("{$prefix}market_purchases", [
            'status'        => 'failed',
            'error_message' => mb_strimwidth($errMsg, 0, 200, ''),
        ], 'id = ?', [$purchase['id']]);

        ?><!DOCTYPE html><html lang="ko"><head><meta charset="utf-8"><title>결제 실패</title>
        <style>body{font-family:-apple-system,sans-serif;text-align:center;padding:60px 20px;color:#334155}h2{color:#dc2626}</style>
        </head><body>
        <h2>❌ 결제 실패</h2>
        <p><?= nb_e($errMsg) ?></p>
        <script>setTimeout(function(){if(window.opener && !window.opener.closed)window.close();else history.back()},3000);</script>
        </body></html><?php
    }
};
Router::get('/market/approval', $mp_approval_handler);
Router::post('/market/approval', $mp_approval_handler);

// ============================================================
// 라우트: 결제 취소/종료 콜백 (closeUrl)
//   /market/close
// ============================================================
$mp_close_handler = function () {
    $prefix = DB::getPrefix();
    $aid = $_REQUEST['aid'] ?? '';
    if ($aid) {
        DB::update(
            "{$prefix}market_purchases",
            ['status' => 'canceled'],
            "aid = ? AND status = 'pending'",
            [$aid]
        );
    }
    ?><!DOCTYPE html><html lang="ko"><head><meta charset="utf-8"><title>결제 취소</title>
    <style>body{font-family:-apple-system,sans-serif;text-align:center;padding:60px 20px;color:#334155}</style>
    </head><body>
    <h2>결제가 취소되었습니다</h2>
    <script>setTimeout(function(){if(window.opener && !window.opener.closed)window.close();else history.back()},1500);</script>
    </body></html><?php
};
Router::get('/market/close', $mp_close_handler);
Router::post('/market/close', $mp_close_handler);

// ============================================================
// 라우트: 내 구매 내역 페이지
//   /market/my-purchases
// ============================================================
Router::get('/market/my-purchases', function () {
    Auth::requireLogin();
    $prefix = DB::getPrefix();
    $list = DB::fetchAll("
        SELECT p.*, mp.name AS plugin_name, mp.thumbnail, mp.version, mp.author, mp.description, mp.zip_file
        FROM {$prefix}market_purchases p
        LEFT JOIN {$prefix}market_plugins mp ON p.plugin_id = mp.id
        WHERE p.member_id = ? AND p.status = 'paid'
        ORDER BY p.paid_at DESC
    ", [Auth::id()]);
    Router::loadTheme('market/my-purchases', ['list' => $list]);
});

// ============================================================
// 라우트: 구매한 플러그인 다운로드 (본인 구매 건만)
//   /market/download/{id}
// ============================================================
Router::get('/market/download/{id}', function ($params) {
    Auth::requireLogin();
    $prefix = DB::getPrefix();
    $purchase = DB::fetch("
        SELECT p.*, mp.zip_file, mp.zip_orig_name, mp.name AS plugin_name
        FROM {$prefix}market_purchases p
        LEFT JOIN {$prefix}market_plugins mp ON p.plugin_id = mp.id
        WHERE p.id = ? AND p.member_id = ? AND p.status = 'paid'
    ", [(int)$params['id'], Auth::id()]);

    if (!$purchase) { http_response_code(403); die('구매 내역이 없습니다.'); }

    $file = NB_ROOT . '/' . $purchase['zip_file'];
    if (!file_exists($file)) { http_response_code(404); die('파일이 존재하지 않습니다.'); }

    DB::query("UPDATE {$prefix}market_purchases SET download_count = download_count + 1 WHERE id = ?", [$purchase['id']]);

    $filename = $purchase['zip_orig_name'] ?: (preg_replace('/[^a-zA-Z0-9_-]/', '_', $purchase['plugin_name']) . '.zip');
    header('Content-Type: application/zip');
    header('Content-Disposition: attachment; filename="' . addslashes($filename) . '"');
    header('Content-Length: ' . filesize($file));
    header('Cache-Control: no-cache, must-revalidate');
    header('Pragma: public');
    readfile($file);
    exit;
});
