Ticket booking system analysis
August 7, 2025 Β· AI Conversations
<script>
window.location='https://fossettscircus.com/success/?eid=" . $order_info->event_date_id . "&oc=" . $order_info->order_code . "';
</script>
' ";
goto continue_with_wordpress;
exit();
}
else
{
sendErrorMail( "Order not saved", "Order not saved but charge paid" . var_export( $order_info, true ) );
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Order could not be saved'
]);
exit;
}
}
}
elseif ($intent->status == 'processing') {
echo json_encode([
'success' => false,
'message' => 'Payment is processing. Please wait a moment and try again.',
'status' => 'processing'
]);
exit;
} elseif ($intent->status == 'requires_payment_method') {
echo json_encode([
'success' => false,
'message' => 'Payment method failed. Please try again with a different card.',
'status' => 'requires_payment_method'
]);
exit;
} elseif ($intent->status == 'requires_confirmation') {
echo json_encode([
'success' => false,
'message' => 'Payment requires confirmation. Please try again.',
'status' => 'requires_confirmation'
]);
exit;
} else {
echo json_encode([
'success' => false,
'message' => 'Payment failed: ' . $intent->status,
'status' => $intent->status,
'errors' => ['There was an error during the transaction, charge was not paid.']
]);
exit;
}
}
catch(StripeErrorCard $e)
{
$body = $e->getJsonBody();
$err = $body['error'];
array_push( $order_errors, $err['message'] );
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => $err['message'],
'errors' => $order_errors
]);
exit;
}
}
catch (Exception $e)
{
if ( is_object( $e ) && method_exists( $e, 'getJsonBody' ) )
{
$body = $e->getJsonBody();
$err = $body['error'];
array_push( $order_errors, $err['message'] );
}
array_push( $order_errors, 'There was an error during the transaction. Payment not performed.' );
sendErrorMail( "Exception", var_export( $e, true ) );
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'message' => 'Payment processing error',
'errors' => $order_errors
]);
exit;
}
}
} // /Process payment
} // /Form submitted
?>
<style>
<?php echo file_get_contents( __DIR__ . '/css/style.css'); ?>
/*.div-wrapper{
padding: 0em 1em !important;
}*/
.payment-fields {
display: block !important;
padding: 1em !important;
border-radius: 5px;
width: 100%;
height: 3em;
color: rgb(66, 109, 143);
background-color: rgb(255,255,255);
font-weight: bold;
}
</style>
<script>
<?php echo file_get_contents( __DIR__ . '/js/hashes.js'); ?>
</script>
<script>
</script>
<script>
var event_seat_location_options;
var eventData = <?php echo json_encode($event_data); ?>;
jQuery(document).ready(function() {
if (!eventData || !eventData.event_dates) {
console.error('Event data not available');
return;
}
var latest_available_seat = jQuery('#event-order [name="event_seat_location"] option:selected').val();
event_seat_location_options = jQuery('#event-order [name="event_seat_location"]').html();
jQuery('#event-order [name="event_seat_location"]').html('<option value="">Pick a seat location</option>');
updateAvailableLocations();
jQuery('#event-order [name="event_seat_location"]').val(latest_available_seat);
});
function updateFormSent()
{
var selectedDatetime = jQuery('#event-order [name="event_datetime"]').val();
jQuery('#event-order [name="event_seat_location"] option[data-date-id="' + selectedDatetime + '"]').removeAttr('disabled');
jQuery('#event-order [name="event_seat_location"] option[data-date-id="' + selectedDatetime + '"]').show();
updateTotal();
}
function updateAvailableLocations() {
if (!eventData || !eventData.event_dates) {
console.error('Event data not available');
return;
}
// reset options
jQuery('#event-order [name="event_seat_location"]').html(event_seat_location_options);
var selectedDatetime = jQuery('#event-order [name="event_datetime"]').val();
jQuery('#event-order [name="event_seat_location"] option').prop('disabled', true).hide();
jQuery('#event-order [name="event_seat_location"] option:first').prop('disabled', false).show();
jQuery('#event-order [name="event_seat_location"] option[data-date-id="' + selectedDatetime + '"]').prop('disabled', false).show();
// remove unwanted options
jQuery('#event-order [name="event_seat_location"]').find('option:disabled').remove();
jQuery('#event-order [name="event_seat_location"]').val('');
}
function updateAvailableSeats()
{
var seatsLeft = parseInt( jQuery('#event-order [name="event_seat_location"] option:selected').attr('data-left') );
var seatsSelected = parseInt( jQuery('#event-order [name="event_seat_full"]').val() ) + parseInt( jQuery("#event-order [name='event_seat_child']").val() ) + parseInt( jQuery("#event-order [name='event_seat_senior']").val() );
if ( seatsSelected > seatsLeft )
{
jQuery('#event-order .warning-seats').html('You selected more tickets than are available right now in ' + jQuery("#event-order [name='event_seat_location'] option:selected").text() + ', please pick a different location or reduce the number of tickets by ' + ( seatsSelected - seatsLeft ) + '.' );
jQuery('#event-order .warning-seats').show();
return false;
}
else
{
jQuery('#event-order .warning-seats').hide();
jQuery('#event-order .warning-seats').html('');
return true;
}
}
var V = JSON.parse( '<?php echo $event_data->event_vouchers; ?>' );
var D = 0;
var T = '';
function voucherValid()
{
var SHA1 = new Hashes.SHA1;
var customer_voucher = SHA1.hex( jQuery("#event-order [name='voucher']").val().toLowerCase() );
for (var i = 0; i < V.length; i++)
{
if ( customer_voucher == V[i].voucher_code )
{
D = V[i].voucher_discount;
T = V[i].voucher_discount_type;
return true;
}
}
return false;
}
function updateTotal()
{
var seatsFull = parseInt( jQuery('#event-order [name="event_seat_full"]').val() );
var seatsChild = parseInt( jQuery('#event-order [name="event_seat_child"]').val() );
var seatsSenior = parseInt( jQuery('#event-order [name="event_seat_senior"]').val() );
var priceFull = parseFloat( jQuery('#event-order [name="event_seat_location"]').find('option:selected').attr( 'data-price-full' ) );
var priceReduced = parseFloat( jQuery('#event-order [name="event_seat_location"]').find('option:selected').attr( 'data-price-reduced' ) );
var total = ( seatsFull * priceFull ) + ( ( seatsChild + seatsSenior ) * priceReduced );
if ( voucherValid() )
{
if ( T == 'percentage' )
{
total = total * ( 1 - ( D / 100 ) );
}
else if ( T == 'amount' )
{
total = total - D;
}
}
var currency = '<?php echo $currency_symbol; ?>';
if ( isNaN( total ) )
{
jQuery('#event-order .total').html('');
}
else
{
jQuery('#event-order .total').html('Total: '+ currency + total );
}
}
jQuery(document).ready(function()
{
/*var stripe = Stripe('pk_test_VBObL72a6bVf4DAEpzOKvlh1', { // thenet
stripeAccount: 'acct_1C7mK9IQtQgABICR' // fossetts
});*/
var stripe = Stripe('pk_live_KSh8MRi76Kliuk4fUpBZ23GS', { // thenet live mode
stripeAccount: 'acct_1C7mK9IQtQgABICR' // fossetts
});
<?php if ( isset( $_POST['payment_intent_client_secret'] ) ): ?>
stripe.handleCardAction(
'<?php echo $_POST['payment_intent_client_secret']; ?>'
).then(function(result)
{
if (result.error)
{
jQuery('#event-order .warning-payment').html( result.error.message );
jQuery('#event-order .warning-payment').show();
}
else
{
jQuery('[name="payment_intent_id"]').val( result.paymentIntent.id );
jQuery('#event-order .warning-payment').hide();
jQuery('#event-order .warning-payment').html( '' );
jQuery("#event-order #event-order-form").trigger('submit');
}
});
<?php else: ?>
var elements = stripe.elements();
var paymentStyle = {
base: {
color: 'rgb(66, 109, 143)',
'::placeholder': {
color: 'rgba(66, 109, 143, 0.7)',
},
}
};
// var card = elements.create('card', { hidePostalCode: true, style: paymentStyle });
var card = elements.create('cardNumber', { showIcon: true, style: paymentStyle });
var cardExpiry = elements.create('cardExpiry', { style: paymentStyle });
var cardCvc = elements.create('cardCvc', { style: paymentStyle });
card.addEventListener('change', function( event )
{
if ( event.error )
{
jQuery('#event-order .warning-payment').html( event.error.message );
jQuery('#event-order .warning-payment').show();
}
else
{
jQuery('#event-order .warning-payment').hide();
jQuery('#event-order .warning-payment').html( '' );
}
});
// card.mount('#event-order #payment-fields');
card.mount('#event-order #card-number');
cardExpiry.mount('#event-order #card-expiry');
cardCvc.mount('#event-order #card-cvc');
<?php endif; ?>
jQuery("#event-order [name='event_datetime']").on("change", function()
{
updateAvailableLocations();
updateTotal();
});
jQuery("#event-order [name='event_seat_location'], #event-order [name='event_seat_full'], #event-order [name='event_seat_child'], #event-order [name='event_seat_senior']").on("change", function()
{
updateAvailableSeats();
updateTotal();
});
jQuery("#event-order [name='voucher']").on('input',function()
{
if ( voucherValid() )
{
jQuery('.warning-voucher').hide();
jQuery('.success-voucher').show();
}
else
{
if ( jQuery(this).val() == '' )
{
jQuery('.success-voucher').hide();
jQuery('.warning-voucher').hide();
}
else
{
jQuery('.success-voucher').hide();
jQuery('.warning-voucher').show();
}
}
updateTotal();
});
jQuery("#event-order .order-submit").on('click', function()
{
jQuery("#event-order .order-submit").prop('disabled', true);
if ( updateAvailableSeats() )
{
// Stripe SCA
stripe.createPaymentMethod('card', card, {
billing_details: { name: jQuery('[name="customer_name"]').val() }
})
//stripe.createToken( card )
.then( function(result)
{
if ( result.error )
{
jQuery('#event-order .warning-payment').html( result.error.message );
jQuery('#event-order .warning-payment').show();
jQuery("#event-order .order-submit").prop('disabled', false);
}
else
{
jQuery('#event-order [name="payment_token"]').val( result.paymentMethod.id );
//jQuery('#event-order [name="payment_token"]').val( result.token.id );
jQuery('#event-order .warning-payment').hide();
jQuery('#event-order .warning-payment').html( '' );
jQuery("#event-order #event-order-form").trigger('submit');
}
});
}
else
{
jQuery("#event-order .order-submit").prop('disabled', false);
}
});
/*jQuery("#event-order #event-order-form").on("submit", function(e)
{
jQuery("#event-order .order-submit").trigger('click');
e.preventDefault();
});*/
updateFormSent();
});
</script>
<script>
jQuery(document).ready(function()
{
jQuery('html, body').animate(
{
scrollTop: ( jQuery('.form-errors').first().offset().top - 10 )
}, 500 );
});
</script>
<script>
jQuery(document).ready(function()
{
function checkEmailValue(value, arr) {
var status = 'not_exist';
for (var i = 0; i < arr.length; i++) {
var name = arr[i];
if (name == value) {
status = 'exist';
break;
}
}
return status;
}
//code to check if a value exists in an array
var email_pool = ['gmail.com','yahoo.com','hotmail.com','aol.com','hotmail.co.uk','hotmail.fr','msn.com','yahoo.fr','wanadoo.fr','orange.fr','comcast.net','yahoo.co.uk','yahoo.com.br','yahoo.co.in','live.com','rediffmail.com','free.fr','gmx.de','web.de','yandex.ru','ymail.com','libero.it','outlook.com','uol.com.br','bol.com.br','mail.ru','cox.net','hotmail.it','sbcglobal.net','sfr.fr','live.fr','verizon.net','live.co.uk','googlemail.com','yahoo.es','ig.com.br','live.nl','bigpond.com','terra.com.br','yahoo.it','neuf.fr','yahoo.de','alice.it','rocketmail.com','att.net','laposte.net','facebook.com','bellsouth.net','yahoo.in','hotmail.es','charter.net','yahoo.ca','yahoo.com.au','rambler.ru','hotmail.de','tiscali.it','shaw.ca','yahoo.co.jp','sky.com','earthlink.net','optonline.net','freenet.de','t-online.de','aliceadsl.fr','virgilio.it','home.nl','qq.com','telenet.be','me.com','yahoo.com.ar','tiscali.co.uk','yahoo.com.mx','voila.fr','gmx.net','mail.com','planet.nl','tin.it','live.it','ntlworld.com','arcor.de','yahoo.co.id','frontiernet.net','hetnet.nl','live.com.au','yahoo.com.sg','zonnet.nl','club-internet.fr','juno.com','optusnet.com.au','blueyonder.co.uk','bluewin.ch','skynet.be','sympatico.ca','windstream.net','mac.com','centurytel.net','chello.nl','live.ca','aim.com','bigpond.net.au'];
jQuery( "#confirm_email_address" ).focusout(function() {
jQuery("#confirm_email_error").css("display", (jQuery("#email_address").val() != jQuery("#confirm_email_address").val()) ? "inline-block" : "none");
var user_email = jQuery("#email_address").val();
var domain_name = user_email.indexOf("@");
var slice_email = user_email.slice((domain_name + 1), user_email.length);
if(jQuery("#email_address").val() == jQuery("#confirm_email_address").val()){
// if(!jQuery.inArray(slice_email, email_pool)){
if(checkEmailValue(slice_email, email_pool) == 'not_exist'){
jQuery("#confirm_email_error2").css("display", "inline-block");
jQuery("#confirm_email_error2").html("Hmmm, are you sure... <strong>"+user_email+"</strong> is the correct email address?");
}else{
jQuery("#confirm_email_error2").css("display", "none");
}
}
});
});
</script>
<style>
.email_address_error{
margin-top: 5px !important;
color: rgb(255, 255, 255);
background-color: rgb(237, 30, 38);
font-weight: normal;
padding: 0.3em 15px;
}.email_address_warning{
margin-top: 5px !important;
color: rgb(255, 255, 255);
background-color: #25a828;
font-weight: normal;
padding: 0.3em 15px;
}
</style>
Contact details
—
## π§ **High-Level Overview**
This script:
– Loads event data.
– Renders the booking form dynamically.
– Handles payments via Stripe.
– Manages vouchers.
– Sends confirmation emails.
– Saves the order to the database.
– Works with multiple formats: classic and AJAX-based.
—
## π§© **1. Initialization & Utilities**
### Error Handling Setup
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('log_errors', 1);
ini_set('error_log', 'includes/php-error.log');
ob_start();
Enables full error reporting, logging to `includes/php-error.log`, and starts output buffering.
—
## π **2. Special GET Requests (debug, version check)**
### a. `/script.php?version_check`
Returns static JSON with version info.
### b. `/script.php?debug_ticket_processing`
Debugs ticket processing logic:
– Fetches event/ticket data.
– Compares seating area and ticket names (case-insensitive).
– Returns detailed debug JSON.
—
## π§± **3. Helper Functions**
### β
`processAddons($postData)`
– Parses `addon_` prefixed inputs.
– Returns string like: `"123-2::456-1"`
### β
`calculateAddonTotal($postData)`
– Matches add-on IDs to DB prices.
– Calculates total add-on cost.
### β
`determineTicketLocation()`
– Figures out ticket location from ticket key or data.
### β
`createTicketBreakdown()`
– Creates a JSON-encoded breakdown of tickets:
{
"ringside_adult": {
"quantity": 2,
"price": 15,
"seating_area": "Ringside",
"ticket_type": "Adult"
}
}
### β
`createOrderInfo(…)`
– Builds a detailed `order_info` object.
– Adds voucher, Stripe info, breakdown, location, and UTM parameters.
### β
`saveOrderAndSendEmails(…)`
– Saves order to DB.
– Sends customer & admin emails.
– Tracks in Brevo.
– Returns redirect JSON or logs error.
—
## β‘ **4. AJAX Request Handling**
Only executed if `$_SERVER['HTTP_X_REQUESTED_WITH'] == 'xmlhttprequest'`.
### a. **Retrieve & Validate Event**
Fetches event data using posted `event_id` and `event_date_id`.
### b. **Ticket Price Calculation (New Structure)**
Loop over `$_POST` keys like `event_seat_ringside_adult`:
– Parse into seating area & ticket type.
– Match with ticket definitions.
– Sum up quantities & calculate total.
### c. **Voucher Handling**
Apply discounts based on code:
– Percentage or flat amount.
### d. **Stripe Payment Intent Creation**
Using **Stripe Connect** for platform fee:
StripePaymentIntent::create([
'amount' => $amount_in_cents,
'application_fee_amount' => $fee_in_cents,
'stripe_account' => 'acct_1C7mK9IQtQgABICR'
])
### e. **Handle 3D Secure or Success**
– If `requires_action`: return `client_secret` for 3DS.
– If `succeeded`: process order, email, redirect.
– Otherwise: show error.
—
## π **5. Legacy Form POST Handling (Non-AJAX)**
Fallback for classic form submission.
### a. **Validates Fields**
Ensures:
– Matching emails.
– Valid phone/date/seat.
– Ticket quantities > 0.
### b. **Calculate Price (Old structure)**
Separate pricing for:
– Full price = adult
– Reduced = child + senior
### c. **Stripe Payment (Classic Flow)**
Similar to AJAX.
### d. **Success Handling**
– Sends emails.
– Tracks via Mailchimp.
– Redirects to `success/?eid=…`.
—
## π¨ **6. HTML + JS Frontend**
### a. Event Form Sections:
– Contact details
– Date/time + seat location
– Ticket quantities
– Payment fields (Stripe Elements)
– Voucher input
– Terms & Conditions
### b. JS Functions:
– `updateAvailableSeats()`: ensures seats
—
## β Prerequisites
1. β
Youβve already enabled:
– Google Pay
– Apple Pay
– Revolut Pay *(available in supported countries via Stripe Checkout / PRB)*
2. β Youβre using **Stripe Elements** in your PHP site.
—
## β Step-by-Step Integration (Express Checkout)
### πΉ 1. Update HTML: Add a container
Inside your form (above or beside card fields):
<div id="payment-request-button"></div>
—
### πΉ 2. Add Stripe JS Setup (already present)
This line is already in your script:
<script src="https://js.stripe.com/v3/"></script>
—
### πΉ 3. JavaScript: Initialize Payment Request Button
Add this *after* your existing Stripe Elements setup (`stripe = Stripe(…)`):
var paymentRequest = stripe.paymentRequest({
country: 'IE', // or 'GB', 'US', etc.
currency: 'eur',
total: {
label: 'Total',
amount: Math.round(total * 100) // in cents
},
requestPayerName: true,
requestPayerEmail: true,
});
var elements = stripe.elements();
var prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
style: {
paymentRequestButton: {
type: 'default',
theme: 'light',
height: '44px',
},
}
});
// Check if the Payment Request is available
paymentRequest.canMakePayment().then(function(result) {
if (result) {
prButton.mount('#payment-request-button');
} else {
document.getElementById('payment-request-button').style.display = 'none';
}
});
—
### πΉ 4. Handle the Payment (JavaScript)
Add this:
paymentRequest.on('paymentmethod', async function(ev) {
// Send paymentMethod.id to your server
const response = await fetch('your-endpoint.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_token: ev.paymentMethod.id,
event_id: '...', // fill as needed
event_date_id: '...', // fill as needed
// ... any other fields
})
});
const paymentResponse = await response.json();
if (paymentResponse.success) {
ev.complete('success');
window.location.href = paymentResponse.redirect_url;
} else {
ev.complete('fail');
alert(paymentResponse.message || 'Payment failed');
}
});
—
### πΉ 5. PHP Backend Handling
Your existing backend already handles `payment_token` via:
if (isset($_POST['payment_token'])) {
// create Stripe customer, attach payment method, create PaymentIntent...
}
**No backend change is needed**, unless you want to distinguish between card + PRB methods for logging.
—
## π‘ Notes
– β
Google Pay appears automatically on **Chrome (Android/Desktop)**.
– β
Apple Pay appears on **Safari + Apple devices**.
– β
Revolut Pay appears *if eligible and enabled on Stripe*.
– No special SDK is needed β **Stripe Elements handles it all**.
—
## π Result
Youβll get:
– Single-click payments via Apple Pay, Google Pay, Revolut Pay.
– Faster conversion.
– SCA/3DS handled automatically.
—
If you'd like:
– β
Working **demo** page template,
– β
Backend `fetch` handler for the PRB submission,
– β
Conditional display logic for desktop vs mobile,
just ask.