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