@gigamusic/checkout 4.4.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/index.d.ts +132 -30
- package/dist/index.js +222 -125
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/checkout.ts +132 -8
- package/src/fulfill.ts +266 -0
- package/src/index.ts +6 -0
- package/src/types.ts +62 -12
- package/src/webhook.ts +24 -198
package/dist/index.js
CHANGED
|
@@ -15,6 +15,24 @@ function resolveUnitAmount(overrideCents, catalogCents) {
|
|
|
15
15
|
}
|
|
16
16
|
return catalogCents;
|
|
17
17
|
}
|
|
18
|
+
function apportion(totalCents, members) {
|
|
19
|
+
const result = /* @__PURE__ */ new Map();
|
|
20
|
+
if (members.length === 0) return result;
|
|
21
|
+
const total = Math.max(0, Math.round(totalCents));
|
|
22
|
+
const sum = members.reduce((s, m) => s + Math.max(0, m.price), 0);
|
|
23
|
+
const weights = sum > 0 ? members.map((m) => Math.max(0, m.price) / sum) : members.map(() => 1 / members.length);
|
|
24
|
+
const exact = weights.map((w) => total * w);
|
|
25
|
+
const base = exact.map((v) => Math.floor(v));
|
|
26
|
+
let remainder = total - base.reduce((s, v) => s + v, 0);
|
|
27
|
+
const order = members.map((m, i) => ({ i, frac: exact[i] - base[i], price: m.price, id: m.id })).sort((a, b) => b.frac - a.frac || b.price - a.price || a.id - b.id);
|
|
28
|
+
for (const { i } of order) {
|
|
29
|
+
if (remainder <= 0) break;
|
|
30
|
+
base[i] += 1;
|
|
31
|
+
remainder -= 1;
|
|
32
|
+
}
|
|
33
|
+
members.forEach((m, i) => result.set(m.id, base[i]));
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
18
36
|
function createCheckoutHandler(deps) {
|
|
19
37
|
const {
|
|
20
38
|
stripeSecret,
|
|
@@ -24,7 +42,8 @@ function createCheckoutHandler(deps) {
|
|
|
24
42
|
successPath = "/checkout/success",
|
|
25
43
|
cancelPath = "/cart",
|
|
26
44
|
currency = "usd",
|
|
27
|
-
catalogPurchase
|
|
45
|
+
catalogPurchase,
|
|
46
|
+
bundlePurchase
|
|
28
47
|
} = deps;
|
|
29
48
|
const stripe = new Stripe(stripeSecret, { typescript: true });
|
|
30
49
|
return async (req) => {
|
|
@@ -41,6 +60,14 @@ function createCheckoutHandler(deps) {
|
|
|
41
60
|
const successUrl = `${baseUrl}${successPath}?session_id={CHECKOUT_SESSION_ID}`;
|
|
42
61
|
const cancelUrl = `${baseUrl}${cancelPath}`;
|
|
43
62
|
const isCatalogPurchase = items.some((i) => i.kind === "catalog");
|
|
63
|
+
const bundleIds = [
|
|
64
|
+
...new Set(
|
|
65
|
+
items.filter((i) => i.kind === "bundle" && typeof i.bundleId === "string").map((i) => i.bundleId)
|
|
66
|
+
)
|
|
67
|
+
];
|
|
68
|
+
if (isCatalogPurchase && bundleIds.length > 0) {
|
|
69
|
+
return jsonError("Cannot mix a catalog purchase with a bundle", 400);
|
|
70
|
+
}
|
|
44
71
|
try {
|
|
45
72
|
if (isCatalogPurchase) {
|
|
46
73
|
const resolved = typeof catalogPurchase === "function" ? await catalogPurchase() : catalogPurchase;
|
|
@@ -73,9 +100,12 @@ function createCheckoutHandler(deps) {
|
|
|
73
100
|
}
|
|
74
101
|
const releaseIds = items.filter((i) => i.kind === "release" && i.id != null).map((i) => i.id);
|
|
75
102
|
const trackIds = items.filter((i) => i.kind === "track" && i.id != null).map((i) => i.id);
|
|
76
|
-
if (releaseIds.length === 0 && trackIds.length === 0) {
|
|
103
|
+
if (releaseIds.length === 0 && trackIds.length === 0 && bundleIds.length === 0) {
|
|
77
104
|
return jsonError("No valid items found", 400);
|
|
78
105
|
}
|
|
106
|
+
if (bundleIds.length > 0 && typeof bundlePurchase !== "function") {
|
|
107
|
+
return jsonError("Bundle purchases are not configured", 400);
|
|
108
|
+
}
|
|
79
109
|
const releaseAmountOverride = new Map(
|
|
80
110
|
items.filter(
|
|
81
111
|
(i) => i.kind === "release" && i.id != null && typeof i.priceCents === "number"
|
|
@@ -97,7 +127,7 @@ function createCheckoutHandler(deps) {
|
|
|
97
127
|
}
|
|
98
128
|
const releaseLineItems = releaseIds.map((id) => releaseById.get(id)).filter((r) => r !== void 0).map((r) => {
|
|
99
129
|
const amount = resolveUnitAmount(releaseAmountOverride.get(r.id), r.price);
|
|
100
|
-
chargedAmounts[`r${r.id}`] = amount;
|
|
130
|
+
chargedAmounts[`r${r.id}`] = (chargedAmounts[`r${r.id}`] ?? 0) + amount;
|
|
101
131
|
return {
|
|
102
132
|
price_data: {
|
|
103
133
|
currency,
|
|
@@ -117,7 +147,7 @@ function createCheckoutHandler(deps) {
|
|
|
117
147
|
trackAmountOverride.get(track.id),
|
|
118
148
|
track.price
|
|
119
149
|
);
|
|
120
|
-
chargedAmounts[`t${track.id}`] = amount;
|
|
150
|
+
chargedAmounts[`t${track.id}`] = (chargedAmounts[`t${track.id}`] ?? 0) + amount;
|
|
121
151
|
return {
|
|
122
152
|
price_data: {
|
|
123
153
|
currency,
|
|
@@ -130,12 +160,57 @@ function createCheckoutHandler(deps) {
|
|
|
130
160
|
quantity: 1
|
|
131
161
|
};
|
|
132
162
|
});
|
|
133
|
-
const
|
|
163
|
+
const bundleLineItems = [];
|
|
164
|
+
const bundleMemberIds = [];
|
|
165
|
+
const unavailableBundleIds = [];
|
|
166
|
+
for (const bundleId of bundleIds) {
|
|
167
|
+
const resolved = await bundlePurchase(bundleId);
|
|
168
|
+
const members = (resolved?.releaseIds ?? []).map((id) => releaseById.get(id)).filter((r) => r !== void 0);
|
|
169
|
+
if (!resolved || members.length === 0) {
|
|
170
|
+
unavailableBundleIds.push(bundleId);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const total = Math.max(
|
|
174
|
+
STRIPE_MIN_CHARGE_CENTS,
|
|
175
|
+
Math.round(resolved.totalCents)
|
|
176
|
+
);
|
|
177
|
+
const cover = members.find((m) => m.coverImageUrl)?.coverImageUrl;
|
|
178
|
+
bundleLineItems.push({
|
|
179
|
+
price_data: {
|
|
180
|
+
currency,
|
|
181
|
+
product_data: {
|
|
182
|
+
name: resolved.productName,
|
|
183
|
+
...cover ? { images: [toAbsoluteUrl(cover, baseUrl)] } : {}
|
|
184
|
+
},
|
|
185
|
+
unit_amount: total
|
|
186
|
+
},
|
|
187
|
+
quantity: 1
|
|
188
|
+
});
|
|
189
|
+
for (const [id, cents] of apportion(
|
|
190
|
+
total,
|
|
191
|
+
members.map((m) => ({ id: m.id, price: m.price }))
|
|
192
|
+
)) {
|
|
193
|
+
chargedAmounts[`r${id}`] = (chargedAmounts[`r${id}`] ?? 0) + cents;
|
|
194
|
+
bundleMemberIds.push(id);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (unavailableBundleIds.length > 0) {
|
|
198
|
+
return Response.json(
|
|
199
|
+
{ error: "bundle-unavailable", bundleIds: unavailableBundleIds },
|
|
200
|
+
{ status: 409 }
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const lineItems = [...bundleLineItems, ...releaseLineItems, ...trackLineItems];
|
|
134
204
|
if (lineItems.length === 0) {
|
|
135
205
|
return jsonError("No valid items found", 400);
|
|
136
206
|
}
|
|
137
|
-
const resolvedReleaseIds =
|
|
138
|
-
|
|
207
|
+
const resolvedReleaseIds = [
|
|
208
|
+
.../* @__PURE__ */ new Set([
|
|
209
|
+
...releaseLineItems.length ? releaseIds.filter((id) => releaseById.has(id)) : [],
|
|
210
|
+
...bundleMemberIds
|
|
211
|
+
])
|
|
212
|
+
];
|
|
213
|
+
const resolvedTrackIds = trackLineItems.length ? [...new Set(trackIds.filter((id) => trackContext.has(id)))] : [];
|
|
139
214
|
const session = await stripe.checkout.sessions.create({
|
|
140
215
|
mode: "payment",
|
|
141
216
|
line_items: lineItems,
|
|
@@ -143,7 +218,10 @@ function createCheckoutHandler(deps) {
|
|
|
143
218
|
site,
|
|
144
219
|
release_ids: JSON.stringify(resolvedReleaseIds),
|
|
145
220
|
track_ids: JSON.stringify(resolvedTrackIds),
|
|
146
|
-
amounts: JSON.stringify(chargedAmounts)
|
|
221
|
+
amounts: JSON.stringify(chargedAmounts),
|
|
222
|
+
// Ignored by fulfillment — bundles decompose into per-release order
|
|
223
|
+
// items — but invaluable when supporting "what did I actually buy?".
|
|
224
|
+
...bundleIds.length > 0 ? { bundle_ids: JSON.stringify(bundleIds) } : {}
|
|
147
225
|
},
|
|
148
226
|
success_url: successUrl,
|
|
149
227
|
cancel_url: cancelUrl
|
|
@@ -170,13 +248,74 @@ function jsonError(message, status, cause) {
|
|
|
170
248
|
}
|
|
171
249
|
return Response.json({ error: message }, { status });
|
|
172
250
|
}
|
|
173
|
-
function
|
|
251
|
+
async function fulfillCheckoutSession(session, deps) {
|
|
252
|
+
const { queries, site } = deps;
|
|
253
|
+
if (session.metadata?.site !== site) {
|
|
254
|
+
return { status: "skipped", reason: "site-mismatch" };
|
|
255
|
+
}
|
|
256
|
+
if (session.payment_status === "unpaid") {
|
|
257
|
+
return { status: "skipped", reason: "unpaid" };
|
|
258
|
+
}
|
|
259
|
+
const existing = await queries.getOrderByStripeSessionId(session.id);
|
|
260
|
+
if (existing) {
|
|
261
|
+
return { status: "already-recorded", order: existing };
|
|
262
|
+
}
|
|
263
|
+
const releaseIds = safeJsonIdList(session.metadata?.release_ids);
|
|
264
|
+
const trackIds = safeJsonIdList(session.metadata?.track_ids);
|
|
265
|
+
const chargedAmounts = safeAmountMap(session.metadata?.amounts);
|
|
266
|
+
if (releaseIds.length === 0 && trackIds.length === 0) {
|
|
267
|
+
return { status: "skipped", reason: "no-items" };
|
|
268
|
+
}
|
|
269
|
+
const allReleases = await queries.listPublishedReleases();
|
|
270
|
+
const releaseById = new Map(allReleases.map((r) => [r.id, r]));
|
|
271
|
+
const trackContext = /* @__PURE__ */ new Map();
|
|
272
|
+
for (const release of allReleases) {
|
|
273
|
+
for (const track of release.tracks) {
|
|
274
|
+
trackContext.set(track.id, { track, release });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const matchedReleases = releaseIds.map((id) => releaseById.get(id)).filter((r) => r !== void 0);
|
|
278
|
+
const matchedTracks = trackIds.map((id) => trackContext.get(id)).filter(
|
|
279
|
+
(c) => c !== void 0
|
|
280
|
+
);
|
|
281
|
+
const orderItems = [
|
|
282
|
+
...matchedReleases.map((r) => ({
|
|
283
|
+
releaseId: r.id,
|
|
284
|
+
price: chargedAmounts[`r${r.id}`] ?? r.price
|
|
285
|
+
})),
|
|
286
|
+
...matchedTracks.map(({ track }) => ({
|
|
287
|
+
trackId: track.id,
|
|
288
|
+
price: chargedAmounts[`t${track.id}`] ?? track.price
|
|
289
|
+
}))
|
|
290
|
+
];
|
|
291
|
+
const customerEmail = session.customer_details?.email ?? "";
|
|
292
|
+
const totalCents = session.amount_total ?? 0;
|
|
293
|
+
let order;
|
|
294
|
+
try {
|
|
295
|
+
order = await queries.recordCompletedOrder({
|
|
296
|
+
stripeSessionId: session.id,
|
|
297
|
+
stripePaymentId: typeof session.payment_intent === "string" ? session.payment_intent : null,
|
|
298
|
+
email: customerEmail,
|
|
299
|
+
amountTotal: totalCents,
|
|
300
|
+
items: orderItems
|
|
301
|
+
});
|
|
302
|
+
} catch (err) {
|
|
303
|
+
if (isUniqueConstraintError(err)) {
|
|
304
|
+
return {
|
|
305
|
+
status: "already-recorded",
|
|
306
|
+
order: await queries.getOrderByStripeSessionId(session.id) ?? null
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
const itemNames = [
|
|
312
|
+
...matchedReleases.map((r) => r.name),
|
|
313
|
+
...matchedTracks.map(({ track, release }) => `${release.name} \u2014 ${track.name}`)
|
|
314
|
+
];
|
|
315
|
+
return { status: "created", order, itemNames, customerEmail, totalCents };
|
|
316
|
+
}
|
|
317
|
+
async function sendPurchaseConfirmation(args, deps) {
|
|
174
318
|
const {
|
|
175
|
-
stripe: stripeOverride,
|
|
176
|
-
stripeSecret,
|
|
177
|
-
webhookSecret,
|
|
178
|
-
queries,
|
|
179
|
-
site,
|
|
180
319
|
email,
|
|
181
320
|
branding,
|
|
182
321
|
emailFrom,
|
|
@@ -186,119 +325,33 @@ function createStripeWebhookHandler(deps) {
|
|
|
186
325
|
verifyPath = "/orders/verify",
|
|
187
326
|
renderConfirmation = renderPurchaseConfirmation
|
|
188
327
|
} = deps;
|
|
189
|
-
if (!
|
|
190
|
-
|
|
191
|
-
|
|
328
|
+
if (!args.customerEmail) {
|
|
329
|
+
console.warn(
|
|
330
|
+
`[gigamusic/checkout] order ${args.orderId} has no customer email \u2014 recorded, but the customer cannot retrieve downloads.`
|
|
192
331
|
);
|
|
332
|
+
return;
|
|
193
333
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
let event;
|
|
202
|
-
try {
|
|
203
|
-
event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
|
|
204
|
-
} catch {
|
|
205
|
-
return Response.json({ error: "Invalid signature" }, { status: 400 });
|
|
206
|
-
}
|
|
207
|
-
if (event.type !== "checkout.session.completed") {
|
|
208
|
-
return Response.json({ received: true });
|
|
209
|
-
}
|
|
210
|
-
const session = event.data.object;
|
|
211
|
-
if (session.metadata?.site !== site) {
|
|
212
|
-
return Response.json({ received: true });
|
|
213
|
-
}
|
|
214
|
-
const existing = await queries.getOrderByStripeSessionId(session.id);
|
|
215
|
-
if (existing) {
|
|
216
|
-
return Response.json({ received: true });
|
|
217
|
-
}
|
|
218
|
-
const releaseIds = safeJsonIdList(session.metadata?.release_ids);
|
|
219
|
-
const trackIds = safeJsonIdList(session.metadata?.track_ids);
|
|
220
|
-
const chargedAmounts = safeAmountMap(session.metadata?.amounts);
|
|
221
|
-
if (releaseIds.length === 0 && trackIds.length === 0) {
|
|
222
|
-
return Response.json({ received: true });
|
|
223
|
-
}
|
|
224
|
-
const allReleases = await queries.listPublishedReleases();
|
|
225
|
-
const releaseById = new Map(allReleases.map((r) => [r.id, r]));
|
|
226
|
-
const trackContext = /* @__PURE__ */ new Map();
|
|
227
|
-
for (const release of allReleases) {
|
|
228
|
-
for (const track of release.tracks) {
|
|
229
|
-
trackContext.set(track.id, { track, release });
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const matchedReleases = releaseIds.map((id) => releaseById.get(id)).filter((r) => r !== void 0);
|
|
233
|
-
const matchedTracks = trackIds.map((id) => trackContext.get(id)).filter(
|
|
234
|
-
(c) => c !== void 0
|
|
235
|
-
);
|
|
236
|
-
const orderItems = [
|
|
237
|
-
...matchedReleases.map((r) => ({
|
|
238
|
-
releaseId: r.id,
|
|
239
|
-
price: chargedAmounts[`r${r.id}`] ?? r.price
|
|
240
|
-
})),
|
|
241
|
-
...matchedTracks.map(({ track }) => ({
|
|
242
|
-
trackId: track.id,
|
|
243
|
-
price: chargedAmounts[`t${track.id}`] ?? track.price
|
|
244
|
-
}))
|
|
245
|
-
];
|
|
246
|
-
const customerEmail = session.customer_details?.email ?? "";
|
|
247
|
-
let recordedOrder;
|
|
248
|
-
try {
|
|
249
|
-
recordedOrder = await queries.recordCompletedOrder({
|
|
250
|
-
stripeSessionId: session.id,
|
|
251
|
-
stripePaymentId: typeof session.payment_intent === "string" ? session.payment_intent : null,
|
|
252
|
-
email: customerEmail,
|
|
253
|
-
amountTotal: session.amount_total ?? 0,
|
|
254
|
-
items: orderItems
|
|
255
|
-
});
|
|
256
|
-
} catch (err) {
|
|
257
|
-
if (isUniqueConstraintError(err)) {
|
|
258
|
-
return Response.json({ received: true });
|
|
259
|
-
}
|
|
260
|
-
throw err;
|
|
261
|
-
}
|
|
262
|
-
if (!customerEmail) {
|
|
263
|
-
console.warn(
|
|
264
|
-
`[stripe-webhook] checkout.session.completed (session=${session.id}) has no customer_details.email \u2014 order recorded but customer cannot retrieve downloads.`
|
|
265
|
-
);
|
|
266
|
-
return Response.json({ received: true });
|
|
267
|
-
}
|
|
268
|
-
const itemNames = [
|
|
269
|
-
...matchedReleases.map((r) => r.name),
|
|
270
|
-
...matchedTracks.map(({ track, release }) => `${release.name} \u2014 ${track.name}`)
|
|
271
|
-
];
|
|
272
|
-
after(async () => {
|
|
273
|
-
try {
|
|
274
|
-
const verifyToken = await signOrderToken({
|
|
275
|
-
orderId: String(recordedOrder.id),
|
|
276
|
-
email: customerEmail,
|
|
277
|
-
secret: orderTokenSecret,
|
|
278
|
-
expiresIn: orderTokenExpiry
|
|
279
|
-
});
|
|
280
|
-
const verifyUrl = `${baseUrl}${verifyPath}?token=${verifyToken}`;
|
|
281
|
-
const { subject, html } = await renderConfirmation({
|
|
282
|
-
branding,
|
|
283
|
-
verifyUrl,
|
|
284
|
-
itemNames,
|
|
285
|
-
totalCents: session.amount_total ?? 0
|
|
286
|
-
});
|
|
287
|
-
await email.send({
|
|
288
|
-
from: emailFrom,
|
|
289
|
-
to: customerEmail,
|
|
290
|
-
subject,
|
|
291
|
-
html
|
|
292
|
-
});
|
|
293
|
-
} catch (err) {
|
|
294
|
-
console.error(
|
|
295
|
-
`[stripe-webhook] failed to send purchase confirmation to ${customerEmail} for session ${session.id}:`,
|
|
296
|
-
err
|
|
297
|
-
);
|
|
298
|
-
}
|
|
334
|
+
try {
|
|
335
|
+
const verifyToken = await signOrderToken({
|
|
336
|
+
orderId: String(args.orderId),
|
|
337
|
+
email: args.customerEmail,
|
|
338
|
+
secret: orderTokenSecret,
|
|
339
|
+
expiresIn: orderTokenExpiry
|
|
299
340
|
});
|
|
300
|
-
|
|
301
|
-
|
|
341
|
+
const verifyUrl = `${baseUrl}${verifyPath}?token=${verifyToken}`;
|
|
342
|
+
const { subject, html } = await renderConfirmation({
|
|
343
|
+
branding,
|
|
344
|
+
verifyUrl,
|
|
345
|
+
itemNames: args.itemNames,
|
|
346
|
+
totalCents: args.totalCents
|
|
347
|
+
});
|
|
348
|
+
await email.send({ from: emailFrom, to: args.customerEmail, subject, html });
|
|
349
|
+
} catch (err) {
|
|
350
|
+
console.error(
|
|
351
|
+
`[gigamusic/checkout] failed to send purchase confirmation to ${args.customerEmail} for order ${args.orderId}:`,
|
|
352
|
+
err
|
|
353
|
+
);
|
|
354
|
+
}
|
|
302
355
|
}
|
|
303
356
|
function safeAmountMap(value) {
|
|
304
357
|
if (!value) return {};
|
|
@@ -326,6 +379,50 @@ function safeJsonIdList(value) {
|
|
|
326
379
|
}
|
|
327
380
|
}
|
|
328
381
|
|
|
382
|
+
// src/webhook.ts
|
|
383
|
+
function createStripeWebhookHandler(deps) {
|
|
384
|
+
const { stripe: stripeOverride, stripeSecret, webhookSecret, queries, site } = deps;
|
|
385
|
+
if (!stripeOverride && !stripeSecret) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
"createStripeWebhookHandler: must supply either `stripe` or `stripeSecret`"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const stripe = stripeOverride ?? new Stripe(stripeSecret, { typescript: true });
|
|
391
|
+
return async (req) => {
|
|
392
|
+
const body = await req.text();
|
|
393
|
+
const sig = req.headers.get("stripe-signature");
|
|
394
|
+
if (!sig) {
|
|
395
|
+
return Response.json({ error: "Missing signature" }, { status: 400 });
|
|
396
|
+
}
|
|
397
|
+
let event;
|
|
398
|
+
try {
|
|
399
|
+
event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
|
|
400
|
+
} catch {
|
|
401
|
+
return Response.json({ error: "Invalid signature" }, { status: 400 });
|
|
402
|
+
}
|
|
403
|
+
if (event.type !== "checkout.session.completed") {
|
|
404
|
+
return Response.json({ received: true });
|
|
405
|
+
}
|
|
406
|
+
const session = event.data.object;
|
|
407
|
+
const result = await fulfillCheckoutSession(session, { queries, site });
|
|
408
|
+
if (result.status !== "created") {
|
|
409
|
+
return Response.json({ received: true });
|
|
410
|
+
}
|
|
411
|
+
after(
|
|
412
|
+
() => sendPurchaseConfirmation(
|
|
413
|
+
{
|
|
414
|
+
orderId: result.order.id,
|
|
415
|
+
customerEmail: result.customerEmail,
|
|
416
|
+
itemNames: result.itemNames,
|
|
417
|
+
totalCents: result.totalCents
|
|
418
|
+
},
|
|
419
|
+
deps
|
|
420
|
+
)
|
|
421
|
+
);
|
|
422
|
+
return Response.json({ received: true });
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
329
426
|
// src/download.ts
|
|
330
427
|
function createDownloadHandler(deps) {
|
|
331
428
|
const { queries, storage } = deps;
|
|
@@ -757,6 +854,6 @@ function createSwZipFallbackHandler() {
|
|
|
757
854
|
};
|
|
758
855
|
}
|
|
759
856
|
|
|
760
|
-
export { createCheckoutHandler, createDownloadHandler, createDownloadZipHandler, createDownloadZipStreamHandler, createStripeWebhookHandler, createSwZipFallbackHandler };
|
|
857
|
+
export { createCheckoutHandler, createDownloadHandler, createDownloadZipHandler, createDownloadZipStreamHandler, createStripeWebhookHandler, createSwZipFallbackHandler, fulfillCheckoutSession, sendPurchaseConfirmation };
|
|
761
858
|
//# sourceMappingURL=index.js.map
|
|
762
859
|
//# sourceMappingURL=index.js.map
|