Skip to content

Commit 1994bf1

Browse files
committed
More updates
1 parent 7fefad9 commit 1994bf1

File tree

4 files changed

+153
-48
lines changed

4 files changed

+153
-48
lines changed

_headers

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
Feature-Policy: geolocation 'self';
1616
Link: </css/index.min.css>; rel=preload; as=style; referrerpolicy=no-referrrer;
1717
Link: </js/index.min.js>; rel=preload; as=script; referrerpolicy=no-referrrer;
18-
Content-Security-Policy: default-src 'self'; img-src *; script-src 'self' cdn.kernvalley.us unpkg.com/ www.google-analytics.com www.googletagmanager.com cdn.polyfill.io/v3/polyfill.min.js; style-src 'self' cdn.kernvalley.us unpkg.com/; connect-src 'self' cdn.kernvalley.us api.kernvalley.us apps.kernvalley.us api.github.com/users/ api.openweathermap.org/data/2.5/weather www.google-analytics.com/ www.googletagmanager.com/gtag/; font-src cdn.kernvalley.us; media-src *; frame-src www.youtube-nocookie.com maps.kernvalley.us/embed; form-action 'self'; manifest-src 'self'; worker-src 'self'; reflected-xss block; upgrade-insecure-requests; block-all-mixed-content; disown-opener;
18+
Content-Security-Policy: default-src 'self'; img-src *; script-src 'self' cdn.kernvalley.us unpkg.com/ www.google-analytics.com www.googletagmanager.com cdn.polyfill.io/v3/polyfill.min.js js.stripe.com/v3/; style-src 'self' cdn.kernvalley.us unpkg.com/; connect-src 'self' cdn.kernvalley.us api.kernvalley.us apps.kernvalley.us api.github.com/users/ api.openweathermap.org/data/2.5/weather www.google-analytics.com/ www.googletagmanager.com/gtag/; font-src cdn.kernvalley.us; media-src *; frame-src www.youtube-nocookie.com maps.kernvalley.us/embed js.stripe.com/v3/; form-action 'self'; manifest-src 'self'; worker-src 'self'; reflected-xss block; upgrade-insecure-requests; block-all-mixed-content; disown-opener;
1919

2020
/reset
2121
Referrer-Policy: no-referrer

api/stripe.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* eslint-env node */
22
const methods = ['GET', 'POST', 'OPTIONS'];
33

4-
async function calculateOrderAmount() {
5-
return 100;
4+
async function calculateOrderAmount(items) {
5+
return 100 * items.length;
66
}
77

88
exports.handler = async function handler(event) {
@@ -54,6 +54,7 @@ exports.handler = async function handler(event) {
5454
if (! Array.isArray(items) || items.length === 0) {
5555
throw new TypeError('Expected an array of items');
5656
}
57+
5758
const { Stripe } = await import('stripe');
5859
const stripe = Stripe(process.env.STRIPE_SECRET);
5960
const paymentIntent = await stripe.paymentIntents.create({

js/store.js

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,65 @@
11
import { on, create } from 'https://cdn.kernvalley.us/js/std-js/dom.js';
2+
import { initElements } from './stripe.js';
23

3-
on('button.add-to-cart','click', () => {
4-
const dialog = create('dialog', {
5-
events: { close: ({ target }) => target.remove() },
6-
children: [
7-
create('p', {
8-
classList: ['status-box','info'],
9-
text: 'WFD Store is in demo mode. Purchases are currently not enabled.'
10-
}),
11-
create('div', {
12-
classList: ['center'],
13-
children: [
14-
create('button', {
15-
classList: ['btn', 'btn-reject'],
16-
text: 'Close',
17-
events: { click: ({ target }) => target.closest('dialog').close() },
18-
})
19-
]
20-
})
21-
],
22-
});
4+
async function getCart() {
5+
return [{ foo: 'bar' }];
6+
}
237

24-
document.body.append(dialog);
25-
dialog.showModal();
26-
});
8+
if (location.pathname.startsWith('/store/checkout')) {
9+
getCart().then(items => {
10+
initElements({
11+
items,
12+
base: 'payment-form',
13+
}).catch(console.error);
14+
});
15+
} else {
16+
on('button.add-to-cart','click', () => {
17+
const dialog = create('dialog', {
18+
events: { close: ({ target }) => target.remove() },
19+
children: [
20+
create('p', {
21+
classList: ['status-box','info'],
22+
text: 'WFD Store is in demo mode. Purchases are currently not enabled.'
23+
}),
24+
create('div', {
25+
classList: ['center'],
26+
children: [
27+
create('button', {
28+
classList: ['btn', 'btn-reject'],
29+
text: 'Close',
30+
events: { click: ({ target }) => target.closest('dialog').close() },
31+
})
32+
]
33+
})
34+
],
35+
});
2736

28-
on('.product-listing .product-img', 'click', ({ target }) => {
29-
const dialog = create('dialog', {
30-
events: { close: ({ target }) => target.remove() },
31-
children: [
32-
create('div', {
33-
classList: ['center'],
34-
children: [target.cloneNode()],
35-
}),
36-
create('div', {
37-
classList: ['center'],
38-
children: [
39-
create('button', {
40-
classList: ['btn', 'btn-reject'],
41-
text: 'Close',
42-
events: { click: ({ target }) => target.closest('dialog').close() },
43-
}),
44-
]
45-
})
46-
],
37+
document.body.append(dialog);
38+
dialog.showModal();
4739
});
4840

49-
document.body.append(dialog);
50-
dialog.showModal();
51-
});
41+
on('.product-listing .product-img', 'click', ({ target }) => {
42+
const dialog = create('dialog', {
43+
events: { close: ({ target }) => target.remove() },
44+
children: [
45+
create('div', {
46+
classList: ['center'],
47+
children: [target.cloneNode()],
48+
}),
49+
create('div', {
50+
classList: ['center'],
51+
children: [
52+
create('button', {
53+
classList: ['btn', 'btn-reject'],
54+
text: 'Close',
55+
events: { click: ({ target }) => target.closest('dialog').close() },
56+
}),
57+
]
58+
})
59+
],
60+
});
61+
62+
document.body.append(dialog);
63+
dialog.showModal();
64+
});
65+
}

js/stripe.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { getJSON } from 'https://cdn.kernvalley.us/js/std-js/http.js';
2+
import { loadScript } from 'https://cdn.kernvalley.us/js/std-js/loader.js';
3+
import { map } from 'https://cdn.kernvalley.us/js/std-js/dom.js';
4+
5+
const STRIPE = 'https://js.stripe.com/v3/';
6+
const ENDPOINT = '/api/stripe';
7+
8+
export const getStripeKey = (async ({ signal } = {}) => {
9+
const { key, error } = await getJSON(ENDPOINT, { signal });
10+
if (typeof error !== 'undefined') {
11+
throw new Error(error.message);
12+
} else if (typeof key !== 'string' || key.length === 0) {
13+
throw new Error('Error loading stripe key');
14+
} else {
15+
return key;
16+
}
17+
}).once();
18+
19+
export const getSecret = async (body = [{}], { signal } = {}) => {
20+
const resp = await fetch(ENDPOINT, {
21+
method: 'POST',
22+
headers: new Headers({ 'Content-Type': 'application/json' }),
23+
body: JSON.stringify(body),
24+
signal,
25+
});
26+
27+
if (resp.ok) {
28+
const { clientSecret } = await resp.json();
29+
return clientSecret;
30+
} else {
31+
throw new Error('Error creating payment request');
32+
}
33+
};
34+
35+
export const loadStripe = (() => loadScript(STRIPE)).once();
36+
37+
export const getStripe = (async ({ signal } = {}) => {
38+
const [key] = await Promise.all([getStripeKey({ signal }), loadStripe()]);
39+
40+
if (! ('Stripe' in globalThis)) {
41+
throw new Error('Stripe failed to load');
42+
} else {
43+
return globalThis.Stripe(key);
44+
}
45+
}).once();
46+
47+
export const getElements = (async (items = [], {
48+
appearance = { theme: 'stripe' },
49+
signal,
50+
} = {}) => {
51+
const [stripe, clientSecret] = await Promise.all([
52+
getStripe({ signal }),
53+
getSecret(items, { signal }),
54+
]);
55+
56+
return stripe.elements({ appearance, clientSecret });
57+
}).once();
58+
59+
export const createElement = async (elements, { type, selector, style, events }) => {
60+
const el = elements.create(type, { style });
61+
62+
if (typeof events === 'object' && ! Object.is(events, null)) {
63+
Object.entries(events).forEach(([event, callback]) => el.on(event, callback));
64+
}
65+
66+
el.mount(selector);
67+
return el;
68+
};
69+
70+
export const initElements = async ({
71+
base = document.body,
72+
items,
73+
events,
74+
styles,
75+
theme,
76+
signal,
77+
} = {}) => {
78+
const elements = await getElements(items, { theme, signal });
79+
80+
return await Promise.all(map('[data-stripe-element][id]', el => {
81+
const type = el.dataset.stripeElement;
82+
83+
return createElement(elements, {
84+
type: type,
85+
selector: `#${el.id}`,
86+
events: typeof events === 'undefined' ? undefined : events[type],
87+
style: typeof styles === 'undefined' ? undefined : styles[type],
88+
});
89+
}, { base: typeof base === 'string' ? document.forms[base] : base }));
90+
};

0 commit comments

Comments
 (0)