Files
itflow/js/portal_stripe.js

129 lines
5.1 KiB
JavaScript

/*
* Client portal - taking a card through Stripe. Loaded only on the pages
* that set $portal_stripe (client/includes/footer.php), after Stripe's own
* script, and kept out of the page itself for the portal's
* Content-Security-Policy:
*
* - Checkout (client/pay.php): Stripe's card form in form[data-stripe-pay],
* on the PaymentIntent the page set up; paying sends the contact to
* Stripe and back to the return URL, which books it
* - Cards & autopay (client/saved_payment_methods.php): Add a card opens
* Stripe's embedded Checkout to save one - straight away when the page
* asks with data-autostart
*
* If Stripe's script didn't load, each says so instead of sitting there.
*/
(function () {
'use strict';
var dark = document.documentElement.getAttribute('data-bs-theme') === 'dark';
function show(el, on) {
if (el) {
el.hidden = !on;
}
}
function unavailable(scope) {
show(scope.querySelector('.itflow-portal-stripe-element'), false);
show(scope.querySelector('[data-stripe-loading]'), false);
show(scope.querySelector('[data-stripe-unavailable]'), true);
}
// ---------- Checkout ----------
var form = document.querySelector('form[data-stripe-pay]');
if (form) {
var submit = form.querySelector('[data-stripe-submit]');
var errorBox = form.querySelector('[data-pay-error]');
var busy = function (on) {
submit.disabled = on;
show(form.querySelector('[data-stripe-busy]'), on);
show(form.querySelector('[data-stripe-idle]'), !on);
};
if (typeof window.Stripe !== 'function') {
unavailable(form);
} else {
var stripe = window.Stripe(form.getAttribute('data-stripe-key'));
var elements = stripe.elements({
clientSecret: form.getAttribute('data-client-secret'),
appearance: {theme: dark ? 'night' : 'stripe'}
});
var payment = elements.create('payment', {layout: 'tabs'});
payment.on('ready', function () {
show(form.querySelector('[data-stripe-loading]'), false);
submit.disabled = false;
});
payment.on('loaderror', function () {
unavailable(form);
});
payment.mount('#payment-element');
form.addEventListener('submit', function (e) {
e.preventDefault();
show(errorBox, false);
busy(true);
stripe.confirmPayment({
elements: elements,
confirmParams: {return_url: form.getAttribute('data-return-url')}
}).then(function (result) {
// Only reached when the payment could not go ahead - otherwise
// the browser is already on its way to the return URL
if (result.error) {
errorBox.textContent = result.error.type === 'card_error' || result.error.type === 'validation_error'
? result.error.message
: 'Something went wrong taking the payment - nothing has been charged. Try again.';
show(errorBox, true);
}
busy(false);
});
});
}
}
// ---------- Adding a card ----------
var holder = document.querySelector('[data-stripe-add-card-holder]');
if (holder) {
var started = false;
var start = function () {
if (started) {
return;
}
started = true;
show(holder, true);
document.querySelectorAll('[data-stripe-add-card]').forEach(function (button) {
show(button, false);
});
if (typeof window.Stripe !== 'function') {
unavailable(holder);
return;
}
var stripe = window.Stripe(holder.getAttribute('data-stripe-key'));
stripe.initEmbeddedCheckout({
fetchClientSecret: function () {
return fetch('/client/post.php?create_stripe_checkout', {method: 'POST', credentials: 'same-origin'})
.then(function (response) { return response.json(); })
.then(function (data) {
if (!data.clientSecret) {
throw new Error(data.error || 'No checkout');
}
return data.clientSecret;
});
}
}).then(function (checkout) {
show(holder.querySelector('[data-stripe-loading]'), false);
checkout.mount('#checkout');
}).catch(function () {
unavailable(holder);
});
};
document.querySelectorAll('[data-stripe-add-card]').forEach(function (button) {
button.addEventListener('click', start);
});
if (holder.hasAttribute('data-autostart')) {
start();
}
}
})();