PHP WebShell

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

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

<?php
// fiat/verify_paystack.php
session_start();
header('Content-Type: text/html; charset=UTF-8');

require_once '../../config/db_config.php'; // provides $conn (mysqli)
// If your Paystack secret is stored in DB/settings, fetch it accordingly:
if (!isset($paystackSecret)) {
  // Example: from settings table (adjust to your storage)
  $stmt = $conn->prepare("SELECT setting_value FROM site_settings WHERE `setting_key`='paystack_secret' LIMIT 1");
  if ($stmt) {
    $stmt->execute();
    $stmt->bind_result($paystackSecretVal);
    if ($stmt->fetch()) $paystackSecret = $paystackSecretVal;
    $stmt->close();
  }
}
$paystackSecret = $paystackSecret ?? ''; // ensure string

// helper: site_settings
function get_site_setting(mysqli $conn, string $setting_key, $default = null) {
  $stmt = $conn->prepare("SELECT setting_value FROM site_settings WHERE `setting_key` = ? LIMIT 1");
  if ($stmt) {
    $stmt->bind_param('s', $setting_key);
    $stmt->execute();
    $stmt->bind_result($val);
    if ($stmt->fetch()) { $stmt->close(); return $val; }
    $stmt->close();
  }
  return $default;
}

// 1) Read setting: how fees are handled
$add_naira_deposit_fee = (int) get_site_setting($conn, 'add_naira_deposit_fee', 1); // 1=add (wallet gets full typed amount), 0=deduct

// 2) Read inputs
$reference        = isset($_GET['reference']) ? trim($_GET['reference']) : '';
$wallet_id        = isset($_GET['wallet_id']) ? trim($_GET['wallet_id']) : '';
$user_amount_hint = isset($_GET['amount']) ? (float)$_GET['amount'] : null; // amount typed in UI
$mode_hint        = isset($_GET['mode'])   ? (int)$_GET['mode']   : null;   // 1 or 0; just a hint

$_SESSION['deposit'] = [
    'ok' => false,
    'message' => '',
    'reference' => $reference,
    'wallet_id' => $wallet_id,
    'amount' => 0,
    'currency' => 'NGN',
    'new_balance' => null,
];

if ($reference === '' || $wallet_id === '') {
    $_SESSION['deposit']['message'] = 'Missing reference or wallet_id.';
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}

// 3) Fetch wallet (we’ll need coin, address, current balance, user_id)
$sql_wallet = "SELECT wallet_id, user_id, wallet_add, bank_name, coin, balance, wallet_status
               FROM user_wallets WHERE wallet_id = ? LIMIT 1";
$stmt = mysqli_prepare($conn, $sql_wallet);
mysqli_stmt_bind_param($stmt, 's', $wallet_id);
mysqli_stmt_execute($stmt);
$res_wallet = mysqli_stmt_get_result($stmt);
$wallet = mysqli_fetch_assoc($res_wallet);
mysqli_stmt_close($stmt);

if (!$wallet) {
    $_SESSION['deposit']['message'] = 'Wallet not found.';
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}
if (strtoupper($wallet['wallet_status'] ?? '') === 'INACTIVE') {
    $_SESSION['deposit']['message'] = 'Wallet is inactive.';
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}

$user_id_for_tx = (int)($wallet['user_id'] ?? 0);

// 4) Verify with Paystack
$verify_url = "https://api.paystack.co/transaction/verify/" . urlencode($reference);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $verify_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer {$paystackSecret}",
    "Accept: application/json"
]);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$raw = curl_exec($ch);
$curl_err = curl_error($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($curl_err) {
    $_SESSION['deposit']['message'] = 'Network error: ' . $curl_err;
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}
if ($http_code < 200 || $http_code >= 300) {
    $_SESSION['deposit']['message'] = 'Paystack verify HTTP ' . $http_code;
    $_SESSION['deposit']['raw'] = $raw;
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}

$resp = json_decode($raw, true);
if (!is_array($resp) || empty($resp['status'])) {
    $_SESSION['deposit']['message'] = 'Malformed Paystack response.';
    $_SESSION['deposit']['raw'] = $raw;
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}
if (!$resp['status']) {
    $_SESSION['deposit']['message'] = isset($resp['message']) ? $resp['message'] : 'Verification failed.';
    $_SESSION['deposit']['raw'] = $raw;
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}

$data = $resp['data'] ?? null;
if (!$data) {
    $_SESSION['deposit']['message'] = 'No transaction data from Paystack.';
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}

$ps_status    = $data['status'] ?? '';
$ps_currency  = strtoupper($data['currency'] ?? 'NGN');
$ps_amount    = isset($data['amount']) ? (float)$data['amount'] / 100.0 : 0.0; // kobo -> NGN
$ps_ref       = $data['reference'] ?? $reference;
$ps_id        = isset($data['id']) ? (string)$data['id'] : null;
$ps_cust_eml  = $data['customer']['email'] ?? 'Paystack';
$ps_fees      = isset($data['fees']) ? (float)$data['fees'] / 100.0 : null; // sometimes present (in kobo)

if ($ps_status !== 'success') {
    $_SESSION['deposit']['message'] = 'Transaction not successful: ' . $ps_status;
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}
if ($ps_currency !== 'NGN') {
    $_SESSION['deposit']['message'] = 'Unsupported currency: ' . $ps_currency;
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}
if ($ps_amount <= 0) {
    $_SESSION['deposit']['message'] = 'Invalid amount from Paystack.';
    header('Location: ../../user/fiat/deposit_success.php'); exit;
}

// 5) Fee determination
// Fallback estimator if Paystack didn't return "fees"
function estimate_paystack_fee(float $amountNgn): float {
    $pct = 0.015;
    $extra = $amountNgn > 2500 ? 100.0 : 0.0;
    $cap = 2000.0;
    $fee = ($amountNgn * $pct) + $extra;
    if ($fee > $cap) $fee = $cap;
    // NOTE: This excludes VAT; if you need to include VAT, adjust here and in front-end preview.
    return round($fee, 2);
}
$feeNgn = ($ps_fees !== null && $ps_fees > 0) ? $ps_fees : estimate_paystack_fee($ps_amount);

// 6) Compute credit amount based on admin setting (and optional mode hint)
$mode = ($mode_hint === 0 || $mode_hint === 1) ? $mode_hint : $add_naira_deposit_fee;

if ($mode === 1) {
    // ADD-ON-TOP: user intended wallet_credit == user_amount_hint
    // We charged ~amount + fee. Credit the intended amount if sensible; otherwise fallback to net.
    $intended = ($user_amount_hint && $user_amount_hint > 0) ? (float)$user_amount_hint : null;
    $netFromPs = max(0.0, $ps_amount - $feeNgn);

    if ($intended !== null && $ps_amount >= $intended) {
        // If Paystack actually charged enough, credit exactly the intended amount
        $credit_amount = $intended;
    } else {
        // safety fallback
        $credit_amount = $netFromPs;
    }
} else {
    // DEDUCT: wallet receives ps_amount - fee
    $credit_amount = max(0.0, $ps_amount - $feeNgn);
}

// 7) Idempotency check by txid (reference)
$sql_check = "SELECT trans_id, applied FROM transactions WHERE txid = ? LIMIT 1";
$stmt = mysqli_prepare($conn, $sql_check);
mysqli_stmt_bind_param($stmt, 's', $ps_ref);
mysqli_stmt_execute($stmt);
$res_check = mysqli_stmt_get_result($stmt);
$existing_tx = mysqli_fetch_assoc($res_check);
mysqli_stmt_close($stmt);

// 8) Apply atomically
mysqli_begin_transaction($conn);

$applied_now = false;
$now = date('Y-m-d H:i:s');

if ($existing_tx && intval($existing_tx['applied']) === 1) {
    $applied_now = false;

} else {
    // Lock wallet row to avoid race
    $sql_lock = "SELECT balance, coin, wallet_add FROM user_wallets WHERE wallet_id = ? FOR UPDATE";
    $stmt = mysqli_prepare($conn, $sql_lock);
    mysqli_stmt_bind_param($stmt, 's', $wallet_id);
    mysqli_stmt_execute($stmt);
    $res_lock = mysqli_stmt_get_result($stmt);
    $lock_row = mysqli_fetch_assoc($res_lock);
    mysqli_stmt_close($stmt);

    if (!$lock_row) {
        mysqli_rollback($conn);
        $_SESSION['deposit']['message'] = 'Wallet lock failed.';
        header('Location: ../../user/fiat/deposit_success.php'); exit;
    }

    $current_balance = (float)$lock_row['balance'];
    $new_balance = $current_balance + $credit_amount;

    if ($existing_tx) {
        // Update existing tx
        $sql_upd = "UPDATE transactions
                    SET coin=?, user_id=?, wallet_id=?, sender_address=?, receiver_address=?, amount=?,
                        type='deposit', confirmation=3, status='success', applied=1, 
                        note=?, updated_at=?
                    WHERE txid=? LIMIT 1";
        $stmt = mysqli_prepare($conn, $sql_upd);
        $sender = $ps_cust_eml;
        $recv   = $lock_row['wallet_add'] ?? '';

        $noteArr = [
          'ps_amount' => $ps_amount,
          'ps_fee'    => $feeNgn,
          'mode'      => $mode === 1 ? 'add' : 'deduct',
        ];
        $noteJson = json_encode($noteArr, JSON_UNESCAPED_SLASHES);

        mysqli_stmt_bind_param($stmt, 'sisssdsss',
            $lock_row['coin'], $user_id_for_tx, $wallet_id, $sender, $recv, $credit_amount, $noteJson, $now, $ps_ref
        );
        if (!mysqli_stmt_execute($stmt)) {
            mysqli_rollback($conn);
            $_SESSION['deposit']['message'] = 'Failed to update transaction.';
            header('Location: ../../user/fiat/deposit_success.php'); exit;
        }
        mysqli_stmt_close($stmt);

    } else {
        // Insert fresh tx
        $sql_ins = "INSERT INTO transactions
            (coin, user_id, wallet_id, sender_address, receiver_address, amount, type, txid, confirmation, status, applied, note, updated_at, created_at)
            VALUES (?, ?, ?, ?, ?, ?, 'deposit', ?, 3, 'success', 1, ?, ?, ?)";
        $stmt = mysqli_prepare($conn, $sql_ins);
        $sender = $ps_cust_eml;
        $recv   = $lock_row['wallet_add'] ?? '';

        $noteArr = [
          'ps_amount' => $ps_amount,
          'ps_fee'    => $feeNgn,
          'mode'      => $mode === 1 ? 'add' : 'deduct',
        ];
        $noteJson = json_encode($noteArr, JSON_UNESCAPED_SLASHES);

        mysqli_stmt_bind_param($stmt, 'sisssdssss',
            $lock_row['coin'], $user_id_for_tx, $wallet_id, $sender, $recv, $credit_amount, $ps_ref, $noteJson, $now, $now
        );
        if (!mysqli_stmt_execute($stmt)) {
            mysqli_rollback($conn);
            $_SESSION['deposit']['message'] = 'Failed to insert transaction.';
            header('Location: ../../user/fiat/deposit_success.php'); exit;
        }
        mysqli_stmt_close($stmt);
    }

    // Credit wallet
    $sql_wu = "UPDATE user_wallets SET balance = ?, updated_at = ? WHERE wallet_id = ? LIMIT 1";
    $stmt = mysqli_prepare($conn, $sql_wu);
    mysqli_stmt_bind_param($stmt, 'dss', $new_balance, $now, $wallet_id);
    if (!mysqli_stmt_execute($stmt)) {
        mysqli_rollback($conn);
        $_SESSION['deposit']['message'] = 'Failed to update wallet balance.';
        header('Location: ../../user/fiat/deposit_success.php'); exit;
    }
    mysqli_stmt_close($stmt);

    mysqli_commit($conn);
    $applied_now = true;

    $_SESSION['deposit']['new_balance'] = $new_balance;
}

// 9) Fill session & redirect to confirmation
$_SESSION['deposit']['ok']        = true;
$_SESSION['deposit']['message']   = 'Deposit verified' . ($applied_now ? ' and applied.' : ' (already applied).');
$_SESSION['deposit']['reference'] = $ps_ref;
$_SESSION['deposit']['amount']    = $credit_amount; // Show credited amount to user
$_SESSION['deposit']['currency']  = $ps_currency;

header('Location: ../../user/fiat/deposit_success.php');
exit;

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


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