/**
* 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