AlixFast PerfectPanel v2 — ALL FILES + ALL STEPS + ALL CODE (EASY METHOD)
=====================================================================
EASY METHOD ka matlab: jo file jaisi hai waisi upload karein, sirf
2 jagah haath lagayein:
  (1) config.php mein apni keys + DB details
  (2) SQL mein agar 110 pehle se liya hua ho to free id
Baaki sab files ready-made hain — copy-paste karne ki zaroorat nahi,
zip se seedha upload karein. Neeche har file ka POORA code isliye diya
hai taake aap dekh sakein ke andar kya hai, aur agar File Manager mein
paste karna ho to wahan se bhi kar sakein.

ORDER (is order mein karein, koi step skip na karein):
  1. alixfast-pay/ upload  ->  2. logo upload  ->  3. config edit
  4. SQL run  ->  5. Initiator upload  ->  6. dispatch line
  7. getForm code  ->  8. methods list code  ->  9. twig code
  10. Admin settings  ->  11. Webhook URL  ->  12. PKR 10 test
=====================================================================


======================================================================
STEP 1 — FILE: public_html/alixfast-pay/config.php  (EDIT KAREIN: keys + DB)
======================================================================

<?php
// ==================================================================
// AlixFast PerfectPanel module v2 — SETTINGS ONLY (edit this file)
// No real keys are shipped in this zip. Fill every value from your
// AlixFast merchant panel (Integration page) and your panel config.
// ==================================================================
define('AFX_GATEWAY_URL', 'https://alixfast.com');

// Merchant keys — AlixFast merchant panel -> Integration.
// NEVER ship this file with real keys inside a public zip.
define('AFX_PUBLIC_KEY', 'pk_live_XXXXXXXXXXXX');
define('AFX_SECRET_KEY', 'sk_live_XXXXXXXXXXXXXXXX');

// Panel database — same values as your panel's own config file
// (public_html/app/config.php in most PerfectPanel installs).
define('AFX_DB_HOST', 'localhost');
define('AFX_DB_NAME', 'PANEL_DB_NAME');
define('AFX_DB_USER', 'PANEL_DB_USER');
define('AFX_DB_PASS', 'PANEL_DB_PASSWORD');

// PerfectPanel user table mapping (Gill install: clients / id / balance).
// Change only if your PerfectPanel version uses different names.
define('AFX_USERS_TABLE', 'clients');
define('AFX_USER_ID_COL', 'id');
define('AFX_BALANCE_COL', 'balance');

// Panel payments table (used by the Initiator in panel-integration/).
// Set to '' to skip the panel payments row and use alixfast_payments only.
define('AFX_PANEL_PAYMENTS_TABLE', 'payments');

// Currencies table for PKR -> USD conversion (panel base currency stays USD).
// Set to '' to disable conversion (balance is then credited in paid amount).
define('AFX_CURRENCIES_TABLE', 'currencies');

// Panel base currency. DO NOT change this to PKR — AlixFast payments are
// PAID in PKR and CONVERTED to this currency on credit (see alixfast_lib.php).
define('AFX_BASE_CURRENCY', 'USD');

// Where the user goes after payment (panel Add Funds page).
define('AFX_RETURN_PAGE', '/addfunds');

// Log file — MUST be outside public_html so it is not web-accessible.
// Example: /home/USERNAME/alixfast-callback.log (one level above public_html).
define('AFX_LOG_FILE', __DIR__ . '/../alixfast-callback.log');


======================================================================
STEP 1 — FILE: public_html/alixfast-pay/alixfast_lib.php  (jaisa hai waisa upload)
======================================================================

<?php
// ==================================================================
// AlixFast PerfectPanel module v2 — shared library.
// Fixes over v1 (all proven on Gill SMM Panel, Oct 2026):
//  1. Webhook + return BOTH credit, exactly once (idempotent guard).
//  2. Return page rechecks AlixFast status — a missed webhook no longer
//     strands a verified payment as "pending" forever.
//  3. Verified statuses: "verified" AND "complete" (v1 accepted only "verified").
//  4. Amount guard: credit only if AlixFast amount matches the panel row.
//  5. Order-ref guard: credit only if AlixFast order_ref matches the row.
//  6. PKR -> USD conversion on credit (panel base stays USD, like Ali SMM
//     Panel): paid PKR is converted at the panel currencies-table rate.
//     Stored payment_amount stays the PKR amount paid; only the balance
//     increment is converted. No double conversion (guarded by status).
//  7. Every callback/return outcome is logged (keys/signatures NEVER logged).
//  8. Nested "data" webhook payloads and alternative status fields accepted.
// ==================================================================
require __DIR__ . '/config.php';

function afx_log($msg) {
    $line = '[' . gmdate('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
    @file_put_contents(AFX_LOG_FILE, $line, FILE_APPEND | LOCK_EX);
}

function afx_api($path, $method = 'GET', $body = null) {
    $ch = curl_init(rtrim(AFX_GATEWAY_URL, '/') . $path);
    $opt = [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 25,
            CURLOPT_HTTPHEADER => ['X-AlixFast-Key: ' . AFX_PUBLIC_KEY,
                                   'Authorization: Bearer ' . AFX_SECRET_KEY,
                                   'Content-Type: application/json']];
    if ($method === 'POST') { $opt[CURLOPT_POST] = true; $opt[CURLOPT_POSTFIELDS] = json_encode($body); }
    curl_setopt_array($ch, $opt);
    $r = curl_exec($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($r === false || $code < 200 || $code >= 300) return null;
    return json_decode($r, true);
}

function afx_status($checkoutId) {
    return afx_api('/api/status.php?id=' . (int)$checkoutId);
}

function afx_db() {
    static $pdo = null;
    if ($pdo === null) {
        $pdo = new PDO('mysql:host=' . AFX_DB_HOST . ';dbname=' . AFX_DB_NAME . ';charset=utf8mb4',
                       AFX_DB_USER, AFX_DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
        $pdo->exec("CREATE TABLE IF NOT EXISTS alixfast_payments (
            id INT AUTO_INCREMENT PRIMARY KEY,
            checkout_id BIGINT NOT NULL UNIQUE,
            user_id INT NOT NULL,
            amount DECIMAL(12,4) NOT NULL,
            order_ref VARCHAR(64) NOT NULL DEFAULT '',
            status VARCHAR(12) NOT NULL DEFAULT 'pending',
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    }
    return $pdo;
}

function afx_is_verified($status) {
    return in_array(strtolower((string)$status), ['verified', 'complete'], true);
}
function afx_is_failed($status) {
    return in_array(strtolower((string)$status), ['failed', 'cancelled', 'canceled', 'expired', 'rejected'], true);
}

// PKR -> USD conversion using the panel currencies table.
// Returns converted USD amount, or null if no rate is available.
// Rate columns follow the PerfectPanel currencies table:
//   PKR row: value = PKR per USD (e.g. 277.07944836), inverse = 1/value.
function afx_convert_pkr_to_base($pkrAmount) {
    if (AFX_BASE_CURRENCY === 'PKR' || AFX_CURRENCIES_TABLE === '') return (float)$pkrAmount;
    try {
        $pdo = afx_db();
        $s = $pdo->prepare("SELECT * FROM `" . AFX_CURRENCIES_TABLE . "` WHERE `code` = 'PKR' OR `currency` = 'PKR' LIMIT 1");
        $s->execute(); $row = $s->fetch(PDO::FETCH_ASSOC);
        if (!$row) return null;
        foreach (['inverse', 'rate_to_usd', 'to_usd'] as $col) {
            if (isset($row[$col]) && (float)$row[$col] > 0) return round((float)$pkrAmount * (float)$row[$col], 4);
        }
        if (isset($row['value']) && (float)$row['value'] > 0) return round((float)$pkrAmount / (float)$row['value'], 4);
    } catch (Throwable $e) { afx_log('convert error: ' . $e->getMessage()); }
    return null;
}

// Credit EXACTLY ONCE for a verified checkout.
// $checkoutId = AlixFast numeric checkout id. Returns true only on a fresh credit.
function afx_credit($checkoutId) {
    $pdo = afx_db();
    $s = $pdo->prepare("SELECT * FROM alixfast_payments WHERE checkout_id = ?");
    $s->execute([(int)$checkoutId]); $row = $s->fetch(PDO::FETCH_ASSOC);
    if (!$row || $row['status'] !== 'pending') return false; // already credited/failed

    $st = afx_status((int)$checkoutId);
    if (!$st) { afx_log("credit id={$checkoutId} status_api_failed"); return false; }
    $status = $st['status'] ?? ($st['data']['status'] ?? '');
    if (!afx_is_verified($status)) { afx_log("credit id={$checkoutId} not_verified status={$status}"); return false; }
    // Order-ref guard (when AlixFast returns it).
    $ref = $st['order_ref'] ?? ($st['data']['order_ref'] ?? '');
    if ($ref !== '' && $row['order_ref'] !== '' && $ref !== $row['order_ref']) {
        afx_log("credit id={$checkoutId} order_ref_mismatch"); return false;
    }
    // Amount guard (0.001 tolerance for rounding).
    $gwAmount = (float)($st['amount'] ?? ($st['data']['amount'] ?? 0));
    if ($gwAmount > 0 && abs($gwAmount - (float)$row['amount']) > 0.001) {
        afx_log("credit id={$checkoutId} amount_mismatch gw={$gwAmount} row={$row['amount']}"); return false;
    }
    // Convert PKR paid -> panel base currency (USD by default).
    $creditAmount = afx_convert_pkr_to_base((float)$row['amount']);
    if ($creditAmount === null || $creditAmount <= 0) {
        afx_log("credit id={$checkoutId} no_conversion_rate"); return false;
    }
    $pdo->beginTransaction();
    try {
        $up = $pdo->prepare("UPDATE alixfast_payments SET status='credited' WHERE id = ? AND status='pending'");
        $up->execute([$row['id']]);
        if ($up->rowCount() !== 1) { $pdo->rollBack(); return false; } // someone else credited first
        $sql = "UPDATE `" . AFX_USERS_TABLE . "` SET `" . AFX_BALANCE_COL . "` = `" . AFX_BALANCE_COL . "` + ? WHERE `" . AFX_USER_ID_COL . "` = ?";
        $pdo->prepare($sql)->execute([$creditAmount, $row['user_id']]);
        // Mirror into the panel payments row if one exists for this order_ref.
        if (AFX_PANEL_PAYMENTS_TABLE !== '' && $row['order_ref'] !== '') {
            try {
                $pdo->prepare("UPDATE `" . AFX_PANEL_PAYMENTS_TABLE . "` SET payment_status = 3, payment_delivery = 2 WHERE payment_extra LIKE ? OR payment_note LIKE ?")
                    ->execute(['%' . $row['order_ref'] . '%', '%' . $row['order_ref'] . '%']);
            } catch (Throwable $e) { /* panel schema varies; standalone credit already committed below */ }
        }
        $pdo->commit();
        afx_log("credit id={$checkoutId} credited paid_pkr={$row['amount']} credited_usd={$creditAmount}");
        return true;
    } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); afx_log("credit id={$checkoutId} error: " . $e->getMessage()); return false; }
}

// Shared recheck used by return.php: credit if AlixFast says verified.
// Returns 'credited' | 'already' | 'pending' | 'failed' | 'error'.
function afx_recheck_and_credit($checkoutId) {
    $pdo = afx_db();
    $s = $pdo->prepare("SELECT status FROM alixfast_payments WHERE checkout_id = ?");
    $s->execute([(int)$checkoutId]); $row = $s->fetch(PDO::FETCH_ASSOC);
    if ($row && $row['status'] === 'credited') return 'already';
    if (afx_credit((int)$checkoutId)) return 'credited';
    $st = afx_status((int)$checkoutId);
    $status = $st['status'] ?? ($st['data']['status'] ?? '');
    if (afx_is_failed($status)) return 'failed';
    return $st ? 'pending' : 'error';
}

function afx_alix_id_from_note($note) {
    if (preg_match('/AlixFast:(\d+)/', (string)$note, $m)) return (int)$m[1];
    return 0;
}


======================================================================
STEP 1 — FILE: public_html/alixfast-pay/pay.php  (jaisa hai waisa upload)
======================================================================

<?php
// AlixFast v2 — payment start (user clicks "Pay with AlixFast" on Add Funds).
// Amount limits are read LIVE from the panel paymentmethods row for this
// method — never hardcoded here. Admin changes apply on the next load.
require __DIR__ . '/alixfast_lib.php';
session_start();

$userId = (int)($_SESSION['user_id'] ?? $_SESSION['uid'] ?? 0);
if ($userId <= 0) exit('Please log in first.');

$amount = (float)($_POST['amount'] ?? $_GET['amount'] ?? 0);

// Live limits from paymentmethods (methodCallback = 'alixfast').
$min = 10.0; $max = 100000.0;
try {
    $pdo = afx_db();
    $s = $pdo->query("SELECT methodMin, methodMax FROM paymentmethods WHERE methodCallback = 'alixfast' LIMIT 1");
    if ($s && ($r = $s->fetch(PDO::FETCH_ASSOC))) {
        if ((float)$r['methodMin'] > 0) $min = (float)$r['methodMin'];
        if ((float)$r['methodMax'] > 0) $max = (float)$r['methodMax'];
    }
} catch (Throwable $e) { /* fall back to defaults above */ }

if ($amount < $min || $amount > $max) {
    exit('Amount must be between PKR ' . $min . ' and PKR ' . $max . '.');
}

$ref = 'ALIXFAST-' . time() . '-' . $userId;
$base = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/');
$resp = afx_api('/api/create.php', 'POST', [
    'amount' => number_format($amount, 2, '.', ''),
    'order_ref' => $ref,
    'success_url' => $base . '/return.php?order_ref=' . urlencode($ref),
    'cancel_url' => $base . '/return.php?order_ref=' . urlencode($ref) . '&cancel=1',
]);
if (!$resp || empty($resp['ok']) || empty($resp['id']) || empty($resp['checkout_url'])) {
    afx_log('pay gateway_error ref=' . $ref);
    exit('Gateway error — try again later.');
}
$pdo = afx_db();
$pdo->prepare("INSERT INTO alixfast_payments (checkout_id, user_id, amount, order_ref) VALUES (?, ?, ?, ?)")
    ->execute([(int)$resp['id'], $userId, $amount, $ref]);
header('Location: ' . $resp['checkout_url']);
exit;


======================================================================
STEP 1 — FILE: public_html/alixfast-pay/return.php  (jaisa hai waisa upload)
======================================================================

<?php
// AlixFast v2 — user returns from the hosted checkout.
// NO output is sent before the decision, so redirects always work
// (v1 printed a heading first, which stranded verified payments).
// Flow: already credited -> redirect to Add Funds (credited banner).
//        still pending -> AlixFast recheck; verified -> credit once, redirect.
//        still pending/failed/error -> message page + "Back to Add Funds" link.
require __DIR__ . '/alixfast_lib.php';

$orderRef = (string)($_GET['order_ref'] ?? '');
$checkoutId = 0; $paidPkr = 0.0;
if ($orderRef !== '') {
    $pdo = afx_db();
    $s = $pdo->prepare("SELECT checkout_id, amount, status FROM alixfast_payments WHERE order_ref = ? ORDER BY id DESC LIMIT 1");
    $s->execute([$orderRef]); $row = $s->fetch(PDO::FETCH_ASSOC);
    if ($row) { $checkoutId = (int)$row['checkout_id']; $paidPkr = (float)$row['amount']; }
}
if ($checkoutId > 0) {
    $result = afx_recheck_and_credit($checkoutId);
    if ($result === 'credited' || $result === 'already') {
        $creditedUsd = afx_convert_pkr_to_base($paidPkr);
        $q = http_build_query(['alixfast' => 'credited', 'order_ref' => $orderRef,
                               'paid_pkr' => number_format($paidPkr, 2, '.', ''),
                               'credited_usd' => number_format((float)$creditedUsd, 4, '.', '')]);
        header('Location: ' . AFX_RETURN_PAGE . '?' . $q);
        exit;
    }
    // Pending / failed / error: show the message page (no fake success redirect).
    $detail = $result === 'failed' ? 'Payment failed or cancelled at AlixFast.'
            : ($result === 'error' ? 'Could not verify payment with AlixFast right now — please try again in a moment.'
            : 'Payment is still pending. Your balance will be credited only after AlixFast confirms payment through the webhook.');
    echo '<h1>AlixFast payment</h1><p>' . htmlspecialchars($detail) . '</p>';
    if ($orderRef !== '') echo '<p>Order reference: ' . htmlspecialchars($orderRef) . '</p>';
    echo '<p><a href="' . htmlspecialchars(AFX_RETURN_PAGE) . '">Back to Add Funds</a></p>';
    exit;
}
// No order_ref: fall back to the panel Add Funds page.
header('Location: ' . AFX_RETURN_PAGE);
exit;


======================================================================
STEP 1 — FILE: public_html/alixfast-pay/callback.php  (jaisa hai waisa upload)
======================================================================

<?php
// AlixFast v2 — webhook endpoint. Credit ONLY from here or return.php recheck,
// never from a browser-only success page.
// AlixFast merchant panel -> Integration -> Webhook URL:
//   https://YOURPANEL.com/alixfast-pay/callback.php
require __DIR__ . '/alixfast_lib.php';

$raw = file_get_contents('php://input');
if ($raw === '' || $raw === false) { afx_log('callback empty_payload'); http_response_code(400); exit('empty'); }

$sig = $_SERVER['HTTP_X_ALIXFAST_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', (string)$raw, AFX_SECRET_KEY);
if ($sig === '' || !hash_equals($expected, (string)$sig)) {
    afx_log('callback invalid_signature');
    http_response_code(401); exit('bad signature');
}

$data = json_decode((string)$raw, true) ?: [];
if (isset($data['data']) && is_array($data['data'])) $data = array_merge($data, $data['data']);

$checkoutId = (int)($data['checkout_id'] ?? $data['id'] ?? 0);
$status = $data['status'] ?? ($data['payment_status'] ?? '');
if ($checkoutId <= 0) { afx_log('callback no_checkout_id'); http_response_code(400); exit('no id'); }

if (afx_is_verified($status)) {
    $ok = afx_credit($checkoutId);
    afx_log("callback id={$checkoutId} status={$status} credited=" . ($ok ? 'yes' : 'already_or_guard'));
} elseif (afx_is_failed($status)) {
    afx_log("callback id={$checkoutId} status={$status} ignored_failed");
} else {
    afx_log("callback id={$checkoutId} status={$status} ignored");
}
http_response_code(200);
echo 'ok';


======================================================================
STEP 2 — FILE: public_html/assets/alixfast-logo.png  (logo, binary — zip se upload karein, yahan code nahi)
======================================================================

Logo file zip mein hai: assets/alixfast-logo.png
Upload karein: public_html/assets/alixfast-logo.png
Check: https://YOURPANEL.com/assets/alixfast-logo.png par logo dikhna chahiye.


======================================================================
STEP 4 — SQL: phpMyAdmin mein panel DB par run karein
======================================================================

-- ==================================================================
-- AlixFast PerfectPanel v2 — install SQL (run in phpMyAdmin, panel DB)
-- 1. Check your current highest methodId first:
--      SELECT MAX(methodId) FROM paymentmethods;
--    If 110 is already taken, change every "110" below to a free id
--    AND change the same id in addfunds-Initiator-alixfast.php.
-- 2. This puts AlixFast FIRST (position 1) and shifts others down by 1.
--    If you prefer it last instead, delete the UPDATE line and set
--    methodPosition to (highest position + 1).
-- 3. methodCurrency stays USD (panel base). AlixFast payments are PAID
--    in PKR and CONVERTED to USD on credit (alixfast_lib.php) — do NOT
--    set methodCurrency to PKR unless your whole panel base is PKR.
-- 4. methodMin/methodMax are read LIVE by the form — edit them later
--    in Admin -> Payments -> AlixFast and the form updates automatically.
-- ==================================================================

UPDATE paymentmethods SET methodPosition = methodPosition + 1 WHERE methodPosition >= 1;

INSERT INTO paymentmethods
  (methodId, methodName, methodVisibleName, methodCallback, methodMin, methodMax,
   methodFee, methodBonusPercentage, methodCurrency, methodStatus, methodPosition,
   methodInstructions, methodLogo)
VALUES
  (110, 'AlixFast', 'AlixFast', 'alixfast', 10, 100000,
   0, 0, 'USD', 1, 1,
   'Pay with AlixFast. Balance is credited only after AlixFast confirms payment through the webhook.',
   '/assets/alixfast-logo.png');

-- Verify:
-- SELECT methodId, methodName, methodStatus, methodPosition, methodMin, methodMax
-- FROM paymentmethods WHERE methodId = 110;


======================================================================
STEP 5 — FILE: public_html/app/controller/addfunds/Initiators/alixfast.php
======================================================================

<?php
// ==================================================================
// AlixFast PerfectPanel v2 — Initiator
// Place as: public_html/app/controller/addfunds/Initiators/alixfast.php
// Mirrors the existing stripe.php initiator pattern in most PerfectPanel
// versions. Adjust variable names ($this->user, payment insert fields)
// to your exact version if they differ — the flow is:
//   create PENDING panel payment row -> redirect to alixfast-pay/pay.php
// Credit happens ONLY via alixfast-pay/callback.php (webhook) or
// alixfast-pay/return.php (verified recheck), never here.
// IMPORTANT: change 110 below if you used a different methodId in SQL.
// ==================================================================
if (!defined('BASEPATH')) exit('No direct script access allowed');

class Alixfast extends CI_Controller {
    public function index() {
        $amount = (float)($this->input->post('amount') ?? 0);
        // Live limits from the paymentmethods row (never hardcode).
        $method = $this->db->where('methodId', 110)->get('paymentmethods')->row();
        $min = $method ? (float)$method->methodMin : 10;
        $max = $method ? (float)$method->methodMax : 100000;
        if ($amount < $min || $amount > $max) {
            $this->session->set_flashdata('error', "Amount must be between PKR {$min} and PKR {$max}.");
            redirect('addfunds');
        }
        $ref = 'ALIXFAST-' . time() . '-' . $this->user['id'];
        $this->db->insert('payments', [
            'client_id'       => $this->user['id'],
            'payment_method'  => 110,
            'payment_amount'  => $amount,
            'payment_status'  => 1, // Pending
            'payment_delivery'=> 1,
            'payment_mode'    => 'Automatic',
            'payment_extra'   => $ref,
            'payment_note'    => 'AlixFast:' . $ref,
        ]);
        redirect('/alixfast-pay/pay.php?order_ref=' . urlencode($ref) . '&amount=' . urlencode($amount));
    }
}

AlixFast PerfectPanel v2 — FULL CODE GUIDE (baqi steps, code ke saath)
=====================================================================
Step 1 (alixfast-pay upload), Step 2 (logo) aur Step 4 (SQL) aapko
samajh aa gaye hain — isliye is guide mein sirf baqi steps ka POORA
CODE hai: kahan, kis file mein, kis line ke paas paste karna hai.
Note: PerfectPanel ke versions mein variable/file naam thode alag ho
sakte hain — maujooda stripe/paypal wali lines ko dekh kar usi style
mein adjust karein, logic wahi rahega.

---------------------------------------------------------------------
STEP 3 — config.php KA POORA CODE
File: public_html/alixfast-pay/config.php
Sirf CAPITAL wali jagah apni details dalein, baaki code chhedein nahi.
---------------------------------------------------------------------
<?php
define('AFX_GATEWAY_URL', 'https://alixfast.com');

// AlixFast merchant panel -> Integration se copy karein:
define('AFX_PUBLIC_KEY', 'pk_live_APKI_PUBLIC_KEY');
define('AFX_SECRET_KEY', 'sk_live_APKI_SECRET_KEY');

// Panel ki apni config (public_html/app/config.php) se copy karein:
define('AFX_DB_HOST', 'localhost');
define('AFX_DB_NAME', 'APKI_PANEL_DB');
define('AFX_DB_USER', 'APKI_DB_USER');
define('AFX_DB_PASS', 'APKI_DB_PASSWORD');

// Users table mapping (default Gill setting):
define('AFX_USERS_TABLE', 'clients');
define('AFX_USER_ID_COL', 'id');
define('AFX_BALANCE_COL', 'balance');

define('AFX_PANEL_PAYMENTS_TABLE', 'payments');
define('AFX_CURRENCIES_TABLE', 'currencies');
define('AFX_BASE_CURRENCY', 'USD');   // PKR mein change NA karein
define('AFX_RETURN_PAGE', '/addfunds');

// Log file — public_html ke BAHAR (hosting home folder mein):
define('AFX_LOG_FILE', '/home/APKA_HOSTING_USERNAME/alixfast-callback.log');
?>
Check: callback.php kholne par "bad signature" aaye to config load ho raha hai.

---------------------------------------------------------------------
STEP 5 — INITIATOR KA POORA CODE
File: public_html/app/controller/addfunds/Initiators/alixfast.php
(File ka naam sirf "alixfast.php" rakhein.)
---------------------------------------------------------------------
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');

class Alixfast extends CI_Controller {
    public function index() {
        $amount = (float)($this->input->post('amount') ?? 0);
        $method = $this->db->where('methodId', 110)->get('paymentmethods')->row();
        $min = $method ? (float)$method->methodMin : 10;
        $max = $method ? (float)$method->methodMax : 100000;
        if ($amount < $min || $amount > $max) {
            $this->session->set_flashdata('error', "Amount must be between PKR {$min} and PKR {$max}.");
            redirect('addfunds');
        }
        $ref = 'ALIXFAST-' . time() . '-' . $this->user['id'];
        $this->db->insert('payments', [
            'client_id'        => $this->user['id'],
            'payment_method'   => 110,
            'payment_amount'   => $amount,
            'payment_status'   => 1, // Pending
            'payment_delivery' => 1,
            'payment_mode'     => 'Automatic',
            'payment_extra'    => $ref,
            'payment_note'     => 'AlixFast:' . $ref,
        ]);
        redirect('/alixfast-pay/pay.php?order_ref=' . urlencode($ref) . '&amount=' . urlencode($amount));
    }
}
?>
Agar Step 4 mein methodId 110 ki jagah koi aur id li hai to is file
mein 110 ki jagah wohi id likhein (2 jagah: where() aur payment_method).

---------------------------------------------------------------------
STEP 6 — DISPATCH LINE (addfunds.php)
File: public_html/app/controller/addfunds.php
Jahan baaki methods ki lines hain, wahan ye line lagayein:
---------------------------------------------------------------------
// Stripe/PayPal wali lines ke saath, unke baad:
elseif ($methodId == 110) {
    $this->load->controller('addfunds/Initiators/alixfast');
}
Agar aapke version mein switch-case hai to wahan ye case lagayein:
    case 110:
        $this->load->controller('addfunds/Initiators/alixfast');
        break;
Check: Add Funds form mein AlixFast select karke submit karein —
Initiator wali pending row payments table mein banani chahiye.

---------------------------------------------------------------------
STEP 7 — GETFORM CODE (getForm.php)
File: public_html/app/controller/addfunds/getForm.php
Method 110 ke liye ye branch lagayein:
---------------------------------------------------------------------
if ($methodId == 110) {
    $method = $this->db->where('methodId', 110)->get('paymentmethods')->row();
    $min = $method ? (float)$method->methodMin : 10;
    $max = $method ? (float)$method->methodMax : 100000;
    echo '
    <div class="form-group">
        <label>Amount (PKR)</label>
        <input type="number" name="amount" class="form-control"
               min="' . $min . '" max="' . $max . '" required
               placeholder="Enter amount in PKR">
        <small class="form-text text-muted">
            Amount is entered in PKR only. It is converted to USD at the
            panel rate when credited.
            Minimum: PKR ' . $min . ' | Maximum: PKR ' . $max . '
        </small>
    </div>
    <button type="submit" class="btn btn-primary btn-block">Initiate Payment</button>';
}
Agar aapka getForm JSON return karta hai to wahi fields JSON mein dein:
  {"label":"Amount (PKR)","min":10,"max":100000,
   "note":"Amount is entered in PKR only. It is converted to USD at the panel rate when credited."}
Check: form mein "Amount (PKR)" aur min/max note dikhna chahiye.

---------------------------------------------------------------------
STEP 8 — METHODS LIST MEIN MIN/MAX (addfunds.php)
Ye v1 ka asal bug tha: list mein min/max tha hi nahi.
Maujooda methods wali loop mein ye 4 fields ADD karein:
---------------------------------------------------------------------
// PEHLE (v1 — ghalat, min/max missing):
//   $methods[] = ['id'=>$row->methodId, 'name'=>$row->methodName,
//                  'instructions'=>$row->methodInstructions, 'fee'=>$row->methodFee];

// AB (v2 — sahi):
$methods[] = [
    'id'           => $row->methodId,
    'name'         => $row->methodName,
    'instructions' => $row->methodInstructions,
    'fee'          => $row->methodFee,
    'min'          => (float)$row->methodMin,
    'max'          => (float)$row->methodMax,
    'methodMin'    => (float)$row->methodMin,
    'methodMax'    => (float)$row->methodMax,
];
Template (addfunds.twig) mein form note is tarah banayein:
  Minimum: PKR {{ method.min }} | Maximum: PKR {{ method.max }}
Check: Admin mein min badlein, form reload karein — naya min turant
aana chahiye, file dobara edit kiye baghair.

---------------------------------------------------------------------
STEP 9 — ADDFUNDS.TWIG KA CODE (Quick Card + History + Banner)
File: public_html/app/views/<THEME>/addfunds.twig
---------------------------------------------------------------------
(a) QUICK DEPOSIT CARD — sab se pehle, baaki cards se pehle:
<div class="quick-method" data-method-id="110"
     onclick="selectPaymentMethod(110)">
    <img src="/assets/alixfast-logo.png" alt="AlixFast" style="height:32px;">
    <span>AlixFast</span>
</div>
<!-- Agar aapke theme mein quick cards kisi aur function se select hote
     hain to wahi function use karein — bas data-method-id="110" hona
     chahiye, taake dropdown mein AlixFast select ho aur form load ho. -->

(b) RETURN BANNER — Add Funds page ke top par, form se pehle:
{% if app.request.get('alixfast') == 'credited' %}
<div class="alert alert-success">
    Paid PKR {{ app.request.get('paid_pkr') }} &rarr;
    credited ${{ app.request.get('credited_usd') }} (converted to USD).
</div>
{% endif %}

(c) USER HISTORY — sirf Successful Credit rows (loop mein filter):
<table>
  <thead><tr>
    <th>ID</th><th>DATE</th><th>METHOD</th><th>AMOUNT</th>
    <th>TRANSACTION ID</th><th>STATUS</th>
  </tr></thead>
  <tbody>
  {% for payment in payments %}
    {% if payment.payment_status == 3 and payment.payment_delivery == 2 %}
    <tr>
      <td>{{ payment.payment_id }}</td>
      <td>{{ payment.payment_created_at }}</td>
      <td>{{ payment.methodName }}</td>
      <td>PKR {{ payment.payment_amount }}</td>
      <td>
        {# Transaction ID: "AlixFast:38" note se 38 nikalna #}
        {{ payment.payment_note|replace({'AlixFast:': ''}) }}
      </td>
      <td>Successful Credit</td>
    </tr>
    {% endif %}
  {% else %}
    <tr><td colspan="6">No successful credits found.</td></tr>
  {% endfor %}
  </tbody>
</table>
Zaroori: loop ke andar if lagayein — Pending/Failed rows render hi
na hon, CSS se chhupana kaafi nahi. Agar aapka template status ke
liye alag field use karta hai to wahi field check karein
(3 = Completed/Successful, 1 = Pending).
Admin ki history file edit NA karein — wahan sab kuch waisa hi rahe.

---------------------------------------------------------------------
STEP 10 — ADMIN SETTINGS CODE (keys + webhook field)
File: Admin -> Payments -> payment settings (method 37 pattern follow karein)
---------------------------------------------------------------------
// 1. Automatic methods wali list mein 110 add karein:
//    $automatic_methods = [..., 37, 110];

// 2. AlixFast edit dialog mein ye 3 fields dikhayein:
//    - Public Key  (input, naam: public_key)
//    - Secret Key  (input, naam: secret_key)
//    - Webhook URL (read-only input, value neeche wali line se):
<input type="text" readonly
       value="https://YOURPANEL.com/alixfast-pay/callback.php">

// 3. Save hone par methodExtras mein is tarah save hon:
//    {"public_key":"pk_live_...","secret_key":"sk_live_..."}

// 4. alixfast-pay/config.php mein keys parhne ka order ye rakhein:
//    PEHLE Admin methodExtras, PHIR constants (fallback):
//    (Is ke liye config.php ke top par ye snippet lagayein — sirf tab
//     jab aapke panel mein methodExtras kaam karta ho, warna constants
//     hi kaafi hain.)
$extras = json_decode($row->methodExtras ?? '{}', true);
if (!empty($extras['public_key']))  define('AFX_PUBLIC_KEY',  $extras['public_key']);
if (!empty($extras['secret_key']))  define('AFX_SECRET_KEY',  $extras['secret_key']);
Check: Admin mein key badal kar save karein, phir dobara edit kholne
par nayi key dikhni chahiye — aur payment bhi nayi key se chalni chahiye.

---------------------------------------------------------------------
STEP 11 — WEBHOOK URL (code nahi, setting hai)
AlixFast merchant panel -> Integration -> Webhook URL mein ye paste karein:
  https://YOURPANEL.com/alixfast-pay/callback.php
Ye step chhoota to webhook kabhi nahi aayega — payment hamesha
pending rahegi, chahe baaki sab kuch sahi ho.

---------------------------------------------------------------------
STEP 12 — TEST KA CHECKLIST (code nahi, verification hai)
[ ] Quick Deposit mein AlixFast pehle, logo ke saath
[ ] Form mein "Amount (PKR)" + min/max note
[ ] PKR 10 payment -> checkout par Total PKR mein
[ ] Wapas Add Funds par green banner: Paid PKR 10 -> credited $0.0361
      (rate panel ki currencies table se aata hai, thoda farq normal hai)
[ ] History mein EK Successful Credit row (PKR, AlixFast, Transaction ID)
[ ] Wahi return URL dobara kholne par balance DOBARA na barhe
[ ] Agar balance na barhe: AFX_LOG_FILE check karein, phir Step 11 check karein
---------------------------------------------------------------------
WARNINGS (dobara, kyunke yehi ghaltiyan hui thin):
- Theme editor "Forbidden" de to File Manager se edit karein.
- config.php kabhi share na karein, public zip mein real keys na dalein.
- Credit sirf webhook ya verified return recheck se — browser success
  page se kabhi balance add na karein.
- Purani pending rows haath se credit na karein jab tak AlixFast se
  verified + order_ref + amount match na ho.


=====================================================================
END — Yehi poora package hai. Koi file missing nahi hai.
Zip mein ye sab files alag-alag bhi maujood hain, taake aapko code
copy-paste na karna pare — seedha upload karein.
=====================================================================
