@reeboot/strapi-payment-plugin 0.0.1 → 0.0.2

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 (84) hide show
  1. package/README.md +378 -119
  2. package/dist/_chunks/Analytics-C2GKvPDX.js +355 -0
  3. package/dist/_chunks/Analytics-Chydh3dg.mjs +355 -0
  4. package/dist/_chunks/App-BCbEuN6h.mjs +68 -0
  5. package/dist/_chunks/App-E2k2mo5p.js +70 -0
  6. package/dist/_chunks/Customers-B8BlQ1BO.mjs +273 -0
  7. package/dist/_chunks/Customers-D0O5ET5C.js +273 -0
  8. package/dist/_chunks/Dashboard-CuqclLak.js +180 -0
  9. package/dist/_chunks/Dashboard-D8-ifJqB.mjs +180 -0
  10. package/dist/_chunks/Orders-5WGSzPGo.js +308 -0
  11. package/dist/_chunks/Orders-ClJJaQrX.mjs +308 -0
  12. package/dist/_chunks/Payments-CIMyOC3n.js +489 -0
  13. package/dist/_chunks/Payments-CRnRBf48.mjs +489 -0
  14. package/dist/_chunks/Settings-BXMLePI_.js +357 -0
  15. package/dist/_chunks/Settings-TeAvV4RH.mjs +357 -0
  16. package/dist/_chunks/en-BJocyOVu.mjs +240 -0
  17. package/dist/_chunks/en-BkVAf_R4.js +240 -0
  18. package/dist/_chunks/index-BTk7a2T5.js +66 -0
  19. package/dist/_chunks/index-Bz8V_5zH.mjs +67 -0
  20. package/dist/admin/index.js +2 -63
  21. package/dist/admin/index.mjs +2 -63
  22. package/dist/admin/src/components/AnalyticsChart.d.ts +19 -0
  23. package/dist/admin/src/components/CustomerList.d.ts +21 -0
  24. package/dist/admin/src/components/OrderList.d.ts +27 -0
  25. package/dist/admin/src/components/PaymentCard.d.ts +39 -0
  26. package/dist/admin/src/components/PaymentList.d.ts +19 -0
  27. package/dist/admin/src/components/RefundModal.d.ts +15 -0
  28. package/dist/admin/src/pages/Analytics.d.ts +2 -0
  29. package/dist/admin/src/pages/Customers.d.ts +2 -0
  30. package/dist/admin/src/pages/Dashboard.d.ts +2 -0
  31. package/dist/admin/src/pages/HomePage.d.ts +1 -1
  32. package/dist/admin/src/pages/Orders.d.ts +2 -0
  33. package/dist/admin/src/pages/Payments.d.ts +2 -0
  34. package/dist/admin/src/pages/Settings.d.ts +2 -0
  35. package/dist/admin/src/pluginId.d.ts +1 -1
  36. package/dist/server/index.js +1746 -984
  37. package/dist/server/index.mjs +1748 -986
  38. package/dist/server/src/bootstrap.d.ts +5 -11
  39. package/dist/server/src/config/index.d.ts +0 -10
  40. package/dist/server/src/content-types/customer/index.d.ts +69 -0
  41. package/dist/server/src/content-types/index.d.ts +123 -39
  42. package/dist/server/src/content-types/{product.d.ts → order/index.d.ts} +26 -19
  43. package/dist/server/src/content-types/{subscription.d.ts → payment/index.d.ts} +30 -21
  44. package/dist/server/src/controllers/controller.d.ts +5 -12
  45. package/dist/server/src/controllers/index.d.ts +29 -34
  46. package/dist/server/src/controllers/stripe.d.ts +104 -0
  47. package/dist/server/src/index.d.ts +179 -139
  48. package/dist/server/src/middlewares/index.d.ts +19 -1
  49. package/dist/server/src/policies/index.d.ts +3 -1
  50. package/dist/server/src/routes/{admin-routes.d.ts → admin/index.d.ts} +4 -4
  51. package/dist/server/src/routes/content-api/index.d.ts +21 -0
  52. package/dist/server/src/routes/index.d.ts +11 -16
  53. package/dist/server/src/services/index.d.ts +2 -38
  54. package/dist/server/src/services/{stripeDriver.d.ts → stripe.d.ts} +52 -59
  55. package/dist/server/src/types/index.d.ts +179 -0
  56. package/package.json +20 -25
  57. package/dist/_chunks/App-DD7GyuRr.mjs +0 -1424
  58. package/dist/_chunks/App-KZVBFRwo.js +0 -1424
  59. package/dist/_chunks/en-B4KWt_jN.js +0 -4
  60. package/dist/_chunks/en-Byx4XI2L.mjs +0 -4
  61. package/dist/admin/src/components/Header.d.ts +0 -2
  62. package/dist/admin/src/components/NavigationMenu.d.ts +0 -2
  63. package/dist/admin/src/components/Sidebar.d.ts +0 -2
  64. package/dist/admin/src/components/TransactionDetailsModal.d.ts +0 -18
  65. package/dist/admin/src/components/TransactionList.d.ts +0 -18
  66. package/dist/admin/src/pages/ConfigurationPage.d.ts +0 -2
  67. package/dist/admin/src/pages/DashboardPage.d.ts +0 -2
  68. package/dist/admin/src/pages/ProductsPage.d.ts +0 -2
  69. package/dist/admin/src/pages/SubscriptionsPage.d.ts +0 -2
  70. package/dist/admin/src/pages/TransactionsPage.d.ts +0 -2
  71. package/dist/server/src/controllers/product.d.ts +0 -18
  72. package/dist/server/src/controllers/subscription.d.ts +0 -16
  73. package/dist/server/src/controllers/webhook.d.ts +0 -10
  74. package/dist/server/src/routes/content-api.d.ts +0 -12
  75. package/dist/server/src/routes/product.d.ts +0 -2
  76. package/dist/server/src/routes/refund-routes.d.ts +0 -13
  77. package/dist/server/src/routes/subscription.d.ts +0 -5
  78. package/dist/server/src/routes/webhook.d.ts +0 -15
  79. package/dist/server/src/services/paypalDriver.d.ts +0 -47
  80. package/dist/server/src/services/product.d.ts +0 -7
  81. package/dist/server/src/services/service.d.ts +0 -26
  82. package/dist/server/src/services/subscription.d.ts +0 -9
  83. package/dist/server/src/services/sync.d.ts +0 -13
  84. package/jest.config.js +0 -13
@@ -1,1237 +1,1999 @@
1
1
  import Stripe from "stripe";
2
- import { factories } from "@strapi/strapi";
3
- const bootstrap = async ({ strapi }) => {
4
- const syncService = strapi.service("plugin::strapi-payment-plugin.sync");
5
- syncService?.startPeriodicSync();
6
- console.log("Periodic sync started");
2
+ const bootstrap = ({ strapi: strapi2 }) => {
7
3
  };
8
- const destroy = ({ strapi }) => {
4
+ const destroy = ({ strapi: strapi2 }) => {
9
5
  };
10
- const register = ({ strapi }) => {
6
+ const register = ({ strapi: strapi2 }) => {
11
7
  };
12
8
  const config = {
13
9
  default: {},
14
10
  validator() {
11
+ }
12
+ };
13
+ const schema$2 = {
14
+ kind: "collectionType",
15
+ collectionName: "customers",
16
+ info: {
17
+ singularName: "customer",
18
+ pluralName: "customers",
19
+ displayName: "Customer",
20
+ description: "Customer information for payment processing"
21
+ },
22
+ options: {
23
+ draftAndPublish: false
15
24
  },
16
- stripe: {
17
- secretKey: process.env.STRIPE_SECRET_KEY,
18
- publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
19
- webhookSecret: process.env.STRIPE_WEBHOOK_SECRET
25
+ pluginOptions: {
26
+ i18n: {
27
+ localized: true
28
+ }
20
29
  },
21
- paypal: {
22
- clientId: process.env.PAYPAL_CLIENT_ID,
23
- clientSecret: process.env.PAYPAL_CLIENT_SECRET,
24
- webhookId: process.env.PAYPAL_WEBHOOK_ID
30
+ attributes: {
31
+ email: {
32
+ type: "email",
33
+ unique: true,
34
+ required: true,
35
+ configurable: false
36
+ },
37
+ stripe_customer_id: {
38
+ type: "string",
39
+ unique: true,
40
+ required: true,
41
+ configurable: false
42
+ },
43
+ first_name: {
44
+ type: "string",
45
+ required: true,
46
+ configurable: false
47
+ },
48
+ last_name: {
49
+ type: "string",
50
+ required: true,
51
+ configurable: false
52
+ },
53
+ phone: {
54
+ type: "string",
55
+ configurable: false
56
+ },
57
+ address: {
58
+ type: "json",
59
+ configurable: false
60
+ },
61
+ metadata: {
62
+ type: "json",
63
+ configurable: false
64
+ },
65
+ orders: {
66
+ type: "relation",
67
+ relation: "oneToMany",
68
+ target: "plugin::payment-plugin.order",
69
+ mappedBy: "customer"
70
+ },
71
+ payments: {
72
+ type: "relation",
73
+ relation: "oneToMany",
74
+ target: "plugin::payment-plugin.payment",
75
+ mappedBy: "customer"
76
+ }
25
77
  }
26
78
  };
79
+ const customer = { schema: schema$2 };
27
80
  const schema$1 = {
28
- "kind": "collectionType",
29
- "collectionName": "products",
30
- "info": {
31
- "singularName": "product",
32
- "pluralName": "products",
33
- "displayName": "Product",
34
- "description": "A product that can be purchased"
81
+ kind: "collectionType",
82
+ collectionName: "orders",
83
+ info: {
84
+ singularName: "order",
85
+ pluralName: "orders",
86
+ displayName: "Order",
87
+ description: "Customer orders linked to payments"
35
88
  },
36
- "options": {
37
- "draftAndPublish": false
89
+ options: {
90
+ draftAndPublish: false
38
91
  },
39
- "pluginOptions": {
40
- "content-manager": {
41
- "visible": true
42
- },
43
- "content-type-builder": {
44
- "visible": true
92
+ pluginOptions: {
93
+ i18n: {
94
+ localized: true
45
95
  }
46
96
  },
47
- "attributes": {
48
- "name": {
49
- "type": "string",
50
- "required": true
51
- },
52
- "description": {
53
- "type": "text"
54
- },
55
- "price": {
56
- "type": "decimal",
57
- "required": true
97
+ attributes: {
98
+ order_number: {
99
+ type: "string",
100
+ unique: true,
101
+ required: true,
102
+ configurable: false
58
103
  },
59
- "currency": {
60
- "type": "string",
61
- "required": true,
62
- "default": "USD"
104
+ total_amount: {
105
+ type: "integer",
106
+ required: true,
107
+ min: 0,
108
+ configurable: false,
109
+ default: 0
63
110
  },
64
- "isSubscription": {
65
- "type": "boolean",
66
- "default": false
111
+ currency: {
112
+ type: "string",
113
+ required: true,
114
+ default: "usd",
115
+ configurable: false
67
116
  },
68
- "subscriptionInterval": {
69
- "type": "enumeration",
70
- "enum": ["day", "week", "month", "year"],
71
- "default": "month"
117
+ status: {
118
+ type: "enumeration",
119
+ enum: ["pending", "processing", "completed", "failed", "refunded"],
120
+ required: true,
121
+ default: "pending",
122
+ configurable: false
72
123
  },
73
- "stripeProductId": {
74
- "type": "string"
124
+ customer: {
125
+ type: "relation",
126
+ relation: "manyToOne",
127
+ target: "plugin::payment-plugin.customer",
128
+ inversedBy: "orders"
75
129
  },
76
- "paypalProductId": {
77
- "type": "string"
130
+ payments: {
131
+ type: "relation",
132
+ relation: "oneToMany",
133
+ target: "plugin::payment-plugin.payment",
134
+ mappedBy: "order"
78
135
  },
79
- "createdAt": {
80
- "type": "datetime"
136
+ items: {
137
+ type: "json",
138
+ required: true,
139
+ configurable: false
81
140
  },
82
- "updatedAt": {
83
- "type": "datetime"
141
+ metadata: {
142
+ type: "json",
143
+ configurable: false
84
144
  }
85
145
  }
86
146
  };
87
- const product$2 = {
88
- schema: schema$1
89
- };
147
+ const order = { schema: schema$1 };
90
148
  const schema = {
91
- "kind": "collectionType",
92
- "collectionName": "subscriptions",
93
- "info": {
94
- "singularName": "subscription",
95
- "pluralName": "subscriptions",
96
- "displayName": "Subscription",
97
- "description": "A subscription to a product"
149
+ kind: "collectionType",
150
+ collectionName: "payments",
151
+ info: {
152
+ singularName: "payment",
153
+ pluralName: "payments",
154
+ displayName: "Payment",
155
+ description: "Payment records for Stripe payment processing"
98
156
  },
99
- "options": {
100
- "draftAndPublish": false
157
+ options: {
158
+ draftAndPublish: false
101
159
  },
102
- "pluginOptions": {
103
- "content-manager": {
104
- "visible": true
105
- },
106
- "content-type-builder": {
107
- "visible": true
160
+ pluginOptions: {
161
+ i18n: {
162
+ localized: true
108
163
  }
109
164
  },
110
- "attributes": {
111
- "user": {
112
- "type": "relation",
113
- "relation": "morphOne",
114
- "target": "plugin::users-permissions.user"
115
- },
116
- "product": {
117
- "type": "relation",
118
- "relation": "oneToOne",
119
- "target": "plugin::strapi-payment-plugin.product"
165
+ attributes: {
166
+ stripe_payment_intent_id: {
167
+ type: "string",
168
+ unique: true,
169
+ required: true,
170
+ configurable: false
120
171
  },
121
- "status": {
122
- "type": "enumeration",
123
- "enum": ["active", "canceled", "past_due", "unpaid", "trialing"],
124
- "default": "active"
172
+ amount: {
173
+ type: "integer",
174
+ required: true,
175
+ min: 0,
176
+ configurable: false,
177
+ default: 0
125
178
  },
126
- "startDate": {
127
- "type": "date",
128
- "required": true
179
+ currency: {
180
+ type: "string",
181
+ required: true,
182
+ default: "usd",
183
+ configurable: false
129
184
  },
130
- "endDate": {
131
- "type": "date"
185
+ status: {
186
+ type: "enumeration",
187
+ enum: ["pending", "succeeded", "failed", "canceled"],
188
+ required: true,
189
+ default: "pending",
190
+ configurable: false
132
191
  },
133
- "stripeSubscriptionId": {
134
- "type": "string"
192
+ payment_method: {
193
+ type: "string",
194
+ configurable: false
135
195
  },
136
- "paypalSubscriptionId": {
137
- "type": "string"
196
+ metadata: {
197
+ type: "json",
198
+ configurable: false
138
199
  },
139
- "createdAt": {
140
- "type": "datetime"
200
+ customer: {
201
+ type: "relation",
202
+ relation: "manyToOne",
203
+ target: "plugin::payment-plugin.customer",
204
+ inversedBy: "payments"
141
205
  },
142
- "updatedAt": {
143
- "type": "datetime"
206
+ order: {
207
+ type: "relation",
208
+ relation: "manyToOne",
209
+ target: "plugin::payment-plugin.order",
210
+ inversedBy: "payments"
144
211
  }
145
212
  }
146
213
  };
147
- const subscription$2 = {
148
- schema
149
- };
214
+ const payment = { schema };
150
215
  const contentTypes = {
151
- product: product$2,
152
- subscription: subscription$2
216
+ customer,
217
+ order,
218
+ payment
153
219
  };
154
- const stripe = new Stripe(config.stripe.secretKey, {
155
- apiVersion: "2025-07-30.basil"
220
+ const controller = ({ strapi: strapi2 }) => ({
221
+ index(ctx) {
222
+ ctx.body = strapi2.plugin("payment-plugin").service("service").getWelcomeMessage();
223
+ }
156
224
  });
157
- const stripeDriver = {
158
- customers: stripe.customers,
159
- async initializePayment(amount, currency, orderId, options) {
160
- const paymentIntent = await stripe.paymentIntents.create({
161
- amount,
162
- currency,
163
- metadata: { orderId },
164
- ...options
165
- });
166
- return paymentIntent;
167
- },
168
- async processWebhook(payload, signature) {
169
- const event = stripe.webhooks.constructEvent(
170
- payload,
171
- signature,
172
- config.stripe.webhookSecret
173
- );
174
- return event;
175
- },
176
- async refundPayment(transactionId, amount, options) {
177
- return stripe.refunds.create({
178
- payment_intent: transactionId,
179
- amount,
180
- ...options
181
- });
182
- },
183
- async getStripeProducts() {
184
- const products = await stripe.products.list({
185
- limit: 100,
186
- expand: ["data.default_price"]
187
- });
188
- return products.data;
189
- },
190
- async getStripeProduct(productId) {
191
- const product2 = await stripe.products.retrieve(productId, {
192
- expand: ["default_price"]
193
- });
194
- return product2;
195
- },
196
- async getProductPrice(productId) {
197
- const product2 = await this.getStripeProduct(productId);
198
- if (product2.default_price) {
199
- if (typeof product2.default_price === "string") {
200
- const price = await stripe.prices.retrieve(product2.default_price);
201
- return {
202
- unit_amount: price.unit_amount,
203
- currency: price.currency
204
- };
205
- } else {
206
- return {
207
- unit_amount: product2.default_price.unit_amount,
208
- currency: product2.default_price.currency
209
- };
225
+ const stripeController = {
226
+ /**
227
+ * Create a payment intent
228
+ */
229
+ async createPaymentIntent(ctx) {
230
+ try {
231
+ const { amount, currency, customerId, orderId, metadata = {} } = ctx.request.body;
232
+ if (!amount || !currency) {
233
+ return ctx.badRequest("Amount and currency are required");
234
+ }
235
+ if (amount <= 0) {
236
+ return ctx.badRequest("Amount must be greater than 0");
210
237
  }
238
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
239
+ const paymentIntent = await stripeService2.createPaymentIntent({
240
+ amount,
241
+ currency,
242
+ metadata,
243
+ customerId,
244
+ orderId
245
+ });
246
+ ctx.body = {
247
+ success: true,
248
+ data: {
249
+ paymentIntentId: paymentIntent.id,
250
+ clientSecret: paymentIntent.client_secret,
251
+ status: paymentIntent.status,
252
+ amount: paymentIntent.amount,
253
+ currency: paymentIntent.currency
254
+ }
255
+ };
256
+ } catch (error) {
257
+ strapi.log.error("Failed to create payment intent", { error });
258
+ ctx.internalServerError("Failed to create payment intent");
211
259
  }
212
- return null;
213
- },
214
- async getPaymentDetails(transactionId) {
215
- return stripe.paymentIntents.retrieve(transactionId);
216
260
  },
217
- async createProduct(productData) {
218
- return stripe.products.create({
219
- name: productData.name,
220
- description: productData.description || "",
221
- default_price_data: {
222
- currency: productData.currency || "usd",
223
- unit_amount: Math.round(productData.price * 100)
224
- // Convert to cents
261
+ /**
262
+ * Confirm a payment intent
263
+ */
264
+ async confirmPayment(ctx) {
265
+ try {
266
+ const { paymentIntentId } = ctx.params;
267
+ if (!paymentIntentId) {
268
+ return ctx.badRequest("Payment intent ID is required");
225
269
  }
226
- });
227
- },
228
- async updateProduct(productId, productData) {
229
- return stripe.products.update(productId, {
230
- name: productData.name,
231
- description: productData.description || "",
232
- default_price: productData.stripePriceId
233
- });
234
- },
235
- async deleteProduct(productId) {
236
- return stripe.products.del(productId);
237
- },
238
- async createSubscription(customerId, priceId, options = {}) {
239
- return stripe.subscriptions.create({
240
- customer: customerId,
241
- items: [{ price: priceId }],
242
- ...options
243
- });
244
- },
245
- async updateSubscription(subscriptionId, options = {}) {
246
- return stripe.subscriptions.update(subscriptionId, options);
247
- },
248
- async cancelSubscription(subscriptionId) {
249
- return stripe.subscriptions.cancel(subscriptionId);
250
- },
251
- async getSubscription(subscriptionId) {
252
- return stripe.subscriptions.retrieve(subscriptionId);
253
- },
254
- async getTransactions(limit = 10, startingAfter = null) {
255
- const params = {
256
- limit: Math.min(limit, 100)
257
- // Max 100 per Stripe API
258
- };
259
- if (startingAfter) {
260
- params.starting_after = startingAfter;
270
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
271
+ const paymentIntent = await stripeService2.confirmPayment(paymentIntentId);
272
+ ctx.body = {
273
+ success: true,
274
+ data: {
275
+ paymentIntentId: paymentIntent.id,
276
+ status: paymentIntent.status,
277
+ amount: paymentIntent.amount,
278
+ currency: paymentIntent.currency
279
+ }
280
+ };
281
+ } catch (error) {
282
+ strapi.log.error("Failed to confirm payment", { error });
283
+ ctx.internalServerError("Failed to confirm payment");
261
284
  }
262
- const payments = await stripe.paymentIntents.list(params);
263
- return payments.data;
264
285
  },
265
- async importProductFromStripe(stripeProductId) {
266
- const product2 = await stripe.products.retrieve(stripeProductId, {
267
- expand: ["default_price"]
268
- });
269
- let priceInfo = null;
270
- if (product2.default_price) {
271
- if (typeof product2.default_price === "string") {
272
- const price = await stripe.prices.retrieve(product2.default_price);
273
- priceInfo = {
274
- unit_amount: price.unit_amount,
275
- currency: price.currency
276
- };
277
- } else {
278
- priceInfo = {
279
- unit_amount: product2.default_price.unit_amount,
280
- currency: product2.default_price.currency
281
- };
286
+ /**
287
+ * Create a Stripe customer
288
+ */
289
+ async createCustomer(ctx) {
290
+ try {
291
+ const { email, firstName, lastName, phone, address, metadata = {} } = ctx.request.body;
292
+ if (!email || !firstName || !lastName) {
293
+ return ctx.badRequest("Email, first name, and last name are required");
282
294
  }
295
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
296
+ const customer2 = await stripeService2.createCustomer({
297
+ email,
298
+ firstName,
299
+ lastName,
300
+ phone,
301
+ address,
302
+ metadata
303
+ });
304
+ ctx.body = {
305
+ success: true,
306
+ data: {
307
+ customerId: customer2.id,
308
+ email: customer2.email,
309
+ name: customer2.name,
310
+ created: customer2.created
311
+ }
312
+ };
313
+ } catch (error) {
314
+ strapi.log.error("Failed to create customer", { error });
315
+ ctx.internalServerError("Failed to create customer");
283
316
  }
284
- return {
285
- name: product2.name,
286
- description: product2.description,
287
- price: priceInfo ? priceInfo.unit_amount / 100 : 0,
288
- currency: priceInfo ? priceInfo.currency : "USD",
289
- stripeProductId: product2.id
290
- };
291
- },
292
- async createPayment(amount, currency, paymentMethod, returnUrl = "https://example.com/return") {
293
- const paymentIntent = await stripe.paymentIntents.create({
294
- amount,
295
- currency,
296
- payment_method: paymentMethod,
297
- confirmation_method: "automatic",
298
- confirm: true,
299
- return_url: returnUrl
300
- });
301
- return {
302
- paymentIntent,
303
- redirectUrl: paymentIntent.next_action?.redirect_to_url?.url || null
304
- };
305
317
  },
306
- async createPaymentWithUrl(amount, currency, paymentMethod, returnUrl = "https://example.com/return") {
307
- const paymentIntent = await stripe.paymentIntents.create({
308
- amount,
309
- currency,
310
- payment_method: paymentMethod,
311
- confirmation_method: "automatic",
312
- confirm: true,
313
- return_url: returnUrl
314
- });
315
- if (paymentIntent.next_action?.type === "redirect_to_url") {
316
- return {
317
- paymentIntent,
318
- redirectUrl: paymentIntent.next_action.redirect_to_url.url
318
+ /**
319
+ * Retrieve a Stripe customer
320
+ */
321
+ async retrieveCustomer(ctx) {
322
+ try {
323
+ const { customerId } = ctx.params;
324
+ if (!customerId) {
325
+ return ctx.badRequest("Customer ID is required");
326
+ }
327
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
328
+ const customer2 = await stripeService2.retrieveCustomer(customerId);
329
+ if ("deleted" in customer2 && customer2.deleted) {
330
+ return ctx.notFound("Customer not found");
331
+ }
332
+ ctx.body = {
333
+ success: true,
334
+ data: {
335
+ customerId: customer2.id,
336
+ email: customer2.email,
337
+ name: customer2.name,
338
+ created: customer2.created,
339
+ metadata: customer2.metadata
340
+ }
319
341
  };
342
+ } catch (error) {
343
+ strapi.log.error("Failed to retrieve customer", { error });
344
+ ctx.internalServerError("Failed to retrieve customer");
320
345
  }
321
- return {
322
- paymentIntent,
323
- redirectUrl: null
324
- };
325
346
  },
326
- async createPaymentLink(productId, options = {}) {
327
- const paymentLink = await stripe.paymentLinks.create({
328
- line_items: [
329
- {
330
- price: productId,
331
- quantity: 1
347
+ /**
348
+ * Create a refund
349
+ */
350
+ async createRefund(ctx) {
351
+ try {
352
+ const { paymentIntentId, amount, reason, metadata = {} } = ctx.request.body;
353
+ if (!paymentIntentId) {
354
+ return ctx.badRequest("Payment intent ID is required");
355
+ }
356
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
357
+ const refund = await stripeService2.createRefund({
358
+ paymentIntentId,
359
+ amount,
360
+ reason,
361
+ metadata
362
+ });
363
+ ctx.body = {
364
+ success: true,
365
+ data: {
366
+ refundId: refund.id,
367
+ amount: refund.amount,
368
+ currency: refund.currency,
369
+ status: refund.status,
370
+ reason: refund.reason
332
371
  }
333
- ],
334
- ...options
335
- });
336
- return paymentLink;
337
- }
338
- };
339
- const controller = ({ strapi }) => ({
340
- async initiatePayment(ctx) {
341
- const { gateway, amount, currency, orderId, options } = ctx.request.body;
342
- const result = await strapi.plugin("strapi-payment-plugin").service("service").initializePayment(gateway, amount, currency, orderId, options);
343
- ctx.body = result;
372
+ };
373
+ } catch (error) {
374
+ strapi.log.error("Failed to create refund", { error });
375
+ ctx.internalServerError("Failed to create refund");
376
+ }
344
377
  },
345
- async processWebhook(ctx) {
346
- const { gateway } = ctx.params;
347
- const payload = ctx.request.body;
348
- const signature = ctx.request.headers["stripe-signature"] || ctx.request.headers["paypal-transmission-id"];
349
- const result = await strapi.plugin("strapi-payment-plugin").service("service").processWebhook(gateway, payload, signature);
350
- ctx.body = result;
378
+ /**
379
+ * Handle Stripe webhooks
380
+ */
381
+ async handleWebhook(ctx) {
382
+ try {
383
+ const signature = ctx.request.headers["stripe-signature"];
384
+ let payload;
385
+ if (typeof ctx.request.body === "string") {
386
+ payload = ctx.request.body;
387
+ } else if (Buffer.isBuffer(ctx.request.body)) {
388
+ payload = ctx.request.body;
389
+ } else {
390
+ payload = JSON.stringify(ctx.request.body);
391
+ }
392
+ if (!signature) {
393
+ return ctx.badRequest("Missing Stripe signature");
394
+ }
395
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
396
+ const event = await stripeService2.constructEvent(payload, signature);
397
+ await stripeService2.handleWebhook(event);
398
+ ctx.body = { received: true };
399
+ } catch (error) {
400
+ strapi.log.error("Failed to handle webhook", { error });
401
+ ctx.badRequest(`Webhook Error: ${error.message}`);
402
+ }
351
403
  },
352
- async refundPayment(ctx) {
353
- const { gateway, transactionId, amount, options } = ctx.request.body;
354
- const result = await strapi.plugin("strapi-payment-plugin").service("service").refundPayment(gateway, transactionId, amount, options);
355
- ctx.body = result;
404
+ /**
405
+ * Get Stripe service status/health check
406
+ */
407
+ async getServiceStatus(ctx) {
408
+ try {
409
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
410
+ let stripe;
411
+ try {
412
+ stripe = await stripeService2.initializeStripe();
413
+ } catch (e) {
414
+ stripe = null;
415
+ }
416
+ ctx.body = {
417
+ success: true,
418
+ data: {
419
+ service: "Stripe Payment Service",
420
+ status: stripe ? "initialized" : "not initialized",
421
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
422
+ }
423
+ };
424
+ } catch (error) {
425
+ strapi.log.error("Failed to get service status", { error });
426
+ ctx.internalServerError("Failed to get service status");
427
+ }
356
428
  },
429
+ /**
430
+ * Get payment details by Stripe payment intent ID
431
+ */
357
432
  async getPaymentDetails(ctx) {
358
- const { gateway, transactionId } = ctx.params;
359
- const result = await strapi.plugin("strapi-payment-plugin").service("service").getPaymentDetails(gateway, transactionId);
360
- ctx.body = result;
433
+ try {
434
+ const { id: paymentIntentId } = ctx.params;
435
+ if (!paymentIntentId) {
436
+ return ctx.badRequest("Payment intent ID is required");
437
+ }
438
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
439
+ const stripe = await stripeService2.initializeStripe();
440
+ if (!stripe) {
441
+ return ctx.serviceUnavailable("Stripe service not initialized");
442
+ }
443
+ const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
444
+ const strapiPayments = await strapi.documents("plugin::payment-plugin.payment").findMany({
445
+ filters: {
446
+ stripe_payment_intent_id: paymentIntentId
447
+ },
448
+ populate: ["customer", "order"]
449
+ });
450
+ const strapiPayment = strapiPayments.length > 0 ? strapiPayments[0] : null;
451
+ ctx.body = {
452
+ success: true,
453
+ data: {
454
+ stripe: {
455
+ id: paymentIntent.id,
456
+ amount: paymentIntent.amount,
457
+ currency: paymentIntent.currency,
458
+ status: paymentIntent.status,
459
+ created: paymentIntent.created,
460
+ metadata: paymentIntent.metadata
461
+ },
462
+ strapi: strapiPayment
463
+ }
464
+ };
465
+ } catch (error) {
466
+ strapi.log.error("Failed to get payment details", { error });
467
+ ctx.internalServerError("Failed to get payment details");
468
+ }
361
469
  },
362
- async getTransactions(ctx) {
363
- console.log("Fetching transactions with params:", ctx.query);
364
- const { gateway, status, limit = 100, offset = 0 } = ctx.query;
365
- const result = await strapi.plugin("strapi-payment-plugin").service("service").getTransactions(gateway, status, limit, offset);
366
- ctx.body = result;
470
+ /**
471
+ * List payments with filtering and pagination
472
+ */
473
+ async listPayments(ctx) {
474
+ try {
475
+ const { page = 1, pageSize = 25, status, customer: customer2, order: order2 } = ctx.query;
476
+ const { id: userId } = ctx.state.user;
477
+ const filters = {};
478
+ if (status) filters.status = { $eq: status };
479
+ if (customer2) filters.customer = { $eq: customer2 };
480
+ if (order2) filters.order = { $eq: order2 };
481
+ const isAdmin = ctx.state.user.role?.name === "Administrator";
482
+ if (!isAdmin) {
483
+ filters.$or = [
484
+ { customer: { $eq: userId } },
485
+ { order: { $eq: userId } }
486
+ ];
487
+ }
488
+ const pageNum = parseInt(page);
489
+ const pageSizeNum = parseInt(pageSize);
490
+ const start = (pageNum - 1) * pageSizeNum;
491
+ const [payments, total] = await Promise.all([
492
+ strapi.documents("plugin::payment-plugin.payment").findMany({
493
+ filters,
494
+ populate: ["customer", "order"],
495
+ sort: { createdAt: "desc" },
496
+ start,
497
+ limit: pageSizeNum
498
+ }),
499
+ strapi.documents("plugin::payment-plugin.payment").count({ filters })
500
+ ]);
501
+ ctx.body = {
502
+ success: true,
503
+ data: payments,
504
+ meta: {
505
+ pagination: {
506
+ page: pageNum,
507
+ pageSize: pageSizeNum,
508
+ pageCount: Math.ceil(total / pageSizeNum),
509
+ total
510
+ }
511
+ }
512
+ };
513
+ } catch (error) {
514
+ strapi.log.error("Failed to list payments", { error });
515
+ ctx.internalServerError("Failed to list payments");
516
+ }
367
517
  },
368
- async saveConfiguration(ctx) {
369
- const configData = ctx.request.body;
370
- const result = await strapi.plugin("strapi-payment-plugin").service("service").saveConfiguration(configData);
371
- ctx.body = result;
518
+ /**
519
+ * List customers with filtering and pagination
520
+ */
521
+ async listCustomers(ctx) {
522
+ try {
523
+ const { page = 1, pageSize = 25, email } = ctx.query;
524
+ const { id: userId } = ctx.state.user;
525
+ const filters = {};
526
+ if (email) filters.email = { $containsi: email };
527
+ const isAdmin = ctx.state.user.role?.name === "Administrator";
528
+ if (!isAdmin) {
529
+ filters.documentId = userId;
530
+ }
531
+ const pageNum = parseInt(page);
532
+ const pageSizeNum = parseInt(pageSize);
533
+ const start = (pageNum - 1) * pageSizeNum;
534
+ const [customers, total] = await Promise.all([
535
+ strapi.documents("plugin::payment-plugin.customer").findMany({
536
+ filters,
537
+ sort: { createdAt: "desc" },
538
+ start,
539
+ limit: pageSizeNum
540
+ }),
541
+ strapi.documents("plugin::payment-plugin.customer").count({ filters })
542
+ ]);
543
+ ctx.body = {
544
+ success: true,
545
+ data: customers,
546
+ meta: {
547
+ pagination: {
548
+ page: pageNum,
549
+ pageSize: pageSizeNum,
550
+ pageCount: Math.ceil(total / pageSizeNum),
551
+ total
552
+ }
553
+ }
554
+ };
555
+ } catch (error) {
556
+ strapi.log.error("Failed to list customers", { error });
557
+ ctx.internalServerError("Failed to list customers");
558
+ }
372
559
  },
373
- async getConfigStatus(ctx) {
374
- console.log("Fetching configuration status");
375
- const result = await strapi.plugin("strapi-payment-plugin").service("service").getConfigStatus();
376
- ctx.body = result;
560
+ /**
561
+ * Create a new order
562
+ */
563
+ async createOrder(ctx) {
564
+ try {
565
+ const { orderNumber, totalAmount, currency, items, customerId, metadata = {} } = ctx.request.body;
566
+ if (!orderNumber || !totalAmount || !currency || !items) {
567
+ return ctx.badRequest("Order number, total amount, currency, and items are required");
568
+ }
569
+ if (totalAmount <= 0) {
570
+ return ctx.badRequest("Total amount must be greater than 0");
571
+ }
572
+ if (customerId) {
573
+ const customers = await strapi.documents("plugin::payment-plugin.customer").findMany({
574
+ filters: {
575
+ documentId: customerId
576
+ }
577
+ });
578
+ if (customers.length === 0) {
579
+ return ctx.badRequest("Customer not found");
580
+ }
581
+ }
582
+ const orderData = {
583
+ order_number: orderNumber,
584
+ total_amount: totalAmount,
585
+ currency,
586
+ status: "pending",
587
+ customer: customerId,
588
+ items,
589
+ metadata
590
+ };
591
+ const order2 = await strapi.documents("plugin::payment-plugin.order").create({
592
+ data: orderData
593
+ });
594
+ ctx.body = {
595
+ success: true,
596
+ data: {
597
+ orderId: order2.documentId,
598
+ orderNumber: order2.order_number,
599
+ status: order2.status,
600
+ totalAmount: order2.total_amount,
601
+ currency: order2.currency,
602
+ created: order2.createdAt
603
+ }
604
+ };
605
+ } catch (error) {
606
+ strapi.log.error("Failed to create order", { error });
607
+ ctx.internalServerError("Failed to create order");
608
+ }
377
609
  },
378
- async testPayment(ctx) {
379
- const { amount, currency, paymentMethod, returnUrl } = ctx.request.body;
610
+ /**
611
+ * Get order details by ID
612
+ */
613
+ async getOrderDetails(ctx) {
380
614
  try {
381
- const result = await stripeDriver.createPayment(amount, currency, paymentMethod, returnUrl);
615
+ const { id } = ctx.params;
616
+ const { id: userId } = ctx.state.user;
617
+ if (!id) {
618
+ return ctx.badRequest("Order ID is required");
619
+ }
620
+ const isAdmin = ctx.state.user.role?.name === "Administrator";
621
+ const filters = { documentId: id };
622
+ if (!isAdmin) {
623
+ filters.$or = [
624
+ { customer: { $eq: userId } },
625
+ { customer: { $eq: userId } }
626
+ ];
627
+ }
628
+ const orders = await strapi.documents("plugin::payment-plugin.order").findMany({
629
+ filters,
630
+ populate: ["customer"]
631
+ });
632
+ if (orders.length === 0) {
633
+ return ctx.notFound("Order not found");
634
+ }
635
+ const order2 = orders[0];
636
+ const payments = await strapi.documents("plugin::payment-plugin.payment").findMany({
637
+ filters: { order: { $eq: order2.documentId } },
638
+ sort: { createdAt: "desc" }
639
+ });
382
640
  ctx.body = {
383
641
  success: true,
384
- paymentId: result.paymentIntent.id,
385
- message: "Payment successful",
386
- redirectUrl: result.redirectUrl
642
+ data: {
643
+ order: order2,
644
+ payments
645
+ }
387
646
  };
388
647
  } catch (error) {
389
- console.error("Payment test error:", error);
648
+ strapi.log.error("Failed to get order details", { error });
649
+ ctx.internalServerError("Failed to get order details");
650
+ }
651
+ },
652
+ /**
653
+ * List orders with filtering and pagination
654
+ */
655
+ async listOrders(ctx) {
656
+ try {
657
+ const { page = 1, pageSize = 25, status, customer: customer2 } = ctx.query;
658
+ const { id: userId } = ctx.state.user;
659
+ const filters = {};
660
+ if (status) filters.status = { $eq: status };
661
+ if (customer2) filters.customer = { $eq: customer2 };
662
+ const isAdmin = ctx.state.user.role?.name === "Administrator";
663
+ if (!isAdmin) {
664
+ filters.customer = { $eq: userId };
665
+ }
666
+ const pageNum = parseInt(page);
667
+ const pageSizeNum = parseInt(pageSize);
668
+ const start = (pageNum - 1) * pageSizeNum;
669
+ const [orders, total] = await Promise.all([
670
+ strapi.documents("plugin::payment-plugin.order").findMany({
671
+ filters,
672
+ populate: ["customer"],
673
+ sort: { createdAt: "desc" },
674
+ start,
675
+ limit: pageSizeNum
676
+ }),
677
+ strapi.documents("plugin::payment-plugin.order").count({ filters })
678
+ ]);
390
679
  ctx.body = {
391
- success: false,
392
- message: error.message
680
+ success: true,
681
+ data: orders,
682
+ meta: {
683
+ pagination: {
684
+ page: pageNum,
685
+ pageSize: pageSizeNum,
686
+ pageCount: Math.ceil(total / pageSizeNum),
687
+ total
688
+ }
689
+ }
393
690
  };
394
- ctx.status = 400;
691
+ } catch (error) {
692
+ strapi.log.error("Failed to list orders", { error });
693
+ ctx.internalServerError("Failed to list orders");
395
694
  }
396
695
  },
397
- async testPaymentWithRedirect(ctx) {
398
- const { productId } = ctx.request.body;
696
+ // Admin Controller Methods
697
+ /**
698
+ * Get dashboard data for admin panel
699
+ */
700
+ async getDashboardData(ctx) {
399
701
  try {
400
- const result = await stripeDriver.createPaymentLink(productId);
401
- console.log("Payment link created:", result);
702
+ const { period = "30d" } = ctx.query;
703
+ const now = /* @__PURE__ */ new Date();
704
+ const days = parseInt(period);
705
+ const startDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
706
+ const [totalPayments, successfulPayments, failedPayments, totalRevenue] = await Promise.all([
707
+ strapi.documents("plugin::payment-plugin.payment").count({}),
708
+ strapi.documents("plugin::payment-plugin.payment").count({ filters: { status: { $eq: "succeeded" } } }),
709
+ strapi.documents("plugin::payment-plugin.payment").count({ filters: { status: { $eq: "failed" } } }),
710
+ strapi.documents("plugin::payment-plugin.payment").findMany({
711
+ filters: {
712
+ status: { $eq: "succeeded" },
713
+ createdAt: { $gte: startDate.toISOString() }
714
+ },
715
+ fields: ["amount"]
716
+ })
717
+ ]);
718
+ const revenue = totalRevenue.reduce((sum, payment2) => sum + payment2.amount, 0);
719
+ const recentTransactions = await strapi.documents("plugin::payment-plugin.payment").findMany({
720
+ filters: { createdAt: { $gte: startDate.toISOString() } },
721
+ populate: ["customer", "order"],
722
+ sort: { createdAt: "desc" },
723
+ limit: 10
724
+ });
725
+ const topCustomers = await strapi.documents("plugin::payment-plugin.customer").findMany({
726
+ sort: { createdAt: "desc" },
727
+ limit: 5
728
+ });
402
729
  ctx.body = {
403
730
  success: true,
404
- paymentLink: result.url,
405
- message: "Payment link created successfully"
731
+ data: {
732
+ summary: {
733
+ totalPayments,
734
+ successfulPayments,
735
+ failedPayments,
736
+ successRate: totalPayments > 0 ? successfulPayments / totalPayments * 100 : 0,
737
+ totalRevenue: revenue,
738
+ period
739
+ },
740
+ recentTransactions,
741
+ topCustomers
742
+ }
406
743
  };
407
744
  } catch (error) {
408
- console.error("Payment test error:", error);
745
+ strapi.log.error("Failed to get dashboard data", { error });
746
+ ctx.internalServerError("Failed to get dashboard data");
747
+ }
748
+ },
749
+ /**
750
+ * Get payment reports
751
+ */
752
+ async getPaymentReports(ctx) {
753
+ try {
754
+ const { startDate, endDate, format = "json" } = ctx.query;
755
+ const filters = {};
756
+ if (startDate || endDate) {
757
+ filters.createdAt = {};
758
+ if (startDate) filters.createdAt.$gte = new Date(startDate).toISOString();
759
+ if (endDate) filters.createdAt.$lte = new Date(endDate).toISOString();
760
+ }
761
+ const payments = await strapi.documents("plugin::payment-plugin.payment").findMany({
762
+ filters,
763
+ populate: ["customer", "order"],
764
+ sort: { createdAt: "desc" }
765
+ });
766
+ const summary = {
767
+ totalPayments: payments.length,
768
+ totalAmount: payments.reduce((sum, p) => sum + p.amount, 0),
769
+ byStatus: payments.reduce((acc, p) => {
770
+ acc[p.status] = (acc[p.status] || 0) + 1;
771
+ return acc;
772
+ }, {}),
773
+ byCurrency: payments.reduce((acc, p) => {
774
+ acc[p.currency] = (acc[p.currency] || 0) + 1;
775
+ return acc;
776
+ }, {})
777
+ };
409
778
  ctx.body = {
410
- success: false,
411
- message: error.message
779
+ success: true,
780
+ data: {
781
+ summary,
782
+ payments,
783
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
784
+ dateRange: { startDate, endDate }
785
+ }
412
786
  };
413
- ctx.status = 400;
787
+ } catch (error) {
788
+ strapi.log.error("Failed to get payment reports", { error });
789
+ ctx.internalServerError("Failed to get payment reports");
414
790
  }
415
- }
416
- });
417
- const product$1 = factories.createCoreController("plugin::strapi-payment-plugin.product", ({ strapi }) => ({
418
- async create(ctx) {
791
+ },
792
+ /**
793
+ * Get plugin configuration
794
+ */
795
+ async getPluginConfig(ctx) {
419
796
  try {
420
- const { data } = ctx.request.body;
421
- const product2 = await strapi.service("plugin::strapi-payment-plugin.product").createProduct(data);
422
- ctx.body = product2;
797
+ const store = strapi.store({ type: "plugin", name: "payment-plugin" });
798
+ const config2 = await store.get({ key: "settings" }) || {};
799
+ const staticConfig = strapi.config.get("plugin::payment-plugin") || {};
800
+ const mergedConfig = { ...staticConfig || {}, ...config2 || {} };
801
+ const sanitizedConfig = {
802
+ ...mergedConfig,
803
+ stripe: mergedConfig.stripe ? {
804
+ ...mergedConfig.stripe,
805
+ publishableKey: mergedConfig.stripe.publishableKey || process.env.STRIPE_PUBLISHABLE_KEY || (process.env.STRIPE_API_KEY?.startsWith("pk_") ? process.env.STRIPE_API_KEY : ""),
806
+ secretKey: mergedConfig.stripe.secretKey || process.env.STRIPE_SECRET_KEY || process.env.STRIPE_WEBHOOK_SECRET?.startsWith("sk_") ? "***configured***" : null,
807
+ webhookSecret: mergedConfig.stripe.webhookSecret || process.env.STRIPE_WEBHOOK_SECRET?.startsWith("whsec_") ? "***configured***" : null
808
+ } : {}
809
+ };
810
+ ctx.body = {
811
+ success: true,
812
+ data: sanitizedConfig
813
+ };
423
814
  } catch (error) {
424
- ctx.throw(500, error);
815
+ strapi.log.error("Failed to get plugin config", { error });
816
+ ctx.internalServerError("Failed to get plugin config");
425
817
  }
426
818
  },
427
- async update(ctx) {
819
+ /**
820
+ * Update plugin configuration
821
+ */
822
+ async updatePluginConfig(ctx) {
428
823
  try {
429
- const { id } = ctx.params;
430
- const { data } = ctx.request.body;
431
- const product2 = await strapi.service("plugin::strapi-payment-plugin.product").updateProduct(id, data);
432
- ctx.body = product2;
824
+ const { stripe: newStripe, payment: payment2, logging } = ctx.request.body;
825
+ const store = strapi.store({ type: "plugin", name: "payment-plugin" });
826
+ const currentConfig = await store.get({ key: "settings" }) || {};
827
+ const currentStripe = currentConfig.stripe || {};
828
+ const stripe = {
829
+ ...newStripe,
830
+ secretKey: newStripe.secretKey === "***configured***" ? currentStripe.secretKey : newStripe.secretKey,
831
+ webhookSecret: newStripe.webhookSecret === "***configured***" ? currentStripe.webhookSecret : newStripe.webhookSecret
832
+ };
833
+ const config2 = {
834
+ stripe: stripe || {},
835
+ payment: payment2 || {},
836
+ logging: logging || {}
837
+ };
838
+ await store.set({ key: "settings", value: config2 });
839
+ ctx.body = {
840
+ success: true,
841
+ data: { message: "Configuration updated successfully" }
842
+ };
433
843
  } catch (error) {
434
- ctx.throw(500, error);
844
+ strapi.log.error("Failed to update plugin config", { error });
845
+ ctx.internalServerError("Failed to update plugin config");
435
846
  }
436
847
  },
437
- async find(ctx) {
848
+ /**
849
+ * Get analytics data
850
+ */
851
+ async getAnalytics(ctx) {
438
852
  try {
439
- const products = await strapi.db.query("plugin::strapi-payment-plugin.product").findMany({
440
- orderBy: { name: "asc" }
853
+ const { period = "30d" } = ctx.query;
854
+ const days = parseInt(period);
855
+ const now = /* @__PURE__ */ new Date();
856
+ const startDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
857
+ const payments = await strapi.documents("plugin::payment-plugin.payment").findMany({
858
+ filters: {
859
+ createdAt: { $gte: startDate.toISOString() },
860
+ status: { $eq: "succeeded" }
861
+ },
862
+ fields: ["amount", "createdAt", "currency"],
863
+ sort: { createdAt: "asc" }
441
864
  });
442
- ctx.body = products;
865
+ const dailyStats = payments.reduce((acc, payment2) => {
866
+ const date = new Date(payment2.createdAt).toISOString().split("T")[0];
867
+ if (!acc[date]) {
868
+ acc[date] = { count: 0, amount: 0, currency: payment2.currency };
869
+ }
870
+ acc[date].count += 1;
871
+ acc[date].amount += payment2.amount;
872
+ return acc;
873
+ }, {});
874
+ ctx.body = {
875
+ success: true,
876
+ data: {
877
+ period,
878
+ dailyStats,
879
+ totalVolume: payments.reduce((sum, p) => sum + p.amount, 0),
880
+ totalTransactions: payments.length,
881
+ averageTransaction: payments.length > 0 ? payments.reduce((sum, p) => sum + p.amount, 0) / payments.length : 0
882
+ }
883
+ };
443
884
  } catch (error) {
444
- ctx.throw(500, error);
885
+ strapi.log.error("Failed to get analytics", { error });
886
+ ctx.internalServerError("Failed to get analytics");
445
887
  }
446
888
  },
447
- async findOne(ctx) {
889
+ /**
890
+ * Admin create refund
891
+ */
892
+ async adminCreateRefund(ctx) {
448
893
  try {
449
- const { id } = ctx.params;
450
- const product2 = await strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
451
- where: { id }
894
+ const { id: paymentId } = ctx.params;
895
+ const { amount, reason, metadata = {} } = ctx.request.body;
896
+ if (!paymentId) {
897
+ return ctx.badRequest("Payment ID is required");
898
+ }
899
+ const payments = await strapi.documents("plugin::payment-plugin.payment").findMany({
900
+ filters: { documentId: paymentId }
452
901
  });
453
- if (!product2) {
454
- return ctx.notFound("Product not found");
902
+ if (payments.length === 0) {
903
+ return ctx.notFound("Payment not found");
455
904
  }
456
- ctx.body = product2;
905
+ const payment2 = payments[0];
906
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
907
+ const refund = await stripeService2.createRefund({
908
+ paymentIntentId: payment2.stripe_payment_intent_id,
909
+ amount,
910
+ reason,
911
+ metadata: { ...metadata, admin_refund: true }
912
+ });
913
+ await strapi.documents("plugin::payment-plugin.payment").update({
914
+ documentId: payment2.documentId,
915
+ data: { status: "refunded" }
916
+ });
917
+ ctx.body = {
918
+ success: true,
919
+ data: {
920
+ refundId: refund.id,
921
+ amount: refund.amount,
922
+ currency: refund.currency,
923
+ status: refund.status,
924
+ reason: refund.reason
925
+ }
926
+ };
457
927
  } catch (error) {
458
- ctx.throw(500, error);
928
+ strapi.log.error("Failed to create admin refund", { error });
929
+ ctx.internalServerError("Failed to create admin refund");
459
930
  }
460
931
  },
461
- async delete(ctx) {
932
+ /**
933
+ * Admin list payments (no user restrictions)
934
+ */
935
+ async adminListPayments(ctx) {
462
936
  try {
463
- const { id } = ctx.params;
464
- const product2 = await strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
465
- where: { id }
466
- });
467
- if (!product2) {
468
- return ctx.notFound("Product not found");
469
- }
470
- if (product2.stripeProductId) {
471
- try {
472
- await stripeDriver.deleteProduct(product2.stripeProductId);
473
- } catch (stripeError) {
474
- console.error("Error deleting product from Stripe:", stripeError);
937
+ const { page = 1, pageSize = 25, status, customer: customer2, order: order2 } = ctx.query;
938
+ const filters = {};
939
+ if (status) filters.status = { $eq: status };
940
+ if (customer2) filters.customer = { $eq: customer2 };
941
+ if (order2) filters.order = { $eq: order2 };
942
+ const pageNum = parseInt(page);
943
+ const pageSizeNum = parseInt(pageSize);
944
+ const start = (pageNum - 1) * pageSizeNum;
945
+ const [payments, total] = await Promise.all([
946
+ strapi.documents("plugin::payment-plugin.payment").findMany({
947
+ filters,
948
+ populate: ["customer", "order"],
949
+ sort: { createdAt: "desc" },
950
+ start,
951
+ limit: pageSizeNum
952
+ }),
953
+ strapi.documents("plugin::payment-plugin.payment").count({ filters })
954
+ ]);
955
+ ctx.body = {
956
+ success: true,
957
+ data: payments,
958
+ meta: {
959
+ pagination: {
960
+ page: pageNum,
961
+ pageSize: pageSizeNum,
962
+ pageCount: Math.ceil(total / pageSizeNum),
963
+ total
964
+ }
475
965
  }
476
- }
477
- await strapi.db.query("plugin::strapi-payment-plugin.product").delete({
478
- where: { id }
479
- });
480
- ctx.body = { message: "Product deleted successfully" };
966
+ };
481
967
  } catch (error) {
482
- ctx.throw(500, error);
968
+ strapi.log.error("Failed to list admin payments", { error });
969
+ ctx.internalServerError("Failed to list admin payments");
483
970
  }
484
971
  },
485
- async sync(ctx) {
972
+ /**
973
+ * Admin list customers (no user restrictions)
974
+ */
975
+ async adminListCustomers(ctx) {
486
976
  try {
487
- const { id } = ctx.params;
488
- const product2 = await strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
489
- where: { id }
977
+ const { page = 1, pageSize = 25, email } = ctx.query;
978
+ const filters = {};
979
+ if (email) filters.email = { $containsi: email };
980
+ const pageNum = parseInt(page);
981
+ const pageSizeNum = parseInt(pageSize);
982
+ const start = (pageNum - 1) * pageSizeNum;
983
+ const [customers, total] = await Promise.all([
984
+ strapi.documents("plugin::payment-plugin.customer").findMany({
985
+ filters,
986
+ populate: {
987
+ payments: {
988
+ filters: { status: { $eq: "succeeded" } },
989
+ fields: ["amount"]
990
+ }
991
+ },
992
+ sort: { createdAt: "desc" },
993
+ start,
994
+ limit: pageSizeNum
995
+ }),
996
+ strapi.documents("plugin::payment-plugin.customer").count({ filters })
997
+ ]);
998
+ const customersWithTotals = customers.map((customer2) => {
999
+ const successfulPayments = customer2.payments || [];
1000
+ const totalSpent = successfulPayments.reduce((sum, p) => sum + p.amount, 0);
1001
+ const paymentCount = successfulPayments.length;
1002
+ return {
1003
+ ...customer2,
1004
+ totalSpent,
1005
+ paymentCount
1006
+ };
490
1007
  });
491
- if (!product2) {
492
- return ctx.notFound("Product not found");
493
- }
494
- if (product2.stripeProductId) {
495
- const stripeProduct = await stripeDriver.updateProduct(product2.stripeProductId, {
496
- name: product2.name,
497
- description: product2.description
498
- });
499
- ctx.body = { message: "Product synced successfully", stripeProduct };
500
- } else {
501
- const stripeProduct = await stripeDriver.createProduct(product2);
502
- await strapi.db.query("plugin::strapi-payment-plugin.product").update({
503
- where: { id },
504
- data: { stripeProductId: stripeProduct.id }
505
- });
506
- ctx.body = { message: "Product created in Stripe successfully", stripeProduct };
507
- }
1008
+ ctx.body = {
1009
+ success: true,
1010
+ data: customersWithTotals,
1011
+ meta: {
1012
+ pagination: {
1013
+ page: pageNum,
1014
+ pageSize: pageSizeNum,
1015
+ pageCount: Math.ceil(total / pageSizeNum),
1016
+ total
1017
+ }
1018
+ }
1019
+ };
508
1020
  } catch (error) {
509
- ctx.throw(500, error);
1021
+ strapi.log.error("Failed to list admin customers", { error });
1022
+ ctx.internalServerError("Failed to list admin customers");
510
1023
  }
511
1024
  },
512
- async getStripeProducts(ctx) {
1025
+ /**
1026
+ * Admin list orders (no user restrictions)
1027
+ */
1028
+ async adminListOrders(ctx) {
513
1029
  try {
514
- const stripeProducts = await stripeDriver.getStripeProducts();
515
- ctx.body = stripeProducts;
1030
+ const { page = 1, pageSize = 25, status, customer: customer2 } = ctx.query;
1031
+ const filters = {};
1032
+ if (status) filters.status = { $eq: status };
1033
+ if (customer2) filters.customer = { $eq: customer2 };
1034
+ const pageNum = parseInt(page);
1035
+ const pageSizeNum = parseInt(pageSize);
1036
+ const start = (pageNum - 1) * pageSizeNum;
1037
+ const [orders, total] = await Promise.all([
1038
+ strapi.documents("plugin::payment-plugin.order").findMany({
1039
+ filters,
1040
+ populate: ["customer"],
1041
+ sort: { createdAt: "desc" },
1042
+ start,
1043
+ limit: pageSizeNum
1044
+ }),
1045
+ strapi.documents("plugin::payment-plugin.order").count({ filters })
1046
+ ]);
1047
+ ctx.body = {
1048
+ success: true,
1049
+ data: orders,
1050
+ meta: {
1051
+ pagination: {
1052
+ page: pageNum,
1053
+ pageSize: pageSizeNum,
1054
+ pageCount: Math.ceil(total / pageSizeNum),
1055
+ total
1056
+ }
1057
+ }
1058
+ };
516
1059
  } catch (error) {
517
- ctx.throw(500, error);
1060
+ strapi.log.error("Failed to list admin orders", { error });
1061
+ ctx.internalServerError("Failed to list admin orders");
518
1062
  }
519
1063
  },
520
- async importFromStripe(ctx) {
1064
+ /**
1065
+ * Test webhook endpoint
1066
+ */
1067
+ async testWebhook(ctx) {
521
1068
  try {
522
- const { stripeProductId } = ctx.request.body;
523
- const productData = await stripeDriver.importProductFromStripe(stripeProductId);
524
- const existingProduct = await strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
525
- where: { stripeProductId }
526
- });
527
- if (existingProduct) {
528
- return ctx.badRequest("Product already imported");
529
- }
530
- const product2 = await strapi.db.query("plugin::strapi-payment-plugin.product").create({
531
- data: productData
1069
+ const { eventType = "payment_intent.succeeded" } = ctx.request.body;
1070
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1071
+ const testEvent = {
1072
+ id: `evt_test_${Date.now()}`,
1073
+ object: "event",
1074
+ type: eventType,
1075
+ data: {
1076
+ object: {
1077
+ id: `pi_test_${Date.now()}`,
1078
+ object: "payment_intent",
1079
+ amount: 1e3,
1080
+ currency: "usd",
1081
+ status: "succeeded",
1082
+ metadata: { test: true }
1083
+ }
1084
+ }
1085
+ };
1086
+ await stripeService2.handleWebhook(testEvent);
1087
+ ctx.body = {
1088
+ success: true,
1089
+ data: {
1090
+ message: "Test webhook processed successfully",
1091
+ eventId: testEvent.id,
1092
+ eventType: testEvent.type
1093
+ }
1094
+ };
1095
+ } catch (error) {
1096
+ strapi.log.error("Failed to test webhook", { error });
1097
+ ctx.internalServerError("Failed to test webhook");
1098
+ }
1099
+ },
1100
+ /**
1101
+ * Initialize a functional payment flow (Customer -> Order -> PI)
1102
+ */
1103
+ async initializePayment(ctx) {
1104
+ try {
1105
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1106
+ const {
1107
+ amount = 5e3,
1108
+ currency = "usd",
1109
+ email = `customer-${Date.now()}@example.com`,
1110
+ firstName = "John",
1111
+ lastName = "Doe",
1112
+ confirm = false,
1113
+ payment_method
1114
+ } = ctx.request.body;
1115
+ const flowResult = await stripeService2.initializePaymentFlow({
1116
+ amount,
1117
+ currency,
1118
+ email,
1119
+ firstName,
1120
+ lastName,
1121
+ confirm,
1122
+ payment_method,
1123
+ metadata: { source: "admin_dashboard_init" }
532
1124
  });
533
- ctx.body = { message: "Product imported successfully", product: product2 };
1125
+ ctx.body = {
1126
+ success: true,
1127
+ data: flowResult
1128
+ };
534
1129
  } catch (error) {
535
- ctx.throw(500, error);
1130
+ strapi.log.error("Failed to initialize payment", { error });
1131
+ ctx.internalServerError(error instanceof Error ? error.message : "Failed to initialize payment");
536
1132
  }
537
1133
  },
538
- async createPaymentLink(ctx) {
1134
+ /**
1135
+ * Create and confirm a sample payment for dashboard demonstration
1136
+ */
1137
+ async createSamplePayment(ctx) {
539
1138
  try {
540
- const { id } = ctx.params;
541
- const product2 = await strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
542
- where: { id }
1139
+ const stripeService2 = strapi.plugin("payment-plugin").service("stripe");
1140
+ const flowResult = await stripeService2.initializePaymentFlow({
1141
+ amount: 2500,
1142
+ currency: "usd",
1143
+ email: `sample@example.com`,
1144
+ firstName: "Sample",
1145
+ lastName: "Customer",
1146
+ confirm: true,
1147
+ payment_method: "pm_card_visa",
1148
+ // Still using this for the 'test' button to show a success
1149
+ metadata: { is_sample: "true" }
543
1150
  });
544
- if (!product2) {
545
- return ctx.notFound("Product not found");
546
- }
547
- if (!product2.stripeProductId) {
548
- return ctx.badRequest("Product is not synced with Stripe");
1151
+ if (flowResult.stripePaymentIntent.status === "succeeded") {
1152
+ await stripeService2.updateStrapiOrder(flowResult.order.documentId, {
1153
+ status: "completed"
1154
+ });
549
1155
  }
550
- const paymentLink = await stripeDriver.createPaymentLink(product2.stripeProductId, {
551
- metadata: {
552
- product_id: product2.id
553
- }
554
- });
555
- ctx.body = { paymentLink };
1156
+ ctx.body = {
1157
+ success: true,
1158
+ data: flowResult
1159
+ };
556
1160
  } catch (error) {
557
- ctx.throw(500, error);
1161
+ strapi.log.error("Failed to create sample payment", { error });
1162
+ ctx.internalServerError(error instanceof Error ? error.message : "Failed to create sample payment");
558
1163
  }
559
1164
  }
560
- }));
561
- const subscription$1 = factories.createCoreController(
562
- "plugin::strapi-payment-plugin.subscription",
563
- ({ strapi }) => ({
564
- async create(ctx) {
565
- try {
566
- const { data } = ctx.request.body;
567
- const subscription2 = await strapi.service("plugin::strapi-payment-plugin.subscription").createSubscription(data);
568
- ctx.body = subscription2;
569
- } catch (error) {
570
- ctx.throw(500, error);
1165
+ };
1166
+ const controllers = {
1167
+ controller,
1168
+ stripe: stripeController
1169
+ };
1170
+ const middlewares = {
1171
+ /**
1172
+ * Webhook signature validator
1173
+ */
1174
+ "webhook-validator": async (ctx, next) => {
1175
+ try {
1176
+ const signature = ctx.request.headers["stripe-signature"];
1177
+ if (!signature) {
1178
+ return ctx.badRequest("Missing Stripe signature header");
571
1179
  }
572
- },
573
- async update(ctx) {
574
- try {
575
- const { id } = ctx.params;
576
- const { data } = ctx.request.body;
577
- const subscription2 = await strapi.service("plugin::strapi-payment-plugin.subscription").updateSubscription(id, data);
578
- ctx.body = subscription2;
579
- } catch (error) {
580
- ctx.throw(500, error);
1180
+ if (!ctx.request.body || typeof ctx.request.body === "object") {
1181
+ strapi.log.warn("Webhook body already parsed - signature verification may fail");
581
1182
  }
582
- },
583
- async find(ctx) {
584
- try {
585
- console.log("Fetching subscriptions...");
586
- const subscriptions = await strapi.db.query("plugin::strapi-payment-plugin.subscription").findMany({
587
- orderBy: { startDate: "desc" },
588
- populate: ["product", "user"]
589
- });
590
- ctx.body = subscriptions;
591
- } catch (error) {
592
- ctx.throw(500, error);
1183
+ ctx.state.isWebhook = true;
1184
+ ctx.state.webhookSignature = signature;
1185
+ await next();
1186
+ } catch (error) {
1187
+ strapi.log.error("Webhook validation failed", { error });
1188
+ return ctx.badRequest("Invalid webhook signature");
1189
+ }
1190
+ },
1191
+ /**
1192
+ * Rate limiter middleware for sensitive operations
1193
+ */
1194
+ rateLimiter: async (ctx, next) => {
1195
+ const clientIP = ctx.request.ip || ctx.request.connection?.remoteAddress || "unknown";
1196
+ const now = Date.now();
1197
+ const windowMs = 60 * 1e3;
1198
+ const maxRequests = 10;
1199
+ if (!strapi.plugin("payment-plugin").service("rateLimiter")) {
1200
+ const config2 = strapi.config.get("plugin::payment-plugin") || {};
1201
+ config2.rateLimits = config2.rateLimits || {};
1202
+ await strapi.config.set("plugin::payment-plugin", config2);
1203
+ }
1204
+ const pluginConfig = strapi.config.get("plugin::payment-plugin") || {};
1205
+ const rateLimits = pluginConfig.rateLimits || {};
1206
+ const clientLimits = rateLimits[clientIP] || { count: 0, resetTime: now + windowMs };
1207
+ if (now > clientLimits.resetTime) {
1208
+ clientLimits.count = 0;
1209
+ clientLimits.resetTime = now + windowMs;
1210
+ }
1211
+ if (clientLimits.count >= maxRequests) {
1212
+ strapi.log.warn("Rate limit exceeded", { clientIP, endpoint: ctx.path });
1213
+ return ctx.tooManyRequests("Too many requests. Please try again later.");
1214
+ }
1215
+ clientLimits.count++;
1216
+ rateLimits[clientIP] = clientLimits;
1217
+ pluginConfig.rateLimits = rateLimits;
1218
+ await strapi.config.set("plugin::payment-plugin", pluginConfig);
1219
+ await next();
1220
+ },
1221
+ /**
1222
+ * Input sanitization middleware
1223
+ */
1224
+ inputSanitizer: async (ctx, next) => {
1225
+ if (ctx.request.body && typeof ctx.request.body === "object") {
1226
+ ctx.request.body = sanitizeObject(ctx.request.body);
1227
+ }
1228
+ if (ctx.query && typeof ctx.query === "object") {
1229
+ ctx.query = sanitizeObject(ctx.query);
1230
+ }
1231
+ await next();
1232
+ }
1233
+ };
1234
+ function sanitizeObject(obj) {
1235
+ if (typeof obj === "string") {
1236
+ return sanitizeString(obj);
1237
+ }
1238
+ if (Array.isArray(obj)) {
1239
+ return obj.map(sanitizeObject);
1240
+ }
1241
+ if (obj && typeof obj === "object") {
1242
+ const sanitized = {};
1243
+ for (const [key, value] of Object.entries(obj)) {
1244
+ if (/^[a-zA-Z0-9_.-]+$/.test(key)) {
1245
+ sanitized[key] = sanitizeObject(value);
1246
+ }
1247
+ }
1248
+ return sanitized;
1249
+ }
1250
+ return obj;
1251
+ }
1252
+ function sanitizeString(str) {
1253
+ return str.replace(/[<>\"']/g, "").trim();
1254
+ }
1255
+ const policies = {
1256
+ isAuthenticated: (ctx) => {
1257
+ if (!ctx.state.user) {
1258
+ return false;
1259
+ }
1260
+ return true;
1261
+ }
1262
+ };
1263
+ const routes$2 = {
1264
+ type: "content-api",
1265
+ routes: [
1266
+ // Stripe Webhook Handler
1267
+ {
1268
+ method: "POST",
1269
+ path: "/webhook",
1270
+ handler: "stripe.handleWebhook",
1271
+ config: {
1272
+ auth: false,
1273
+ policies: []
593
1274
  }
594
1275
  },
595
- async findOne(ctx) {
596
- try {
597
- const { id } = ctx.params;
598
- const subscription2 = await strapi.db.query("plugin::strapi-payment-plugin.subscription").findOne({
599
- where: { id },
600
- populate: ["product", "user"]
601
- });
602
- if (!subscription2) {
603
- return ctx.notFound("Subscription not found");
604
- }
605
- ctx.body = subscription2;
606
- } catch (error) {
607
- ctx.throw(500, error);
1276
+ // Payment Intent Operations
1277
+ {
1278
+ method: "POST",
1279
+ path: "/payments/create-intent",
1280
+ handler: "stripe.createPaymentIntent",
1281
+ config: {
1282
+ policies: []
608
1283
  }
609
1284
  },
610
- async delete(ctx) {
611
- try {
612
- const { id } = ctx.params;
613
- const subscription2 = await strapi.db.query("plugin::strapi-payment-plugin.subscription").findOne({
614
- where: { id }
615
- });
616
- if (!subscription2) {
617
- return ctx.notFound("Subscription not found");
618
- }
619
- if (subscription2.stripeSubscriptionId) {
620
- try {
621
- const stripeDriver2 = strapi.service("plugin::strapi-payment-plugin.stripeDriver");
622
- await stripeDriver2.cancelSubscription(subscription2.stripeSubscriptionId);
623
- } catch (stripeError) {
624
- console.error("Error canceling subscription in Stripe:", stripeError);
625
- }
626
- }
627
- await strapi.db.query("plugin::strapi-payment-plugin.subscription").delete({
628
- where: { id }
629
- });
630
- ctx.body = { message: "Subscription deleted successfully" };
631
- } catch (error) {
632
- ctx.throw(500, error);
1285
+ {
1286
+ method: "POST",
1287
+ path: "/payments/confirm/:id",
1288
+ handler: "stripe.confirmPayment",
1289
+ config: {
1290
+ policies: []
633
1291
  }
634
1292
  },
635
- async sync(ctx) {
636
- try {
637
- const { id } = ctx.params;
638
- const subscription2 = await strapi.service("plugin::strapi-payment-plugin.subscription").syncSubscriptionStatus(id);
639
- ctx.body = { message: "Subscription synced successfully", subscription: subscription2 };
640
- } catch (error) {
641
- ctx.throw(500, error);
1293
+ {
1294
+ method: "GET",
1295
+ path: "/payments/:id",
1296
+ handler: "stripe.getPaymentDetails",
1297
+ config: {
1298
+ policies: []
642
1299
  }
643
1300
  },
644
- async cancel(ctx) {
645
- try {
646
- const { id } = ctx.params;
647
- const subscription2 = await strapi.service("plugin::strapi-payment-plugin.subscription").cancelSubscription(id);
648
- ctx.body = { message: "Subscription canceled successfully", subscription: subscription2 };
649
- } catch (error) {
650
- ctx.throw(500, error);
651
- }
652
- }
653
- })
654
- );
655
- const controllers = {
656
- controller,
657
- product: product$1,
658
- subscription: subscription$1
659
- };
660
- const middlewares = {};
661
- const policies = {};
662
- const adminRoutes = {
663
- type: "admin",
664
- routes: [
665
1301
  {
666
1302
  method: "GET",
667
- path: "/transactions",
668
- handler: "controller.getTransactions",
1303
+ path: "/payments",
1304
+ handler: "stripe.listPayments",
1305
+ config: {
1306
+ policies: []
1307
+ }
1308
+ },
1309
+ // Customer Operations
1310
+ {
1311
+ method: "POST",
1312
+ path: "/customers",
1313
+ handler: "stripe.createCustomer",
669
1314
  config: {
670
- policies: [],
671
- auth: false
1315
+ policies: []
672
1316
  }
673
1317
  },
674
1318
  {
675
1319
  method: "GET",
676
- path: "/products",
677
- handler: "product.find",
1320
+ path: "/customers/:id",
1321
+ handler: "stripe.retrieveCustomer",
678
1322
  config: {
679
- policies: [],
680
- auth: false
1323
+ policies: []
681
1324
  }
682
1325
  },
683
1326
  {
684
1327
  method: "GET",
685
- path: "/subscriptions",
686
- handler: "subscription.find",
1328
+ path: "/customers",
1329
+ handler: "stripe.listCustomers",
687
1330
  config: {
688
- policies: [],
689
- auth: false
1331
+ policies: []
690
1332
  }
691
1333
  },
1334
+ // Order Operations
692
1335
  {
693
1336
  method: "POST",
694
- path: "/config",
695
- handler: "controller.saveConfiguration",
1337
+ path: "/orders",
1338
+ handler: "stripe.createOrder",
696
1339
  config: {
697
- policies: [],
698
- auth: false
1340
+ policies: []
699
1341
  }
700
1342
  },
701
1343
  {
702
1344
  method: "GET",
703
- path: "/config-status",
704
- handler: "controller.getConfigStatus",
1345
+ path: "/orders/:id",
1346
+ handler: "stripe.getOrderDetails",
705
1347
  config: {
706
- policies: [],
707
- auth: false
1348
+ policies: []
708
1349
  }
709
1350
  },
710
1351
  {
711
- method: "POST",
712
- path: "/products",
713
- handler: "product.create",
1352
+ method: "GET",
1353
+ path: "/orders",
1354
+ handler: "stripe.listOrders",
714
1355
  config: {
715
- policies: [],
716
- auth: false
1356
+ policies: []
717
1357
  }
718
1358
  },
1359
+ // Refund Operations
719
1360
  {
720
1361
  method: "POST",
721
- path: "/products/:id/sync",
722
- handler: "product.sync",
1362
+ path: "/payments/:id/refund",
1363
+ handler: "stripe.createRefund",
1364
+ config: {
1365
+ policies: []
1366
+ }
1367
+ }
1368
+ ]
1369
+ };
1370
+ const routes$1 = {
1371
+ type: "admin",
1372
+ routes: [
1373
+ // Admin Dashboard and Analytics
1374
+ {
1375
+ method: "GET",
1376
+ path: "/admin/dashboard",
1377
+ handler: "stripe.getDashboardData",
723
1378
  config: {
724
- policies: [],
725
- auth: false
1379
+ policies: ["admin::isAuthenticatedAdmin"],
1380
+ middlewares: []
726
1381
  }
727
1382
  },
1383
+ // Payment Reports
728
1384
  {
729
- method: "DELETE",
730
- path: "/products/:id",
731
- handler: "product.delete",
1385
+ method: "GET",
1386
+ path: "/admin/reports",
1387
+ handler: "stripe.getPaymentReports",
732
1388
  config: {
733
- policies: [],
734
- auth: false
1389
+ policies: ["admin::isAuthenticatedAdmin"],
1390
+ middlewares: []
735
1391
  }
736
1392
  },
1393
+ // Configuration Management
737
1394
  {
738
- method: "POST",
739
- path: "/subscriptions/:id/sync",
740
- handler: "subscription.sync",
1395
+ method: "GET",
1396
+ path: "/admin/config",
1397
+ handler: "stripe.getPluginConfig",
741
1398
  config: {
742
- policies: [],
743
- auth: false
1399
+ policies: ["admin::isAuthenticatedAdmin"],
1400
+ middlewares: []
744
1401
  }
745
1402
  },
746
1403
  {
747
- method: "POST",
748
- path: "/products/import",
749
- handler: "product.importFromStripe",
1404
+ method: "PUT",
1405
+ path: "/admin/config",
1406
+ handler: "stripe.updatePluginConfig",
1407
+ config: {
1408
+ policies: ["admin::isAuthenticatedAdmin"],
1409
+ middlewares: []
1410
+ }
1411
+ },
1412
+ // Admin Analytics
1413
+ {
1414
+ method: "GET",
1415
+ path: "/admin/analytics",
1416
+ handler: "stripe.getAnalytics",
750
1417
  config: {
751
- policies: [],
752
- auth: false
1418
+ policies: ["admin::isAuthenticatedAdmin"],
1419
+ middlewares: []
753
1420
  }
754
1421
  },
1422
+ // Admin Payment Management
755
1423
  {
756
1424
  method: "POST",
757
- path: "/subscriptions/:id/sync",
758
- handler: "subscription.sync",
1425
+ path: "/admin/payments/:id/refund",
1426
+ handler: "stripe.adminCreateRefund",
759
1427
  config: {
760
- policies: [],
761
- auth: false
1428
+ policies: ["admin::isAuthenticatedAdmin"],
1429
+ middlewares: []
762
1430
  }
763
1431
  },
764
1432
  {
765
1433
  method: "GET",
766
- path: "/products/stripeProducts",
767
- handler: "product.getStripeProducts",
1434
+ path: "/admin/payments",
1435
+ handler: "stripe.adminListPayments",
768
1436
  config: {
769
- policies: [],
770
- auth: false
1437
+ policies: ["admin::isAuthenticatedAdmin"],
1438
+ middlewares: []
771
1439
  }
772
1440
  },
1441
+ // Admin Customer Management
773
1442
  {
774
- method: "POST",
775
- path: "/test-payment",
776
- handler: "controller.testPayment",
1443
+ method: "GET",
1444
+ path: "/admin/customers",
1445
+ handler: "stripe.adminListCustomers",
777
1446
  config: {
778
- policies: [],
779
- auth: false
1447
+ policies: ["admin::isAuthenticatedAdmin"],
1448
+ middlewares: []
780
1449
  }
781
1450
  },
1451
+ // Admin Order Management
782
1452
  {
783
- method: "POST",
784
- path: "/test-payment-with-redirect",
785
- handler: "controller.testPaymentWithRedirect",
1453
+ method: "GET",
1454
+ path: "/admin/orders",
1455
+ handler: "stripe.adminListOrders",
786
1456
  config: {
787
- policies: [],
788
- auth: false
1457
+ policies: ["admin::isAuthenticatedAdmin"],
1458
+ middlewares: []
789
1459
  }
790
- }
791
- ]
792
- };
793
- const refundRoutes = {
794
- type: "admin",
795
- routes: [
1460
+ },
1461
+ // Test Webhook
796
1462
  {
797
1463
  method: "POST",
798
- path: "/refund",
799
- handler: "controller.refundPayment",
1464
+ path: "/admin/test-webhook",
1465
+ handler: "stripe.testWebhook",
800
1466
  config: {
801
- policies: [],
802
- auth: false
1467
+ policies: ["admin::isAuthenticatedAdmin"],
1468
+ middlewares: []
803
1469
  }
804
- }
805
- ]
806
- };
807
- const contentApi = {
808
- type: "content-api",
809
- routes: [
1470
+ },
1471
+ // Functional Flow Initializer (Admin/Manual)
810
1472
  {
811
1473
  method: "POST",
812
- path: "/initiate-payment",
813
- handler: "controller.initiatePayment",
1474
+ path: "/admin/initialize-payment",
1475
+ handler: "stripe.initializePayment",
814
1476
  config: {
815
- policies: []
1477
+ policies: ["admin::isAuthenticatedAdmin"],
1478
+ middlewares: []
816
1479
  }
817
1480
  },
1481
+ // Sample Payment (for Dashboard)
818
1482
  {
819
1483
  method: "POST",
820
- path: "/webhook/:gateway",
821
- handler: "controller.processWebhook",
1484
+ path: "/admin/create-sample-payment",
1485
+ handler: "stripe.createSamplePayment",
822
1486
  config: {
823
- policies: []
1487
+ policies: ["admin::isAuthenticatedAdmin"],
1488
+ middlewares: []
824
1489
  }
825
1490
  }
826
1491
  ]
827
1492
  };
828
- const productRoutes = factories.createCoreRouter("plugin::strapi-payment-plugin.product", {
829
- config: {}
830
- });
831
- const subscriptionRoutes = factories.createCoreRouter("plugin::strapi-payment-plugin.subscription");
832
1493
  const routes = {
833
- adminRoutes,
834
- refundRoutes,
835
- contentApi,
836
- productRoutes,
837
- subscriptionRoutes
838
- };
839
- const paypalDriver = {
840
- async initializePayment(amount, currency, orderId, options) {
841
- console.warn("PayPal is disabled");
842
- return {
843
- status: "disabled",
844
- message: "PayPal functionality is currently disabled"
845
- };
846
- },
847
- async processWebhook(payload, signature) {
848
- console.warn("PayPal is disabled");
849
- return {
850
- status: "disabled",
851
- message: "PayPal functionality is currently disabled"
852
- };
853
- },
854
- async refundPayment(transactionId, amount, options) {
855
- console.warn("PayPal is disabled");
856
- return {
857
- status: "disabled",
858
- message: "PayPal functionality is currently disabled"
859
- };
860
- },
861
- async getPaymentDetails(transactionId) {
862
- console.warn("PayPal is disabled");
863
- return {
864
- status: "disabled",
865
- message: "PayPal functionality is currently disabled"
866
- };
867
- },
868
- async createProduct(productData) {
869
- console.warn("PayPal is disabled");
870
- return {
871
- status: "disabled",
872
- message: "PayPal functionality is currently disabled"
873
- };
874
- },
875
- async updateProduct(productId, productData) {
876
- console.warn("PayPal is disabled");
877
- return {
878
- status: "disabled",
879
- message: "PayPal functionality is currently disabled"
880
- };
881
- },
882
- async createSubscription(customerId, planId, options) {
883
- console.warn("PayPal is disabled");
884
- return {
885
- status: "disabled",
886
- message: "PayPal functionality is currently disabled"
887
- };
888
- },
889
- async updateSubscription(subscriptionId, options) {
890
- console.warn("PayPal is disabled");
891
- return {
892
- status: "disabled",
893
- message: "PayPal functionality is currently disabled"
894
- };
895
- },
896
- async cancelSubscription(subscriptionId, reason) {
897
- console.warn("PayPal is disabled");
898
- return {
899
- status: "disabled",
900
- message: "PayPal functionality is currently disabled"
901
- };
902
- },
903
- async getSubscription(subscriptionId) {
904
- console.warn("PayPal is disabled");
905
- return {
906
- status: "disabled",
907
- message: "PayPal functionality is currently disabled"
908
- };
909
- },
910
- async getTransactions(limit, page) {
911
- console.warn("PayPal is disabled");
912
- return {
913
- status: "disabled",
914
- message: "PayPal functionality is currently disabled"
915
- };
916
- }
917
- };
918
- const drivers = {
919
- stripe: stripeDriver,
920
- paypal: paypalDriver
1494
+ "content-api": routes$2,
1495
+ admin: routes$1
921
1496
  };
922
- const service = ({ strapi }) => ({
923
- async initializePayment(gateway, amount, currency, orderId, options) {
924
- const driver = drivers[gateway];
925
- if (!driver) throw new Error("Invalid payment gateway");
926
- return driver.initializePayment(amount, currency, orderId, options);
927
- },
928
- async processWebhook(gateway, payload, signature) {
929
- const driver = drivers[gateway];
930
- if (!driver) throw new Error("Invalid payment gateway");
931
- return driver.processWebhook(payload, signature);
932
- },
933
- async refundPayment(gateway, transactionId, amount, options) {
934
- const driver = drivers[gateway];
935
- if (!driver) throw new Error("Invalid payment gateway");
936
- return driver.refundPayment(transactionId, amount, options);
937
- },
938
- async getPaymentDetails(gateway, transactionId) {
939
- const driver = drivers[gateway];
940
- if (!driver) throw new Error("Invalid payment gateway");
941
- return driver.getPaymentDetails(transactionId);
942
- },
943
- async getTransactions(gateway, status, limit = 10, offset = 0) {
944
- if (gateway == "all") {
945
- const allTransactions = [];
946
- for (const key of Object.keys(drivers)) {
947
- const transactions = await this.getTransactions(key, status, limit, offset);
948
- allTransactions.push(...transactions.data);
949
- }
950
- return {
951
- data: allTransactions,
952
- meta: {
953
- total: allTransactions.length,
954
- limit,
955
- offset
956
- }
1497
+ const stripeService = ({ strapi: strapi2 }) => {
1498
+ let stripe = null;
1499
+ const getConfig = () => {
1500
+ return strapi2.config.get("plugin::payment-plugin") || {};
1501
+ };
1502
+ const getLogger = () => {
1503
+ return strapi2.log;
1504
+ };
1505
+ const getCombinedConfig = async () => {
1506
+ const staticConfig = strapi2.config.get("plugin::payment-plugin") || {};
1507
+ try {
1508
+ const store = strapi2.store({ type: "plugin", name: "payment-plugin" });
1509
+ const dbConfig = await store.get({ key: "settings" });
1510
+ const safeDbConfig = dbConfig && typeof dbConfig === "object" ? dbConfig : {};
1511
+ return { ...staticConfig, ...safeDbConfig };
1512
+ } catch (error) {
1513
+ return staticConfig;
1514
+ }
1515
+ };
1516
+ const initializeStripe = async () => {
1517
+ if (stripe) {
1518
+ return stripe;
1519
+ }
1520
+ const config2 = await getCombinedConfig();
1521
+ const stripeConfig = config2.stripe || {};
1522
+ const secretKey = stripeConfig.secretKey || process.env.STRIPE_SECRET_KEY || (process.env.STRIPE_WEBHOOK_SECRET?.startsWith("sk_") ? process.env.STRIPE_WEBHOOK_SECRET : null) || (process.env.STRIPE_API_KEY?.startsWith("sk_") ? process.env.STRIPE_API_KEY : null);
1523
+ if (!secretKey) {
1524
+ throw new Error("Stripe Secret Key not found. Please configure it in the plugin settings or set the STRIPE_SECRET_KEY environment variable.");
1525
+ }
1526
+ const apiVersion = stripeConfig.apiVersion || "2024-06-20";
1527
+ stripe = new Stripe(secretKey, {
1528
+ apiVersion
1529
+ });
1530
+ getLogger().info("Stripe service initialized successfully");
1531
+ return stripe;
1532
+ };
1533
+ const getStripe = () => {
1534
+ return stripe;
1535
+ };
1536
+ const createPaymentIntent = async (params) => {
1537
+ try {
1538
+ const stripe2 = await initializeStripe();
1539
+ const config2 = await getCombinedConfig();
1540
+ const logger = getLogger();
1541
+ const { amount, currency, metadata = {}, customer: customer2, orderId, customerId, payment_method, confirm } = params;
1542
+ const enhancedMetadata = {
1543
+ ...metadata,
1544
+ ...orderId && { strapi_order_id: orderId.toString() },
1545
+ ...customerId && { strapi_customer_id: customerId.toString() },
1546
+ source: "strapi_payment_plugin"
957
1547
  };
958
- } else if (drivers[gateway]) {
959
- const driver = drivers[gateway];
960
- let transactions = [];
961
- if (gateway === "stripe") {
962
- let startingAfter = null;
963
- if (offset > 0) {
964
- startingAfter = offset.toString();
1548
+ const paymentIntentData = {
1549
+ amount,
1550
+ currency,
1551
+ metadata: enhancedMetadata,
1552
+ automatic_payment_methods: {
1553
+ enabled: true,
1554
+ ...confirm && { allow_redirects: "never" }
965
1555
  }
966
- transactions = await driver.getTransactions(limit, startingAfter);
967
- } else if (gateway === "paypal") {
968
- console.warn("Paypal is disabled for now");
1556
+ };
1557
+ if (customer2) {
1558
+ paymentIntentData.customer = customer2;
969
1559
  }
970
- if (!transactions) {
971
- return;
1560
+ if (payment_method) {
1561
+ paymentIntentData.payment_method = payment_method;
972
1562
  }
973
- const formattedTransactions = transactions.map((txn) => {
974
- let amount, currency, customer, paymentMethod, txnStatus, date;
975
- if (gateway === "stripe") {
976
- amount = (txn.amount_received || txn.amount) / 100;
977
- currency = txn.currency.toUpperCase();
978
- customer = txn.metadata?.customer || txn.customer || "unknown";
979
- paymentMethod = txn.payment_method_types?.[0] || "unknown";
980
- txnStatus = txn.status;
981
- date = new Date(txn.created * 1e3).toISOString();
982
- } else if (gateway === "paypal") {
983
- amount = parseFloat(txn.amount_with_breakdown?.gross_amount?.value || "0");
984
- currency = txn.amount_with_breakdown?.gross_amount?.currency_code || "USD";
985
- customer = txn.payer_info?.email_address || "unknown";
986
- paymentMethod = txn.payment_source?.paypal?.name || "paypal";
987
- txnStatus = txn.status;
988
- date = new Date(txn.create_time).toISOString();
989
- }
990
- return {
991
- id: txn.id,
992
- amount,
993
- currency,
994
- status: txnStatus,
995
- date,
996
- customer,
997
- paymentMethod,
998
- gateway
999
- };
1563
+ if (confirm) {
1564
+ paymentIntentData.confirm = confirm;
1565
+ }
1566
+ const paymentIntent = await stripe2.paymentIntents.create(paymentIntentData);
1567
+ logger.info("Payment intent created", {
1568
+ paymentIntentId: paymentIntent.id,
1569
+ amount,
1570
+ currency,
1571
+ orderId,
1572
+ customerId
1000
1573
  });
1001
- return {
1002
- data: formattedTransactions,
1003
- meta: {
1004
- total: formattedTransactions.length,
1005
- limit,
1006
- offset
1574
+ return paymentIntent;
1575
+ } catch (error) {
1576
+ const logger = getLogger();
1577
+ logger.error("Failed to create payment intent", { error, params });
1578
+ throw error;
1579
+ }
1580
+ };
1581
+ const confirmPayment = async (paymentIntentId) => {
1582
+ try {
1583
+ const stripe2 = await initializeStripe();
1584
+ const logger = getLogger();
1585
+ const paymentIntent = await stripe2.paymentIntents.confirm(paymentIntentId);
1586
+ logger.info("Payment confirmed", { paymentIntentId, status: paymentIntent.status });
1587
+ return paymentIntent;
1588
+ } catch (error) {
1589
+ const logger = getLogger();
1590
+ logger.error("Failed to confirm payment", { error, paymentIntentId });
1591
+ throw error;
1592
+ }
1593
+ };
1594
+ const createCustomer = async (params) => {
1595
+ try {
1596
+ const stripe2 = await initializeStripe();
1597
+ const logger = getLogger();
1598
+ const { email, firstName, lastName, phone, address, metadata = {}, strapiCustomerId } = params;
1599
+ const customerData = {
1600
+ email,
1601
+ name: `${firstName} ${lastName}`,
1602
+ phone,
1603
+ address,
1604
+ metadata: {
1605
+ ...metadata,
1606
+ ...strapiCustomerId && { strapi_customer_id: strapiCustomerId.toString() },
1607
+ first_name: firstName,
1608
+ last_name: lastName,
1609
+ source: "strapi_payment_plugin"
1007
1610
  }
1008
1611
  };
1009
- } else {
1010
- throw new Error("Invalid payment gateway");
1612
+ const customer2 = await stripe2.customers.create(customerData);
1613
+ logger.info("Customer created in Stripe", {
1614
+ customerId: customer2.id,
1615
+ email,
1616
+ strapiCustomerId
1617
+ });
1618
+ return customer2;
1619
+ } catch (error) {
1620
+ const logger = getLogger();
1621
+ logger.error("Failed to create customer", { error, params });
1622
+ throw error;
1011
1623
  }
1012
- },
1013
- async saveConfiguration(configData) {
1014
- return {
1015
- success: true,
1016
- message: "Configuration saved successfully",
1017
- data: configData
1018
- };
1019
- },
1020
- async getConfigStatus() {
1021
- const stripeConfigured = !!(process.env.STRIPE_SECRET_KEY && process.env.STRIPE_PUBLISHABLE_KEY && process.env.STRIPE_WEBHOOK_SECRET);
1022
- const paypalConfigured = !!(process.env.PAYPAL_CLIENT_ID && process.env.PAYPAL_CLIENT_SECRET && process.env.PAYPAL_WEBHOOK_ID);
1023
- return {
1024
- stripe: stripeConfigured,
1025
- paypal: paypalConfigured
1026
- };
1027
- }
1028
- });
1029
- const product = factories.createCoreService("plugin::strapi-payment-plugin.product", ({ strapi }) => ({
1030
- async createProduct(productData) {
1031
- const strapiProduct = await strapi.db.query("plugin::strapi-payment-plugin.product").create({
1032
- data: productData
1033
- });
1034
- if (config.stripe.secretKey) {
1035
- try {
1036
- const stripeProduct = await stripeDriver.createProduct(productData);
1037
- await strapi.db.query("plugin::strapi-payment-plugin.product").update({
1038
- where: { id: strapiProduct.id },
1039
- data: {
1040
- stripeProductId: stripeProduct.id
1041
- }
1042
- });
1043
- } catch (error) {
1044
- console.error("Error creating product in Stripe:", error);
1045
- }
1624
+ };
1625
+ const retrieveCustomer = async (customerId) => {
1626
+ try {
1627
+ const stripe2 = await initializeStripe();
1628
+ const logger = getLogger();
1629
+ const customer2 = await stripe2.customers.retrieve(customerId);
1630
+ logger.info("Customer retrieved from Stripe", { customerId });
1631
+ return customer2;
1632
+ } catch (error) {
1633
+ const logger = getLogger();
1634
+ logger.error("Failed to retrieve customer", { error, customerId });
1635
+ throw error;
1046
1636
  }
1047
- if (config.paypal.clientId && config.paypal.clientSecret) {
1048
- try {
1049
- console.warn("PayPal is disabled");
1050
- } catch (error) {
1051
- console.error("Error creating product in PayPal:", error);
1637
+ };
1638
+ const createRefund = async (params) => {
1639
+ try {
1640
+ const stripe2 = await initializeStripe();
1641
+ const logger = getLogger();
1642
+ const { paymentIntentId, amount, reason, metadata = {} } = params;
1643
+ const refundData = {
1644
+ payment_intent: paymentIntentId,
1645
+ metadata: {
1646
+ ...metadata,
1647
+ source: "strapi_payment_plugin",
1648
+ refunded_at: (/* @__PURE__ */ new Date()).toISOString()
1649
+ }
1650
+ };
1651
+ if (amount) {
1652
+ refundData.amount = amount;
1052
1653
  }
1053
- }
1054
- return strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
1055
- where: { id: strapiProduct.id }
1056
- });
1057
- },
1058
- async updateProduct(id, productData) {
1059
- const updatedProduct = await strapi.db.query("plugin::strapi-payment-plugin.product").update({
1060
- where: { id },
1061
- data: productData
1062
- });
1063
- if (updatedProduct.stripeProductId && config.stripe.secretKey) {
1064
- try {
1065
- await stripeDriver.updateProduct(updatedProduct.stripeProductId, productData);
1066
- } catch (error) {
1067
- console.error("Error updating product in Stripe:", error);
1654
+ if (reason) {
1655
+ refundData.reason = reason;
1068
1656
  }
1657
+ const refund = await stripe2.refunds.create(refundData);
1658
+ logger.info("Refund processed", {
1659
+ refundId: refund.id,
1660
+ paymentIntentId,
1661
+ amount: refund.amount
1662
+ });
1663
+ return refund;
1664
+ } catch (error) {
1665
+ const logger = getLogger();
1666
+ logger.error("Failed to create refund", { error, params });
1667
+ throw error;
1069
1668
  }
1070
- if (updatedProduct.paypalProductId && config.paypal.clientId && config.paypal.clientSecret) {
1071
- try {
1072
- await paypalDriver.updateProduct(updatedProduct.paypalProductId, productData);
1073
- } catch (error) {
1074
- console.error("Error updating product in PayPal:", error);
1075
- }
1669
+ };
1670
+ const constructEvent = async (payload, signature) => {
1671
+ const stripe2 = await initializeStripe();
1672
+ const config2 = await getCombinedConfig();
1673
+ const stripeConfig = config2.stripe || {};
1674
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET || stripeConfig.webhookSecret;
1675
+ if (!webhookSecret) {
1676
+ throw new Error("Stripe webhook secret not configured. Please set STRIPE_WEBHOOK_SECRET environment variable.");
1076
1677
  }
1077
- return updatedProduct;
1078
- }
1079
- }));
1080
- const subscription = factories.createCoreService("plugin::strapi-payment-plugin.subscription", ({ strapi }) => ({
1081
- async createSubscription(subscriptionData) {
1082
- const strapiSubscription = await strapi.db.query("plugin::strapi-payment-plugin.subscription").create({
1083
- data: subscriptionData
1084
- });
1085
- const product2 = await strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
1086
- where: { id: subscriptionData.product }
1087
- });
1088
- if (product2.stripeProductId && config.stripe.secretKey) {
1089
- try {
1090
- let customerId = subscriptionData.stripeCustomerId;
1091
- if (!customerId) {
1092
- const customer = await stripeDriver.customers.create({
1093
- email: subscriptionData.email || "customer@example.com",
1094
- name: subscriptionData.customerName || "Customer"
1095
- });
1096
- customerId = customer.id;
1097
- await strapi.db.query("plugin::strapi-payment-plugin.subscription").update({
1098
- where: { id: strapiSubscription.id },
1099
- data: {
1100
- stripeCustomerId: customerId
1101
- }
1102
- });
1678
+ try {
1679
+ return stripe2.webhooks.constructEvent(payload, signature, webhookSecret);
1680
+ } catch (error) {
1681
+ const logger = getLogger();
1682
+ logger.error("Failed to construct webhook event", { error });
1683
+ throw error;
1684
+ }
1685
+ };
1686
+ const updateStrapiPayment = async (stripePaymentIntentId, data) => {
1687
+ try {
1688
+ const existingPayments = await strapi2.documents("plugin::payment-plugin.payment").findMany({
1689
+ filters: {
1690
+ stripe_payment_intent_id: stripePaymentIntentId
1103
1691
  }
1104
- const stripeSubscription = await stripeDriver.createSubscription(
1105
- customerId,
1106
- product2.stripeProductId,
1107
- {
1108
- metadata: { strapiSubscriptionId: strapiSubscription.id }
1109
- }
1110
- );
1111
- await strapi.db.query("plugin::strapi-payment-plugin.subscription").update({
1112
- where: { id: strapiSubscription.id },
1113
- data: {
1114
- stripeSubscriptionId: stripeSubscription.id
1115
- }
1692
+ });
1693
+ if (existingPayments.length > 0) {
1694
+ await strapi2.documents("plugin::payment-plugin.payment").update({
1695
+ documentId: existingPayments[0].documentId,
1696
+ data
1116
1697
  });
1117
- } catch (error) {
1118
- console.error("Error creating subscription in Stripe:", error);
1119
- }
1120
- }
1121
- if (product2.paypalProductId && config.paypal.clientId && config.paypal.clientSecret) {
1122
- try {
1123
- console.warn("PayPal is disabled");
1124
- } catch (error) {
1125
- console.error("Error creating subscription in PayPal:", error);
1126
1698
  }
1699
+ } catch (error) {
1700
+ const logger = getLogger();
1701
+ logger.error("Failed to update Strapi payment record", {
1702
+ error,
1703
+ stripePaymentIntentId,
1704
+ data
1705
+ });
1127
1706
  }
1128
- return strapi.db.query("plugin::strapi-payment-plugin.subscription").findOne({
1129
- where: { id: strapiSubscription.id }
1130
- });
1131
- },
1132
- async updateSubscription(id, subscriptionData) {
1133
- const updatedSubscription = await strapi.db.query("plugin::strapi-payment-plugin.subscription").update({
1134
- where: { id },
1135
- data: subscriptionData
1136
- });
1137
- if (updatedSubscription.stripeSubscriptionId && config.stripe.secretKey) {
1138
- try {
1139
- await stripeDriver.updateSubscription(updatedSubscription.stripeSubscriptionId, {
1140
- metadata: { strapiSubscriptionId: updatedSubscription.id }
1141
- });
1142
- } catch (error) {
1143
- console.error("Error updating subscription in Stripe:", error);
1144
- }
1707
+ };
1708
+ const updateStrapiOrder = async (orderId, data) => {
1709
+ try {
1710
+ await strapi2.documents("plugin::payment-plugin.order").update({
1711
+ documentId: orderId,
1712
+ data
1713
+ });
1714
+ } catch (error) {
1715
+ const logger = getLogger();
1716
+ logger.error("Failed to update Strapi order record", {
1717
+ error,
1718
+ orderId,
1719
+ data
1720
+ });
1145
1721
  }
1146
- if (updatedSubscription.paypalSubscriptionId && config.paypal.clientId && config.paypal.clientSecret) {
1147
- try {
1148
- const product2 = await strapi.db.query("plugin::strapi-payment-plugin.product").findOne({
1149
- where: { id: updatedSubscription.product }
1150
- });
1151
- await paypalDriver.updateSubscription(updatedSubscription.paypalSubscriptionId, {
1152
- plan_id: product2.paypalProductId
1153
- });
1154
- } catch (error) {
1155
- console.error("Error updating subscription in PayPal:", error);
1156
- }
1722
+ };
1723
+ const updateStrapiCustomer = async (customerId, data) => {
1724
+ try {
1725
+ await strapi2.documents("plugin::payment-plugin.customer").update({
1726
+ documentId: customerId,
1727
+ data
1728
+ });
1729
+ } catch (error) {
1730
+ const logger = getLogger();
1731
+ logger.error("Failed to update Strapi customer record", {
1732
+ error,
1733
+ customerId,
1734
+ data
1735
+ });
1157
1736
  }
1158
- return updatedSubscription;
1159
- },
1160
- async cancelSubscription(id) {
1161
- const subscription2 = await strapi.db.query("plugin::strapi-payment-plugin.subscription").findOne({
1162
- where: { id }
1737
+ };
1738
+ const createCustomerRecord = async (data) => {
1739
+ return strapi2.documents("plugin::payment-plugin.customer").create({
1740
+ data
1163
1741
  });
1164
- if (!subscription2) {
1165
- throw new Error("Subscription not found");
1166
- }
1167
- if (subscription2.stripeSubscriptionId && config.stripe.secretKey) {
1168
- try {
1169
- await stripeDriver.cancelSubscription(subscription2.stripeSubscriptionId);
1170
- } catch (error) {
1171
- console.error("Error canceling subscription in Stripe:", error);
1172
- }
1173
- }
1174
- if (subscription2.paypalSubscriptionId && config.paypal.clientId && config.paypal.clientSecret) {
1175
- try {
1176
- console.warn("PayPal is disabled");
1177
- } catch (error) {
1178
- console.error("Error canceling subscription in PayPal:", error);
1179
- }
1180
- }
1181
- return strapi.db.query("plugin::strapi-payment-plugin.subscription").update({
1182
- where: { id },
1742
+ };
1743
+ const createOrderRecord = async (data) => {
1744
+ return strapi2.documents("plugin::payment-plugin.order").create({
1183
1745
  data: {
1184
- status: "canceled",
1185
- endDate: /* @__PURE__ */ new Date()
1746
+ ...data,
1747
+ status: data.status || "pending"
1186
1748
  }
1187
1749
  });
1188
- },
1189
- async syncSubscriptionStatus(id) {
1190
- const subscription2 = await strapi.db.query("plugin::strapi-payment-plugin.subscription").findOne({
1191
- where: { id }
1750
+ };
1751
+ const createStrapiPaymentRecord = async (paymentIntent) => {
1752
+ try {
1753
+ const metadata = paymentIntent.metadata || {};
1754
+ const customerId = metadata.strapi_customer_id;
1755
+ const orderId = metadata.strapi_order_id;
1756
+ const paymentData = {
1757
+ stripe_payment_intent_id: paymentIntent.id,
1758
+ amount: paymentIntent.amount / 100,
1759
+ currency: paymentIntent.currency,
1760
+ status: paymentIntent.status,
1761
+ payment_method: paymentIntent.payment_method || "card",
1762
+ metadata: paymentIntent.metadata,
1763
+ customer: customerId,
1764
+ order: orderId
1765
+ };
1766
+ const record = await strapi2.documents("plugin::payment-plugin.payment").create({
1767
+ data: paymentData
1768
+ });
1769
+ getLogger().info("Created Strapi payment record", {
1770
+ paymentIntentId: paymentIntent.id,
1771
+ strapiOrderId: orderId,
1772
+ strapiCustomerId: customerId
1773
+ });
1774
+ return record;
1775
+ } catch (error) {
1776
+ const logger = getLogger();
1777
+ logger.error("Failed to create Strapi payment record", {
1778
+ error,
1779
+ paymentIntentId: paymentIntent.id
1780
+ });
1781
+ throw error;
1782
+ }
1783
+ };
1784
+ const initializePaymentFlow = async (params) => {
1785
+ const {
1786
+ amount,
1787
+ currency,
1788
+ email,
1789
+ firstName,
1790
+ lastName,
1791
+ items = [{ name: "Default Item", amount, quantity: 1 }],
1792
+ metadata = {},
1793
+ confirm = false,
1794
+ payment_method,
1795
+ orderNumber = `ORD-${Date.now()}`
1796
+ } = params;
1797
+ const stripeCustomer = await createCustomer({
1798
+ email,
1799
+ firstName,
1800
+ lastName
1192
1801
  });
1193
- if (!subscription2) {
1194
- throw new Error("Subscription not found");
1802
+ const customers = await strapi2.documents("plugin::payment-plugin.customer").findMany({
1803
+ filters: { email: { $eq: email } }
1804
+ });
1805
+ let strapiCustomer;
1806
+ if (customers.length > 0) {
1807
+ strapiCustomer = customers[0];
1808
+ await updateStrapiCustomer(strapiCustomer.documentId, {
1809
+ stripe_customer_id: stripeCustomer.id
1810
+ });
1811
+ } else {
1812
+ strapiCustomer = await createCustomerRecord({
1813
+ email,
1814
+ first_name: firstName,
1815
+ last_name: lastName,
1816
+ stripe_customer_id: stripeCustomer.id
1817
+ });
1195
1818
  }
1196
- if (subscription2.stripeSubscriptionId && config.stripe.secretKey) {
1197
- try {
1198
- const stripeSubscription = await stripeDriver.getSubscription(
1199
- subscription2.stripeSubscriptionId
1200
- );
1201
- await strapi.db.query("plugin::strapi-payment-plugin.subscription").update({
1202
- where: { id },
1203
- data: {
1204
- status: stripeSubscription.status
1205
- }
1819
+ const strapiOrder = await createOrderRecord({
1820
+ order_number: orderNumber,
1821
+ total_amount: amount / 100,
1822
+ currency,
1823
+ customer: strapiCustomer.documentId,
1824
+ items,
1825
+ metadata
1826
+ });
1827
+ const paymentIntent = await createPaymentIntent({
1828
+ amount,
1829
+ currency,
1830
+ customerId: strapiCustomer.documentId,
1831
+ orderId: strapiOrder.documentId,
1832
+ customer: stripeCustomer.id,
1833
+ confirm,
1834
+ payment_method,
1835
+ metadata: { ...metadata, flow: "functional_api" }
1836
+ });
1837
+ const strapiPayment = await createStrapiPaymentRecord(paymentIntent);
1838
+ return {
1839
+ payment: strapiPayment,
1840
+ order: strapiOrder,
1841
+ customer: strapiCustomer,
1842
+ stripePaymentIntent: paymentIntent
1843
+ };
1844
+ };
1845
+ const handleWebhook = async (event) => {
1846
+ const logger = getLogger();
1847
+ const config2 = getConfig();
1848
+ const webhookConfig = config2.logging || {};
1849
+ try {
1850
+ if (webhookConfig.enableWebhookLogging) {
1851
+ logger.info("Processing Stripe webhook", {
1852
+ eventType: event.type,
1853
+ eventId: event.id
1206
1854
  });
1207
- } catch (error) {
1208
- console.error("Error syncing subscription status from Stripe:", error);
1209
1855
  }
1210
- }
1211
- if (subscription2.paypalSubscriptionId && config.paypal.clientId && config.paypal.clientSecret) {
1212
- try {
1213
- const paypalSubscription = await paypalDriver.getSubscription(
1214
- subscription2.paypalSubscriptionId
1215
- );
1216
- await strapi.db.query("plugin::strapi-payment-plugin.subscription").update({
1217
- where: { id },
1218
- data: {
1219
- status: paypalSubscription.status
1856
+ switch (event.type) {
1857
+ case "payment_intent.succeeded": {
1858
+ const paymentIntent = event.data.object;
1859
+ try {
1860
+ const existingPayments = await strapi2.documents("plugin::payment-plugin.payment").findMany({
1861
+ filters: {
1862
+ stripe_payment_intent_id: paymentIntent.id
1863
+ }
1864
+ });
1865
+ if (existingPayments.length > 0) {
1866
+ await strapi2.documents("plugin::payment-plugin.payment").update({
1867
+ documentId: existingPayments[0].documentId,
1868
+ data: {
1869
+ status: "succeeded",
1870
+ metadata: paymentIntent.metadata
1871
+ }
1872
+ });
1873
+ } else {
1874
+ await createStrapiPaymentRecord(paymentIntent);
1875
+ }
1876
+ } catch (error) {
1877
+ await createStrapiPaymentRecord(paymentIntent);
1220
1878
  }
1221
- });
1222
- } catch (error) {
1223
- console.error("Error syncing subscription status from PayPal:", error);
1879
+ const orderId = paymentIntent.metadata?.strapi_order_id;
1880
+ if (orderId) {
1881
+ await updateStrapiOrder(orderId, {
1882
+ status: "completed"
1883
+ });
1884
+ }
1885
+ logger.info("Payment succeeded", {
1886
+ paymentIntentId: paymentIntent.id,
1887
+ amount: paymentIntent.amount,
1888
+ orderId
1889
+ });
1890
+ break;
1891
+ }
1892
+ case "payment_intent.payment_failed": {
1893
+ const paymentIntent = event.data.object;
1894
+ await updateStrapiPayment(paymentIntent.id, {
1895
+ status: "failed",
1896
+ metadata: paymentIntent.metadata
1897
+ });
1898
+ const orderId = paymentIntent.metadata?.strapi_order_id;
1899
+ if (orderId) {
1900
+ await updateStrapiOrder(orderId, {
1901
+ status: "failed"
1902
+ });
1903
+ }
1904
+ logger.warn("Payment failed", {
1905
+ paymentIntentId: paymentIntent.id,
1906
+ orderId,
1907
+ lastError: paymentIntent.last_payment_error
1908
+ });
1909
+ break;
1910
+ }
1911
+ case "payment_intent.canceled": {
1912
+ const paymentIntent = event.data.object;
1913
+ await updateStrapiPayment(paymentIntent.id, {
1914
+ status: "canceled",
1915
+ metadata: paymentIntent.metadata
1916
+ });
1917
+ const orderId = paymentIntent.metadata?.strapi_order_id;
1918
+ if (orderId) {
1919
+ await updateStrapiOrder(orderId, {
1920
+ status: "failed"
1921
+ });
1922
+ }
1923
+ logger.info("Payment canceled", {
1924
+ paymentIntentId: paymentIntent.id,
1925
+ orderId
1926
+ });
1927
+ break;
1928
+ }
1929
+ case "charge.refunded": {
1930
+ const charge = event.data.object;
1931
+ if (charge.payment_intent) {
1932
+ await updateStrapiPayment(charge.payment_intent, {
1933
+ status: "refunded"
1934
+ });
1935
+ const orderId = charge.metadata?.strapi_order_id;
1936
+ if (orderId) {
1937
+ await updateStrapiOrder(orderId, {
1938
+ status: "refunded"
1939
+ });
1940
+ }
1941
+ }
1942
+ logger.info("Charge refunded", {
1943
+ chargeId: charge.id,
1944
+ paymentIntentId: charge.payment_intent,
1945
+ amount: charge.amount_refunded,
1946
+ orderId: charge.metadata?.strapi_order_id
1947
+ });
1948
+ break;
1949
+ }
1950
+ case "customer.created": {
1951
+ const customer2 = event.data.object;
1952
+ const strapiCustomerId = customer2.metadata?.strapi_customer_id;
1953
+ if (strapiCustomerId) {
1954
+ await updateStrapiCustomer(strapiCustomerId, {
1955
+ stripe_customer_id: customer2.id,
1956
+ metadata: customer2.metadata
1957
+ });
1958
+ }
1959
+ logger.info("Customer created in Stripe", {
1960
+ stripeCustomerId: customer2.id,
1961
+ strapiCustomerId
1962
+ });
1963
+ break;
1964
+ }
1965
+ default:
1966
+ logger.info("Unhandled webhook event type", { eventType: event.type });
1224
1967
  }
1968
+ } catch (error) {
1969
+ logger.error("Error processing webhook", {
1970
+ error,
1971
+ eventType: event.type,
1972
+ eventId: event.id
1973
+ });
1974
+ throw error;
1225
1975
  }
1226
- return strapi.db.query("plugin::strapi-payment-plugin.subscription").findOne({
1227
- where: { id }
1228
- });
1229
- }
1230
- }));
1976
+ };
1977
+ return {
1978
+ initializeStripe,
1979
+ createPaymentIntent,
1980
+ confirmPayment,
1981
+ createCustomer,
1982
+ retrieveCustomer,
1983
+ createRefund,
1984
+ handleWebhook,
1985
+ constructEvent,
1986
+ updateStrapiOrder,
1987
+ updateStrapiCustomer,
1988
+ createStrapiPaymentRecord,
1989
+ createCustomerRecord,
1990
+ createOrderRecord,
1991
+ initializePaymentFlow,
1992
+ getStripe
1993
+ };
1994
+ };
1231
1995
  const services = {
1232
- service,
1233
- product,
1234
- subscription
1996
+ stripe: stripeService
1235
1997
  };
1236
1998
  const index = {
1237
1999
  register,