PHP WebShell

Текущая директория: /var/www/bitcardoApp/user/giftcards

Просмотр файла: card_transactions.php

<?php
// users/giftcards/card_transactions.php
include '../common/header.php';
require_once '../../config/db_config.php';
if (session_status() === PHP_SESSION_NONE) { session_start(); }

if (empty($_SESSION['user_id'])) {
    $_SESSION['error'] = 'Please sign in to view your card transactions.';
    header("Location: ../login.php");
    exit;
}
$user_id = (int)$_SESSION['user_id'];

// Base URL for card evidence images (same as admin)
$IMG_BASE = 'https://wallet.bitcardo.com/backyard/uploads/cards/';


// Pagination (by group/batch_ref)
$per_page = 10;
$page = max(1, (int)($_GET['page'] ?? 1));
$offset = ($page - 1) * $per_page;

// Count total groups
$count_stmt = $conn->prepare("
    SELECT COUNT(*) AS total
    FROM (
        SELECT batch_ref
        FROM card_trade
        WHERE user_id = ?
        GROUP BY batch_ref
    ) t
");
$count_stmt->bind_param("i", $user_id);
$count_stmt->execute();
$total_groups = (int)($count_stmt->get_result()->fetch_assoc()['total'] ?? 0);
$count_stmt->close();

$total_pages = max(1, (int)ceil($total_groups / $per_page));

// Fetch paginated groups summary
$group_stmt = $conn->prepare("
    SELECT batch_ref,
           MIN(trade_created) AS created_at,
           COUNT(*) AS items_count,
           COALESCE(SUM(est_payout_ngn), 0) AS est_total,
           GROUP_CONCAT(DISTINCT trade_status) AS status_concat
    FROM card_trade
    WHERE user_id = ?
    GROUP BY batch_ref
    ORDER BY created_at DESC
    LIMIT ? OFFSET ?
");
$group_stmt->bind_param("iii", $user_id, $per_page, $offset);
$group_stmt->execute();
$groups_res = $group_stmt->get_result();

function badgeForStatus($status) {
    $status = strtolower((string)$status);
    return match ($status) {
        'pending'    => '<span class="badge bg-warning text-dark">Pending</span>',
        'processing' => '<span class="badge bg-info text-dark">Processing</span>',
        'approved', 'completed', 'paid'
                      => '<span class="badge bg-success">Completed</span>',
        'rejected', 'failed', 'declined'
                      => '<span class="badge bg-danger">Rejected</span>',
        'partial'    => '<span class="badge bg-secondary">Partial</span>',
        'review', 'on_hold'
                      => '<span class="badge bg-secondary">On Review</span>',
        default      => '<span class="badge bg-light text-dark">'.htmlspecialchars(ucfirst($status)).'</span>',
    };
}

function deriveGroupStatus($status_concat) {
    if (!$status_concat) return 'pending';
    $statuses = array_map('strtolower', array_filter(array_map('trim', explode(',', $status_concat))));
    $unique = array_unique($statuses);

    $allCompleted = !array_diff($unique, ['approved','completed','paid']);
    $allRejected  = !array_diff($unique, ['rejected','failed','declined']);

    if ($allCompleted) return 'completed';
    if ($allRejected)  return 'rejected';

    if (in_array('rejected', $unique, true) || in_array('failed', $unique, true) || in_array('declined', $unique, true)) {
        return 'partial';
    }
    if (in_array('processing', $unique, true)) return 'processing';
    if (in_array('pending', $unique, true))    return 'pending';

    return 'partial';
}
?>

<div class="container mt-4">
  <div class="row">
    <?php include '../common/nav.php'; ?>

    <main class="col-md-9 col-lg-10 px-md-5 mb-5">
      <?php include '../common/page-header.php'; ?>

      <div class="container my-5 px-md-5">
        <div class="d-flex align-items-center justify-content-between mb-3">
          <h5 class="mb-0 ps-3">Card Trades</h5>
          <a href="submit_card.php" class="btn btn-primary btn-sm pe-3">
            <i class="bi bi-plus-circle"></i> Trade New Cards
          </a>
        </div>

        <?php if (isset($_SESSION['error'])): ?>
          <div class="alert alert-danger"><?= $_SESSION['error']; unset($_SESSION['error']); ?></div>
        <?php elseif (isset($_SESSION['success'])): ?>
          <div class="alert alert-success"><?= $_SESSION['success']; unset($_SESSION['success']); ?></div>
        <?php endif; ?>

        <?php if ($total_groups === 0): ?>
          <div class="alert alert-info">No card transactions yet.</div>
        <?php else: ?>

        <div class="accordion" id="txAccordion">
          <?php
          $idx = 0;
          while ($g = $groups_res->fetch_assoc()):
            $idx++;
            $batch_ref   = $g['batch_ref'];
            $created_at  = $g['created_at'];
            $items_count = (int)$g['items_count'];
            $est_total   = (float)$g['est_total'];
            $group_status = deriveGroupStatus($g['status_concat']);

            // Fetch items for this batch
            $items_stmt = $conn->prepare("
                SELECT ct.trade_id, ct.card_ref, ct.cbrand_id, ct.gc_id, ct.card_value, ct.card_curr,
                       ct.buy_price_snapshot, ct.est_payout_ngn, ct.trade_status, ct.trade_created,
                       cb.card_brand, gc.demon,
                       (SELECT COUNT(*) FROM card_trade_images i WHERE i.trade_id = ct.trade_id) AS img_count
                FROM card_trade ct
                JOIN card_brands cb ON cb.cbrand_id = ct.cbrand_id
                JOIN gift_cards gc ON gc.gc_id = ct.gc_id
                WHERE ct.user_id = ? AND ct.batch_ref = ?
                ORDER BY ct.trade_created ASC, ct.trade_id ASC
            ");
            $items_stmt->bind_param("is", $user_id, $batch_ref);
            $items_stmt->execute();
            $items = $items_stmt->get_result();
          ?>
          <div class="accordion-item mb-2 rounded-3 overflow-hidden">
            <h2 class="accordion-header" id="heading<?= $idx ?>">
              <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse<?= $idx ?>" aria-expanded="false" aria-controls="collapse<?= $idx ?>">
                <div class="w-100 d-flex justify-content-between align-items-center">
                  <div>
                    <div class="fw-semibold">Batch: <?= htmlspecialchars($batch_ref) ?></div>
                    <small class="text-muted"><?= htmlspecialchars(date('M j, Y g:i A', strtotime($created_at))) ?></small>
                  </div>
                  <div class="text-end pe-2">
                    <div><?= badgeForStatus($group_status) ?></div>
                    <small class="text-muted">
                      <?= $items_count ?> card<?= $items_count>1?'s':'' ?> • Est: ₦<?= number_format($est_total, 2) ?>
                    </small>
                  </div>
                </div>
              </button>
            </h2>
            <div id="collapse<?= $idx ?>" class="accordion-collapse collapse" aria-labelledby="heading<?= $idx ?>" data-bs-parent="#txAccordion">
              <div class="accordion-body">
                <div class="table-responsive">
                <table class="table align-middle">
                  <thead>
                    <tr>
                      <th class="text-muted small">#</th>
                      <th class="d-none d-md-table-cell">Gift Card</th>

                      <!-- Desktop-only column headers -->
                      <th class="text-end d-none d-md-table-cell">Value</th>
                      <th class="text-end d-none d-md-table-cell">Price (₦)</th>
                      <th class="text-end d-none d-md-table-cell">Est. (₦)</th>
                      <th class="d-none d-md-table-cell">Status</th>
                      <th class="d-none d-md-table-cell">Created</th>
                    </tr>
                  </thead>

                  <tbody>
                  <tbody>
                    <?php 
                    $n = 0; 
                    while ($r = $items->fetch_assoc()): 
                        $n++; 

                        // Determine status and prepare evidence image (only for declined/failed/rejected)
                        $statusLower = strtolower((string)$r['trade_status']);
                        $evidenceImgUrl = null;

                        if ((int)$r['img_count'] > 0 && in_array($statusLower, ['declined','failed','rejected'], true)) {
                            $img_stmt = $conn->prepare("
                                SELECT path 
                                FROM card_trade_images 
                                WHERE trade_id = ? 
                                ORDER BY image_id DESC 
                                LIMIT 1
                            ");
                            $img_stmt->bind_param("i", $r['trade_id']);
                            $img_stmt->execute();
                            $img_stmt->bind_result($imgPath);
                            if ($img_stmt->fetch() && $imgPath) {
                                // Build full URL; if path already absolute, keep it
                                $evidenceImgUrl = preg_match('#^https?://#i', $imgPath)
                                    ? $imgPath
                                    : $IMG_BASE . ltrim($imgPath, '/');
                            }
                            $img_stmt->close();
                        }
                    ?>
                      <tr>
                        <!-- # -->
                        <td class="text-muted small text-nowrap"><?= $n ?></td>

                        <!-- GiftCard + Mobile details stack -->
                        <td>
                          <div class="fw-semibold mb-0"><?= htmlspecialchars($r['card_brand']) ?></div>
                          <small class="text-muted d-block"><?= htmlspecialchars($r['demon']) ?></small>
                          <small class="text-muted d-block">Ref: <?= htmlspecialchars($r['card_ref']) ?></small>

                          <!-- MOBILE-ONLY: stacked details (labels + values) -->
                          <div class="d-md-none mt-2 border-top pt-2">
                            <div class="d-flex justify-content-between">
                              <small class="text-muted">Value</small>
                              <span class="text-nowrap">
                                <?= number_format((float)$r['card_value'], 2) ?>
                                <small class="text-muted"><?= htmlspecialchars($r['card_curr']) ?></small>
                              </span>
                            </div>
                            <div class="d-flex justify-content-between">
                              <small class="text-muted">Price</small>
                              <span class="text-nowrap">₦<?= number_format((float)$r['buy_price_snapshot'], 2) ?></span>
                            </div>
                            <div class="d-flex justify-content-between">
                              <small class="text-muted">Est.</small>
                              <span class="fw-semibold text-nowrap">₦<?= number_format((float)$r['est_payout_ngn'], 2) ?></span>
                            </div>
                            <div class="d-flex justify-content-between align-items-center">
                              <small class="text-muted">Status</small>
                              <span><?= badgeForStatus($r['trade_status']) ?></span>
                            </div>
                            <div class="d-flex justify-content-between">
                              <small class="text-muted">Created</small>
                              <small class="text-muted text-nowrap"><?= htmlspecialchars(date('M j, Y g:i A', strtotime($r['trade_created']))) ?></small>
                            </div>
                          </div>
                          <?php if (!empty($evidenceImgUrl)): ?>
                            <div class="mt-2">
                              <small class="text-muted d-block mb-1">Decline evidence</small>
                              <button
                                type="button"
                                class="btn btn-link p-0 border-0 card-evidence-btn"
                                data-bs-toggle="modal"
                                data-bs-target="#evidenceModal"
                                data-img="<?= htmlspecialchars($evidenceImgUrl) ?>"
                              >
                                <img
                                  src="<?= htmlspecialchars($evidenceImgUrl) ?>"
                                  alt="Decline evidence"
                                  class="img-thumbnail"
                                  style="max-height: 120px;"
                                >
                              </button>
                            </div>
                          <?php endif; ?>
                        </td>

                        <!-- DESKTOP-ONLY cells -->
                        <td class="text-end d-none d-md-table-cell text-nowrap">
                          <?= number_format((float)$r['card_value'], 2) ?>
                          <small class="text-muted"><?= htmlspecialchars($r['card_curr']) ?></small>
                        </td>
                        <td class="text-end d-none d-md-table-cell text-nowrap">₦<?= number_format((float)$r['buy_price_snapshot'], 2) ?></td>
                        <td class="text-end d-none d-md-table-cell fw-semibold text-nowrap">₦<?= number_format((float)$r['est_payout_ngn'], 2) ?></td>
                        <td class="d-none d-md-table-cell"><?= badgeForStatus($r['trade_status']) ?></td>
                        <td class="d-none d-md-table-cell"><small class="text-muted"><?= htmlspecialchars(date('M j, Y g:i A', strtotime($r['trade_created']))) ?></small></td>
                      </tr>
                    <?php endwhile; ?>
                  </tbody>
                </table>

                </div>
              </div>
            </div>
          </div>
          <?php
            $items_stmt->close();
          endwhile;
          $group_stmt->close();
          ?>
        </div>

        <!-- Pagination -->
        <?php if ($total_pages > 1): ?>
          <nav class="mt-3">
            <ul class="pagination">
              <?php
              $base = 'card_transactions.php';
              $q = $_GET; unset($q['page']);
              $qs = http_build_query($q);
              $url = fn($p) => $base . '?' . ($qs ? $qs.'&' : '') . 'page=' . $p;
              ?>
              <li class="page-item <?= $page<=1?'disabled':'' ?>">
                <a class="page-link" href="<?= $page<=1?'#':htmlspecialchars($url($page-1)) ?>">Previous</a>
              </li>
              <?php
                $start = max(1, $page-2);
                $end   = min($total_pages, $page+2);
                if ($start > 1) {
                  echo '<li class="page-item"><a class="page-link" href="'.htmlspecialchars($url(1)).'">1</a></li>';
                  if ($start > 2) echo '<li class="page-item disabled"><span class="page-link">…</span></li>';
                }
                for ($p=$start; $p<=$end; $p++) {
                  echo '<li class="page-item '.($p==$page?'active':'').'"><a class="page-link" href="'.htmlspecialchars($url($p)).'">'.$p.'</a></li>';
                }
                if ($end < $total_pages) {
                  if ($end < $total_pages-1) echo '<li class="page-item disabled"><span class="page-link">…</span></li>';
                  echo '<li class="page-item"><a class="page-link" href="'.htmlspecialchars($url($total_pages)).'">'.$total_pages.'</a></li>';
                }
              ?>
              <li class="page-item <?= $page>=$total_pages?'disabled':'' ?>">
                <a class="page-link" href="<?= $page>=$total_pages?'#':htmlspecialchars($url($page+1)) ?>">Next</a>
              </li>
            </ul>
          </nav>
        <?php endif; ?>

        <?php endif; ?>
      </div>
    </main>
  </div>
</div>


<!-- Evidence Image Modal -->
<div class="modal fade" id="evidenceModal" tabindex="-1" aria-labelledby="evidenceModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h6 class="modal-title" id="evidenceModalLabel">Decline evidence</h6>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="ratio ratio-16x9">
          <img id="evidenceModalImg" src="" alt="Evidence" class="w-100 h-100" style="object-fit:contain;">
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>

<script>
// When a decline evidence thumbnail is clicked, set image in modal
document.addEventListener('click', function (e) {
  const btn = e.target.closest('.card-evidence-btn');
  if (!btn) return;

  const imgUrl = btn.getAttribute('data-img') || '';
  const imgEl = document.getElementById('evidenceModalImg');
  if (imgEl) {
    imgEl.src = imgUrl;
  }
});
</script>


<?php include '../common/footer.php'; ?>

Выполнить команду


Для локальной разработки. Не используйте в интернете!