@nexo-labs/payload-stripe-inventory 1.6.1

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.
@@ -0,0 +1,1031 @@
1
+ import { S as generateUserInventory, _ as PricingType, a as getPermissionsSlugs, b as permissionSlugs, d as COLLECTION_SLUG_PRICES, f as COLLECTION_SLUG_PRODUCTS, g as PricingPlanInterval, m as MAX_UNLOCKS_PER_WEEK, n as countWeeklyUnlocksQuery, p as COLLECTION_SLUG_USER, r as checkIfUserCanUnlockQuery, u as COLLECTION_SLUG_CUSTOMERS, x as generateCustomerInventory, y as formatOptions } from "../src-BmlQoR4x.mjs";
2
+ import Stripe from "stripe";
3
+ import { headers } from "next/headers.js";
4
+ import { NextResponse } from "next/server.js";
5
+ import { COLLECTION_SLUG_TAXONOMY, buildTaxonomyRelationship } from "@nexo-labs/payload-taxonomies";
6
+ import { stripePlugin } from "@payloadcms/plugin-stripe";
7
+
8
+ //#region src/server/utils/payload/upsert.ts
9
+ const payloadUpsert = async ({ payload, collection, data, where }) => {
10
+ try {
11
+ const existingDocId = (await payload.find({
12
+ collection,
13
+ where,
14
+ pagination: false,
15
+ limit: 1
16
+ })).docs?.at(0)?.id;
17
+ if (existingDocId) return await payload.update({
18
+ collection,
19
+ id: existingDocId,
20
+ data
21
+ }) || null;
22
+ return await payload.create({
23
+ collection,
24
+ data
25
+ });
26
+ } catch (error) {
27
+ console.error(`Error in payloadUpsert: ${error}`);
28
+ throw new Error(`Failed to upsert document in collection ${collection} ${error}`);
29
+ }
30
+ };
31
+
32
+ //#endregion
33
+ //#region src/server/utils/stripe/stripe-builder.ts
34
+ const stripeBuilder = () => {
35
+ return new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2024-09-30.acacia" });
36
+ };
37
+
38
+ //#endregion
39
+ //#region src/server/actions/price.ts
40
+ const updatePrices = async (payload) => {
41
+ const promises = (await (await stripeBuilder()).prices.list({
42
+ limit: 100,
43
+ active: true
44
+ })).data.map((price) => priceUpsert(price, payload));
45
+ const pricesByProductId = (await Promise.all(promises)).mapNotNull((t) => t).reduce((acc, { productId, priceId }) => {
46
+ if (!acc[productId]) acc[productId] = [];
47
+ acc[productId].push(priceId);
48
+ return acc;
49
+ }, {});
50
+ Object.entries(pricesByProductId).map(async ([productId, prices$1]) => {
51
+ await payload.update({
52
+ collection: COLLECTION_SLUG_PRODUCTS,
53
+ data: { prices: prices$1 },
54
+ where: { stripeID: { equals: productId } }
55
+ });
56
+ });
57
+ };
58
+ async function priceUpsert(price, payload) {
59
+ const stripeProductID = typeof price.product === "string" ? price.product : price.product.id;
60
+ if (price.deleted !== void 0) {
61
+ priceDeleted(price, payload);
62
+ return null;
63
+ }
64
+ if (price.unit_amount == null) return null;
65
+ const priceUpserted = await payloadUpsert({
66
+ payload,
67
+ collection: COLLECTION_SLUG_PRICES,
68
+ data: {
69
+ stripeID: price.id,
70
+ stripeProductId: stripeProductID,
71
+ active: price.active,
72
+ unitAmount: price.unit_amount,
73
+ currency: price.currency,
74
+ type: price.type,
75
+ interval: price.recurring?.interval,
76
+ intervalCount: price.recurring?.interval_count
77
+ },
78
+ where: { stripeID: { equals: price.id } }
79
+ });
80
+ if (!priceUpserted) return null;
81
+ return {
82
+ productId: stripeProductID,
83
+ priceId: priceUpserted.id
84
+ };
85
+ }
86
+ const priceDeleted = async (price, payload) => {
87
+ const { id } = price;
88
+ try {
89
+ await payload.delete({
90
+ collection: COLLECTION_SLUG_PRICES,
91
+ where: { stripeID: { equals: id } }
92
+ });
93
+ } catch (error) {
94
+ payload.logger.error(`- Error deleting price: ${error}`);
95
+ throw error;
96
+ }
97
+ };
98
+
99
+ //#endregion
100
+ //#region src/server/actions/product.ts
101
+ const updateProducts = async (payload) => {
102
+ (await (await stripeBuilder()).products.list({
103
+ limit: 100,
104
+ active: true
105
+ })).data.forEach((product) => productSync(product, payload));
106
+ };
107
+ const productSync = async (object, payload) => {
108
+ const { id: stripeProductID, name, description, images } = object;
109
+ if (object.deleted !== void 0) return productDeleted(object, payload);
110
+ try {
111
+ await payloadUpsert({
112
+ payload,
113
+ collection: COLLECTION_SLUG_PRODUCTS,
114
+ data: {
115
+ prices: [],
116
+ stripeID: stripeProductID,
117
+ active: true,
118
+ metadata: object.metadata,
119
+ type: object.type,
120
+ name,
121
+ description,
122
+ images: images?.map((image) => ({ url: image })) || []
123
+ },
124
+ where: { stripeID: { equals: stripeProductID } }
125
+ });
126
+ } catch (error) {
127
+ console.error(error);
128
+ throw error;
129
+ }
130
+ };
131
+ const productDeleted = async (object, payload) => {
132
+ const { id: stripeProductID } = object;
133
+ try {
134
+ const payloadProductID = (await payload.find({
135
+ collection: COLLECTION_SLUG_PRODUCTS,
136
+ where: { stripeID: { equals: stripeProductID } }
137
+ })).docs?.[0]?.id;
138
+ if (payloadProductID) await payload.delete({
139
+ collection: COLLECTION_SLUG_PRODUCTS,
140
+ id: payloadProductID
141
+ });
142
+ } catch (error) {
143
+ payload.logger.error(`- Error deleting product: ${error}`);
144
+ throw error;
145
+ }
146
+ };
147
+
148
+ //#endregion
149
+ //#region src/server/utils/payload/sync-customer-by-email.ts
150
+ async function syncCustomerByEmail({ email, payload }) {
151
+ const customerId = (await payload.find({
152
+ collection: COLLECTION_SLUG_CUSTOMERS,
153
+ where: { email: { equals: email } }
154
+ })).docs?.[0]?.id;
155
+ await payload.update({
156
+ collection: "users",
157
+ data: { customer: customerId },
158
+ where: { email: { equals: email } }
159
+ });
160
+ }
161
+
162
+ //#endregion
163
+ //#region src/server/utils/payload/upsert-customer-inventory-and-sync-with-user.ts
164
+ async function upsertCustomerInventoryAndSyncWithUser(payload, inventory, email, stripeCustomerId) {
165
+ await payloadUpsert({
166
+ payload,
167
+ collection: COLLECTION_SLUG_CUSTOMERS,
168
+ data: {
169
+ email,
170
+ stripeId: stripeCustomerId,
171
+ inventory: inventory ?? generateCustomerInventory()
172
+ },
173
+ where: { email: { equals: email } }
174
+ });
175
+ await syncCustomerByEmail({
176
+ email,
177
+ payload
178
+ });
179
+ }
180
+
181
+ //#endregion
182
+ //#region src/server/utils/payload/get-userId-by-email.ts
183
+ /**
184
+ * Gets a user ID by email address using Payload's find method
185
+ * @param email - The email address to search for
186
+ * @returns The user ID if found, null otherwise
187
+ */
188
+ async function getUserIdByEmail({ email, payload }) {
189
+ return ((await payload.find({
190
+ collection: COLLECTION_SLUG_USER,
191
+ where: { email: { equals: email } }
192
+ })).docs?.[0])?.id;
193
+ }
194
+
195
+ //#endregion
196
+ //#region src/server/utils/stripe/get-customer.ts
197
+ async function getCustomer({ stripe, email }) {
198
+ stripe = stripe ?? stripeBuilder();
199
+ const customers$1 = await stripe.customers.search({ query: `email:'${email}'` });
200
+ return customers$1.data.length ? customers$1.data[0] : null;
201
+ }
202
+ async function resolveStripeCustomer({ customer }) {
203
+ const stripe = stripeBuilder();
204
+ if (typeof customer === "string") return await stripe.customers.retrieve(customer);
205
+ return customer;
206
+ }
207
+
208
+ //#endregion
209
+ //#region src/server/utils/payload/remove-customer-by-stripe-id.ts
210
+ async function removeCustomerByStripeId({ stripeId, payload }) {
211
+ await payload.delete({
212
+ collection: COLLECTION_SLUG_CUSTOMERS,
213
+ where: { stripeId: { equals: stripeId } }
214
+ });
215
+ payload.logger.info(`✅ Successfully removed customer with Stripe ID: ${stripeId}`);
216
+ }
217
+
218
+ //#endregion
219
+ //#region src/server/utils/payload/find-or-create-customer.ts
220
+ /**
221
+ * Finds a customer by email address in the customers collection, or creates one if not found
222
+ * @param email - The email address to search for
223
+ * @param payload - Payload instance
224
+ * @param stripeId - Optional Stripe customer ID to set when creating
225
+ * @returns The customer document (found or created)
226
+ */
227
+ async function findOrCreateCustomer({ email, payload, stripeId }) {
228
+ if (!email) {
229
+ payload.logger.error("Email is required to find or create customer");
230
+ return null;
231
+ }
232
+ try {
233
+ let existingCustomer = (await payload.find({
234
+ collection: COLLECTION_SLUG_CUSTOMERS,
235
+ where: { email: { equals: email } }
236
+ })).docs?.[0];
237
+ if (existingCustomer) {
238
+ existingCustomer.inventory = existingCustomer?.inventory ? existingCustomer.inventory : generateCustomerInventory();
239
+ return existingCustomer;
240
+ }
241
+ payload.logger.info(`Creating new customer for email: ${email}`);
242
+ const newCustomer = await payload.create({
243
+ collection: COLLECTION_SLUG_CUSTOMERS,
244
+ data: {
245
+ email,
246
+ stripeId: stripeId || "",
247
+ inventory: generateCustomerInventory()
248
+ }
249
+ });
250
+ payload.logger.info(`✅ Successfully created customer for email: ${email}`);
251
+ return newCustomer;
252
+ } catch (error) {
253
+ payload.logger.error(`Error finding or creating customer for email ${email}: ${error}`);
254
+ throw error;
255
+ }
256
+ }
257
+
258
+ //#endregion
259
+ //#region src/server/actions/subscription.ts
260
+ const subscriptionUpsert = async (subscription, payload, onSubscriptionUpdate) => {
261
+ const { id: stripeID, status, customer: stripeCustomer } = subscription;
262
+ const customer = await resolveStripeCustomer({ customer: stripeCustomer });
263
+ const error = (message) => payload.logger.error("Subscription Upsert: ", message);
264
+ const info = (message) => payload.logger.info("Subscription Upsert: ", message);
265
+ if (!customer) {
266
+ error("No stripe customer found for subscription");
267
+ return;
268
+ }
269
+ if (customer.deleted) {
270
+ await removeCustomerByStripeId({
271
+ stripeId: customer.id,
272
+ payload
273
+ });
274
+ return;
275
+ }
276
+ if (!customer.email) {
277
+ error("No email found for stripe customer");
278
+ return;
279
+ }
280
+ const email = customer.email;
281
+ const stripeId = customer.id;
282
+ try {
283
+ const customer$1 = await findOrCreateCustomer({
284
+ email,
285
+ payload,
286
+ stripeId
287
+ });
288
+ const item = subscription.items.data.at(0);
289
+ if (!item || !customer$1) {
290
+ error(`No item ${item} or customer ${customer$1} found`);
291
+ return;
292
+ }
293
+ const { docs: products$1 } = await payload.find({
294
+ collection: COLLECTION_SLUG_PRODUCTS,
295
+ where: { stripeID: { equals: item.price.product } }
296
+ });
297
+ const product = products$1.at(0);
298
+ if (!product) return;
299
+ const inventory = customer$1.inventory;
300
+ inventory.subscriptions[stripeID] = {
301
+ ...subscription,
302
+ permissions: getPermissionsSlugs({ permissions: product.roles })
303
+ };
304
+ info(`INVENTORY OF THE SUBSCRIPTION ${inventory}`);
305
+ await upsertCustomerInventoryAndSyncWithUser(payload, inventory, email, stripeId);
306
+ if (["active", "trialing"].includes(status)) {
307
+ const userId = await getUserIdByEmail({
308
+ email,
309
+ payload
310
+ });
311
+ if (!userId) return;
312
+ await onSubscriptionUpdate("create", userId);
313
+ }
314
+ info(`✅ Successfully updated subscription with ID: ${stripeID} for user: ${email}`);
315
+ } catch (e) {
316
+ error(`- Error managing subscription: ${e}`);
317
+ throw e;
318
+ }
319
+ };
320
+ const subscriptionDeleted = async (subscription, payload, onSubscriptionUpdate) => {
321
+ const { id, customer: customerId } = subscription;
322
+ const customer = await resolveStripeCustomer({ customer: customerId });
323
+ const stripeId = customer?.id;
324
+ if (!customer) {
325
+ payload.logger.error("No stripe customer found for subscription");
326
+ return;
327
+ }
328
+ if (customer.deleted) {
329
+ await removeCustomerByStripeId({
330
+ stripeId: customer.id,
331
+ payload
332
+ });
333
+ return;
334
+ }
335
+ if (!customer.email) {
336
+ payload.logger.error("No email found for stripe customer");
337
+ return;
338
+ }
339
+ const email = customer.email;
340
+ try {
341
+ const customer$1 = await findOrCreateCustomer({
342
+ email,
343
+ payload,
344
+ stripeId
345
+ });
346
+ if (!customer$1) {
347
+ payload.logger.error("No customer found for subscription");
348
+ return;
349
+ }
350
+ const inventory = customer$1.inventory;
351
+ delete inventory.subscriptions[id];
352
+ await upsertCustomerInventoryAndSyncWithUser(payload, inventory, email, stripeId);
353
+ const userId = await getUserIdByEmail({
354
+ email,
355
+ payload
356
+ });
357
+ if (!userId) {
358
+ payload.logger.error("No user found for subscription");
359
+ return;
360
+ }
361
+ await onSubscriptionUpdate("delete", userId);
362
+ payload.logger.info(`✅ Successfully deleted subscription: ${id} for user: ${email}`);
363
+ } catch (error) {
364
+ payload.logger.error(`- Error deleting subscription: ${error}`);
365
+ throw error;
366
+ }
367
+ };
368
+
369
+ //#endregion
370
+ //#region src/server/actions/donation.ts
371
+ const paymentSucceeded = async (paymentIntent, payload) => {
372
+ const { id, customer: paymentCustomer } = paymentIntent;
373
+ const stripeCustomer = await resolveStripeCustomer({ customer: paymentCustomer });
374
+ if (!stripeCustomer) {
375
+ payload.logger.error("No stripe customer found for payment");
376
+ return;
377
+ }
378
+ if (stripeCustomer.deleted) {
379
+ await removeCustomerByStripeId({
380
+ stripeId: stripeCustomer.id,
381
+ payload
382
+ });
383
+ return;
384
+ }
385
+ if (!stripeCustomer.email) {
386
+ payload.logger.error("No email found for stripe customer");
387
+ return;
388
+ }
389
+ try {
390
+ const customer = await findOrCreateCustomer({
391
+ email: stripeCustomer.email,
392
+ payload,
393
+ stripeId: stripeCustomer.id
394
+ });
395
+ if (!customer) return;
396
+ if (!customer) {
397
+ payload.logger.error(`User not found for payment: ${stripeCustomer.email}`);
398
+ return;
399
+ }
400
+ let inventory = customer.inventory;
401
+ inventory.payments[id] = paymentIntent;
402
+ await payload.update({
403
+ collection: COLLECTION_SLUG_CUSTOMERS,
404
+ data: { inventory },
405
+ where: { email: { equals: stripeCustomer.email } }
406
+ });
407
+ payload.logger.info(`✅ Successfully recorded ${stripeCustomer.metadata?.type ?? "subscription"} with Payment Intent ID: ${id} for user: ${stripeCustomer.email}`);
408
+ } catch (error) {
409
+ payload.logger.error(`- Error recording payment: ${error}`);
410
+ throw error;
411
+ }
412
+ };
413
+
414
+ //#endregion
415
+ //#region src/server/actions/invoice.ts
416
+ const invoiceSucceeded = async (invoiceIntent, payload) => {
417
+ const { id, customer: paymentCustomer } = invoiceIntent;
418
+ const stripeCustomer = await resolveStripeCustomer({ customer: paymentCustomer });
419
+ if (!stripeCustomer) {
420
+ payload.logger.error("No stripe customer found for payment");
421
+ return;
422
+ }
423
+ if (stripeCustomer.deleted) {
424
+ await removeCustomerByStripeId({
425
+ stripeId: stripeCustomer.id,
426
+ payload
427
+ });
428
+ return;
429
+ }
430
+ if (!stripeCustomer.email) {
431
+ payload.logger.error("No email found for stripe customer");
432
+ return;
433
+ }
434
+ try {
435
+ const customer = await findOrCreateCustomer({
436
+ email: stripeCustomer.email,
437
+ payload,
438
+ stripeId: stripeCustomer.id
439
+ });
440
+ if (!customer) return;
441
+ let inventory = customer.inventory;
442
+ inventory.invoices[id] = invoiceIntent;
443
+ await payload.update({
444
+ collection: COLLECTION_SLUG_CUSTOMERS,
445
+ data: { inventory },
446
+ where: { email: { equals: stripeCustomer.email } }
447
+ });
448
+ payload.logger.info(`✅ Successfully recorded ${stripeCustomer.metadata?.type ?? "subscription"} with Payment Intent ID: ${id} for user: ${stripeCustomer.email}`);
449
+ } catch (error) {
450
+ payload.logger.error(`- Error recording payment: ${error}`);
451
+ throw error;
452
+ }
453
+ };
454
+
455
+ //#endregion
456
+ //#region src/server/actions/customer.ts
457
+ const customerDeleted = async (customer, payload) => {
458
+ const { id, email } = customer;
459
+ try {
460
+ await payload.delete({
461
+ collection: COLLECTION_SLUG_CUSTOMERS,
462
+ where: { email: { equals: email } }
463
+ });
464
+ payload.logger.info(`✅ Successfully deleted customer with Stripe ID: ${id}`);
465
+ } catch (error) {
466
+ payload.logger.error(`- Error deleting subscription: ${error}`);
467
+ throw error;
468
+ }
469
+ };
470
+
471
+ //#endregion
472
+ //#region src/server/access/get-current-user-query.ts
473
+ async function getCurrentUserQuery(payload) {
474
+ const headers$1 = await headers();
475
+ return (await payload.auth({ headers: headers$1 })).user;
476
+ }
477
+
478
+ //#endregion
479
+ //#region src/server/actions/unlock-item-for-user-action.ts
480
+ const addUniqueUnlock = (unlocks, collection, contentId) => {
481
+ if (unlocks.some((unlock) => unlock.collection === collection && unlock.id === contentId)) return unlocks;
482
+ return [...unlocks, {
483
+ collection,
484
+ id: contentId,
485
+ dateUnlocked: /* @__PURE__ */ new Date()
486
+ }];
487
+ };
488
+ const unlockItemForUser = async (getPayload, collection, contentId) => {
489
+ const payload = await getPayload();
490
+ const user = await getCurrentUserQuery(payload);
491
+ if (!user || !user.id) return { error: "Usuario no válido" };
492
+ const item = await payload.findByID({
493
+ collection,
494
+ id: contentId.toString()
495
+ });
496
+ if (!item) return { error: "Elemento no encontrado" };
497
+ if (!checkIfUserCanUnlockQuery(user, getPermissionsSlugs({ permissions: item.permissions }))) return { error: "No tienes permisos para desbloquear este elemento" };
498
+ if (countWeeklyUnlocksQuery(user) >= MAX_UNLOCKS_PER_WEEK) return { error: `Has alcanzado el límite de ${MAX_UNLOCKS_PER_WEEK} desbloqueos para esta semana` };
499
+ const inventory = user.inventory ?? generateUserInventory();
500
+ const updatedUnlocks = addUniqueUnlock(inventory.unlocks, collection, contentId);
501
+ if (updatedUnlocks.length === inventory.unlocks.length) return { data: true };
502
+ try {
503
+ await payload.update({
504
+ collection: COLLECTION_SLUG_USER,
505
+ id: user.id.toString(),
506
+ data: { inventory: {
507
+ ...inventory,
508
+ unlocks: updatedUnlocks
509
+ } }
510
+ });
511
+ return { data: true };
512
+ } catch (error) {
513
+ console.error("Error al actualizar el inventario del usuario:", error);
514
+ return { error: "Error al actualizar el inventario del usuario" };
515
+ }
516
+ };
517
+
518
+ //#endregion
519
+ //#region src/server/actions/update-products-and-prices-action.ts
520
+ async function updateProductsAndPrices(payload) {
521
+ await updateProducts(payload);
522
+ await updatePrices(payload);
523
+ }
524
+
525
+ //#endregion
526
+ //#region src/server/utils/stripe/create-customer-at-stripe.ts
527
+ async function createCustomerAtStripe({ stripe, email, name }) {
528
+ stripe = stripe ?? stripeBuilder();
529
+ return await stripe.customers.create({
530
+ email,
531
+ name: name || void 0
532
+ });
533
+ }
534
+
535
+ //#endregion
536
+ //#region src/server/utils/stripe/get-customer-from-stripe-or-create.ts
537
+ async function getCustomerFromStripeOrCreate(email, name) {
538
+ const stripe = stripeBuilder();
539
+ let customer = await getCustomer({
540
+ stripe,
541
+ email
542
+ });
543
+ if (!customer) customer = await createCustomerAtStripe({
544
+ stripe,
545
+ email,
546
+ name
547
+ });
548
+ return customer.id;
549
+ }
550
+
551
+ //#endregion
552
+ //#region src/server/api/handle-donation.ts
553
+ async function handleDonation(request, getPayload, getRoutes) {
554
+ const payload = await getPayload();
555
+ const payloadUser = await getCurrentUserQuery(payload);
556
+ if (!payloadUser || !payloadUser.email) throw new Error("You must be logged in to make a donation");
557
+ const amountParam = new URL(request.url).searchParams.get("amount");
558
+ if (!amountParam) throw new Error("Amount is required");
559
+ const amount = parseInt(amountParam);
560
+ if (amount < 100) throw new Error("Minimum donation amount is €1");
561
+ const stripe = stripeBuilder();
562
+ const customerId = await getCustomerFromStripeOrCreate(payloadUser.email, payloadUser.name);
563
+ await upsertCustomerInventoryAndSyncWithUser(payload, payloadUser.customer?.inventory, payloadUser.email, customerId);
564
+ const metadata = { type: "donation" };
565
+ const session = await stripe.checkout.sessions.create({
566
+ customer: customerId,
567
+ payment_method_types: ["card"],
568
+ line_items: [{
569
+ price_data: {
570
+ currency: "eur",
571
+ product_data: {
572
+ name: "Donación - Portal Escohotado",
573
+ description: "Apoyo al mantenimiento del legado digital de Antonio Escohotado"
574
+ },
575
+ unit_amount: amount
576
+ },
577
+ quantity: 1
578
+ }],
579
+ mode: "payment",
580
+ success_url: `${process.env.DOMAIN}${getRoutes().nextJS.subscriptionPageHref}?success=donation`,
581
+ cancel_url: `${process.env.DOMAIN}${getRoutes().nextJS.subscriptionPageHref}?error=donation_cancelled`,
582
+ metadata,
583
+ payment_intent_data: { metadata },
584
+ invoice_creation: {
585
+ enabled: true,
586
+ invoice_data: { metadata }
587
+ }
588
+ });
589
+ return NextResponse.json({ url: session.url });
590
+ }
591
+
592
+ //#endregion
593
+ //#region src/server/api/handle-update.ts
594
+ async function handleUpdate(request, getPayload, getRoutes) {
595
+ const payload = await getPayload();
596
+ const payloadUser = await getCurrentUserQuery(payload);
597
+ if (!payloadUser) throw new Error("You must be logged in to access this page");
598
+ const { searchParams } = new URL(request.url);
599
+ const subscriptionId = searchParams.get("subscriptionId");
600
+ const cancelAtPeriodEnd = searchParams.get("cancelAtPeriodEnd") === "true";
601
+ if (!subscriptionId) throw Error("SubscriptionId could not be found.");
602
+ await stripeBuilder().subscriptions.update(subscriptionId, { cancel_at_period_end: cancelAtPeriodEnd });
603
+ const customer = payloadUser.customer;
604
+ console.error("UPDATE: customer", customer);
605
+ const inventory = customer.inventory;
606
+ if (inventory && inventory.subscriptions && inventory.subscriptions[subscriptionId]) inventory.subscriptions[subscriptionId].cancel_at_period_end = cancelAtPeriodEnd;
607
+ await upsertCustomerInventoryAndSyncWithUser(payload, inventory, customer.email);
608
+ const routes = getRoutes();
609
+ return NextResponse.redirect(`${process.env.DOMAIN}${routes.nextJS.subscriptionPageHref}?refresh=${Date.now()}`, 303);
610
+ }
611
+
612
+ //#endregion
613
+ //#region src/server/api/handle-portal.ts
614
+ async function handlePortal(request, getPayload, getRoutes) {
615
+ const payload = await getPayload();
616
+ const payloadUser = await getCurrentUserQuery(payload);
617
+ if (!payloadUser || !payloadUser.email) throw new Error("You must be logged in to access this page");
618
+ const url = new URL(request.url);
619
+ const cancelSubscriptionId = url.searchParams.get("cancelSubscriptionId");
620
+ const updateSubscriptionId = url.searchParams.get("updateSubscriptionId");
621
+ let flowData;
622
+ if (cancelSubscriptionId) flowData = {
623
+ type: "subscription_cancel",
624
+ subscription_cancel: { subscription: cancelSubscriptionId }
625
+ };
626
+ else if (updateSubscriptionId) flowData = {
627
+ type: "subscription_update",
628
+ subscription_update: { subscription: updateSubscriptionId }
629
+ };
630
+ const stripe = stripeBuilder();
631
+ const routes = getRoutes();
632
+ const customerId = await getCustomerFromStripeOrCreate(payloadUser.email, payloadUser.name);
633
+ await upsertCustomerInventoryAndSyncWithUser(payload, payloadUser.customer?.inventory, payloadUser.email, customerId);
634
+ const session = await stripe.billingPortal.sessions.create({
635
+ flow_data: flowData,
636
+ customer: customerId,
637
+ return_url: `${process.env.DOMAIN}${routes.nextJS.subscriptionPageHref}`
638
+ });
639
+ return NextResponse.redirect(session.url, 303);
640
+ }
641
+
642
+ //#endregion
643
+ //#region src/server/api/handle-checkout.ts
644
+ async function handleCheckout(request, getPayload, getRoutes) {
645
+ const payload = await getPayload();
646
+ const payloadUser = await getCurrentUserQuery(payload);
647
+ const priceId = new URL(request.url).searchParams.get("priceId");
648
+ if (!priceId || !payloadUser || !payloadUser.email) throw new Error("Invalid request");
649
+ const stripe = stripeBuilder();
650
+ const routes = getRoutes();
651
+ const customerId = await getCustomerFromStripeOrCreate(payloadUser.email, payloadUser.name);
652
+ await upsertCustomerInventoryAndSyncWithUser(payload, payloadUser.customer?.inventory, payloadUser.email, customerId);
653
+ const metadata = { type: "subscription" };
654
+ const checkoutResult = await stripe.checkout.sessions.create({
655
+ success_url: `${process.env.DOMAIN}${routes.nextJS.subscriptionPageHref}?success=${Date.now()}`,
656
+ cancel_url: `${process.env.DOMAIN}${routes.nextJS.subscriptionPageHref}?error=${Date.now()}`,
657
+ mode: "subscription",
658
+ customer: customerId,
659
+ client_reference_id: String(payloadUser.id),
660
+ line_items: [{
661
+ price: priceId,
662
+ quantity: 1
663
+ }],
664
+ metadata,
665
+ tax_id_collection: { enabled: true },
666
+ customer_update: {
667
+ name: "auto",
668
+ address: "auto",
669
+ shipping: "auto"
670
+ },
671
+ subscription_data: { metadata }
672
+ });
673
+ if (checkoutResult.url) return NextResponse.redirect(checkoutResult.url, 303);
674
+ else return NextResponse.json("Create checkout url failed", { status: 406 });
675
+ }
676
+
677
+ //#endregion
678
+ //#region src/server/api/index.ts
679
+ function createStripeInventoryHandlers(getPayload, getRoutes) {
680
+ return {
681
+ checkout: { GET: (request) => handleCheckout(request, getPayload, getRoutes) },
682
+ portal: { GET: (request) => handlePortal(request, getPayload, getRoutes) },
683
+ update: { GET: (request) => handleUpdate(request, getPayload, getRoutes) },
684
+ donation: { GET: (request) => handleDonation(request, getPayload, getRoutes) }
685
+ };
686
+ }
687
+ function createRouteHandlers(getPayload, getRoutes) {
688
+ return { GET: async (request, { params }) => {
689
+ const path = (await params).stripe[0];
690
+ const handlers = createStripeInventoryHandlers(getPayload, getRoutes);
691
+ if (path === "checkout" || path === "portal" || path === "update" || path === "donation") return handlers[path].GET(request);
692
+ return NextResponse.json({ error: "Route not found" }, { status: 404 });
693
+ } };
694
+ }
695
+
696
+ //#endregion
697
+ //#region src/server/access/access-queries.ts
698
+ const isAdmin = ({ req }) => {
699
+ return req?.user?.roles?.includes(permissionSlugs.webAdmin) || false;
700
+ };
701
+ const isAnyone = () => true;
702
+ const isAdminOrCurrentUser = ({ req }) => {
703
+ if (req?.user?.roles?.includes(permissionSlugs.webAdmin)) return true;
704
+ return { id: { equals: req.user?.id } };
705
+ };
706
+ const isAdminOrPublished = ({ req: { user } }) => {
707
+ if (user && user?.roles?.includes(permissionSlugs.webAdmin)) return true;
708
+ return { _status: { equals: "published" } };
709
+ };
710
+ const isAdminOrStripeActive = ({ req: { user } }) => {
711
+ if (user && user?.roles?.includes(permissionSlugs.webAdmin)) return true;
712
+ return { active: { equals: true } };
713
+ };
714
+ const isAdminOrUserFieldMatchingCurrentUser = ({ req: { user } }) => {
715
+ if (user) {
716
+ if (user?.roles?.includes(permissionSlugs.webAdmin)) return true;
717
+ return { user: { equals: user?.id } };
718
+ }
719
+ return false;
720
+ };
721
+ const loggedInOrPublished = ({ req: { user } }) => {
722
+ if (user) return true;
723
+ return { _status: { equals: "published" } };
724
+ };
725
+
726
+ //#endregion
727
+ //#region src/server/collections/customers.ts
728
+ const customers = {
729
+ slug: COLLECTION_SLUG_CUSTOMERS,
730
+ admin: {
731
+ useAsTitle: "email",
732
+ group: "Stripe",
733
+ defaultColumns: [
734
+ "email",
735
+ "stripeId",
736
+ "createdAt"
737
+ ]
738
+ },
739
+ access: {
740
+ read: () => true,
741
+ create: () => false,
742
+ update: () => false,
743
+ delete: isAdmin
744
+ },
745
+ fields: [
746
+ {
747
+ name: "email",
748
+ type: "email",
749
+ required: true,
750
+ unique: true,
751
+ admin: { position: "sidebar" }
752
+ },
753
+ {
754
+ name: "stripeId",
755
+ type: "text",
756
+ required: true,
757
+ unique: true,
758
+ admin: {
759
+ position: "sidebar",
760
+ readOnly: true
761
+ }
762
+ },
763
+ {
764
+ name: "inventory",
765
+ type: "json",
766
+ label: "Inventario",
767
+ admin: {
768
+ description: "Datos de inventario de Stripe almacenados como JSON",
769
+ readOnly: true
770
+ }
771
+ }
772
+ ]
773
+ };
774
+
775
+ //#endregion
776
+ //#region src/server/collections/fields/permission-evaluation-field.ts
777
+ const permissionEvaluationField = {
778
+ type: "row",
779
+ fields: [{
780
+ type: "select",
781
+ name: "type_of_permissions",
782
+ options: [
783
+ {
784
+ label: "Todos",
785
+ value: "all"
786
+ },
787
+ {
788
+ label: "Permisos por roles",
789
+ value: "roles"
790
+ },
791
+ {
792
+ label: "Solo para usuarios sin roles",
793
+ value: "only_no_roles"
794
+ },
795
+ {
796
+ label: "Solo invitados",
797
+ value: "only_guess"
798
+ }
799
+ ],
800
+ defaultValue: "all",
801
+ label: "Tipo de permisos"
802
+ }, {
803
+ type: "relationship",
804
+ name: "permissions",
805
+ relationTo: [COLLECTION_SLUG_TAXONOMY],
806
+ hasMany: false,
807
+ admin: { condition: (_, siblingData) => siblingData.type_of_permissions === "roles" }
808
+ }]
809
+ };
810
+
811
+ //#endregion
812
+ //#region src/server/collections/prices.ts
813
+ const prices = {
814
+ slug: COLLECTION_SLUG_PRICES,
815
+ admin: {
816
+ useAsTitle: "unitAmount",
817
+ group: "Stripe"
818
+ },
819
+ access: {
820
+ read: isAdminOrStripeActive,
821
+ create: () => false,
822
+ update: () => false,
823
+ delete: isAdmin
824
+ },
825
+ fields: [
826
+ {
827
+ name: "stripeID",
828
+ label: "Stripe ID",
829
+ type: "text",
830
+ required: true,
831
+ admin: {
832
+ position: "sidebar",
833
+ readOnly: true
834
+ }
835
+ },
836
+ {
837
+ name: "stripeProductId",
838
+ type: "text",
839
+ required: true,
840
+ admin: {
841
+ position: "sidebar",
842
+ readOnly: true
843
+ }
844
+ },
845
+ {
846
+ name: "product",
847
+ type: "join",
848
+ collection: COLLECTION_SLUG_PRODUCTS,
849
+ on: "prices",
850
+ hasMany: false
851
+ },
852
+ {
853
+ name: "active",
854
+ type: "checkbox",
855
+ required: true,
856
+ admin: { position: "sidebar" }
857
+ },
858
+ {
859
+ name: "description",
860
+ type: "textarea"
861
+ },
862
+ {
863
+ type: "row",
864
+ fields: [
865
+ {
866
+ name: "unitAmount",
867
+ type: "number",
868
+ required: true
869
+ },
870
+ {
871
+ name: "currency",
872
+ type: "text",
873
+ required: true
874
+ },
875
+ {
876
+ name: "type",
877
+ type: "select",
878
+ options: formatOptions(PricingType),
879
+ required: true
880
+ }
881
+ ]
882
+ },
883
+ {
884
+ type: "row",
885
+ fields: [
886
+ {
887
+ name: "interval",
888
+ type: "select",
889
+ options: formatOptions(PricingPlanInterval)
890
+ },
891
+ {
892
+ name: "intervalCount",
893
+ type: "number"
894
+ },
895
+ {
896
+ name: "trialPeriodDays",
897
+ type: "number"
898
+ }
899
+ ]
900
+ },
901
+ {
902
+ name: "metadata",
903
+ type: "json",
904
+ label: "Metadata"
905
+ }
906
+ ]
907
+ };
908
+
909
+ //#endregion
910
+ //#region src/server/collections/products.ts
911
+ const products = {
912
+ slug: COLLECTION_SLUG_PRODUCTS,
913
+ admin: {
914
+ useAsTitle: "name",
915
+ group: "Stripe"
916
+ },
917
+ access: {
918
+ read: isAdminOrStripeActive,
919
+ create: () => false
920
+ },
921
+ fields: [
922
+ {
923
+ name: "stripeID",
924
+ label: "Stripe ID",
925
+ type: "text",
926
+ required: true,
927
+ admin: {
928
+ position: "sidebar",
929
+ readOnly: true
930
+ }
931
+ },
932
+ {
933
+ name: "type",
934
+ type: "select",
935
+ options: [{
936
+ value: "good",
937
+ label: "Bienes"
938
+ }, {
939
+ value: "service",
940
+ label: "Service"
941
+ }]
942
+ },
943
+ {
944
+ name: "active",
945
+ type: "checkbox",
946
+ required: true,
947
+ admin: { position: "sidebar" }
948
+ },
949
+ {
950
+ name: "show",
951
+ type: "checkbox",
952
+ defaultValue: false,
953
+ admin: { position: "sidebar" }
954
+ },
955
+ {
956
+ name: "name",
957
+ type: "text",
958
+ required: true
959
+ },
960
+ {
961
+ name: "description",
962
+ type: "textarea"
963
+ },
964
+ {
965
+ name: "images",
966
+ type: "array",
967
+ fields: [{
968
+ type: "text",
969
+ name: "url"
970
+ }]
971
+ },
972
+ {
973
+ name: "prices",
974
+ type: "relationship",
975
+ relationTo: COLLECTION_SLUG_PRICES,
976
+ hasMany: true,
977
+ required: false
978
+ },
979
+ {
980
+ name: "metadata",
981
+ type: "json",
982
+ label: "Metadata"
983
+ },
984
+ {
985
+ type: "array",
986
+ name: "features",
987
+ fields: [{
988
+ type: "text",
989
+ name: "title"
990
+ }]
991
+ },
992
+ buildTaxonomyRelationship({
993
+ name: "roles",
994
+ label: "Roles",
995
+ defaultValue: [],
996
+ filterOptions: () => {
997
+ return { "payload.types": { in: ["role"] } };
998
+ },
999
+ required: false
1000
+ })
1001
+ ]
1002
+ };
1003
+
1004
+ //#endregion
1005
+ //#region src/server/plugin.ts
1006
+ const plugin = (onSubscriptionUpdate) => {
1007
+ return stripePlugin({
1008
+ isTestKey: process.env.STRIPE_SECRET_KEY?.includes("sk_test"),
1009
+ stripeSecretKey: process.env.STRIPE_SECRET_KEY || "",
1010
+ stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOK_SECRET,
1011
+ webhooks: {
1012
+ "price.deleted": async ({ event, payload }) => await priceDeleted(event.data.object, payload),
1013
+ "customer.subscription.created": async ({ event, payload }) => await subscriptionUpsert(event.data.object, payload, onSubscriptionUpdate),
1014
+ "customer.subscription.paused": async ({ event, payload }) => await subscriptionUpsert(event.data.object, payload, onSubscriptionUpdate),
1015
+ "customer.subscription.updated": async ({ event, payload }) => await subscriptionUpsert(event.data.object, payload, onSubscriptionUpdate),
1016
+ "customer.subscription.deleted": async ({ event, payload }) => await subscriptionDeleted(event.data.object, payload, onSubscriptionUpdate),
1017
+ "customer.deleted": async ({ event, payload }) => await customerDeleted(event.data.object, payload),
1018
+ "product.deleted": async ({ event, payload }) => await productDeleted(event.data.object, payload),
1019
+ "payment_intent.succeeded": async ({ event, payload }) => {
1020
+ await paymentSucceeded(event.data.object, payload);
1021
+ },
1022
+ "invoice.paid": async ({ event, payload }) => {
1023
+ await invoiceSucceeded(event.data.object, payload);
1024
+ }
1025
+ }
1026
+ });
1027
+ };
1028
+
1029
+ //#endregion
1030
+ export { createCustomerAtStripe, createRouteHandlers, createStripeInventoryHandlers, customerDeleted, customers, getCustomer, getCustomerFromStripeOrCreate, invoiceSucceeded, isAdmin, isAdminOrCurrentUser, isAdminOrPublished, isAdminOrStripeActive, isAdminOrUserFieldMatchingCurrentUser, isAnyone, loggedInOrPublished, payloadUpsert, paymentSucceeded, permissionEvaluationField, plugin, priceDeleted, priceUpsert, prices, productDeleted, productSync, products, resolveStripeCustomer, stripeBuilder, subscriptionDeleted, subscriptionUpsert, syncCustomerByEmail, unlockItemForUser, updatePrices, updateProducts, updateProductsAndPrices, upsertCustomerInventoryAndSyncWithUser };
1031
+ //# sourceMappingURL=index.mjs.map