@reeboot/strapi-payment-plugin 0.0.9 → 0.0.10

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.
Files changed (36) hide show
  1. package/dist/_chunks/{Analytics-kChERzX4.mjs → Analytics-CPXtqS6s.mjs} +1 -1
  2. package/dist/_chunks/{Analytics-DjXqUZy1.js → Analytics-ZpHHWBQt.js} +1 -1
  3. package/dist/_chunks/{App-D5x-l0Sz.mjs → App-CZNZ1JKf.mjs} +13 -7
  4. package/dist/_chunks/{App-BpwS5-wg.js → App-CsbyykNC.js} +13 -7
  5. package/dist/_chunks/{Customers-ChrS373z.js → Customers-D4RVSZ_m.js} +1 -1
  6. package/dist/_chunks/{Customers-BYQZcZeb.mjs → Customers-DMDEBpH9.mjs} +1 -1
  7. package/dist/_chunks/{Dashboard-DUWxJQ7q.mjs → Dashboard-D-ckyWDO.mjs} +1 -1
  8. package/dist/_chunks/{Dashboard-DANhyMZK.js → Dashboard-NNqGJmYw.js} +1 -1
  9. package/dist/_chunks/{Orders-BXyc3CTW.js → Orders-BELfgToU.js} +1 -1
  10. package/dist/_chunks/{Orders-B3GZULs3.mjs → Orders-BIkRttRZ.mjs} +1 -1
  11. package/dist/_chunks/{Payments-DmAzroEv.js → Payments-CIEb4f07.js} +1 -1
  12. package/dist/_chunks/{Payments-DL6hne_y.mjs → Payments-CKlL-PCV.mjs} +1 -1
  13. package/dist/_chunks/{Settings-Ck1G0iaS.mjs → Settings-BQXkYm-c.mjs} +1 -1
  14. package/dist/_chunks/{Settings-CMEzIMqt.js → Settings-CcY98Hyw.js} +1 -1
  15. package/dist/_chunks/Subscriptions--cI3HC2x.js +178 -0
  16. package/dist/_chunks/Subscriptions-Dq8Uk7sK.mjs +178 -0
  17. package/dist/_chunks/{index-Bo7VLX9i.mjs → index-BBarHYyt.mjs} +1 -1
  18. package/dist/_chunks/{index-Cmn7Tfmf.js → index-DBZ4rcUW.js} +1 -1
  19. package/dist/admin/index.js +1 -1
  20. package/dist/admin/index.mjs +1 -1
  21. package/dist/admin/src/index.d.ts +22 -1
  22. package/dist/admin/src/pages/Subscriptions.d.ts +2 -0
  23. package/dist/admin/src/types/index.d.ts +20 -0
  24. package/dist/server/index.js +618 -46
  25. package/dist/server/index.mjs +618 -46
  26. package/dist/server/src/content-types/customer/index.d.ts +6 -0
  27. package/dist/server/src/content-types/index.d.ts +89 -0
  28. package/dist/server/src/content-types/subscription/index.d.ts +84 -0
  29. package/dist/server/src/controllers/index.d.ts +7 -0
  30. package/dist/server/src/controllers/stripe.d.ts +34 -2
  31. package/dist/server/src/index.d.ts +99 -6
  32. package/dist/server/src/middlewares/index.d.ts +3 -3
  33. package/dist/server/src/services/stripe.d.ts +49 -10
  34. package/dist/server/src/services/types/api.d.ts +24 -5
  35. package/dist/server/src/types/plugin-config.d.ts +360 -0
  36. package/package.json +1 -1
@@ -13,7 +13,7 @@ const config = {
13
13
  validator() {
14
14
  }
15
15
  };
16
- const schema$2 = {
16
+ const schema$3 = {
17
17
  kind: "collectionType",
18
18
  collectionName: "customers",
19
19
  info: {
@@ -76,11 +76,17 @@ const schema$2 = {
76
76
  relation: "oneToMany",
77
77
  target: "plugin::payment-plugin.payment",
78
78
  mappedBy: "customer"
79
+ },
80
+ subscriptions: {
81
+ type: "relation",
82
+ relation: "oneToMany",
83
+ target: "plugin::payment-plugin.subscription",
84
+ mappedBy: "customer"
79
85
  }
80
86
  }
81
87
  };
82
- const customer = { schema: schema$2 };
83
- const schema$1 = {
88
+ const customer = { schema: schema$3 };
89
+ const schema$2 = {
84
90
  kind: "collectionType",
85
91
  collectionName: "orders",
86
92
  info: {
@@ -165,8 +171,8 @@ const schema$1 = {
165
171
  }
166
172
  }
167
173
  };
168
- const order = { schema: schema$1 };
169
- const schema = {
174
+ const order = { schema: schema$2 };
175
+ const schema$1 = {
170
176
  kind: "collectionType",
171
177
  collectionName: "payments",
172
178
  info: {
@@ -238,17 +244,115 @@ const schema = {
238
244
  }
239
245
  }
240
246
  };
241
- const payment = { schema };
247
+ const payment = { schema: schema$1 };
248
+ const kind = "collectionType";
249
+ const collectionName = "payment_plugin_subscriptions";
250
+ const info = {
251
+ singularName: "subscription",
252
+ pluralName: "subscriptions",
253
+ displayName: "Subscription",
254
+ description: "Stripe subscription records for recurring billing"
255
+ };
256
+ const options = {
257
+ draftAndPublish: false
258
+ };
259
+ const attributes = {
260
+ stripe_subscription_id: {
261
+ type: "string",
262
+ unique: true,
263
+ required: true,
264
+ configurable: false
265
+ },
266
+ stripe_customer_id: {
267
+ type: "string",
268
+ required: true,
269
+ configurable: false
270
+ },
271
+ stripe_price_id: {
272
+ type: "string",
273
+ required: true,
274
+ configurable: false
275
+ },
276
+ stripe_product_id: {
277
+ type: "string",
278
+ configurable: false
279
+ },
280
+ status: {
281
+ type: "enumeration",
282
+ "enum": [
283
+ "active",
284
+ "canceled",
285
+ "incomplete",
286
+ "incomplete_expired",
287
+ "past_due",
288
+ "paused",
289
+ "trialing",
290
+ "unpaid"
291
+ ],
292
+ required: true,
293
+ "default": "incomplete",
294
+ configurable: false
295
+ },
296
+ current_period_start: {
297
+ type: "datetime",
298
+ configurable: false
299
+ },
300
+ current_period_end: {
301
+ type: "datetime",
302
+ configurable: false
303
+ },
304
+ cancel_at_period_end: {
305
+ type: "boolean",
306
+ "default": false,
307
+ configurable: false
308
+ },
309
+ canceled_at: {
310
+ type: "datetime",
311
+ configurable: false
312
+ },
313
+ trial_start: {
314
+ type: "datetime",
315
+ configurable: false
316
+ },
317
+ trial_end: {
318
+ type: "datetime",
319
+ configurable: false
320
+ },
321
+ latest_invoice_id: {
322
+ type: "string",
323
+ configurable: false
324
+ },
325
+ metadata: {
326
+ type: "json",
327
+ configurable: false
328
+ },
329
+ customer: {
330
+ type: "relation",
331
+ relation: "manyToOne",
332
+ target: "plugin::payment-plugin.customer",
333
+ inversedBy: "subscriptions"
334
+ }
335
+ };
336
+ const schema = {
337
+ kind,
338
+ collectionName,
339
+ info,
340
+ options,
341
+ attributes
342
+ };
343
+ const subscription = { schema };
242
344
  const contentTypes = {
243
345
  customer,
244
346
  order,
245
- payment
347
+ payment,
348
+ subscription
246
349
  };
247
350
  const controller = ({ strapi: strapi2 }) => ({
248
351
  index(ctx) {
249
352
  ctx.body = strapi2.plugin("payment-plugin").service("service").getWelcomeMessage();
250
353
  }
251
354
  });
355
+ const getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
252
356
  const stripeController = {
253
357
  /**
254
358
  * Create a payment intent
@@ -419,12 +523,13 @@ const stripeController = {
419
523
  await stripeService2.handleWebhook(event);
420
524
  ctx.body = { received: true };
421
525
  } catch (error) {
422
- if (error.message.includes("No signatures found matching") || error.message.includes("Webhook payload must be provided as a string or a Buffer")) {
526
+ const message = getErrorMessage(error);
527
+ if (message.includes("No signatures found matching") || message.includes("Webhook payload must be provided as a string or a Buffer")) {
423
528
  strapi.log.error('Stripe Webhook Error: Payload is not raw. Ensure "includeUnparsed: true" is set in config/middlewares.ts for "strapi::body".');
424
529
  return ctx.badRequest("Webhook verification failed: Payload must be raw. Check your Strapi configuration for strapi::body middleware.");
425
530
  }
426
- strapi.log.error("Failed to handle webhook", { error: error.message, stack: error.stack });
427
- ctx.badRequest(`Webhook Error: ${error.message}`);
531
+ strapi.log.error("Failed to handle webhook", { message, stack: error instanceof Error ? error.stack : void 0 });
532
+ ctx.badRequest(`Webhook Error: ${message}`);
428
533
  }
429
534
  },
430
535
  /**
@@ -782,24 +887,28 @@ const stripeController = {
782
887
  const { startDate, endDate, format = "json" } = ctx.query;
783
888
  const filters = {};
784
889
  if (startDate || endDate) {
785
- filters.createdAt = {};
786
- if (startDate) filters.createdAt.$gte = new Date(startDate).toISOString();
787
- if (endDate) filters.createdAt.$lte = new Date(endDate).toISOString();
890
+ const dateFilter = {};
891
+ if (startDate) dateFilter.$gte = new Date(startDate).toISOString();
892
+ if (endDate) dateFilter.$lte = new Date(endDate).toISOString();
893
+ filters.createdAt = dateFilter;
788
894
  }
789
- const payments = await strapi.documents("plugin::payment-plugin.payment").findMany({
895
+ const rawPayments = await strapi.documents("plugin::payment-plugin.payment").findMany({
790
896
  filters,
791
897
  populate: ["customer", "order"],
792
898
  sort: { createdAt: "desc" }
793
899
  });
900
+ const payments = rawPayments;
794
901
  const summary = {
795
902
  totalPayments: payments.length,
796
903
  totalAmount: payments.reduce((sum, p) => sum + p.amount, 0),
797
904
  byStatus: payments.reduce((acc, p) => {
798
- acc[p.payment_status] = (acc[p.payment_status] || 0) + 1;
905
+ const s = p.payment_status || "unknown";
906
+ acc[s] = (acc[s] || 0) + 1;
799
907
  return acc;
800
908
  }, {}),
801
909
  byCurrency: payments.reduce((acc, p) => {
802
- acc[p.currency] = (acc[p.currency] || 0) + 1;
910
+ const c = p.currency || "unknown";
911
+ acc[c] = (acc[c] || 0) + 1;
803
912
  return acc;
804
913
  }, {})
805
914
  };
@@ -890,8 +999,9 @@ const stripeController = {
890
999
  fields: ["amount", "createdAt", "currency"],
891
1000
  sort: { createdAt: "asc" }
892
1001
  });
893
- const dailyStats = payments.reduce((acc, payment2) => {
894
- const date = new Date(payment2.createdAt).toISOString().split("T")[0];
1002
+ const typedPayments = payments;
1003
+ const dailyStats = typedPayments.reduce((acc, payment2) => {
1004
+ const date = new Date(payment2.createdAt || "").toISOString().split("T")[0];
895
1005
  if (!acc[date]) {
896
1006
  acc[date] = { count: 0, amount: 0, currency: payment2.currency };
897
1007
  }
@@ -899,14 +1009,15 @@ const stripeController = {
899
1009
  acc[date].amount += payment2.amount;
900
1010
  return acc;
901
1011
  }, {});
1012
+ const totalVolume = typedPayments.reduce((sum, p) => sum + p.amount, 0);
902
1013
  ctx.body = {
903
1014
  success: true,
904
1015
  data: {
905
1016
  period,
906
1017
  dailyStats,
907
- totalVolume: payments.reduce((sum, p) => sum + p.amount, 0),
908
- totalTransactions: payments.length,
909
- averageTransaction: payments.length > 0 ? payments.reduce((sum, p) => sum + p.amount, 0) / payments.length : 0
1018
+ totalVolume,
1019
+ totalTransactions: typedPayments.length,
1020
+ averageTransaction: typedPayments.length > 0 ? totalVolume / typedPayments.length : 0
910
1021
  }
911
1022
  };
912
1023
  } catch (error) {
@@ -968,12 +1079,11 @@ const stripeController = {
968
1079
  };
969
1080
  } catch (error) {
970
1081
  strapi.log.error("Failed to create admin refund:", {
971
- message: error.message,
972
- stack: error.stack,
973
- ...error.raw && { stripeError: error.raw }
1082
+ message: getErrorMessage(error),
1083
+ stack: error instanceof Error ? error.stack : void 0,
1084
+ ...error instanceof Error && "raw" in error && { stripeError: error.raw }
974
1085
  });
975
- const errorMessage = error.message || "Failed to create admin refund";
976
- ctx.internalServerError(errorMessage);
1086
+ ctx.internalServerError(getErrorMessage(error) || "Failed to create admin refund");
977
1087
  }
978
1088
  },
979
1089
  /**
@@ -1225,9 +1335,207 @@ const stripeController = {
1225
1335
  }
1226
1336
  }
1227
1337
  };
1338
+ const subscriptionController = {
1339
+ /**
1340
+ * POST /subscriptions
1341
+ * Body: { stripeCustomerId, priceId, trialPeriodDays?, metadata?, defaultPaymentMethod? }
1342
+ */
1343
+ async createSubscription(ctx) {
1344
+ try {
1345
+ const { stripeCustomerId, priceId, trialPeriodDays, metadata, defaultPaymentMethod } = ctx.request.body;
1346
+ if (!stripeCustomerId || !priceId) {
1347
+ return ctx.badRequest("stripeCustomerId and priceId are required");
1348
+ }
1349
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1350
+ const customers = await strapi.documents("plugin::payment-plugin.customer").findMany({
1351
+ filters: { stripe_customer_id: { $eq: stripeCustomerId } }
1352
+ });
1353
+ const strapiCustomerId = customers.length > 0 ? customers[0].documentId : void 0;
1354
+ const subscription2 = await stripeService2.createSubscription({
1355
+ strapiCustomerId: strapiCustomerId || "",
1356
+ stripeCustomerId,
1357
+ priceId,
1358
+ trialPeriodDays,
1359
+ metadata,
1360
+ defaultPaymentMethod
1361
+ });
1362
+ const invoice = typeof subscription2.latest_invoice === "object" ? subscription2.latest_invoice : null;
1363
+ const invoiceExt = invoice;
1364
+ const paymentIntent = typeof invoiceExt?.payment_intent === "object" ? invoiceExt.payment_intent : null;
1365
+ const setupIntent = typeof subscription2.pending_setup_intent === "object" ? subscription2.pending_setup_intent : null;
1366
+ ctx.body = {
1367
+ subscriptionId: subscription2.id,
1368
+ status: subscription2.status,
1369
+ clientSecret: paymentIntent?.client_secret || setupIntent?.client_secret || null,
1370
+ currentPeriodEnd: subscription2.current_period_end
1371
+ };
1372
+ } catch (error) {
1373
+ strapi.log.error("Failed to create subscription", { error: getErrorMessage(error) });
1374
+ ctx.internalServerError(getErrorMessage(error) || "Failed to create subscription");
1375
+ }
1376
+ },
1377
+ /**
1378
+ * POST /subscriptions/:id/cancel
1379
+ * Body: { cancelAtPeriodEnd? }
1380
+ */
1381
+ async cancelSubscription(ctx) {
1382
+ try {
1383
+ const { id } = ctx.params;
1384
+ const { cancelAtPeriodEnd = false } = ctx.request.body || {};
1385
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1386
+ const subscription2 = await stripeService2.cancelSubscription(id, cancelAtPeriodEnd);
1387
+ ctx.body = {
1388
+ subscriptionId: subscription2.id,
1389
+ status: subscription2.status,
1390
+ cancelAtPeriodEnd: subscription2.cancel_at_period_end,
1391
+ canceledAt: subscription2.canceled_at
1392
+ };
1393
+ } catch (error) {
1394
+ strapi.log.error("Failed to cancel subscription", { error: getErrorMessage(error) });
1395
+ ctx.internalServerError(getErrorMessage(error) || "Failed to cancel subscription");
1396
+ }
1397
+ },
1398
+ /**
1399
+ * PUT /subscriptions/:id
1400
+ * Body: { priceId?, cancelAtPeriodEnd?, trialEnd?, metadata?, defaultPaymentMethod? }
1401
+ */
1402
+ async updateSubscription(ctx) {
1403
+ try {
1404
+ const { id } = ctx.params;
1405
+ const params = ctx.request.body || {};
1406
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1407
+ const subscription2 = await stripeService2.updateSubscription(id, params);
1408
+ ctx.body = {
1409
+ subscriptionId: subscription2.id,
1410
+ status: subscription2.status,
1411
+ cancelAtPeriodEnd: subscription2.cancel_at_period_end,
1412
+ currentPeriodEnd: subscription2.current_period_end
1413
+ };
1414
+ } catch (error) {
1415
+ strapi.log.error("Failed to update subscription", { error: getErrorMessage(error) });
1416
+ ctx.internalServerError(getErrorMessage(error) || "Failed to update subscription");
1417
+ }
1418
+ },
1419
+ /**
1420
+ * GET /subscriptions/:id
1421
+ */
1422
+ async getSubscriptionDetails(ctx) {
1423
+ try {
1424
+ const { id } = ctx.params;
1425
+ const records = await strapi.documents("plugin::payment-plugin.subscription").findMany({
1426
+ filters: { stripe_subscription_id: { $eq: id } },
1427
+ populate: ["customer"]
1428
+ });
1429
+ if (records.length === 0) {
1430
+ return ctx.notFound("Subscription not found");
1431
+ }
1432
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1433
+ const stripeSubscription = await stripeService2.retrieveSubscription(id);
1434
+ ctx.body = {
1435
+ strapi: records[0],
1436
+ stripe: stripeSubscription
1437
+ };
1438
+ } catch (error) {
1439
+ strapi.log.error("Failed to get subscription", { error: getErrorMessage(error) });
1440
+ ctx.internalServerError(getErrorMessage(error) || "Failed to get subscription");
1441
+ }
1442
+ },
1443
+ /**
1444
+ * GET /subscriptions
1445
+ * Query: page, pageSize, status, stripeCustomerId
1446
+ */
1447
+ async listSubscriptions(ctx) {
1448
+ try {
1449
+ const { page = 1, pageSize = 25, status, stripeCustomerId } = ctx.query;
1450
+ const filters = {};
1451
+ if (status) filters.status = { $eq: status };
1452
+ if (stripeCustomerId) filters.stripe_customer_id = { $eq: stripeCustomerId };
1453
+ const subscriptions = await strapi.documents("plugin::payment-plugin.subscription").findMany({
1454
+ filters,
1455
+ populate: ["customer"],
1456
+ sort: { createdAt: "desc" },
1457
+ start: (Number(page) - 1) * Number(pageSize),
1458
+ limit: Number(pageSize)
1459
+ });
1460
+ const total = await strapi.documents("plugin::payment-plugin.subscription").count({ filters });
1461
+ ctx.body = {
1462
+ data: subscriptions,
1463
+ meta: {
1464
+ pagination: {
1465
+ page: Number(page),
1466
+ pageSize: Number(pageSize),
1467
+ total,
1468
+ pageCount: Math.ceil(total / Number(pageSize))
1469
+ }
1470
+ }
1471
+ };
1472
+ } catch (error) {
1473
+ strapi.log.error("Failed to list subscriptions", { error: getErrorMessage(error) });
1474
+ ctx.internalServerError(getErrorMessage(error) || "Failed to list subscriptions");
1475
+ }
1476
+ },
1477
+ /**
1478
+ * GET /admin/subscriptions (admin only)
1479
+ */
1480
+ async adminListSubscriptions(ctx) {
1481
+ try {
1482
+ const { page = 1, pageSize = 25, status } = ctx.query;
1483
+ const filters = {};
1484
+ if (status) filters.status = { $eq: status };
1485
+ const subscriptions = await strapi.documents("plugin::payment-plugin.subscription").findMany({
1486
+ filters,
1487
+ populate: ["customer"],
1488
+ sort: { createdAt: "desc" },
1489
+ start: (Number(page) - 1) * Number(pageSize),
1490
+ limit: Number(pageSize)
1491
+ });
1492
+ const total = await strapi.documents("plugin::payment-plugin.subscription").count({ filters });
1493
+ const enriched = subscriptions.map((s) => ({
1494
+ ...s,
1495
+ customerEmail: s.customer?.email || "",
1496
+ customerName: s.customer ? `${s.customer.first_name} ${s.customer.last_name}` : ""
1497
+ }));
1498
+ ctx.body = {
1499
+ data: enriched,
1500
+ meta: {
1501
+ pagination: {
1502
+ page: Number(page),
1503
+ pageSize: Number(pageSize),
1504
+ total,
1505
+ pageCount: Math.ceil(total / Number(pageSize))
1506
+ }
1507
+ }
1508
+ };
1509
+ } catch (error) {
1510
+ strapi.log.error("Failed to list subscriptions (admin)", { error: getErrorMessage(error) });
1511
+ ctx.internalServerError(getErrorMessage(error) || "Failed to list subscriptions");
1512
+ }
1513
+ },
1514
+ /**
1515
+ * POST /admin/subscriptions/:id/cancel (admin only)
1516
+ */
1517
+ async adminCancelSubscription(ctx) {
1518
+ try {
1519
+ const { id } = ctx.params;
1520
+ const { cancelAtPeriodEnd = false } = ctx.request.body || {};
1521
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1522
+ const subscription2 = await stripeService2.cancelSubscription(id, cancelAtPeriodEnd);
1523
+ ctx.body = {
1524
+ success: true,
1525
+ subscriptionId: subscription2.id,
1526
+ status: subscription2.status,
1527
+ cancelAtPeriodEnd: subscription2.cancel_at_period_end
1528
+ };
1529
+ } catch (error) {
1530
+ strapi.log.error("Failed to cancel subscription (admin)", { error: getErrorMessage(error) });
1531
+ ctx.internalServerError(getErrorMessage(error) || "Failed to cancel subscription");
1532
+ }
1533
+ }
1534
+ };
1535
+ const stripeControllerFull = { ...stripeController, ...subscriptionController };
1228
1536
  const controllers = {
1229
1537
  controller,
1230
- stripe: stripeController
1538
+ stripe: stripeControllerFull
1231
1539
  };
1232
1540
  const middlewares = {
1233
1541
  /**
@@ -1426,6 +1734,37 @@ const routes$2 = {
1426
1734
  config: {
1427
1735
  policies: []
1428
1736
  }
1737
+ },
1738
+ // Subscription Operations
1739
+ {
1740
+ method: "POST",
1741
+ path: "/subscriptions",
1742
+ handler: "stripe.createSubscription",
1743
+ config: { policies: [] }
1744
+ },
1745
+ {
1746
+ method: "GET",
1747
+ path: "/subscriptions",
1748
+ handler: "stripe.listSubscriptions",
1749
+ config: { policies: [] }
1750
+ },
1751
+ {
1752
+ method: "GET",
1753
+ path: "/subscriptions/:id",
1754
+ handler: "stripe.getSubscriptionDetails",
1755
+ config: { policies: [] }
1756
+ },
1757
+ {
1758
+ method: "PUT",
1759
+ path: "/subscriptions/:id",
1760
+ handler: "stripe.updateSubscription",
1761
+ config: { policies: [] }
1762
+ },
1763
+ {
1764
+ method: "POST",
1765
+ path: "/subscriptions/:id/cancel",
1766
+ handler: "stripe.cancelSubscription",
1767
+ config: { policies: [] }
1429
1768
  }
1430
1769
  ]
1431
1770
  };
@@ -1549,6 +1888,25 @@ const routes$1 = {
1549
1888
  policies: ["admin::isAuthenticatedAdmin"],
1550
1889
  middlewares: []
1551
1890
  }
1891
+ },
1892
+ // Admin Subscription Management
1893
+ {
1894
+ method: "GET",
1895
+ path: "/admin/subscriptions",
1896
+ handler: "stripe.adminListSubscriptions",
1897
+ config: {
1898
+ policies: ["admin::isAuthenticatedAdmin"],
1899
+ middlewares: []
1900
+ }
1901
+ },
1902
+ {
1903
+ method: "POST",
1904
+ path: "/admin/subscriptions/:id/cancel",
1905
+ handler: "stripe.adminCancelSubscription",
1906
+ config: {
1907
+ policies: ["admin::isAuthenticatedAdmin"],
1908
+ middlewares: []
1909
+ }
1552
1910
  }
1553
1911
  ]
1554
1912
  };
@@ -1752,14 +2110,14 @@ const stripeService = ({ strapi: strapi2 }) => {
1752
2110
  return refund;
1753
2111
  } catch (error) {
1754
2112
  const logger = getLogger();
1755
- const errorMessage = error instanceof Error ? error.message : "Unknown Stripe error";
2113
+ const errorMessage = getErrorMessage(error);
1756
2114
  logger.error("Failed to create Stripe refund", {
1757
2115
  message: errorMessage,
1758
2116
  error: error instanceof Error ? {
1759
2117
  message: error.message,
1760
2118
  stack: error.stack,
1761
- ...error.raw && { raw: error.raw }
1762
- } : error,
2119
+ ..."raw" in error && { raw: error.raw }
2120
+ } : String(error),
1763
2121
  params
1764
2122
  });
1765
2123
  throw error;
@@ -1782,8 +2140,8 @@ const stripeService = ({ strapi: strapi2 }) => {
1782
2140
  return stripe2.webhooks.constructEvent(verifiedPayload, signature, webhookSecret);
1783
2141
  } catch (error) {
1784
2142
  logger.error("Failed to construct webhook event", {
1785
- message: error.message,
1786
- type: error.type
2143
+ message: getErrorMessage(error),
2144
+ ...error instanceof Error && "type" in error && { type: error.type }
1787
2145
  });
1788
2146
  throw error;
1789
2147
  }
@@ -1856,10 +2214,7 @@ const stripeService = ({ strapi: strapi2 }) => {
1856
2214
  };
1857
2215
  const createOrderRecord = async (data) => {
1858
2216
  return strapi2.documents("plugin::payment-plugin.order").create({
1859
- data: {
1860
- ...data,
1861
- order_status: data.order_status || "pending"
1862
- }
2217
+ data: { ...data, order_status: data.order_status || "pending" }
1863
2218
  });
1864
2219
  };
1865
2220
  const createStrapiPaymentRecord = async (paymentIntent) => {
@@ -1976,10 +2331,18 @@ const stripeService = ({ strapi: strapi2 }) => {
1976
2331
  switch (event.type) {
1977
2332
  case "payment_intent.succeeded":
1978
2333
  case "charge.succeeded": {
1979
- const obj = event.data.object;
1980
- const paymentIntentId = event.type === "payment_intent.succeeded" ? obj.id : obj.payment_intent;
2334
+ let paymentIntentId;
2335
+ let metadata;
2336
+ if (event.type === "payment_intent.succeeded") {
2337
+ const piObj = event.data.object;
2338
+ paymentIntentId = piObj.id;
2339
+ metadata = piObj.metadata || {};
2340
+ } else {
2341
+ const chargeObj = event.data.object;
2342
+ paymentIntentId = chargeObj.payment_intent;
2343
+ metadata = chargeObj.metadata || {};
2344
+ }
1981
2345
  if (!paymentIntentId) break;
1982
- const metadata = obj.metadata || {};
1983
2346
  await updateStrapiPayment(paymentIntentId, {
1984
2347
  payment_status: "succeeded",
1985
2348
  metadata
@@ -1987,12 +2350,14 @@ const stripeService = ({ strapi: strapi2 }) => {
1987
2350
  const existing = await strapi2.documents("plugin::payment-plugin.payment").findMany({
1988
2351
  filters: { stripe_payment_intent_id: paymentIntentId }
1989
2352
  });
1990
- if (existing.length === 0 && event.type === "payment_intent.succeeded") {
1991
- await createStrapiPaymentRecord(obj);
1992
- } else if (existing.length === 0 && event.type === "charge.succeeded" && obj.payment_intent) {
1993
- const stripe2 = await initializeStripe();
1994
- const pi = await stripe2.paymentIntents.retrieve(obj.payment_intent);
1995
- await createStrapiPaymentRecord(pi);
2353
+ if (existing.length === 0) {
2354
+ if (event.type === "payment_intent.succeeded") {
2355
+ await createStrapiPaymentRecord(event.data.object);
2356
+ } else {
2357
+ const stripeClient = await initializeStripe();
2358
+ const pi = await stripeClient.paymentIntents.retrieve(paymentIntentId);
2359
+ await createStrapiPaymentRecord(pi);
2360
+ }
1996
2361
  }
1997
2362
  const orderId = metadata.strapi_order_id;
1998
2363
  if (orderId) {
@@ -2099,6 +2464,73 @@ const stripeService = ({ strapi: strapi2 }) => {
2099
2464
  });
2100
2465
  break;
2101
2466
  }
2467
+ case "customer.subscription.created": {
2468
+ const subscription2 = event.data.object;
2469
+ const strapiCustomerId = subscription2.metadata?.strapi_customer_id;
2470
+ const existing = await strapi2.documents("plugin::payment-plugin.subscription").findMany({
2471
+ filters: { stripe_subscription_id: subscription2.id }
2472
+ });
2473
+ if (existing.length === 0) {
2474
+ await createStrapiSubscriptionRecord(subscription2, strapiCustomerId);
2475
+ }
2476
+ logger.info("Subscription created via webhook", { subscriptionId: subscription2.id });
2477
+ break;
2478
+ }
2479
+ case "customer.subscription.updated": {
2480
+ const subscription2 = event.data.object;
2481
+ const sub = subscription2;
2482
+ const firstItem = subscription2.items.data[0];
2483
+ await updateStrapiSubscription(subscription2.id, {
2484
+ status: mapStripeSubscriptionStatus(subscription2.status),
2485
+ stripe_price_id: firstItem?.price?.id,
2486
+ stripe_product_id: firstItem?.price?.product,
2487
+ current_period_start: toISOOrNull(sub.current_period_start),
2488
+ current_period_end: toISOOrNull(sub.current_period_end),
2489
+ cancel_at_period_end: subscription2.cancel_at_period_end,
2490
+ canceled_at: toISOOrNull(subscription2.canceled_at),
2491
+ trial_start: toISOOrNull(subscription2.trial_start),
2492
+ trial_end: toISOOrNull(subscription2.trial_end),
2493
+ latest_invoice_id: typeof subscription2.latest_invoice === "string" ? subscription2.latest_invoice : subscription2.latest_invoice?.id,
2494
+ metadata: subscription2.metadata
2495
+ });
2496
+ logger.info("Subscription updated via webhook", { subscriptionId: subscription2.id, status: subscription2.status });
2497
+ break;
2498
+ }
2499
+ case "customer.subscription.deleted": {
2500
+ const subscription2 = event.data.object;
2501
+ await updateStrapiSubscription(subscription2.id, {
2502
+ status: "canceled",
2503
+ canceled_at: toISOOrNull(subscription2.canceled_at) || (/* @__PURE__ */ new Date()).toISOString()
2504
+ });
2505
+ logger.info("Subscription deleted via webhook", { subscriptionId: subscription2.id });
2506
+ break;
2507
+ }
2508
+ case "invoice.payment_succeeded": {
2509
+ const invoice = event.data.object;
2510
+ const subDetails = invoice.parent?.subscription_details;
2511
+ const subscriptionId = typeof subDetails?.subscription === "string" ? subDetails.subscription : subDetails?.subscription?.id ?? null;
2512
+ if (subscriptionId) {
2513
+ await updateStrapiSubscription(subscriptionId, {
2514
+ status: "active",
2515
+ latest_invoice_id: invoice.id
2516
+ });
2517
+ }
2518
+ logger.info("Invoice payment succeeded", { invoiceId: invoice.id, subscriptionId });
2519
+ break;
2520
+ }
2521
+ case "invoice.payment_failed": {
2522
+ const invoice = event.data.object;
2523
+ const subDetails = invoice.parent?.subscription_details;
2524
+ const subscriptionId = typeof subDetails?.subscription === "string" ? subDetails.subscription : subDetails?.subscription?.id ?? null;
2525
+ if (subscriptionId) {
2526
+ await updateStrapiSubscription(subscriptionId, {
2527
+ status: "past_due",
2528
+ latest_invoice_id: invoice.id
2529
+ });
2530
+ }
2531
+ logger.warn("Invoice payment failed", { invoiceId: invoice.id, subscriptionId });
2532
+ break;
2533
+ }
2102
2534
  default:
2103
2535
  logger.info("Unhandled webhook event type", { eventType: event.type });
2104
2536
  }
@@ -2111,6 +2543,140 @@ const stripeService = ({ strapi: strapi2 }) => {
2111
2543
  throw error;
2112
2544
  }
2113
2545
  };
2546
+ const mapStripeSubscriptionStatus = (status) => {
2547
+ const valid = ["active", "canceled", "incomplete", "incomplete_expired", "past_due", "paused", "trialing", "unpaid"];
2548
+ return valid.includes(status) ? status : "incomplete";
2549
+ };
2550
+ const toISOOrNull = (ts) => {
2551
+ if (!ts) return null;
2552
+ return new Date(ts * 1e3).toISOString();
2553
+ };
2554
+ const createStrapiSubscriptionRecord = async (subscription2, strapiCustomerId) => {
2555
+ const logger = getLogger();
2556
+ try {
2557
+ const firstItem = subscription2.items.data[0];
2558
+ const price = firstItem?.price;
2559
+ const data = {
2560
+ stripe_subscription_id: subscription2.id,
2561
+ stripe_customer_id: subscription2.customer,
2562
+ stripe_price_id: price?.id || "",
2563
+ stripe_product_id: price?.product,
2564
+ status: mapStripeSubscriptionStatus(subscription2.status),
2565
+ current_period_start: toISOOrNull(subscription2.current_period_start) || void 0,
2566
+ current_period_end: toISOOrNull(subscription2.current_period_end) || void 0,
2567
+ cancel_at_period_end: subscription2.cancel_at_period_end,
2568
+ canceled_at: toISOOrNull(subscription2.canceled_at) || void 0,
2569
+ trial_start: toISOOrNull(subscription2.trial_start) || void 0,
2570
+ trial_end: toISOOrNull(subscription2.trial_end) || void 0,
2571
+ latest_invoice_id: typeof subscription2.latest_invoice === "string" ? subscription2.latest_invoice : subscription2.latest_invoice?.id,
2572
+ metadata: subscription2.metadata,
2573
+ customer: strapiCustomerId
2574
+ };
2575
+ const record = await strapi2.documents("plugin::payment-plugin.subscription").create({ data });
2576
+ logger.info("Created Strapi subscription record", {
2577
+ stripeSubscriptionId: subscription2.id,
2578
+ strapiCustomerId
2579
+ });
2580
+ return record;
2581
+ } catch (error) {
2582
+ logger.error("Failed to create Strapi subscription record", { error, subscriptionId: subscription2.id });
2583
+ throw error;
2584
+ }
2585
+ };
2586
+ const updateStrapiSubscription = async (stripeSubscriptionId, data) => {
2587
+ const logger = getLogger();
2588
+ try {
2589
+ const existing = await strapi2.documents("plugin::payment-plugin.subscription").findMany({
2590
+ filters: { stripe_subscription_id: stripeSubscriptionId }
2591
+ });
2592
+ if (existing.length > 0) {
2593
+ await strapi2.documents("plugin::payment-plugin.subscription").update({
2594
+ documentId: existing[0].documentId,
2595
+ data
2596
+ });
2597
+ }
2598
+ } catch (error) {
2599
+ logger.error("Failed to update Strapi subscription record", { error, stripeSubscriptionId });
2600
+ }
2601
+ };
2602
+ const createSubscription = async (params) => {
2603
+ const stripe2 = await initializeStripe();
2604
+ const { strapiCustomerId, stripeCustomerId, priceId, trialPeriodDays, metadata = {}, defaultPaymentMethod } = params;
2605
+ const subscriptionParams = {
2606
+ customer: stripeCustomerId,
2607
+ items: [{ price: priceId }],
2608
+ payment_behavior: "default_incomplete",
2609
+ payment_settings: { save_default_payment_method: "on_subscription" },
2610
+ expand: ["latest_invoice.payment_intent", "pending_setup_intent"],
2611
+ metadata: { ...metadata, strapi_customer_id: strapiCustomerId }
2612
+ };
2613
+ if (trialPeriodDays) {
2614
+ subscriptionParams.trial_period_days = trialPeriodDays;
2615
+ }
2616
+ if (defaultPaymentMethod) {
2617
+ subscriptionParams.default_payment_method = defaultPaymentMethod;
2618
+ }
2619
+ const subscription2 = await stripe2.subscriptions.create(subscriptionParams);
2620
+ await createStrapiSubscriptionRecord(subscription2, strapiCustomerId);
2621
+ getLogger().info("Subscription created", { subscriptionId: subscription2.id, stripeCustomerId });
2622
+ return subscription2;
2623
+ };
2624
+ const cancelSubscription = async (stripeSubscriptionId, cancelAtPeriodEnd = false) => {
2625
+ const stripe2 = await initializeStripe();
2626
+ let subscription2;
2627
+ if (cancelAtPeriodEnd) {
2628
+ subscription2 = await stripe2.subscriptions.update(stripeSubscriptionId, { cancel_at_period_end: true });
2629
+ await updateStrapiSubscription(stripeSubscriptionId, { cancel_at_period_end: true });
2630
+ } else {
2631
+ subscription2 = await stripe2.subscriptions.cancel(stripeSubscriptionId);
2632
+ await updateStrapiSubscription(stripeSubscriptionId, {
2633
+ status: "canceled",
2634
+ canceled_at: (/* @__PURE__ */ new Date()).toISOString(),
2635
+ cancel_at_period_end: false
2636
+ });
2637
+ }
2638
+ getLogger().info("Subscription canceled", { stripeSubscriptionId, cancelAtPeriodEnd });
2639
+ return subscription2;
2640
+ };
2641
+ const updateSubscription = async (stripeSubscriptionId, params) => {
2642
+ const stripe2 = await initializeStripe();
2643
+ const updateParams = {};
2644
+ if (params.priceId) {
2645
+ const current = await stripe2.subscriptions.retrieve(stripeSubscriptionId);
2646
+ const itemId = current.items.data[0]?.id;
2647
+ updateParams.items = [{ id: itemId, price: params.priceId }];
2648
+ updateParams.proration_behavior = "create_prorations";
2649
+ }
2650
+ if (params.cancelAtPeriodEnd !== void 0) {
2651
+ updateParams.cancel_at_period_end = params.cancelAtPeriodEnd;
2652
+ }
2653
+ if (params.trialEnd !== void 0) {
2654
+ updateParams.trial_end = params.trialEnd;
2655
+ }
2656
+ if (params.metadata) {
2657
+ updateParams.metadata = params.metadata;
2658
+ }
2659
+ if (params.defaultPaymentMethod) {
2660
+ updateParams.default_payment_method = params.defaultPaymentMethod;
2661
+ }
2662
+ const subscription2 = await stripe2.subscriptions.update(stripeSubscriptionId, updateParams);
2663
+ const firstItem = subscription2.items.data[0];
2664
+ await updateStrapiSubscription(stripeSubscriptionId, {
2665
+ stripe_price_id: firstItem?.price?.id,
2666
+ stripe_product_id: firstItem?.price?.product,
2667
+ cancel_at_period_end: subscription2.cancel_at_period_end,
2668
+ status: mapStripeSubscriptionStatus(subscription2.status),
2669
+ metadata: subscription2.metadata
2670
+ });
2671
+ getLogger().info("Subscription updated", { stripeSubscriptionId });
2672
+ return subscription2;
2673
+ };
2674
+ const retrieveSubscription = async (stripeSubscriptionId) => {
2675
+ const stripe2 = await initializeStripe();
2676
+ return stripe2.subscriptions.retrieve(stripeSubscriptionId, {
2677
+ expand: ["latest_invoice", "customer"]
2678
+ });
2679
+ };
2114
2680
  return {
2115
2681
  initializeStripe,
2116
2682
  createPaymentIntent,
@@ -2126,7 +2692,13 @@ const stripeService = ({ strapi: strapi2 }) => {
2126
2692
  createCustomerRecord,
2127
2693
  createOrderRecord,
2128
2694
  initializePaymentFlow,
2129
- getStripe
2695
+ getStripe,
2696
+ createSubscription,
2697
+ cancelSubscription,
2698
+ updateSubscription,
2699
+ retrieveSubscription,
2700
+ createStrapiSubscriptionRecord,
2701
+ updateStrapiSubscription
2130
2702
  };
2131
2703
  };
2132
2704
  const services = {