PageSourceSearch

https://sollos.ai/wp-content/plugins/sollos-stripe-checkout/assets/js/checkout.js?ver=1789570565

js sollos.ai collected 2026-10-05 13:54:33 UTC 8,030 bytes, 285 lines download raw bytes

1/**
2 * Opens Stripe Embedded Checkout in a modal so plan purchases never leave the
3 * site. In live mode every failure path falls back to the button's own Payment
4 * Link href, so a broken script degrades to the previous behaviour rather than
5 * to nothing. In test mode there is no fallback by design - see fallback().
6 */
7( function () {
8	'use strict';
9
10	var config = window.sollosStripe || {};
11	if ( ! config.publishableKey || ! config.endpoint ) {
12		return;
13	}
14
15	var strings = config.strings || {};
16
17	// wp_localize_script stringifies booleans: true arrives as '1', false as ''.
18	var testMode = !! config.testMode && '0' !== String( config.testMode );
19
20	var stripeJsPromise = null;
21	var modal = null;
22	var checkout = null;
23	var trigger = null;
24	var pending = false;
25
26	/**
27	 * Fetch Stripe.js on demand. Loading it up front would put a third-party
28	 * request on every pricing page view.
29	 *
30	 * @return {Promise} Resolves with the Stripe constructor.
31	 */
32	function loadStripeJs() {
33		if ( stripeJsPromise ) {
34			return stripeJsPromise;
35		}
36
37		stripeJsPromise = new Promise( function ( resolve, reject ) {
38			if ( window.Stripe ) {
39				resolve( window.Stripe );
40				return;
41			}
42
43			var existing = document.querySelector( 'script[data-sollos-stripe-js]' );
44			if ( existing ) {
45				existing.addEventListener( 'load', function () {
46					window.Stripe ? resolve( window.Stripe ) : reject( new Error( 'Stripe.js unavailable' ) );
47				} );
48				existing.addEventListener( 'error', function () {
49					reject( new Error( 'Stripe.js failed to load' ) );
50				} );
51				return;
52			}
53
54			var script = document.createElement( 'script' );
55			script.src = 'https://js.stripe.com/v3/';
56			script.async = true;
57			script.setAttribute( 'data-sollos-stripe-js', '1' );
58			script.onload = function () {
59				window.Stripe ? resolve( window.Stripe ) : reject( new Error( 'Stripe.js unavailable' ) );
60			};
61			script.onerror = function () {
62				reject( new Error( 'Stripe.js failed to load' ) );
63			};
64			document.head.appendChild( script );
65		} );
66
67		return stripeJsPromise;
68	}
69
70	function buildModal() {
71		var overlay = document.createElement( 'div' );
72		overlay.className = 'sollos-checkout-modal';
73		overlay.setAttribute( 'role', 'dialog' );
74		overlay.setAttribute( 'aria-modal', 'true' );
75		overlay.setAttribute( 'aria-label', strings.title || 'Secure checkout' );
76		overlay.hidden = true;
77
78		var backdrop = document.createElement( 'div' );
79		backdrop.className = 'sollos-checkout-modal__backdrop';
80		backdrop.setAttribute( 'data-sollos-close', '' );
81
82		var dialog = document.createElement( 'div' );
83		dialog.className = 'sollos-checkout-modal__dialog';
84
85		var close = document.createElement( 'button' );
86		close.type = 'button';
87		close.className = 'sollos-checkout-modal__close';
88		close.setAttribute( 'data-sollos-close', '' );
89		close.setAttribute( 'aria-label', strings.close || 'Close checkout' );
90		close.innerHTML = '×';
91
92		var status = document.createElement( 'p' );
93		status.className = 'sollos-checkout-modal__status';
94		status.setAttribute( 'role', 'status' );
95
96		var mount = document.createElement( 'div' );
97		mount.className = 'sollos-checkout-modal__mount';
98		mount.id = 'sollos-checkout-mount';
99
100		dialog.appendChild( close );
101		dialog.appendChild( status );
102		dialog.appendChild( mount );
103		overlay.appendChild( backdrop );
104		overlay.appendChild( dialog );
105		document.body.appendChild( overlay );
106
107		overlay.addEventListener( 'click', function ( event ) {
108			if ( event.target instanceof Element && event.target.hasAttribute( 'data-sollos-close' ) ) {
109				closeModal();
110			}
111		} );
112
113		return overlay;
114	}
115
116	function setStatus( message ) {
117		if ( ! modal ) {
118			return;
119		}
120
121		var status = modal.querySelector( '.sollos-checkout-modal__status' );
122		if ( ! status ) {
123			return;
124		}
125
126		status.textContent = message || '';
127		status.hidden = ! message;
128	}
129
130	function onKeydown( event ) {
131		if ( 'Escape' === event.key || 'Esc' === event.key ) {
132			closeModal();
133		}
134	}
135
136	function openModal() {
137		if ( ! modal ) {
138			modal = buildModal();
139		}
140
141		modal.hidden = false;
142		document.body.classList.add( 'sollos-checkout-open' );
143		setStatus( strings.loading || 'Loading secure checkout...' );
144		document.addEventListener( 'keydown', onKeydown );
145
146		var close = modal.querySelector( '.sollos-checkout-modal__close' );
147		if ( close ) {
148			close.focus();
149		}
150	}
151
152	function closeModal() {
153		if ( ! modal || modal.hidden ) {
154			return;
155		}
156
157		modal.hidden = true;
158		pending = false;
159		document.body.classList.remove( 'sollos-checkout-open' );
160		document.removeEventListener( 'keydown', onKeydown );
161
162		if ( checkout ) {
163			try {
164				checkout.destroy();
165			} catch ( error ) {
166				// Already torn down; nothing to recover.
167			}
168			checkout = null;
169		}
170
171		if ( trigger ) {
172			trigger.focus();
173			trigger = null;
174		}
175	}
176
177	/**
178	 * Give up on the embedded flow and use the hosted Payment Link instead.
179	 *
180	 * In test mode the server replaces the href with '#', so there is nothing
181	 * to fall back to and the customer is never bounced to a live payment page
182	 * from a test-keyed site. The error is reported in place instead.
183	 *
184	 * @param {string} url   Payment Link URL from the button's href.
185	 * @param {Error}  error What went wrong.
186	 */
187	function fallback( url, error ) {
188		if ( window.console && window.console.warn ) {
189			window.console.warn( '[sollos] embedded checkout unavailable:', error );
190		}
191
192		if ( url && '#' !== url && ! testMode ) {
193			setStatus( strings.error || 'Checkout could not load.' );
194			window.location.href = url;
195			return;
196		}
197
198		setStatus( ( testMode ? strings.errorDev : strings.error ) || 'Checkout could not load.' );
199	}
200
201	/**
202	 * @param {string}  plan        Plan slug.
203	 * @param {string}  fallbackUrl Payment Link URL to use if anything fails.
204	 * @param {Element} source      The clicked button, for focus restore.
205	 */
206	function startCheckout( plan, fallbackUrl, source ) {
207		if ( pending ) {
208			return;
209		}
210
211		pending = true;
212		trigger = source || null;
213		openModal();
214
215		var session = fetch( config.endpoint, {
216			method: 'POST',
217			headers: { 'Content-Type': 'application/json' },
218			credentials: 'same-origin',
219			body: JSON.stringify( { plan: plan } )
220		} ).then( function ( response ) {
221			return response.json().then( function ( data ) {
222				if ( ! response.ok || ! data || ! data.clientSecret ) {
223					throw new Error( ( data && data.message ) || 'Could not start checkout.' );
224				}
225				return data;
226			} );
227		} );
228
229		Promise.all( [ loadStripeJs(), session ] ).then( function ( results ) {
230			var Stripe = results[ 0 ];
231			var data = results[ 1 ];
232			var stripe = Stripe( config.publishableKey );
233
234			// Stripe renamed the API and its mount function together, so the
235			// session's ui_mode decides which one to call.
236			var create = ( 'embedded_page' === data.uiMode && 'function' === typeof stripe.createEmbeddedCheckoutPage )
237				? stripe.createEmbeddedCheckoutPage.bind( stripe )
238				: stripe.initEmbeddedCheckout.bind( stripe );
239
240			return create( { clientSecret: data.clientSecret } );
241		} ).then( function ( instance ) {
242			pending = false;
243
244			// The customer closed the modal while Stripe was still loading.
245			if ( ! modal || modal.hidden ) {
246				try {
247					instance.destroy();
248				} catch ( error ) {
249					// Nothing to clean up.
250				}
251				return;
252			}
253
254			checkout = instance;
255			setStatus( '' );
256			instance.mount( '#sollos-checkout-mount' );
257		} ).catch( function ( error ) {
258			pending = false;
259			fallback( fallbackUrl, error );
260		} );
261	}
262
263	document.addEventListener( 'click', function ( event ) {
264		if ( 0 !== event.button || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) {
265			return;
266		}
267
268		if ( ! ( event.target instanceof Element ) ) {
269			return;
270		}
271
272		var button = event.target.closest( '[data-sollos-plan]' );
273		if ( ! button ) {
274			return;
275		}
276
277		var plan = button.getAttribute( 'data-sollos-plan' );
278		if ( ! plan ) {
279			return;
280		}
281
282		event.preventDefault();
283		startCheckout( plan, button.getAttribute( 'href' ) || '', button );
284	} );
285}() );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.