Remove index.php CodeIgniter
August 25, 2025 · AI Conversations
http://example.com/index.php/controller/method
To remove `index.php` from the URL, you need to do two things:
—
### **1. Update `config.php`**
Open `application/config/config.php` (CodeIgniter 3) or `app/Config/App.php` (CodeIgniter 4) and set:
$config['index_page'] = '';
For CI4:
public string $indexPage = '';
—
### **2. Add `.htaccess` file in your project root**
If using **Apache**, create an `.htaccess` file in your root (where `index.php` is) with this content:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Redirect to non-www (optional)
# RewriteCond %{HTTP_HOST} ^www.(.*)$ [NC]
# RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
# Send everything to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
</IfModule>
—
### **3. Enable mod_rewrite in Apache**
Run in terminal (if not already enabled):
a2enmod rewrite
systemctl restart apache2
—
### **4. Allow overrides in Apache config**
Edit your Apache site config (`/etc/apache2/sites-available/000-default.conf` or your vhost):
<Directory /var/www/html>
AllowOverride All
</Directory>
Then restart Apache:
systemctl restart apache2
—
✅ Now your URLs will look like:
http://example.com/controller/method
—
Do you want me to give you the **exact `.htaccess`** depending on whether you’re using **CodeIgniter 3** or **CodeIgniter 4**?
# Manual editing of this file may result in unexpected behavior.
# To make changes to this file, use the cPanel MultiPHP INI Editor (Home >> Software >> MultiPHP INI Editor)
# For more information, read our documentation (https://go.cpanel.net/EA4ModifyINI)
# END cPanel-generated php ini directives, do not edit
# BEGIN URL Rewriting
RewriteEngine On
# Removes index.php from URLs
RewriteCond %{THE_REQUEST} ^GET.*index.php [NC]
RewriteRule (.*?)index.php/*(.*) /$1$2 [R=301,L]
# Directs all web requests through the site index file
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
# END URL Rewriting
Below are **drop‑in .htaccess** snippets you can use on cPanel, depending on your CodeIgniter version and whether the app lives in a subfolder.
—
## CodeIgniter 3 (root or subfolder)
**Also set** `application/config/config.php` →
`$config['index_page'] = '';`
**.htaccess (root of the CI app):**
# BEGIN cPanel-generated php ini directives, do not edit
# (leave your cPanel lines as-is)
# END cPanel-generated php ini directives, do not edit
# BEGIN URL Rewriting
<IfModule mod_rewrite.c>
RewriteEngine On
# If your app is in a subfolder, change to /your-subfolder/
RewriteBase /
# Redirect /index.php/... to /...
RewriteCond %{THE_REQUEST} s/+index.php[?/ ] [NC]
RewriteRule ^index.php/?(.*)$ /$1 [R=301,L]
# Front controller: send everything to index.php, except real files/dirs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
</IfModule>
# END URL Rewriting
> If the app is in a subfolder (e.g., `example.com/app/`), set `RewriteBase /app/`.
—
## CodeIgniter 4 (served from **public/**)
**Also set** `app/Config/App.php` →
`public string $indexPage = '';`
**If your domain points to the app root and NOT directly to /public, use this at the project root**:
# BEGIN cPanel-generated php ini directives, do not edit
# (leave your cPanel lines as-is)
# END cPanel-generated php ini directives, do not edit
# BEGIN URL Rewriting
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Redirect /index.php/... to clean URL
RewriteCond %{THE_REQUEST} s/+index.php[?/ ] [NC]
RewriteRule ^index.php/?(.*)$ /$1 [R=301,L]
# CI4 front controller is public/index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ public/index.php?/$1 [L,QSA]
</IfModule>
# END URL Rewriting
**If your hosting lets you set the DocumentRoot to `/public`,** then put this simpler file inside `/public/.htaccess` and point the domain there:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Clean /index.php
RewriteCond %{THE_REQUEST} s/+index.php[?/ ] [NC]
RewriteRule ^index.php/?(.*)$ /$1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L,QSA]
</IfModule>
—
### Common gotchas to check
– **Apache overrides:** In cPanel, ensure your vhost/htaccess allows `AllowOverride All` (usually already true).
– **mod_rewrite enabled:** Typically enabled on cPanel. If not, ask support to enable.
– **Cache:** Clear browser cache after changing rewrite rules to avoid stale redirects.
– **HTTPS/WWW preferences:** Add your preferred canonical redirect *above* the front‑controller rules if needed.
If you tell me your CI version (3 or 4) and whether it’s in a subfolder, I’ll tailor the exact file for your setup.
this in staging/api/api.php
fossets_db.php in staging/includes
{
"status": "error",
"message": "No such file or directory",
"trace": "#0 /home/fossetts/public_html/staging/includes/fossets_db.php(28): mysqli->__construct('localhost', 'fossetts_ct_adm', Object(SensitiveParameterValue), 'fossetts_ticket…', 3306, '/Applications/M…')n#1 /home/fossetts/public_html/staging/includes/fossets_db.php(371): Fossets_db->connect()n#2 /home/fossetts/public_html/staging/api/events.php(23): Fossets_db->getUpcomingEvents()n#3 {main}"
}
The error is from **MySQL**, not the require. Your stack trace shows mysqli trying to use a **Mac socket path**:
mysqli->__construct('localhost', 'fossetts_ct_adm', ******, 'fossetts_ticket...', 3306, '/Applications/M...')
On cPanel/Linux that socket doesn’t exist → “No such file or directory”.
### Quick fix (most reliable on cPanel)
Force TCP and **remove the socket argument**:
// in fossets_db.php (where you connect)
$host = '127.0.0.1'; // NOT 'localhost' → forces TCP
$user = 'fossetts_ct_adm';
$pass = '...';
$db = 'fossetts_ticket...';
$port = 3306;
$mysqli = new mysqli($host, $user, $pass, $db, $port); // no socket arg
if ($mysqli->connect_errno) {
throw new Exception('DB connect failed: ' . $mysqli->connect_error);
}
Why this works: `localhost` makes PHP try the Unix socket; `127.0.0.1` uses TCP and ignores any wrong socket path.
### If you really want to use a socket
Only pass a socket if it exists on the server:
$socket = ini_get('mysqli.default_socket'); // e.g. /var/lib/mysql/mysql.sock or /tmp/mysql.sock
if ($socket && file_exists($socket)) {
$mysqli = new mysqli('localhost', $user, $pass, $db, 3306, $socket);
} else {
$mysqli = new mysqli('127.0.0.1', $user, $pass, $db, 3306);
}
### Sanity checks
– Keep your `require_once(__DIR__ . '/../includes/fossets_db.php');`
– Add a quick guard once:
`if (!file_exists(__DIR__ . '/../includes/fossets_db.php')) die('includes missing');`
– Confirm DB creds in cPanel → MySQL® Databases (user, db name, and that the user is added to the DB).
– If your host uses a **remote DB hostname** (not localhost), use that instead of `127.0.0.1`.
Make that one-line change (`localhost` → `127.0.0.1` and drop the socket param) and you should be good.
1) You’re forcing a **Mac/MAMP socket** on Linux cPanel → MySQL can’t find it.
2) You’re mixing one `connect()` method with many direct `new mysqli(…)` calls that **bypass** your staging/production settings.
Here’s a clean, drop‑in fix you can paste over your class. It:
– Auto‑selects **remote vs local** without ever using the MAMP socket on Linux.
– Forces **TCP** (`127.0.0.1` or remote host) so no bad socket paths.
– Makes **every DB call** go through one `connect()` (no stray `new mysqli`).
– Fixes the stray **PDO** usage in `orderExistsByFingerprint`.
> Replace only credentials/DB names to your real ones (I left placeholders where sensible).
<?php
class Fossets_db {
/*** === CONFIG (edit these as needed) === ***/
// Remote (production/staging) – use this on cPanel
private $remote = [
'host' => 'server3.thenet.ie', // or the host your DB uses
'user' => 'fossetts_ct_adm',
'pass' => 'YOUR_SECURE_PASSWORD',
'name' => 'fossetts_circus2025',
'port' => 3306,
];
// Local dev (MAMP/XAMPP) – optional
private $local = [
'host' => '127.0.0.1', // use TCP, not 'localhost'
'user' => 'fossetts_ct_adm',
'pass' => 'YOUR_LOCAL_PASSWORD',
'name' => 'fossetts_tickets_staging',
'port' => 3306,
// 'socket' => '/Applications/MAMP/tmp/mysql/mysql.sock', // we won't force this
];
// Simple environment toggle: if MAMP socket exists, assume local dev; else remote
private function isLocalDev(): bool {
return file_exists('/Applications/MAMP/tmp/mysql/mysql.sock');
}
private $log_file;
public function __construct() {
$this->log_file = __DIR__ . '/orders-debug.log';
}
/*** === SINGLE CONNECTION POINT === ***/
private function connect(): mysqli {
// Pick config
$cfg = $this->isLocalDev() ? $this->local : $this->remote;
// Always use TCP to avoid wrong sockets
$host = ($cfg['host'] === 'localhost') ? '127.0.0.1' : $cfg['host'];
$user = $cfg['user'];
$pass = $cfg['pass'];
$name = $cfg['name'];
$port = (int)$cfg['port'];
$this->log("DB connect to {$host}:{$port} / {$name} (env=" . ($this->isLocalDev() ? 'local' : 'remote') . ")");
// Optional: try a known socket only if it actually exists
$socket = ini_get('mysqli.default_socket');
if ($socket && @file_exists($socket)) {
$mysqli = @new mysqli($host, $user, $pass, $name, $port, $socket);
} else {
$mysqli = @new mysqli($host, $user, $pass, $name, $port);
}
if ($mysqli->connect_errno) {
$this->log("DB connect failed ({$mysqli->connect_errno}): {$mysqli->connect_error}");
throw new Exception('Database connection failed: ' . $mysqli->connect_error);
}
// Use named timezone (safer if MySQL has time zone tables); if not, ignore errors
@$mysqli->query("SET time_zone = 'Europe/Dublin'");
return $mysqli;
}
/*** === LOG === ***/
private function log($message) {
$date = date('Y-m-d H:i:s');
@file_put_contents($this->log_file, "[$date] $messagen", FILE_APPEND);
}
/*** === QUERIES === ***/
function getEventData($event_id, $event_date_id = null) {
try {
$this->log("getEventData: event_id={$event_id}, event_date_id={$event_date_id}");
$mysqli = $this->connect();
$stmt_event = $mysqli->prepare("SELECT * FROM events WHERE event_id = ?");
$stmt_event->bind_param('i', $event_id);
$stmt_event->execute();
$res_event = $stmt_event->get_result();
$data = new stdClass();
$data->event = $res_event->fetch_assoc();
if ($event_date_id) {
$stmt_dates = $mysqli->prepare("SELECT * FROM events_dates WHERE event_date_id = ? AND event_id = ?");
$stmt_dates->bind_param('ii', $event_date_id, $event_id);
} else {
$stmt_dates = $mysqli->prepare("SELECT * FROM events_dates WHERE event_id = ? AND event_datetime > NOW() ORDER BY event_datetime ASC");
$stmt_dates->bind_param('i', $event_id);
}
$stmt_dates->execute();
$result_dates = $stmt_dates->get_result();
$data->event_dates = $result_dates->fetch_all(MYSQLI_ASSOC);
// Per-showtime extra data
$ist = new DateTimeZone('Europe/Dublin');
foreach ($data->event_dates as $k => $date) {
$edid = (int)$date['event_date_id'];
$showtime_dt = new DateTime($date['event_datetime'], $ist);
$now_dt = new DateTime('now', $ist);
$minutes_until_show = ($showtime_dt->getTimestamp() - $now_dt->getTimestamp()) / 60;
$data->event_dates[$k]['booking_closed'] = $minutes_until_show <= 30 && $minutes_until_show > 0;
$data->event_dates[$k]['minutes_until_show'] = $minutes_until_show;
$data->event_dates[$k]['showtime_timestamp'] = $showtime_dt->getTimestamp();
// capacities
$stmt_caps = $mysqli->prepare("SELECT seating_area, capacity FROM event_capacities WHERE event_date_id = ?");
$stmt_caps->bind_param('i', $edid);
$stmt_caps->execute();
$data->event_dates[$k]['capacities'] = $stmt_caps->get_result()->fetch_all(MYSQLI_ASSOC);
// ticket definitions
$stmt_defs = $mysqli->prepare("SELECT * FROM event_ticket_definitions WHERE event_date_id = ?");
$stmt_defs->bind_param('i', $edid);
$stmt_defs->execute();
$data->event_dates[$k]['ticket_definitions'] = $stmt_defs->get_result()->fetch_all(MYSQLI_ASSOC);
}
// vouchers (hashed codes)
$stmt_v = $mysqli->prepare("SELECT SHA1(voucher_code) AS voucher_code, voucher_discount, voucher_discount_type FROM vouchers WHERE event_id = ? AND voucher_uses != 0");
$stmt_v->bind_param('i', $event_id);
$stmt_v->execute();
$data->event_vouchers = json_encode($stmt_v->get_result()->fetch_all(MYSQLI_ASSOC));
$mysqli->close();
return $data;
} catch (Exception $e) {
$this->log('getEventData error: ' . $e->getMessage());
throw $e;
}
}
function getEventDateData($event_date_id, $ticket_def_id, $num_seats) {
$mysqli = $this->connect();
$stmt_def = $mysqli->prepare("SELECT * FROM event_ticket_definitions WHERE id = ?");
$stmt_def->bind_param('i', $ticket_def_id);
$stmt_def->execute();
$ticket_def = $stmt_def->get_result()->fetch_assoc();
if (!$ticket_def) { $mysqli->close(); return false; }
$seating_area = $ticket_def['seating_area'];
$seats_required = $num_seats * (int)$ticket_def['seats_per_ticket'];
$stmt_cap = $mysqli->prepare("SELECT capacity FROM event_capacities WHERE event_date_id = ? AND seating_area = ?");
$stmt_cap->bind_param('is', $event_date_id, $seating_area);
$stmt_cap->execute();
$row_cap = $stmt_cap->get_result()->fetch_assoc();
$total_capacity = $row_cap ? (int)$row_cap['capacity'] : 0;
$stmt_sold = $mysqli->prepare("SELECT SUM(o.ticket_num_child + o.ticket_num_adult + o.ticket_num_senior) AS sold_seats FROM orders o WHERE o.event_date_id = ? AND o.ticket_location = ?");
$stmt_sold->bind_param('is', $event_date_id, $seating_area);
$stmt_sold->execute();
$row_sold = $stmt_sold->get_result()->fetch_assoc();
$seats_sold = $row_sold ? (int)$row_sold['sold_seats'] : 0;
$mysqli->close();
return (($total_capacity - $seats_sold) >= $seats_required) ? $ticket_def : false;
}
function getVoucherData($event_id, $voucher_code) {
$mysqli = $this->connect();
$query = "SELECT * FROM vouchers
WHERE event_id = ?
AND LOWER(voucher_code) = LOWER(?)
AND (voucher_uses > 0 OR voucher_uses = -1)
LIMIT 1";
$stmt = $mysqli->prepare($query);
$stmt->bind_param('is', $event_id, $voucher_code);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc() ?: [];
$mysqli->close();
return $row;
}
function insertOrder($order_info) {
$mysqli = $this->connect();
$insert_result = false;
$this->log("insertOrder payload: " . print_r($order_info, true));
$voucher_data = $order_info->order_voucher ?? null;
$order_info->order_voucher = (!empty($voucher_data)) ? ($voucher_data['voucher_code'] ?? '') : '';
try {
$mysqli->autocommit(false);
$stmt = $mysqli->prepare("
INSERT INTO orders
(order_datetime, order_code, customer_name, customer_email, customer_phone,
event_id, event_date_id, event_venue, event_datetime, ticket_location,
ticket_num_price_full, ticket_num_price_child, ticket_num_price_senior,
ticket_addons, order_voucher, order_total, order_payment, ticket_breakdown,
wheelchair_user_count, order_fingerprint)
VALUES
(NOW(), ?, ?, ?, ?,
?, ?, ?, ?, ?,
?, ?, ?,
?, ?, ?, ?, ?,
?, ?)
");
$ticket_breakdown = $order_info->ticket_breakdown ?? '';
$wheelchair = (int)($order_info->wheelchair_user_count ?? 0);
$stmt->bind_param(
'ssssiiisssiiisssssiss',
$order_info->order_code,
$order_info->customer_name,
$order_info->customer_email,
$order_info->customer_phone,
$order_info->event_id,
$order_info->event_date_id,
$order_info->event_venue,
$order_info->event_datetime,
$order_info->ticket_location,
$order_info->ticket_num_price_full,
$order_info->ticket_num_price_child,
$order_info->ticket_num_price_senior,
$order_info->ticket_addons,
$order_info->order_voucher,
$order_info->order_total,
$order_info->order_payment,
$ticket_breakdown,
$wheelchair,
$order_info->order_fingerprint
);
if (!$stmt->execute()) {
throw new Exception("Order insert failed: " . $stmt->error);
}
// Voucher decrement
if (!empty($voucher_data) && !empty($voucher_data['voucher_id']) && (int)$voucher_data['voucher_uses'] > 0) {
$stmtV = $mysqli->prepare("UPDATE vouchers SET voucher_uses = voucher_uses - 1 WHERE voucher_id = ?");
$stmtV->bind_param('i', $voucher_data['voucher_id']);
if (!$stmtV->execute()) {
throw new Exception("Voucher update failed: " . $stmtV->error);
}
}
$mysqli->commit();
$insert_result = true;
} catch (Exception $e) {
$this->log("insertOrder exception: " . $e->getMessage());
$mysqli->rollback();
throw $e;
} finally {
$mysqli->autocommit(true);
$mysqli->close();
}
return $insert_result;
}
function getUpcomingEvents($limit = false) {
try {
$mysqli = $this->connect();
$sql = "SELECT e.*, ed.event_date_id, ed.event_datetime, ed.showtime_status
FROM events e
INNER JOIN events_dates ed ON e.event_id = ed.event_id
WHERE e.status = 'published' AND ed.event_datetime > NOW()
ORDER BY e.event_id ASC, ed.event_datetime ASC";
if ($limit && is_numeric($limit)) {
$sql .= " LIMIT " . intval($limit);
}
$res = $mysqli->query($sql);
if (!$res) throw new Exception('Error fetching upcoming events: ' . $mysqli->error);
$events = [];
$ist = new DateTimeZone('Europe/Dublin');
while ($row = $res->fetch_assoc()) {
$eid = (int)$row['event_id'];
if (!isset($events[$eid])) {
$events[$eid] = [
'event_id' => $row['event_id'],
'venue_title' => $row['venue_title'],
'venue_full' => $row['venue_full'],
'area' => $row['area'],
'logo' => $row['logo'],
'dates' => []
];
}
$showtime_dt = new DateTime($row['event_datetime'], $ist);
$now_dt = new DateTime('now', $ist);
$minutes_until_show = ($showtime_dt->getTimestamp() - $now_dt->getTimestamp()) / 60;
$events[$eid]['dates'][] = [
'event_date_id' => $row['event_date_id'],
'event_datetime' => $row['event_datetime'],
'showtime_status' => $row['showtime_status'] ?? 'active',
'booking_closed' => $minutes_until_show <= 30 && $minutes_until_show > 0,
'minutes_until_show' => $minutes_until_show,
'showtime_timestamp' => $showtime_dt->getTimestamp()
];
}
$mysqli->close();
return array_values($events);
} catch (Exception $e) {
$this->log("getUpcomingEvents error: " . $e->getMessage());
throw $e;
}
}
function insertEvent($event_slug, $event_venue, $event_venue_full, $dates) {
$mysqli = $this->connect();
$ok = true;
try {
$mysqli->autocommit(false);
$stmtE = $mysqli->prepare("INSERT INTO events (slug, venue_title, venue_full) VALUES (?, ?, ?)");
$stmtE->bind_param('sss', $event_slug, $event_venue, $event_venue_full);
if (!$stmtE->execute()) throw new Exception($stmtE->error);
$event_id = $mysqli->insert_id;
$stmtD = $mysqli->prepare("INSERT INTO events_dates (event_id, event_datetime) VALUES (?, ?)");
foreach ($dates as $d) {
$stmtD->bind_param('is', $event_id, $d);
if (!$stmtD->execute()) { $ok = false; break; }
}
if ($ok) $mysqli->commit(); else $mysqli->rollback();
} catch (Exception $e) {
$ok = false;
$mysqli->rollback();
}
$mysqli->autocommit(true);
$mysqli->close();
return $ok;
}
function getOrders($event_date_id) {
$mysqli = $this->connect();
$stmt = $mysqli->prepare("SELECT * FROM orders WHERE event_date_id = ?");
$stmt->bind_param('i', $event_date_id);
$stmt->execute();
$rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$mysqli->close();
return $rows ?: [];
}
function getOrder($order_id) {
$mysqli = $this->connect();
$stmt = $mysqli->prepare("SELECT * FROM orders WHERE order_id = ?");
$stmt->bind_param('i', $order_id);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc() ?: [];
$mysqli->close();
return $row;
}
function getOrderByCode($order_code) {
$mysqli = $this->connect();
$stmt = $mysqli->prepare("SELECT * FROM orders WHERE order_code = ?");
$stmt->bind_param('s', $order_code);
$stmt->execute();
$row = $stmt->get_result()->fetch_assoc() ?: [];
$mysqli->close();
return $row;
}
public function real_escape_string($string) {
$mysqli = $this->connect();
$escaped = $mysqli->real_escape_string($string ?? '');
$mysqli->close();
return $escaped;
}
function get_all_addons() {
$mysqli = $this->connect();
$res = $mysqli->query("SELECT * FROM addons ORDER BY id ASC");
if (!$res) { $err = $mysqli->error; $mysqli->close(); throw new Exception('Failed to get add-ons: ' . $err); }
$addons = [];
while ($row = $res->fetch_object()) {
if (!empty($row->thumb)) {
$thumb = ltrim($row->thumb, "@ tnr x0B");
$row->thumb = (strpos($thumb, '/elements/') === 0) ? $thumb : '/elements/' . ltrim($thumb, '/');
}
$addons[] = $row;
}
$mysqli->close();
return $addons;
}
// Fixed: use mysqli instead of undefined PDO handle
public function orderExistsByFingerprint(string $fingerprint): bool {
try {
$mysqli = $this->connect();
$stmt = $mysqli->prepare("SELECT 1 FROM orders WHERE order_fingerprint = ? LIMIT 1");
$stmt->bind_param('s', $fingerprint);
$stmt->execute();
$exists = (bool)$stmt->get_result()->fetch_row();
$mysqli->close();
return $exists;
} catch (Exception $e) {
$this->log("orderExistsByFingerprint error: " . $e->getMessage());
return false;
}
}
}
?>
### Why this fixes your error
– Your trace showed PHP trying to open a **Mac socket path** on Linux. By forcing **TCP** and removing the hardcoded socket, the connection works on cPanel.
– All methods now share the same **env‑aware** connection logic.
– The PDO call is gone (it referenced an undefined `$this->db`).
If you want to keep your current file and only do the **minimal change**, do this in your existing class:
– Change `$db_host = 'localhost'` → `'127.0.0.1'`
– **Remove** the `$db_socket` from the `new mysqli(…)` call in `connect()`.
– Replace every `new mysqli(…)` in other methods with `$this->connect()` (and close when done).
– Fix `orderExistsByFingerprint()` to use `mysqli` as shown above.
That’s it.
at $_ (stripe.js:1:580766)
at new e (stripe.js:1:583618)
at ah (stripe.js:1:639807)
at initializeStripe (buy-tickets.js?ver=1.10.10:1069:18)
at goToStep (buy-tickets.js?ver=1.10.10:877:13)
at buy-tickets.js?ver=1.10.10:1017:21
VersionError: ... Can not provide apiVersion to Stripe()
### Fix (front‑end)
Find where you do this (your stack shows `initializeStripe` in `buy-tickets.js:1069`):
// ❌ remove apiVersion here – not allowed in browser
const stripe = Stripe(PUBLISHABLE_KEY, {
// apiVersion: '2023-10-16', <-- remove this
// other allowed options are fine (locale, betas, apiClient, advancedFraudSignals)
});
Use:
const stripe = Stripe(PUBLISHABLE_KEY, {
locale: 'auto',
// advancedFraudSignals: true, // optional, allowed
// betas: ['some_beta'] // only if Stripe doc told you to
});
If you’re using `@stripe/stripe-js`:
import { loadStripe } from '@stripe/stripe-js';
// ❌ don’t pass apiVersion here either
const stripe = await loadStripe(PUBLISHABLE_KEY, { locale: 'auto' });
### Where to set the API version (server‑side only)
Move the version pin to your backend SDK:
**Node:**
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-06-20',
});
**PHP:**
StripeStripe::setApiKey('sk_live_...');
StripeStripe::setApiVersion('2024-06-20');
// or
$stripe = new StripeStripeClient([
'api_key' => 'sk_live_...',
'stripe_version' => '2024-06-20',
]);
### Extra checks for “Express Checkout” flows
– Make sure you’re on **HTTPS** and your publishable key is correct.
– For Apple Pay: verify your domain in the Stripe Dashboard.
– For Google Pay: enable it in the Dashboard or via the Payment Element/Payment Request Button config.
– If you’re using the **Payment Element** or **Payment Request Button**, initializing `elements` stays the same after removing `apiVersion`.
Once you remove that one option from `Stripe()` and pin the version on the server, the error will disappear.
Here’s how to set it properly (pick your stack):
### What to send
Content-Security-Policy: frame-ancestors 'self' https://partner.example https://*.trusted.com;
– This controls **who can embed your site in an iframe**.
– Use `'none'` to block all; use `'self'` for only same‑origin.
—
### Apache (.htaccess or vhost)
<IfModule mod_headers.c>
Header always set Content-Security-Policy "frame-ancestors 'self' https://partner.example"
</IfModule>
### Nginx (server block)
add_header Content-Security-Policy "frame-ancestors 'self' https://partner.example" always;
### PHP (very early, before any output)
header("Content-Security-Policy: frame-ancestors 'self' https://partner.example");
### WordPress (functions.php or a small must‑use plugin)
add_action('send_headers', function () {
header("Content-Security-Policy: frame-ancestors 'self' https://partner.example");
});
### Cloudflare
Use **Rules → Transform Rules → HTTP Response Header Modification** to add:
– Header name: `Content-Security-Policy`
– Value: `frame-ancestors 'self' https://partner.example`
—
### Gotchas
– Remove or align old `X-Frame-Options` headers. It’s deprecated and can conflict (e.g., `DENY` will block embedding even if CSP allows it on some browsers).
– Don’t also put CSP in “—it’s fine for some directives, but **`frame-ancestors` is ignored** there.
– Verify in DevTools → Network → (your page) → Response Headers.
– If you need to **test only**, use the report‑only header:
`Content-Security-Policy-Report-Only: frame-ancestors …`
If you tell me your hosting (cPanel/Apache vs Nginx) and whether it’s WordPress, I’ll give you the exact snippet to paste.
Header unset ETag
Header append Vary: Accept-Encoding
alsso add https://js.stripe.com
For **`frame-ancestors`**, remember: it controls **who can embed *your site*** inside an “.
– `https://pay.google.com` = Google Pay embedding your checkout.
– Stripe’s **`https://js.stripe.com`** is just where you load JS from — it does **not** embed your site. You don’t need it in `frame-ancestors`.
👉 If your actual goal is “allow my site to be embedded by Stripe’s Checkout / Google Pay,” then you only whitelist the domains that actually put *your site* inside their iframe. Stripe Checkout doesn’t embed your site — it iframes **the other way around** (you embed Stripe’s iframe). So `frame-ancestors` for Stripe isn’t needed.
But if you still want to allow both (e.g. you know Stripe needs to embed something of yours), the Apache snippet would be:
<IfModule mod_headers.c>
# Allow only self, Google Pay, and Stripe to embed your pages
Header set Content-Security-Policy "frame-ancestors 'self' https://pay.google.com https://js.stripe.com"
# Other headers you had
Header unset ETag
Header append Vary "Accept-Encoding"
</IfModule>
—
✅ **Alternative (cleaner, production-style):**
<IfModule mod_headers.c>
Header always set Content-Security-Policy "frame-ancestors 'self' https://pay.google.com"
Header unset ETag
Header append Vary "Accept-Encoding"
</IfModule>
– Keep only `pay.google.com` in `frame-ancestors`.
– You still need to load Stripe’s JS via **`script-src`** in your CSP, not `frame-ancestors`. Example:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; frame-ancestors 'self' https://pay.google.com"
—
👉 Question for you:
Do you want a **full CSP** (covering `script-src`, `style-src`, etc. with Stripe + Google Pay) or just to patch `frame-ancestors` for iframe embedding?