PageSourceSearch

https://www.mindfulhealthandharmony.com/js/stripe-checkout.4ktfzu0eu3.js

js mindfulhealthandharmony.com collected 2026-10-08 08:50:12 UTC 8,135 bytes, 170 lines download raw bytes

1// Embedded Stripe Payment Element checkout.
2//
3// Activates on any page that renders a `#cc-embedded-checkout` container (the
4// buy page, behind the Checkout:Embedded flag). When there are multiple
5// household members, the buyer picks one first (`[data-cc-member-step]`), then
6// clicks continue — only THEN do we create the PaymentIntent, so changing the
7// recipient never mints extra intents / discount reservations. With a single
8// member it starts immediately. It asks our own endpoint to create the
9// PaymentIntent (+ reserve any discount), mounts Stripe's Payment Element on
10// the provider's connected account, and confirms in-page — the buyer never
11// leaves CoachCentral. Fulfillment is entirely webhook-driven
12// (payment_intent.succeeded → grant); a client that lies about success grants
13// nothing.
14//
15// Follows the app's enhanced-navigation pattern (see forms.js /
16// courses-editor-init.js and CLAUDE.md): init is idempotent (a data-marker on
17// the container) and re-runs after each Blazor enhancedload content swap.
18(function () {
19    function initEmbeddedCheckout() {
20        var container = document.querySelector('#cc-embedded-checkout');
21        if (!container || container.__ccStripeBound) return;
22        container.__ccStripeBound = true;
23
24        var memberStep = container.querySelector('[data-cc-member-step]');
25        if (memberStep) {
26            // Multi-member: defer intent creation until the buyer commits to a
27            // recipient (avoids a PaymentIntent + discount reservation per
28            // radio click).
29            var continueBtn = memberStep.querySelector('[data-cc-continue]');
30            if (!continueBtn) return;
31            continueBtn.addEventListener('click', function () {
32                var checked = memberStep.querySelector('input[name="cc-member"]:checked');
33                var forClientId = checked ? checked.value : container.getAttribute('data-for-client-id');
34                var planChecked = memberStep.querySelector('input[name="cc-payplan"]:checked');
35                var payPlan = planChecked ? planChecked.value === 'true' : false;
36                var paymentStep = container.querySelector('[data-cc-payment-step]');
37                memberStep.hidden = true;
38                if (paymentStep) paymentStep.hidden = false;
39                startPayment(container, forClientId, payPlan);
40            });
41        } else {
42            // Single member, no plan choice: start straight away with the default
43            // contact, pay-in-full. (A plan offer renders the options step above,
44            // so we never auto-start when there's a choice to make.)
45            startPayment(container, container.getAttribute('data-for-client-id'), false);
46        }
47    }
48
49    // Blazor SSR bakes an antiforgery token into the page via
50    // <AntiforgeryToken />, but enhanced navigation (and the inline sign-in
51    // identity change on the buy page) can leave that DOM token stale relative
52    // to the antiforgery cookie — a stale token 400s in middleware before our
53    // endpoint runs. Fetch a fresh token (the endpoint also re-syncs the
54    // cookie) right before POSTing; fall back to the DOM token if unreachable.
55    async function getAntiforgeryToken() {
56        try {
57            var r = await fetch('/buy/antiforgery-token', {
58                headers: { 'Accept': 'application/json' },
59            });
60            if (r.ok) {
61                var j = await r.json();
62                if (j && j.token) return j.token;
63            }
64        } catch (e) { /* fall through to the DOM token */ }
65        var el = document.querySelector('input[name="__RequestVerificationToken"]');
66        return el ? el.value : '';
67    }
68
69    async function startPayment(container, forClientId, payPlan) {
70        var intentUrl = container.getAttribute('data-intent-url');
71        var promoCode = container.getAttribute('data-promo-code') || '';
72        var returnUrl = container.getAttribute('data-return-url');
73
74        var errorEl = container.querySelector('[data-cc-checkout-error]');
75        var submitBtn = container.querySelector('[data-cc-checkout-submit]');
76        var mountEl = container.querySelector('#payment-element');
77        if (!intentUrl || !mountEl || typeof Stripe === 'undefined') return;
78
79        function showError(msg) {
80            if (!errorEl) return;
81            errorEl.textContent = msg;
82            errorEl.hidden = false;
83        }
84
85        // 1. Create the PaymentIntent server-side (also reserves the discount).
86        // Fetch a FRESH antiforgery token first: a token scraped from the DOM
87        // (rendered by <AntiforgeryToken />) can be stale after enhanced
88        // navigation or the inline sign-in identity change, which makes this
89        // POST fail antiforgery validation in middleware and 400 before our
90        // endpoint runs. The fresh token always matches the current cookie.
91        var token = await getAntiforgeryToken();
92        var body = new FormData();
93        body.append('forClientId', forClientId);
94        body.append('payPlan', payPlan ? 'true' : 'false');
95        if (promoCode) body.append('promoCode', promoCode);
96        if (token) body.append('__RequestVerificationToken', token);
97
98        var resp, data;
99        try {
100            resp = await fetch(intentUrl, {
101                method: 'POST',
102                body: body,
103                headers: { 'Accept': 'application/json' },
104            });
105            data = await resp.json();
106        } catch (e) {
107            showError('Could not start checkout. Please try again.');
108            return;
109        }
110
111        // $0 order (free / 100%-off): already materialized server-side.
112        if (data && data.completed) {
113            window.location.assign(returnUrl);
114            return;
115        }
116        if (!resp.ok || !data || !data.clientSecret || !data.publishableKey) {
117            showError((data && data.error) || 'Could not start checkout.');
118            return;
119        }
120
121        // 2. Mount the Payment Element against the provider's connected account.
122        var stripe = data.stripeAccountId
123            ? Stripe(data.publishableKey, { stripeAccount: data.stripeAccountId })
124            : Stripe(data.publishableKey);
125        var elements = stripe.elements({ clientSecret: data.clientSecret });
126        var paymentElement = elements.create('payment');
127        // Clear the "Loading…" placeholder so Stripe mounts into an empty node
128        // (it warns about child nodes otherwise); the Element replaces it anyway.
129        mountEl.textContent = '';
130        paymentElement.mount(mountEl);
131        if (submitBtn) submitBtn.disabled = false;
132
133        // 3. Confirm on submit. confirmPayment redirects to return_url on
134        //    success (incl. after any 3DS challenge); it only resolves here on
135        //    a validation / card error, which we surface without leaving.
136        var form = container.querySelector('form');
137        if (!form) return;
138        form.addEventListener('submit', async function (e) {
139            e.preventDefault();
140            if (submitBtn) submitBtn.disabled = true;
141            if (errorEl) errorEl.hidden = true;
142
143            var result = await stripe.confirmPayment({
144                elements: elements,
145                confirmParams: { return_url: returnUrl },
146            });
147            if (result.error) {
148                showError(result.error.message || 'Payment could not be completed.');
149                if (submitBtn) submitBtn.disabled = false;
150            }
151        });
152    }
153
154    if (document.readyState === 'loading') {
155        document.addEventListener('DOMContentLoaded', initEmbeddedCheckout);
156    } else {
157        initEmbeddedCheckout();
158    }
159
160    // window.Blazor may not exist yet when this script loads — retry briefly.
161    function tryHookBlazor(retries) {
162        if (typeof Blazor !== 'undefined' && Blazor.addEventListener) {
163            Blazor.addEventListener('enhancedload', initEmbeddedCheckout);
164            return;
165        }
166        if (retries <= 0) return;
167        setTimeout(function () { tryHookBlazor(retries - 1); }, 50);
168    }
169    tryHookBlazor(20);
170})();

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.