Ticket booking system analysis

User
<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

Assistant
User is working on a Fossetts Circus ticket booking system with Stripe payment integration and PHP-based backend.
Assistant
Here’s a detailed step-by-step breakdown and understanding of the provided codebase, which is a comprehensive ticket booking system for **Fossetts Circus** using **PHP**, **Stripe**, **AJAX**, and custom HTML forms:

## 🧠 **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

User
Implement Express Checkout using Google Wallet or Apple Pay or Revolute Pay if possible? All services are enabled and configured in stripe
Assistant
To implement **Express Checkout** using **Google Pay**, **Apple Pay**, and optionally **Revolut Pay** through **Stripe**, you can use **Stripe Payment Request Button (PRB)**, which supports all of them via a unified interface.

## βœ… 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.

Leave a Comment

Your email address will not be published. Required fields are marked *