Part 8 of 13 · Elite Limo Booking

Adding Card Payments With Stripe, and the Weight of Handling Money

Secure Stripe card payment integration for the Elite Limo Booking system showing safe online payment processing without storing customer card details.

Adding card payments changed how the whole project felt. Every feature before this, a bug meant inconvenience. A payments bug means someone’s money, and that weight pushed me toward the one architecture where my code, and my database, never hold a card number at all. This post is the real Stripe integration from the plugin, and the two rules that kept it safe.

Stripe’s model for this is the PaymentIntent. Your server asks Stripe to create an intent for an amount, Stripe returns a client secret, and the customer’s browser uses that secret with Stripe’s own form elements to complete payment directly with Stripe. The card number travels from the customer to Stripe, never through your site. Here is the plugin’s actual server-side creation:

public function create_payment_intent($amount, $currency = 'usd', $booking_id = null) {
    try {
        $settings   = get_option('elb_settings', []);
        $stripe_key = !empty($settings['stripe_live_key'])
            ? $settings['stripe_live_key']
            : $settings['stripe_test_key'];

        if (empty($stripe_key)) {
            throw new Exception('Stripe not configured');
        }

        StripeStripe::setApiKey($stripe_key);

        $intent = StripePaymentIntent::create([
            'amount'   => intval($amount * 100),   // dollars to cents
            'currency' => $currency,
            'automatic_payment_methods' => ['enabled' => true],
            'metadata' => ['booking_id' => $booking_id],
        ]);

        return [
            'success'       => true,
            'client_secret' => $intent->client_secret,
        ];
    } catch (Exception $e) {
        error_log('Stripe Payment Intent Error: ' . $e->getMessage());
        return ['success' => false, 'message' => 'Payment setup failed'];
    }
}

The line that bites everyone once: intval($amount * 100). Stripe counts in the smallest currency unit, cents, so a 50 dollar ride is 5000. Send 50 and you have charged fifty cents, a bug customers will not report. Burn it in, Stripe amounts are cents.

Second, the key handling. Keys live in the database through get_option, entered on a settings page, never written into code, code gets committed and shared, secrets in code leak. And the test-versus-live split is a workflow, Stripe’s test keys accept fake cards like 4242 4242 4242 4242 so the whole flow can be exercised end to end without a real cent moving, live keys enter the settings only when everything already works. Note also what the catch block does, the detailed exception goes to error_log for me, the customer gets a generic message, payment errors can contain internals no visitor should see.

The metadata booking_id ties each Stripe payment back to its ride, so the payments table can answer, is this booking paid, with a prepared query. And what my database stores is only that, statuses and amounts and Stripe’s IDs, never card data. That is not just caution, storing card numbers drags you under PCI compliance obligations no small plugin should ever volunteer for.

A few things people ask me about this

Why did Stripe charge fifty cents instead of fifty dollars? Amounts are in the smallest unit. 50 means fifty cents, 5000 means fifty dollars. Multiply by one hundred before sending.

Do I need to store card numbers to track payments? No, and you must not. Store Stripe’s payment intent ID, status, and amount against your booking. Stripe holds the card, you hold the reference.

Next

Paid bookings need drivers, which meant building the other side of the platform, the dispatcher’s dashboard with drag-to-assign, and the AJAX that powers it. That is the next post.

Leave a Reply

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