@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.
- package/README.md +24 -0
- package/dist/index-lWjbVzU5.d.mts +2282 -0
- package/dist/index-lWjbVzU5.d.mts.map +1 -0
- package/dist/index.d.mts +123 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/server/index.d.mts +24589 -0
- package/dist/server/index.d.mts.map +1 -0
- package/dist/server/index.mjs +1031 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/src-BmlQoR4x.mjs +193 -0
- package/dist/src-BmlQoR4x.mjs.map +1 -0
- package/package.json +63 -0
|
@@ -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
|