@webbycrown/webbycommerce 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +21 -3
  2. package/admin/app.js +3 -0
  3. package/admin/jsconfig.json +20 -0
  4. package/admin/src/components/ApiCollectionsContent.jsx +4626 -0
  5. package/admin/src/components/CompareContent.jsx +300 -0
  6. package/admin/src/components/ConfigureContent.jsx +407 -0
  7. package/admin/src/components/Initializer.jsx +64 -0
  8. package/admin/src/components/LoginRegisterContent.jsx +280 -0
  9. package/admin/src/components/PluginIcon.jsx +6 -0
  10. package/admin/src/components/ShippingTypeContent.jsx +230 -0
  11. package/admin/src/components/SmtpContent.jsx +316 -0
  12. package/admin/src/components/WishlistContent.jsx +273 -0
  13. package/admin/src/index.js +81 -0
  14. package/admin/src/pages/ApiCollections.jsx +169 -0
  15. package/admin/src/pages/Configure.jsx +55 -0
  16. package/admin/src/pages/Settings.jsx +93 -0
  17. package/admin/src/pluginId.js +4 -0
  18. package/{dist/_chunks/en-CiQ97iC8.js → admin/src/translations/en.json} +712 -574
  19. package/bin/setup.js +50 -3
  20. package/package.json +14 -13
  21. package/server/bootstrap.js +3 -0
  22. package/server/register.js +3 -0
  23. package/server/src/bootstrap.js +3826 -0
  24. package/server/src/components/content-block.json +37 -0
  25. package/server/src/components/shipping-zone-location.json +27 -0
  26. package/server/src/config/index.js +7 -0
  27. package/server/src/content-types/address/index.js +7 -0
  28. package/server/src/content-types/address/schema.json +74 -0
  29. package/server/src/content-types/cart/index.js +61 -0
  30. package/server/src/content-types/cart-item/index.js +79 -0
  31. package/server/src/content-types/compare.js +73 -0
  32. package/server/src/content-types/coupon/index.js +7 -0
  33. package/server/src/content-types/coupon/schema.json +67 -0
  34. package/server/src/content-types/index.js +42 -0
  35. package/server/src/content-types/order/index.js +7 -0
  36. package/server/src/content-types/order/schema.json +121 -0
  37. package/server/src/content-types/payment-transaction/index.js +7 -0
  38. package/server/src/content-types/payment-transaction/schema.json +73 -0
  39. package/server/src/content-types/product/index.js +7 -0
  40. package/server/src/content-types/product/schema.json +104 -0
  41. package/server/src/content-types/product-attribute/index.js +7 -0
  42. package/server/src/content-types/product-attribute/schema.json +80 -0
  43. package/server/src/content-types/product-attribute-value/index.js +7 -0
  44. package/server/src/content-types/product-attribute-value/schema.json +52 -0
  45. package/server/src/content-types/product-category/index.js +7 -0
  46. package/server/src/content-types/product-category/schema.json +54 -0
  47. package/server/src/content-types/product-tag/index.js +7 -0
  48. package/server/src/content-types/product-tag/schema.json +38 -0
  49. package/server/src/content-types/product-variation/index.js +7 -0
  50. package/server/src/content-types/product-variation/schema.json +74 -0
  51. package/server/src/content-types/shipping-method/index.js +7 -0
  52. package/server/src/content-types/shipping-method/schema.json +91 -0
  53. package/server/src/content-types/shipping-rate/index.js +7 -0
  54. package/server/src/content-types/shipping-rate/schema.json +73 -0
  55. package/server/src/content-types/shipping-rule/index.js +7 -0
  56. package/server/src/content-types/shipping-rule/schema.json +84 -0
  57. package/server/src/content-types/shipping-zone/index.js +7 -0
  58. package/server/src/content-types/shipping-zone/schema.json +57 -0
  59. package/server/src/content-types/wishlist.js +66 -0
  60. package/server/src/controllers/address.js +374 -0
  61. package/server/src/controllers/auth.js +1409 -0
  62. package/server/src/controllers/cart.js +337 -0
  63. package/server/src/controllers/category.js +388 -0
  64. package/server/src/controllers/compare.js +246 -0
  65. package/server/src/controllers/controller.js +168 -0
  66. package/server/src/controllers/ecommerce.js +20 -0
  67. package/server/src/controllers/index.js +34 -0
  68. package/server/src/controllers/order.js +1100 -0
  69. package/server/src/controllers/payment.js +243 -0
  70. package/server/src/controllers/product.js +1006 -0
  71. package/server/src/controllers/productTag.js +370 -0
  72. package/server/src/controllers/productVariation.js +181 -0
  73. package/server/src/controllers/shipping.js +1046 -0
  74. package/server/src/controllers/wishlist.js +332 -0
  75. package/server/src/destroy.js +6 -0
  76. package/server/src/index.js +26 -0
  77. package/server/src/middlewares/index.js +4 -0
  78. package/server/src/policies/index.js +4 -0
  79. package/server/src/register.js +67 -0
  80. package/server/src/routes/index.js +1130 -0
  81. package/server/src/services/cart.js +531 -0
  82. package/server/src/services/compare.js +300 -0
  83. package/server/src/services/index.js +16 -0
  84. package/server/src/services/service.js +19 -0
  85. package/server/src/services/shipping.js +513 -0
  86. package/server/src/services/wishlist.js +238 -0
  87. package/server/src/utils/check-ecommerce-permission.js +204 -0
  88. package/server/src/utils/extend-user-schema.js +161 -0
  89. package/server/src/utils/seed-data.js +639 -0
  90. package/server/src/utils/send-email.js +98 -0
  91. package/strapi-server.js +1 -6
  92. package/dist/_chunks/Settings-Bg2JyQ4c.js +0 -31518
  93. package/dist/_chunks/Settings-BonPzbwr.mjs +0 -31499
  94. package/dist/_chunks/en-DE15m4xZ.mjs +0 -574
  95. package/dist/_chunks/index-BWVy9o1d.mjs +0 -128
  96. package/dist/_chunks/index-NRuOdjd7.js +0 -127
  97. package/dist/admin/index.js +0 -3
  98. package/dist/admin/index.mjs +0 -4
  99. package/dist/robots.txt +0 -3
  100. package/dist/server/index.js +0 -27336
  101. package/dist/uploads/.gitkeep +0 -0
  102. package/dist/uploads/accessories_category_2a5631094b.jpeg +0 -0
  103. package/dist/uploads/beauty_personal_care_category_57f8a8f1e3.jpeg +0 -0
  104. package/dist/uploads/books_category_a9a253eada.jpeg +0 -0
  105. package/dist/uploads/classic_cotton_tshirt_1_cd713425f6.png +0 -0
  106. package/dist/uploads/clothing_category_d5c60ef07b.jpeg +0 -0
  107. package/dist/uploads/daviddoe_strapi_adbcd41787.jpeg +0 -0
  108. package/dist/uploads/electronics_category_fc3e5ef571.jpeg +0 -0
  109. package/dist/uploads/ergonomic_office_chair_1_c751cffb07.png +0 -0
  110. package/dist/uploads/home_garden_category_4f6eb3f8d6.jpeg +0 -0
  111. package/dist/uploads/istockphoto_1188462138_612x612_11f295b9c0.jpg +0 -0
  112. package/dist/uploads/istockphoto_1188462138_612x612_396fb272fd.jpg +0 -0
  113. package/dist/uploads/large_daviddoe_strapi_adbcd41787.jpeg +0 -0
  114. package/dist/uploads/leather_travel_backpack_1_238bc1ae4d.png +0 -0
  115. package/dist/uploads/mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  116. package/dist/uploads/medium_classic_cotton_tshirt_1_cd713425f6.png +0 -0
  117. package/dist/uploads/medium_daviddoe_strapi_adbcd41787.jpeg +0 -0
  118. package/dist/uploads/medium_ergonomic_office_chair_1_c751cffb07.png +0 -0
  119. package/dist/uploads/medium_leather_travel_backpack_1_238bc1ae4d.png +0 -0
  120. package/dist/uploads/medium_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  121. package/dist/uploads/medium_smart_watch_series_5_1_cdc2511fb7.png +0 -0
  122. package/dist/uploads/medium_smartphone_x_pro_1_c3f0cbd080.png +0 -0
  123. package/dist/uploads/medium_the_great_gatsby_special_1_2e7c76d997.png +0 -0
  124. package/dist/uploads/medium_wireless_headphones_1_fa75cd50c3.png +0 -0
  125. package/dist/uploads/medium_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  126. package/dist/uploads/predictive_maintenance_icons_industry_automation_600nw_2685943461_e18a8aa3b0.webp +0 -0
  127. package/dist/uploads/small_classic_cotton_tshirt_1_cd713425f6.png +0 -0
  128. package/dist/uploads/small_daviddoe_strapi_adbcd41787.jpeg +0 -0
  129. package/dist/uploads/small_ergonomic_office_chair_1_c751cffb07.png +0 -0
  130. package/dist/uploads/small_leather_travel_backpack_1_238bc1ae4d.png +0 -0
  131. package/dist/uploads/small_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  132. package/dist/uploads/small_smart_watch_series_5_1_cdc2511fb7.png +0 -0
  133. package/dist/uploads/small_smartphone_x_pro_1_c3f0cbd080.png +0 -0
  134. package/dist/uploads/small_the_great_gatsby_special_1_2e7c76d997.png +0 -0
  135. package/dist/uploads/small_wireless_headphones_1_fa75cd50c3.png +0 -0
  136. package/dist/uploads/small_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  137. package/dist/uploads/smart_watch_series_5_1_cdc2511fb7.png +0 -0
  138. package/dist/uploads/smartphone_x_pro_1_c3f0cbd080.png +0 -0
  139. package/dist/uploads/the_great_gatsby_special_1_2e7c76d997.png +0 -0
  140. package/dist/uploads/thumbnail_accessories_category_2a5631094b.jpeg +0 -0
  141. package/dist/uploads/thumbnail_beauty_personal_care_category_57f8a8f1e3.jpeg +0 -0
  142. package/dist/uploads/thumbnail_books_category_a9a253eada.jpeg +0 -0
  143. package/dist/uploads/thumbnail_classic_cotton_tshirt_1_cd713425f6.png +0 -0
  144. package/dist/uploads/thumbnail_clothing_category_d5c60ef07b.jpeg +0 -0
  145. package/dist/uploads/thumbnail_daviddoe_strapi_adbcd41787.jpeg +0 -0
  146. package/dist/uploads/thumbnail_electronics_category_fc3e5ef571.jpeg +0 -0
  147. package/dist/uploads/thumbnail_ergonomic_office_chair_1_c751cffb07.png +0 -0
  148. package/dist/uploads/thumbnail_home_garden_category_4f6eb3f8d6.jpeg +0 -0
  149. package/dist/uploads/thumbnail_istockphoto_1188462138_612x612_11f295b9c0.jpg +0 -0
  150. package/dist/uploads/thumbnail_istockphoto_1188462138_612x612_396fb272fd.jpg +0 -0
  151. package/dist/uploads/thumbnail_leather_travel_backpack_1_238bc1ae4d.png +0 -0
  152. package/dist/uploads/thumbnail_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  153. package/dist/uploads/thumbnail_predictive_maintenance_icons_industry_automation_600nw_2685943461_e18a8aa3b0.webp +0 -0
  154. package/dist/uploads/thumbnail_smart_watch_series_5_1_cdc2511fb7.png +0 -0
  155. package/dist/uploads/thumbnail_smartphone_x_pro_1_c3f0cbd080.png +0 -0
  156. package/dist/uploads/thumbnail_the_great_gatsby_special_1_2e7c76d997.png +0 -0
  157. package/dist/uploads/thumbnail_wireless_headphones_1_fa75cd50c3.png +0 -0
  158. package/dist/uploads/thumbnail_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  159. package/dist/uploads/webby-commerce.png +0 -0
  160. package/dist/uploads/wireless_headphones_1_fa75cd50c3.png +0 -0
  161. package/dist/uploads/yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  162. /package/{dist → server/src}/data/demo-data.json +0 -0
@@ -0,0 +1,337 @@
1
+ 'use strict';
2
+
3
+ const { ensureEcommercePermission } = require('../utils/check-ecommerce-permission');
4
+ const { randomUUID } = require('crypto');
5
+
6
+ const getGuestIdFromRequest = (ctx) => {
7
+ const header = ctx?.request?.header || {};
8
+ const fromHeader = header['x-guest-id'] || header['x-guestid'] || header['guest-id'];
9
+ const fromQuery = ctx?.query?.guest_id || ctx?.query?.guestId;
10
+ const fromBody = ctx?.request?.body?.guest_id || ctx?.request?.body?.guestId;
11
+ const value = fromHeader || fromQuery || fromBody;
12
+ return value ? String(value) : null;
13
+ };
14
+
15
+ module.exports = {
16
+ async getCart(ctx) {
17
+ try {
18
+ const user = ctx.state.user;
19
+ const hasPermission = await ensureEcommercePermission(ctx);
20
+ if (!hasPermission) return;
21
+
22
+ const cartService = strapi.plugin('webbycommerce').service('cart');
23
+ const guestId = user ? null : getGuestIdFromRequest(ctx) || randomUUID();
24
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
25
+ const items = await cartService.getCartItems({ cartId: cart.id });
26
+ const totals = await cartService.getTotalsFromItems(items, cart.coupon);
27
+
28
+ ctx.send({
29
+ data: {
30
+ cart: {
31
+ id: cart.id,
32
+ guest_id: cart.guest_id || guestId || null,
33
+ currency: cart.currency || 'USD',
34
+ },
35
+ items,
36
+ totals,
37
+ },
38
+ });
39
+ } catch (error) {
40
+ strapi.log.error('Error fetching cart:', error);
41
+ ctx.badRequest('Failed to fetch cart', { error: error.message });
42
+ }
43
+ },
44
+
45
+ // Optional alias used by bootstrap middleware (getCart is preferred)
46
+ async getItems(ctx) {
47
+ return await this.getCart(ctx);
48
+ },
49
+
50
+ async createCart(ctx) {
51
+ // Ensure a cart exists and return its id (for guests we auto-generate guest_id)
52
+ return await this.getCart(ctx);
53
+ },
54
+
55
+ async addItem(ctx) {
56
+ try {
57
+ const user = ctx.state.user;
58
+ const hasPermission = await ensureEcommercePermission(ctx);
59
+ if (!hasPermission) return;
60
+
61
+ const { productId, quantity } = ctx.request.body || {};
62
+
63
+ const cartService = strapi.plugin('webbycommerce').service('cart');
64
+ const guestId = user ? null : getGuestIdFromRequest(ctx) || randomUUID();
65
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
66
+ await cartService.addOrUpdateItem({ cartId: cart.id, userId: user?.id, productId, quantity });
67
+
68
+ const items = await cartService.getCartItems({ cartId: cart.id });
69
+ const updatedCart = await strapi.db.query('plugin::webbycommerce.cart').findOne({
70
+ where: { id: cart.id },
71
+ populate: { coupon: true },
72
+ });
73
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
74
+
75
+ ctx.send({
76
+ data: {
77
+ cart: {
78
+ id: cart.id,
79
+ guest_id: cart.guest_id || guestId || null,
80
+ currency: cart.currency || 'USD',
81
+ },
82
+ items,
83
+ totals,
84
+ },
85
+ message: 'Item added to cart',
86
+ });
87
+ } catch (error) {
88
+ strapi.log.error('Error adding item to cart:', error);
89
+ const status = error?.status || 400;
90
+ if (status === 404) return ctx.notFound(error.message);
91
+ return ctx.badRequest(error.message);
92
+ }
93
+ },
94
+
95
+ async updateItem(ctx) {
96
+ try {
97
+ const user = ctx.state.user;
98
+ const hasPermission = await ensureEcommercePermission(ctx);
99
+ if (!hasPermission) return;
100
+
101
+ const { id } = ctx.params || {};
102
+ const { quantity } = ctx.request.body || {};
103
+
104
+ const cartService = strapi.plugin('webbycommerce').service('cart');
105
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
106
+ if (!user && !guestId) return ctx.badRequest('guest_id is required for guest cart');
107
+
108
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
109
+ await cartService.updateItemQuantity({ cartId: cart.id, userId: user?.id, cartItemId: id, quantity });
110
+
111
+ const items = await cartService.getCartItems({ cartId: cart.id });
112
+ const updatedCart = await strapi.db.query('plugin::webbycommerce.cart').findOne({
113
+ where: { id: cart.id },
114
+ populate: { coupon: true },
115
+ });
116
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
117
+
118
+ ctx.send({
119
+ data: {
120
+ cart: {
121
+ id: cart.id,
122
+ guest_id: cart.guest_id || guestId || null,
123
+ currency: cart.currency || 'USD',
124
+ },
125
+ items,
126
+ totals,
127
+ },
128
+ message: 'Cart item updated',
129
+ });
130
+ } catch (error) {
131
+ strapi.log.error('Error updating cart item:', error);
132
+ const status = error?.status || 400;
133
+ if (status === 404) return ctx.notFound(error.message);
134
+ return ctx.badRequest(error.message);
135
+ }
136
+ },
137
+
138
+ async removeItem(ctx) {
139
+ try {
140
+ const user = ctx.state.user;
141
+ const hasPermission = await ensureEcommercePermission(ctx);
142
+ if (!hasPermission) return;
143
+
144
+ const { id } = ctx.params || {};
145
+ const cartService = strapi.plugin('webbycommerce').service('cart');
146
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
147
+ if (!user && !guestId) return ctx.badRequest('guest_id is required for guest cart');
148
+
149
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
150
+ await cartService.removeItem({ cartId: cart.id, cartItemId: id });
151
+
152
+ const items = await cartService.getCartItems({ cartId: cart.id });
153
+ const updatedCart = await strapi.db.query('plugin::webbycommerce.cart').findOne({
154
+ where: { id: cart.id },
155
+ populate: { coupon: true },
156
+ });
157
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
158
+
159
+ ctx.send({
160
+ data: {
161
+ cart: {
162
+ id: cart.id,
163
+ guest_id: cart.guest_id || guestId || null,
164
+ currency: cart.currency || 'USD',
165
+ },
166
+ items,
167
+ totals,
168
+ },
169
+ message: 'Cart item removed',
170
+ });
171
+ } catch (error) {
172
+ strapi.log.error('Error removing cart item:', error);
173
+ const status = error?.status || 400;
174
+ if (status === 404) return ctx.notFound(error.message);
175
+ return ctx.badRequest(error.message);
176
+ }
177
+ },
178
+
179
+ async clearCart(ctx) {
180
+ try {
181
+ const user = ctx.state.user;
182
+ const hasPermission = await ensureEcommercePermission(ctx);
183
+ if (!hasPermission) return;
184
+
185
+ const cartService = strapi.plugin('webbycommerce').service('cart');
186
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
187
+ if (!user && !guestId) return ctx.badRequest('guest_id is required for guest cart');
188
+
189
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
190
+ await cartService.clearCart(cart.id);
191
+
192
+ ctx.send({
193
+ data: {
194
+ cart: {
195
+ id: cart.id,
196
+ guest_id: cart.guest_id || guestId || null,
197
+ currency: cart.currency || 'USD',
198
+ },
199
+ items: [],
200
+ totals: await cartService.getTotalsFromItems([]),
201
+ },
202
+ message: 'Cart cleared',
203
+ });
204
+ } catch (error) {
205
+ strapi.log.error('Error clearing cart:', error);
206
+ ctx.badRequest('Failed to clear cart', { error: error.message });
207
+ }
208
+ },
209
+
210
+ async getTotals(ctx) {
211
+ try {
212
+ const user = ctx.state.user;
213
+ const hasPermission = await ensureEcommercePermission(ctx);
214
+ if (!hasPermission) return;
215
+
216
+ const cartService = strapi.plugin('webbycommerce').service('cart');
217
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
218
+ if (!user && !guestId) return ctx.badRequest('guest_id is required for guest cart');
219
+
220
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
221
+ const items = await cartService.getCartItems({ cartId: cart.id });
222
+ const totals = await cartService.getTotalsFromItems(items, cart.coupon);
223
+
224
+ ctx.send({
225
+ data: {
226
+ cart: {
227
+ id: cart.id,
228
+ guest_id: cart.guest_id || guestId || null,
229
+ currency: cart.currency || 'USD',
230
+ },
231
+ totals,
232
+ },
233
+ });
234
+ } catch (error) {
235
+ strapi.log.error('Error calculating cart totals:', error);
236
+ ctx.badRequest('Failed to calculate totals', { error: error.message });
237
+ }
238
+ },
239
+
240
+ async applyCoupon(ctx) {
241
+ try {
242
+ const user = ctx.state.user;
243
+ const hasPermission = await ensureEcommercePermission(ctx);
244
+ if (!hasPermission) return;
245
+
246
+ const { coupon_code } = ctx.request.body || {};
247
+ if (!coupon_code) {
248
+ return ctx.badRequest('Coupon code is required');
249
+ }
250
+
251
+ const cartService = strapi.plugin('webbycommerce').service('cart');
252
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
253
+ if (!user && !guestId) return ctx.badRequest('guest_id is required for guest cart');
254
+
255
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
256
+ const coupon = await cartService.validateAndApplyCoupon({
257
+ cartId: cart.id,
258
+ couponCode: coupon_code,
259
+ });
260
+
261
+ const items = await cartService.getCartItems({ cartId: cart.id });
262
+ const updatedCart = await strapi.db.query('plugin::webbycommerce.cart').findOne({
263
+ where: { id: cart.id },
264
+ populate: { coupon: true },
265
+ });
266
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
267
+
268
+ ctx.send({
269
+ data: {
270
+ cart: {
271
+ id: cart.id,
272
+ guest_id: cart.guest_id || guestId || null,
273
+ currency: cart.currency || 'USD',
274
+ },
275
+ coupon: {
276
+ code: coupon.code,
277
+ type: coupon.type,
278
+ value: coupon.value,
279
+ discount_amount: totals.discount,
280
+ },
281
+ items,
282
+ totals,
283
+ },
284
+ message: 'Coupon applied successfully',
285
+ });
286
+ } catch (error) {
287
+ strapi.log.error('Error applying coupon:', error);
288
+ const status = error?.status || 400;
289
+ if (status === 404) return ctx.notFound(error.message);
290
+ return ctx.badRequest(error.message || 'Invalid coupon code');
291
+ }
292
+ },
293
+
294
+ async removeCoupon(ctx) {
295
+ try {
296
+ const user = ctx.state.user;
297
+ const hasPermission = await ensureEcommercePermission(ctx);
298
+ if (!hasPermission) return;
299
+
300
+ const cartService = strapi.plugin('webbycommerce').service('cart');
301
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
302
+ if (!user && !guestId) return ctx.badRequest('guest_id is required for guest cart');
303
+
304
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
305
+ await cartService.removeCoupon({ cartId: cart.id });
306
+
307
+ const items = await cartService.getCartItems({ cartId: cart.id });
308
+ const totals = await cartService.getTotalsFromItems(items, null);
309
+
310
+ ctx.send({
311
+ data: {
312
+ cart: {
313
+ id: cart.id,
314
+ guest_id: cart.guest_id || guestId || null,
315
+ currency: cart.currency || 'USD',
316
+ },
317
+ items,
318
+ totals,
319
+ },
320
+ message: 'Coupon removed successfully',
321
+ });
322
+ } catch (error) {
323
+ strapi.log.error('Error removing coupon:', error);
324
+ ctx.badRequest('Failed to remove coupon', { error: error.message });
325
+ }
326
+ },
327
+
328
+ async checkout(ctx) {
329
+ // Delegate to existing order checkout implementation
330
+ const orderController = strapi.plugin('webbycommerce').controller('order');
331
+ if (orderController && typeof orderController.checkout === 'function') {
332
+ return await orderController.checkout(ctx);
333
+ }
334
+ return ctx.badRequest('Checkout is not available');
335
+ },
336
+ };
337
+