/** * bm-stripe-checkout.js * * Handles Stripe Card Element mounting and payment tokenization. * * Strategy — pre-tokenize on card complete: * 1. Card Element mounts when Stripe gateway is selected. * 2. When the customer finishes entering card details (card complete), * we immediately call stripe.createPaymentMethod() and store the * payment_method_id in memory. * 3. On "Place Order", we SYNCHRONOUSLY add the stored ID to the form * as a hidden field — no async blocking, no WooCommerce timing issues. * 4. WooCommerce's AJAX call includes bm_stripe_payment_method_id. * 5. PHP gateway creates + confirms a PaymentIntent server-side. */ (function ($) { 'use strict'; /* ── Guard ──────────────────────────────────────────────── */ if (typeof Stripe === 'undefined' || !window.bmStripeData) { return; } var stripe = Stripe(bmStripeData.publishableKey); var cardElement = null; var elementsInstance = null; // Stores the pre-tokenized payment method ID (pm_xxxxxxx). // null = card not complete or not yet tokenized. var paymentMethodId = null; var isTokenizing = false; // prevent concurrent tokenize calls /* ── Mount the Card Element ─────────────────────────────── */ function mountCardElement() { if (cardElement) { return; // already mounted — no-op } var container = document.getElementById('bm-stripe-card-element'); if (!container) { return; } elementsInstance = stripe.elements(); cardElement = elementsInstance.create('card', { hidePostalCode: true, // already collected in WC billing fields above style: { base: { fontFamily: "'Barlow', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", fontSize: '15px', color: '#1a1a1a', fontWeight: '400', '::placeholder': { color: '#aaa' }, }, invalid: { color: '#bd183f', iconColor: '#bd183f', }, }, }); cardElement.mount('#bm-stripe-card-element'); // Visual states. cardElement.on('focus', function () { container.classList.add('is-focused'); }); cardElement.on('blur', function () { container.classList.remove('is-focused'); }); // Validation + pre-tokenize when card is fully entered. cardElement.on('change', function (event) { var errorEl = document.getElementById('bm-stripe-card-errors'); if (event.error) { if (errorEl) errorEl.textContent = event.error.message; container.classList.add('is-invalid'); paymentMethodId = null; // card is invalid, clear stored token return; } if (errorEl) errorEl.textContent = ''; container.classList.remove('is-invalid'); if (event.complete) { // Card number + expiry + CVC are all filled and valid. // Pre-tokenize now so submit is synchronous (no AJAX race conditions). preTokenize(); } else { // Card is incomplete — invalidate any previously stored token. paymentMethodId = null; } }); } /* ── Pre-tokenize the card when complete ───────────────── */ function preTokenize() { if (isTokenizing) return; // already in progress isTokenizing = true; paymentMethodId = null; stripe.createPaymentMethod({ type: 'card', card: cardElement, }).then(function (result) { isTokenizing = false; if (result.error) { // Stripe rejected the card at tokenize time (rare). var errorEl = document.getElementById('bm-stripe-card-errors'); if (errorEl) errorEl.textContent = result.error.message; paymentMethodId = null; return; } // Store for use at submit time. paymentMethodId = result.paymentMethod.id; }); } /* ── Helpers ────────────────────────────────────────────── */ function isStripeSelected() { var selected = document.querySelector('input[name="payment_method"]:checked'); return selected && selected.value === bmStripeData.gatewayId; } function showError(message) { var el = document.getElementById('bm-stripe-card-errors'); if (el) el.textContent = message; // Scroll into view so user sees the error. var payment = document.getElementById('bm-stripe-card-element'); if (payment) payment.scrollIntoView({ behavior: 'smooth', block: 'center' }); } function clearError() { var el = document.getElementById('bm-stripe-card-errors'); if (el) el.textContent = ''; } /* ── Auto-mount when gateway is active ──────────────────── */ $(document.body).on('updated_checkout', function () { if (isStripeSelected()) { // WC refreshed the #payment div — card element is new, reset state. cardElement = null; paymentMethodId = null; setTimeout(mountCardElement, 150); } }); $(document.body).on('click', 'input[name="payment_method"]', function () { if ($(this).val() === bmStripeData.gatewayId) { setTimeout(mountCardElement, 150); } }); // Fallback: mount on page load if Stripe is already selected. $(document).ready(function () { setTimeout(function () { if (isStripeSelected()) { mountCardElement(); } }, 900); }); /* ── Intercept "Place Order" synchronously ──────────────── */ /** * WooCommerce fires checkout_place_order_{gateway_id} just before its * AJAX checkout call. We handle it SYNCHRONOUSLY: * - If token is ready → add hidden field → return true → WC proceeds * - If card incomplete → show error → return false → WC stops * - If still tokenizing (rare) → wait then retry */ $(document.body).on('checkout_place_order_' + bmStripeData.gatewayId, function () { // Token is ready — add it synchronously and let WooCommerce proceed. if (paymentMethodId) { clearError(); $('input[name="bm_stripe_payment_method_id"]').remove(); $('').attr({ type: 'hidden', name: 'bm_stripe_payment_method_id', value: paymentMethodId, }).appendTo('form.checkout'); return true; // allow WooCommerce AJAX to proceed } // Still tokenizing (user clicked Place Order very quickly after completing card). if (isTokenizing) { showError('Payment is still loading. Please wait a moment and try again.'); return false; } // Card element not filled in yet. if (!cardElement) { showError('Please fill in your card details to continue.'); return false; } // Card incomplete — trigger a tokenize attempt now (fallback). showError('Please check your card details and try again.'); return false; }); }(jQuery));
Warning: Cannot modify header information - headers already sent by (output started at /home/ve80y1dsmsdz/public_html/wp-content/themes/perukar-child/includes/class-bm-stripe-gateway.php:1) in /home/ve80y1dsmsdz/public_html/wp-includes/sitemaps/class-wp-sitemaps-renderer.php on line 126
https://thebigmoustachebarbershop.ca/wp-sitemap-posts-page-1.xmlhttps://thebigmoustachebarbershop.ca/wp-sitemap-posts-product-1.xmlhttps://thebigmoustachebarbershop.ca/wp-sitemap-taxonomies-product_brand-1.xmlhttps://thebigmoustachebarbershop.ca/wp-sitemap-taxonomies-product_cat-1.xmlhttps://thebigmoustachebarbershop.ca/wp-sitemap-taxonomies-product_tag-1.xmlhttps://thebigmoustachebarbershop.ca/wp-sitemap-users-1.xml