@webbycrown/webbycommerce 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1350,6 +1350,153 @@ var require_check_ecommerce_permission = __commonJS({
1350
1350
  }
1351
1351
  });
1352
1352
 
1353
+ // server/src/utils/extend-user-schema.js
1354
+ var require_extend_user_schema = __commonJS({
1355
+ "server/src/utils/extend-user-schema.js"(exports2, module2) {
1356
+ "use strict";
1357
+ async function extendUserSchemaWithOtpFields(strapi2) {
1358
+ try {
1359
+ const db = strapi2.db;
1360
+ const client = db.config.connection.client;
1361
+ const tableName = "up_users";
1362
+ let fieldsExist = false;
1363
+ try {
1364
+ const contentType = strapi2.contentTypes["plugin::users-permissions.user"];
1365
+ if (contentType && contentType.attributes) {
1366
+ fieldsExist = "otp" in contentType.attributes && "isOtpVerified" in contentType.attributes;
1367
+ }
1368
+ } catch (err) {
1369
+ try {
1370
+ await db.query("plugin::users-permissions.user").findOne({
1371
+ select: ["id", "otp", "isOtpVerified"],
1372
+ limit: 1
1373
+ });
1374
+ fieldsExist = true;
1375
+ } catch (queryErr) {
1376
+ fieldsExist = false;
1377
+ }
1378
+ }
1379
+ if (fieldsExist) {
1380
+ strapi2.log.info("[webbycommerce] OTP fields already exist in user schema");
1381
+ return true;
1382
+ }
1383
+ strapi2.log.info("[webbycommerce] OTP fields not found, adding them to user schema...");
1384
+ const connection = db.connection;
1385
+ const knex = connection;
1386
+ let otpAdded = false;
1387
+ let isOtpVerifiedAdded = false;
1388
+ if (client === "sqlite" || client === "sqlite3") {
1389
+ const tableInfo = await knex.raw(`PRAGMA table_info(${tableName})`);
1390
+ const columns = tableInfo.map((col) => col.name);
1391
+ if (!columns.includes("otp")) {
1392
+ await knex.schema.alterTable(tableName, (table) => {
1393
+ table.integer("otp").nullable();
1394
+ });
1395
+ otpAdded = true;
1396
+ strapi2.log.info('[webbycommerce] Added "otp" column to user table');
1397
+ }
1398
+ if (!columns.includes("is_otp_verified")) {
1399
+ await knex.schema.alterTable(tableName, (table) => {
1400
+ table.boolean("is_otp_verified").defaultTo(false);
1401
+ });
1402
+ isOtpVerifiedAdded = true;
1403
+ strapi2.log.info('[webbycommerce] Added "is_otp_verified" column to user table');
1404
+ }
1405
+ } else if (client === "postgres") {
1406
+ const otpExists = await knex.raw(`
1407
+ SELECT column_name
1408
+ FROM information_schema.columns
1409
+ WHERE table_name='${tableName}' AND column_name='otp'
1410
+ `);
1411
+ if (otpExists.rows.length === 0) {
1412
+ await knex.raw(`ALTER TABLE ${tableName} ADD COLUMN otp INTEGER`);
1413
+ otpAdded = true;
1414
+ strapi2.log.info('[webbycommerce] Added "otp" column to user table');
1415
+ }
1416
+ const isOtpVerifiedExists = await knex.raw(`
1417
+ SELECT column_name
1418
+ FROM information_schema.columns
1419
+ WHERE table_name='${tableName}' AND column_name='is_otp_verified'
1420
+ `);
1421
+ if (isOtpVerifiedExists.rows.length === 0) {
1422
+ await knex.raw(`ALTER TABLE ${tableName} ADD COLUMN is_otp_verified BOOLEAN DEFAULT false`);
1423
+ isOtpVerifiedAdded = true;
1424
+ strapi2.log.info('[webbycommerce] Added "is_otp_verified" column to user table');
1425
+ }
1426
+ } else if (client === "mysql" || client === "mysql2") {
1427
+ const otpExists = await knex.raw(`
1428
+ SELECT COLUMN_NAME
1429
+ FROM INFORMATION_SCHEMA.COLUMNS
1430
+ WHERE TABLE_SCHEMA = DATABASE()
1431
+ AND TABLE_NAME = '${tableName}'
1432
+ AND COLUMN_NAME = 'otp'
1433
+ `);
1434
+ if (otpExists[0].length === 0) {
1435
+ await knex.raw(`ALTER TABLE \`${tableName}\` ADD COLUMN \`otp\` INT NULL`);
1436
+ otpAdded = true;
1437
+ strapi2.log.info('[webbycommerce] Added "otp" column to user table');
1438
+ }
1439
+ const isOtpVerifiedExists = await knex.raw(`
1440
+ SELECT COLUMN_NAME
1441
+ FROM INFORMATION_SCHEMA.COLUMNS
1442
+ WHERE TABLE_SCHEMA = DATABASE()
1443
+ AND TABLE_NAME = '${tableName}'
1444
+ AND COLUMN_NAME = 'is_otp_verified'
1445
+ `);
1446
+ if (isOtpVerifiedExists[0].length === 0) {
1447
+ await knex.raw(`ALTER TABLE \`${tableName}\` ADD COLUMN \`is_otp_verified\` BOOLEAN DEFAULT false`);
1448
+ isOtpVerifiedAdded = true;
1449
+ strapi2.log.info('[webbycommerce] Added "is_otp_verified" column to user table');
1450
+ }
1451
+ } else {
1452
+ strapi2.log.warn(
1453
+ `[webbycommerce] Database client "${client}" not supported for automatic schema extension. Please manually add OTP fields to user schema.`
1454
+ );
1455
+ return false;
1456
+ }
1457
+ try {
1458
+ const contentType = strapi2.contentTypes["plugin::users-permissions.user"];
1459
+ if (contentType && contentType.attributes) {
1460
+ if (otpAdded || !("otp" in contentType.attributes)) {
1461
+ contentType.attributes.otp = {
1462
+ type: "integer",
1463
+ required: false,
1464
+ private: true
1465
+ };
1466
+ strapi2.log.info('[webbycommerce] Registered "otp" field in Strapi content-type');
1467
+ }
1468
+ if (isOtpVerifiedAdded || !("isOtpVerified" in contentType.attributes)) {
1469
+ contentType.attributes.isOtpVerified = {
1470
+ type: "boolean",
1471
+ default: false,
1472
+ required: false,
1473
+ private: true
1474
+ };
1475
+ strapi2.log.info('[webbycommerce] Registered "isOtpVerified" field in Strapi content-type');
1476
+ }
1477
+ }
1478
+ } catch (schemaError) {
1479
+ strapi2.log.warn("[webbycommerce] Could not register fields in content-type schema:", schemaError.message);
1480
+ strapi2.log.warn("[webbycommerce] Database columns were added, but schema registration failed.");
1481
+ strapi2.log.warn(
1482
+ "[webbycommerce] You may need to restart Strapi or create a schema extension file in your main Strapi project."
1483
+ );
1484
+ }
1485
+ strapi2.log.info("[webbycommerce] User schema extension completed successfully");
1486
+ return true;
1487
+ } catch (error) {
1488
+ strapi2.log.error("[webbycommerce] Failed to extend user schema with OTP fields:", error);
1489
+ strapi2.log.error("[webbycommerce] Error details:", error.message);
1490
+ strapi2.log.error(
1491
+ "[webbycommerce] Please manually extend the user schema by creating a schema extension file in your Strapi project."
1492
+ );
1493
+ return false;
1494
+ }
1495
+ }
1496
+ module2.exports = { extendUserSchemaWithOtpFields };
1497
+ }
1498
+ });
1499
+
1353
1500
  // server/src/content-types/address/schema.json
1354
1501
  var require_schema = __commonJS({
1355
1502
  "server/src/content-types/address/schema.json"(exports2, module2) {
@@ -2945,11 +3092,21 @@ var require_bootstrap = __commonJS({
2945
3092
  "server/src/bootstrap.js"(exports2, module2) {
2946
3093
  "use strict";
2947
3094
  var { registerEcommerceActions, ensureEcommercePermission } = require_check_ecommerce_permission();
3095
+ var { extendUserSchemaWithOtpFields } = require_extend_user_schema();
2948
3096
  module2.exports = async ({ strapi: strapi2 }) => {
2949
3097
  try {
2950
3098
  strapi2.log.info("[webbycommerce] ========================================");
2951
3099
  strapi2.log.info("[webbycommerce] Bootstrapping plugin...");
2952
- if (process.env.STRAPI_PLUGIN_ADVANCED_ECOMMERCE_SEED_DATA === "true") {
3100
+ try {
3101
+ await extendUserSchemaWithOtpFields(strapi2);
3102
+ } catch (schemaError) {
3103
+ strapi2.log.warn("[webbycommerce] Could not automatically extend user schema:", schemaError.message);
3104
+ strapi2.log.warn(
3105
+ "[webbycommerce] Please manually extend the user schema. See README for instructions."
3106
+ );
3107
+ }
3108
+ const disableSeeding = process.env.STRAPI_PLUGIN_ADVANCED_ECOMMERCE_DISABLE_SEED_DEMO === "true" || process.env.STRAPI_PLUGIN_ADVANCED_ECOMMERCE_DISABLE_SEED_DEMO === "1" || process.env.STRAPI_PLUGIN_ADVANCED_ECOMMERCE_DISABLE_SEED_DEMO === "yes";
3109
+ if (!disableSeeding && process.env.STRAPI_PLUGIN_ADVANCED_ECOMMERCE_SEED_DATA === "true") {
2953
3110
  try {
2954
3111
  await new Promise((resolve) => setTimeout(resolve, 1e3));
2955
3112
  strapi2.log.info("[webbycommerce] Auto-seeding demo data as requested by environment variable...");
@@ -2963,6 +3120,10 @@ var require_bootstrap = __commonJS({
2963
3120
  strapi2.log.error("[webbycommerce] Auto-seeding failed:", seedError.message);
2964
3121
  strapi2.log.error("[webbycommerce] Stack:", seedError.stack);
2965
3122
  }
3123
+ } else if (disableSeeding && process.env.STRAPI_PLUGIN_ADVANCED_ECOMMERCE_SEED_DATA === "true") {
3124
+ strapi2.log.info(
3125
+ "[webbycommerce] Demo seeding is disabled by STRAPI_PLUGIN_ADVANCED_ECOMMERCE_DISABLE_SEED_DEMO; skipping auto-seed."
3126
+ );
2966
3127
  }
2967
3128
  const contentTypes2 = require_content_types();
2968
3129
  strapi2.log.info("[webbycommerce] Content types loaded:", Object.keys(contentTypes2));
@@ -4903,17 +5064,61 @@ var require_auth = __commonJS({
4903
5064
  const otp = Math.floor(1e5 + Math.random() * 9e5);
4904
5065
  const otpDigits = otp.toString().split("");
4905
5066
  try {
4906
- await strapi.db.query("plugin::users-permissions.user").update({
4907
- where: { id: user.id },
4908
- data: { otp, isOtpVerified: false }
4909
- });
5067
+ const db = strapi.db;
5068
+ const knex = db.connection;
5069
+ const tableName = "up_users";
5070
+ const client = db.config.connection.client;
5071
+ if (client === "postgres") {
5072
+ await knex.raw(
5073
+ `UPDATE ${tableName} SET otp = ?, is_otp_verified = ? WHERE id = ?`,
5074
+ [otp, false, user.id]
5075
+ );
5076
+ } else if (client === "mysql" || client === "mysql2") {
5077
+ await knex.raw(
5078
+ `UPDATE \`${tableName}\` SET \`otp\` = ?, \`is_otp_verified\` = ? WHERE \`id\` = ?`,
5079
+ [otp, false, user.id]
5080
+ );
5081
+ } else {
5082
+ await knex.raw(
5083
+ `UPDATE ${tableName} SET otp = ?, is_otp_verified = ? WHERE id = ?`,
5084
+ [otp, false, user.id]
5085
+ );
5086
+ }
4910
5087
  } catch (err) {
4911
5088
  strapi.log.warn(
4912
- `[${PLUGIN_ID}] OTP fields not found in user schema. Please extend the user schema to include 'otp' and 'isOtpVerified' fields.`
4913
- );
4914
- throw new Error(
4915
- "OTP fields are not available in the user schema. Please extend the user schema as described in the plugin README."
5089
+ `[${PLUGIN_ID}] OTP fields not found in database. Attempting to add them...`
4916
5090
  );
5091
+ try {
5092
+ const { extendUserSchemaWithOtpFields } = require_extend_user_schema();
5093
+ await extendUserSchemaWithOtpFields(strapi);
5094
+ const db = strapi.db;
5095
+ const knex = db.connection;
5096
+ const tableName = "up_users";
5097
+ const client = db.config.connection.client;
5098
+ if (client === "postgres") {
5099
+ await knex.raw(
5100
+ `UPDATE ${tableName} SET otp = ?, is_otp_verified = ? WHERE id = ?`,
5101
+ [otp, false, user.id]
5102
+ );
5103
+ } else if (client === "mysql" || client === "mysql2") {
5104
+ await knex.raw(
5105
+ `UPDATE \`${tableName}\` SET \`otp\` = ?, \`is_otp_verified\` = ? WHERE \`id\` = ?`,
5106
+ [otp, false, user.id]
5107
+ );
5108
+ } else {
5109
+ await knex.raw(
5110
+ `UPDATE ${tableName} SET otp = ?, is_otp_verified = ? WHERE id = ?`,
5111
+ [otp, false, user.id]
5112
+ );
5113
+ }
5114
+ } catch (retryErr) {
5115
+ strapi.log.error(
5116
+ `[${PLUGIN_ID}] Failed to add OTP fields: ${retryErr.message}`
5117
+ );
5118
+ throw new Error(
5119
+ "OTP fields are not available in the user schema. Please extend the user schema as described in the plugin README."
5120
+ );
5121
+ }
4917
5122
  }
4918
5123
  let emailSent = false;
4919
5124
  if (type === "email") {
@@ -4996,7 +5201,7 @@ var require_auth = __commonJS({
4996
5201
  try {
4997
5202
  await sendEmail({
4998
5203
  to: email,
4999
- subject: "Your OTP Code - Strapi Advanced Ecommerce",
5204
+ subject: "Your OTP Code - Strapi WebbyCommerce",
5000
5205
  html: otpEmailHTML
5001
5206
  });
5002
5207
  emailSent = true;
@@ -5045,21 +5250,110 @@ var require_auth = __commonJS({
5045
5250
  where: { [type === "email" ? "email" : "phone_no"]: identifier }
5046
5251
  });
5047
5252
  if (!user) return ctx.badRequest("User not found.");
5048
- if (user.isOtpVerified === void 0 || user.otp === void 0) {
5253
+ const db = strapi.db;
5254
+ const knex = db.connection;
5255
+ const tableName = "up_users";
5256
+ const client = db.config.connection.client;
5257
+ let userOtpData;
5258
+ let columnsExist = false;
5259
+ try {
5260
+ if (client === "postgres") {
5261
+ const result = await knex.raw(
5262
+ `SELECT otp, is_otp_verified FROM ${tableName} WHERE id = ?`,
5263
+ [user.id]
5264
+ );
5265
+ userOtpData = result.rows[0];
5266
+ columnsExist = userOtpData && userOtpData.hasOwnProperty("otp") && userOtpData.hasOwnProperty("is_otp_verified");
5267
+ } else if (client === "mysql" || client === "mysql2") {
5268
+ const result = await knex.raw(
5269
+ `SELECT \`otp\`, \`is_otp_verified\` FROM \`${tableName}\` WHERE \`id\` = ?`,
5270
+ [user.id]
5271
+ );
5272
+ userOtpData = result[0][0];
5273
+ columnsExist = userOtpData && userOtpData.hasOwnProperty("otp") && userOtpData.hasOwnProperty("is_otp_verified");
5274
+ } else {
5275
+ const result = await knex.raw(
5276
+ `SELECT otp, is_otp_verified FROM ${tableName} WHERE id = ?`,
5277
+ [user.id]
5278
+ );
5279
+ userOtpData = result[0];
5280
+ columnsExist = userOtpData && userOtpData.hasOwnProperty("otp") && userOtpData.hasOwnProperty("is_otp_verified");
5281
+ }
5282
+ } catch (queryErr) {
5283
+ strapi.log.warn(`[${PLUGIN_ID}] OTP columns not found, attempting to add them:`, queryErr.message);
5284
+ columnsExist = false;
5285
+ }
5286
+ if (!columnsExist) {
5287
+ const { extendUserSchemaWithOtpFields } = require_extend_user_schema();
5288
+ const schemaExtended = await extendUserSchemaWithOtpFields(strapi);
5289
+ if (!schemaExtended) {
5290
+ return ctx.badRequest(
5291
+ "OTP fields are not available in the user schema. Please extend the user schema as described in the plugin README."
5292
+ );
5293
+ }
5294
+ await new Promise((resolve) => setTimeout(resolve, 200));
5295
+ try {
5296
+ if (client === "postgres") {
5297
+ const result = await knex.raw(
5298
+ `SELECT otp, is_otp_verified FROM ${tableName} WHERE id = ?`,
5299
+ [user.id]
5300
+ );
5301
+ userOtpData = result.rows[0];
5302
+ columnsExist = userOtpData && userOtpData.hasOwnProperty("otp") && userOtpData.hasOwnProperty("is_otp_verified");
5303
+ } else if (client === "mysql" || client === "mysql2") {
5304
+ const result = await knex.raw(
5305
+ `SELECT \`otp\`, \`is_otp_verified\` FROM \`${tableName}\` WHERE \`id\` = ?`,
5306
+ [user.id]
5307
+ );
5308
+ userOtpData = result[0][0];
5309
+ columnsExist = userOtpData && userOtpData.hasOwnProperty("otp") && userOtpData.hasOwnProperty("is_otp_verified");
5310
+ } else {
5311
+ const result = await knex.raw(
5312
+ `SELECT otp, is_otp_verified FROM ${tableName} WHERE id = ?`,
5313
+ [user.id]
5314
+ );
5315
+ userOtpData = result[0];
5316
+ columnsExist = userOtpData && userOtpData.hasOwnProperty("otp") && userOtpData.hasOwnProperty("is_otp_verified");
5317
+ }
5318
+ if (!columnsExist) {
5319
+ return ctx.badRequest(
5320
+ "OTP fields were added but could not be queried. Please restart Strapi."
5321
+ );
5322
+ }
5323
+ } catch (retryErr) {
5324
+ strapi.log.error(`[${PLUGIN_ID}] Failed to query OTP fields after extension:`, retryErr);
5325
+ return ctx.badRequest(
5326
+ "OTP fields are not available. Please restart Strapi after extending the user schema."
5327
+ );
5328
+ }
5329
+ }
5330
+ const userOtp = userOtpData?.otp;
5331
+ const isOtpVerified = userOtpData?.is_otp_verified;
5332
+ if (isOtpVerified) return ctx.badRequest("User already verified.");
5333
+ if (userOtp !== parseInt(otp, 10)) return ctx.badRequest("Invalid OTP.");
5334
+ try {
5335
+ if (client === "postgres") {
5336
+ await knex.raw(
5337
+ `UPDATE ${tableName} SET is_otp_verified = ?, confirmed = true, otp = NULL WHERE id = ?`,
5338
+ [true, user.id]
5339
+ );
5340
+ } else if (client === "mysql" || client === "mysql2") {
5341
+ await knex.raw(
5342
+ `UPDATE \`${tableName}\` SET \`is_otp_verified\` = ?, \`confirmed\` = true, \`otp\` = NULL WHERE \`id\` = ?`,
5343
+ [true, user.id]
5344
+ );
5345
+ } else {
5346
+ await knex.raw(
5347
+ `UPDATE ${tableName} SET is_otp_verified = ?, confirmed = 1, otp = NULL WHERE id = ?`,
5348
+ [true, user.id]
5349
+ );
5350
+ }
5351
+ } catch (dbErr) {
5352
+ strapi.log.error(`[${PLUGIN_ID}] Database error during OTP verification:`, dbErr);
5049
5353
  return ctx.badRequest(
5050
5354
  "OTP fields are not available in the user schema. Please extend the user schema as described in the plugin README."
5051
5355
  );
5052
5356
  }
5053
- if (user.isOtpVerified) return ctx.badRequest("User already verified.");
5054
- if (user.otp !== parseInt(otp, 10)) return ctx.badRequest("Invalid OTP.");
5055
- await strapi.db.query("plugin::users-permissions.user").update({
5056
- where: { id: user.id },
5057
- data: {
5058
- isOtpVerified: true,
5059
- confirmed: true,
5060
- otp: null
5061
- }
5062
- });
5063
5357
  const jwt = strapi.plugins["users-permissions"].services.jwt.issue({
5064
5358
  id: user.id
5065
5359
  });
@@ -5602,7 +5896,7 @@ var require_compare2 = __commonJS({
5602
5896
  ctx.send({
5603
5897
  data: compare,
5604
5898
  meta: {
5605
- totalProducts: compare.products.length,
5899
+ totalProducts: compare?.products?.length || 0,
5606
5900
  comparisonData: compareData.comparisonData
5607
5901
  }
5608
5902
  });
@@ -5626,7 +5920,7 @@ var require_compare2 = __commonJS({
5626
5920
  ctx.send({
5627
5921
  data: compare,
5628
5922
  meta: {
5629
- totalProducts: compare.products.length,
5923
+ totalProducts: compare?.products?.length || 0,
5630
5924
  comparisonData: compareData.comparisonData
5631
5925
  },
5632
5926
  message: "Product added to compare list successfully"
@@ -5651,7 +5945,7 @@ var require_compare2 = __commonJS({
5651
5945
  ctx.send({
5652
5946
  data: compare,
5653
5947
  meta: {
5654
- totalProducts: compare.products.length,
5948
+ totalProducts: compare?.products?.length || 0,
5655
5949
  comparisonData: compareData.comparisonData
5656
5950
  },
5657
5951
  message: "Product removed from compare list successfully"
@@ -5726,9 +6020,12 @@ var require_compare2 = __commonJS({
5726
6020
  const compare = await strapi2.plugin("webbycommerce").service("compare").findUserCompare(user.id);
5727
6021
  const productIdArray = Array.isArray(productIds) ? productIds.map((id) => parseInt(id)) : [parseInt(productIds)];
5728
6022
  const inCompare = {};
5729
- if (compare) {
6023
+ if (compare && compare.products && Array.isArray(compare.products)) {
5730
6024
  productIdArray.forEach((productId) => {
5731
- inCompare[productId] = compare.products.some((product) => product.id === productId);
6025
+ inCompare[productId] = compare.products.some((product) => {
6026
+ const productIdValue = typeof product === "object" && product !== null ? product.id : product;
6027
+ return productIdValue === productId;
6028
+ });
5732
6029
  });
5733
6030
  } else {
5734
6031
  productIdArray.forEach((productId) => {
@@ -5770,12 +6067,22 @@ var require_order2 = __commonJS({
5770
6067
  shipping_address,
5771
6068
  payment_method,
5772
6069
  shipping_method,
5773
- notes
6070
+ notes,
6071
+ tax_amount,
6072
+ shipping_amount,
6073
+ discount_amount
5774
6074
  } = ctx.request.body;
5775
6075
  if (!billing_address || !shipping_address || !payment_method) {
5776
6076
  return ctx.badRequest("Billing address, shipping address, and payment method are required");
5777
6077
  }
5778
- const normalizedPaymentMethod = payment_method;
6078
+ let normalizedPaymentMethod = "COD";
6079
+ if (payment_method) {
6080
+ if (typeof payment_method === "object" && payment_method !== null) {
6081
+ normalizedPaymentMethod = payment_method.type || payment_method.method || payment_method.name || "COD";
6082
+ } else if (typeof payment_method === "string") {
6083
+ normalizedPaymentMethod = payment_method;
6084
+ }
6085
+ }
5779
6086
  const cart = await strapi.db.query("plugin::webbycommerce.cart").findOne({
5780
6087
  where: { user: user.id },
5781
6088
  select: ["id"]
@@ -5816,30 +6123,37 @@ var require_order2 = __commonJS({
5816
6123
  product_image: product.images?.[0]?.url || null
5817
6124
  });
5818
6125
  }
5819
- const taxAmount = 0;
5820
- const shippingAmount = 0;
5821
- const discountAmount = 0;
5822
- const total = subtotal + taxAmount + shippingAmount - discountAmount;
6126
+ const taxAmount = tax_amount != null ? parseFloat(tax_amount) || 0 : 0;
6127
+ const finalShippingAmount = shipping_amount != null ? parseFloat(shipping_amount) || 0 : 0;
6128
+ const finalDiscountAmount = discount_amount != null ? parseFloat(discount_amount) || 0 : 0;
6129
+ const total = subtotal + taxAmount + finalShippingAmount - finalDiscountAmount;
5823
6130
  const orderNumber = await this.generateOrderNumber();
6131
+ const itemsConnect = cartItems.length > 0 ? { connect: cartItems.map((item) => ({ id: item.product.id })) } : void 0;
6132
+ const orderData = {
6133
+ order_number: orderNumber,
6134
+ status: "pending",
6135
+ user: user.id,
6136
+ subtotal: subtotal.toFixed(2),
6137
+ tax_amount: taxAmount.toFixed(2),
6138
+ shipping_amount: finalShippingAmount.toFixed(2),
6139
+ // Use from request
6140
+ discount_amount: finalDiscountAmount.toFixed(2),
6141
+ // Use from request
6142
+ total: total.toFixed(2),
6143
+ currency: "USD",
6144
+ billing_address,
6145
+ shipping_address,
6146
+ payment_method: normalizedPaymentMethod,
6147
+ // Store as string
6148
+ payment_status: "pending",
6149
+ shipping_method: shipping_method || null,
6150
+ notes: notes || null
6151
+ };
6152
+ if (itemsConnect) {
6153
+ orderData.items = itemsConnect;
6154
+ }
5824
6155
  const order = await strapi.db.query("plugin::webbycommerce.order").create({
5825
- data: {
5826
- order_number: orderNumber,
5827
- status: "pending",
5828
- user: user.id,
5829
- items: cartItems.map((item) => item.product.id),
5830
- subtotal: subtotal.toFixed(2),
5831
- tax_amount: taxAmount.toFixed(2),
5832
- shipping_amount: shippingAmount.toFixed(2),
5833
- discount_amount: discountAmount.toFixed(2),
5834
- total: total.toFixed(2),
5835
- currency: "USD",
5836
- billing_address,
5837
- shipping_address,
5838
- payment_method: normalizedPaymentMethod,
5839
- payment_status: "pending",
5840
- shipping_method: shipping_method || null,
5841
- notes: notes || null
5842
- }
6156
+ data: orderData
5843
6157
  });
5844
6158
  for (const cartItem of cartItems) {
5845
6159
  const newStockQuantity = cartItem.product.stock_quantity - cartItem.quantity;
@@ -5865,8 +6179,15 @@ var require_order2 = __commonJS({
5865
6179
  order_id: order.id,
5866
6180
  order_number: order.order_number,
5867
6181
  status: order.status,
6182
+ subtotal: parseFloat(order.subtotal),
6183
+ tax_amount: parseFloat(order.tax_amount),
6184
+ shipping_amount: parseFloat(order.shipping_amount),
6185
+ discount_amount: parseFloat(order.discount_amount),
5868
6186
  total: parseFloat(order.total),
5869
6187
  currency: order.currency,
6188
+ payment_method: order.payment_method,
6189
+ // Return payment method as string
6190
+ shipping_method: order.shipping_method,
5870
6191
  items: order.items,
5871
6192
  created_at: order.createdAt
5872
6193
  },
@@ -5892,7 +6213,7 @@ var require_order2 = __commonJS({
5892
6213
  const query = {
5893
6214
  where: { user: user.id },
5894
6215
  orderBy: { createdAt: "desc" },
5895
- populate: ["items"],
6216
+ populate: ["items", "billing_address", "shipping_address", "user", "payment_transactions"],
5896
6217
  limit: parseInt(limit),
5897
6218
  offset: (parseInt(page) - 1) * parseInt(limit)
5898
6219
  };
@@ -5903,17 +6224,80 @@ var require_order2 = __commonJS({
5903
6224
  const total = await strapi.db.query("plugin::webbycommerce.order").count({
5904
6225
  where: { user: user.id, ...status && { status } }
5905
6226
  });
5906
- const formattedOrders = orders.map((order) => ({
5907
- id: order.id,
5908
- order_number: order.order_number,
5909
- status: order.status,
5910
- payment_method: order.payment_method,
5911
- payment_status: order.payment_status,
5912
- total: parseFloat(order.total),
5913
- currency: order.currency,
5914
- items_count: order.items.length,
5915
- created_at: order.createdAt,
5916
- estimated_delivery: order.estimated_delivery
6227
+ const formattedOrders = await Promise.all(orders.map(async (order) => {
6228
+ let formattedItems = [];
6229
+ if (order.items && order.items.length > 0) {
6230
+ const itemIds = order.items.map((item) => typeof item === "object" && item.id ? item.id : item);
6231
+ const products = await strapi.db.query("plugin::webbycommerce.product").findMany({
6232
+ where: {
6233
+ id: { $in: itemIds }
6234
+ },
6235
+ populate: {
6236
+ images: true
6237
+ }
6238
+ });
6239
+ formattedItems = products.map((product) => ({
6240
+ id: product.id,
6241
+ name: product.name,
6242
+ sku: product.sku,
6243
+ price: parseFloat(product.price || 0),
6244
+ sale_price: product.sale_price ? parseFloat(product.sale_price) : null,
6245
+ images: product.images || [],
6246
+ slug: product.slug,
6247
+ description: product.description
6248
+ }));
6249
+ } else if (order.items && Array.isArray(order.items)) {
6250
+ formattedItems = order.items.map((item) => ({
6251
+ id: item.id,
6252
+ name: item.name,
6253
+ sku: item.sku,
6254
+ price: parseFloat(item.price || 0),
6255
+ sale_price: item.sale_price ? parseFloat(item.sale_price) : null,
6256
+ images: item.images || [],
6257
+ slug: item.slug,
6258
+ description: item.description
6259
+ }));
6260
+ }
6261
+ const shippingAmount = parseFloat(order.shipping_amount || 0);
6262
+ const subtotalAmount = parseFloat(order.subtotal || 0);
6263
+ const discountAmount = parseFloat(order.discount_amount || 0);
6264
+ return {
6265
+ id: order.id,
6266
+ order_number: order.order_number,
6267
+ status: order.status,
6268
+ payment_status: order.payment_status,
6269
+ items: formattedItems,
6270
+ items_count: formattedItems.length,
6271
+ subtotal: subtotalAmount,
6272
+ tax_amount: parseFloat(order.tax_amount || 0),
6273
+ shipping: shippingAmount,
6274
+ shipping_amount: shippingAmount,
6275
+ discount: discountAmount,
6276
+ discount_amount: discountAmount,
6277
+ total: parseFloat(order.total || 0),
6278
+ currency: order.currency,
6279
+ billing_address: order.billing_address,
6280
+ shipping_address: order.shipping_address,
6281
+ payment_method: (() => {
6282
+ if (!order.payment_method) return "N/A";
6283
+ if (typeof order.payment_method === "object" && order.payment_method !== null) {
6284
+ return order.payment_method.type || order.payment_method.method || order.payment_method.name || String(order.payment_method);
6285
+ }
6286
+ return String(order.payment_method);
6287
+ })(),
6288
+ shipping_method: order.shipping_method,
6289
+ notes: order.notes,
6290
+ tracking_number: order.tracking_number,
6291
+ estimated_delivery: order.estimated_delivery,
6292
+ payment_transactions: order.payment_transactions || [],
6293
+ user: order.user ? {
6294
+ id: order.user.id,
6295
+ username: order.user.username,
6296
+ email: order.user.email
6297
+ } : null,
6298
+ created_at: order.createdAt,
6299
+ updated_at: order.updatedAt
6300
+ };
5917
6301
  }));
5918
6302
  ctx.send({
5919
6303
  data: formattedOrders,
@@ -5944,34 +6328,86 @@ var require_order2 = __commonJS({
5944
6328
  }
5945
6329
  const { id } = ctx.params;
5946
6330
  if (!id) {
5947
- return ctx.badRequest("Order ID is required");
6331
+ return ctx.badRequest("Order ID or order number is required");
5948
6332
  }
5949
- const order = await strapi.db.query("plugin::webbycommerce.order").findOne({
5950
- where: { id },
5951
- populate: ["billing_address", "shipping_address", "items"]
6333
+ const isOrderNumber = id.toString().startsWith("ORD-");
6334
+ const whereClause = {
6335
+ user: user.id
6336
+ // Filter by user ID directly for security
6337
+ };
6338
+ if (isOrderNumber) {
6339
+ whereClause.order_number = id;
6340
+ } else {
6341
+ whereClause.id = id;
6342
+ }
6343
+ let order = await strapi.db.query("plugin::webbycommerce.order").findOne({
6344
+ where: whereClause,
6345
+ populate: ["billing_address", "shipping_address", "items", "user"]
5952
6346
  });
5953
6347
  if (!order) {
5954
6348
  return ctx.notFound("Order not found");
5955
6349
  }
5956
- if (order.user !== user.id) {
5957
- return ctx.forbidden("You can only view your own orders");
5958
- }
6350
+ let formattedItems = [];
6351
+ if (order.items && order.items.length > 0) {
6352
+ const itemIds = order.items.map((item) => typeof item === "object" && item.id ? item.id : item);
6353
+ const products = await strapi.db.query("plugin::webbycommerce.product").findMany({
6354
+ where: {
6355
+ id: { $in: itemIds }
6356
+ },
6357
+ populate: {
6358
+ images: true
6359
+ }
6360
+ });
6361
+ formattedItems = products.map((product) => ({
6362
+ id: product.id,
6363
+ name: product.name,
6364
+ sku: product.sku,
6365
+ price: parseFloat(product.price || 0),
6366
+ sale_price: product.sale_price ? parseFloat(product.sale_price) : null,
6367
+ images: product.images || [],
6368
+ slug: product.slug,
6369
+ description: product.description
6370
+ }));
6371
+ } else if (order.items && Array.isArray(order.items)) {
6372
+ formattedItems = order.items.map((item) => ({
6373
+ id: item.id,
6374
+ name: item.name,
6375
+ sku: item.sku,
6376
+ price: parseFloat(item.price || 0),
6377
+ sale_price: item.sale_price ? parseFloat(item.sale_price) : null,
6378
+ images: item.images || [],
6379
+ slug: item.slug,
6380
+ description: item.description
6381
+ }));
6382
+ }
6383
+ const shippingAmount = parseFloat(order.shipping_amount || 0);
6384
+ const subtotalAmount = parseFloat(order.subtotal || 0);
6385
+ const discountAmount = parseFloat(order.discount_amount || 0);
5959
6386
  ctx.send({
5960
6387
  data: {
5961
6388
  id: order.id,
5962
6389
  order_number: order.order_number,
5963
6390
  status: order.status,
5964
6391
  payment_status: order.payment_status,
5965
- items: order.items,
5966
- subtotal: parseFloat(order.subtotal),
5967
- tax_amount: parseFloat(order.tax_amount),
5968
- shipping_amount: parseFloat(order.shipping_amount),
5969
- discount_amount: parseFloat(order.discount_amount),
5970
- total: parseFloat(order.total),
6392
+ items: formattedItems,
6393
+ items_count: formattedItems.length,
6394
+ subtotal: subtotalAmount,
6395
+ tax_amount: parseFloat(order.tax_amount || 0),
6396
+ shipping: shippingAmount,
6397
+ shipping_amount: shippingAmount,
6398
+ discount: discountAmount,
6399
+ discount_amount: discountAmount,
6400
+ total: parseFloat(order.total || 0),
5971
6401
  currency: order.currency,
5972
6402
  billing_address: order.billing_address,
5973
6403
  shipping_address: order.shipping_address,
5974
- payment_method: order.payment_method,
6404
+ payment_method: (() => {
6405
+ if (!order.payment_method) return "N/A";
6406
+ if (typeof order.payment_method === "object" && order.payment_method !== null) {
6407
+ return order.payment_method.type || order.payment_method.method || order.payment_method.name || String(order.payment_method);
6408
+ }
6409
+ return String(order.payment_method);
6410
+ })(),
5975
6411
  shipping_method: order.shipping_method,
5976
6412
  notes: order.notes,
5977
6413
  tracking_number: order.tracking_number,
@@ -5985,64 +6421,162 @@ var require_order2 = __commonJS({
5985
6421
  ctx.badRequest("Failed to retrieve order");
5986
6422
  }
5987
6423
  },
5988
- // Cancel order (only if pending)
6424
+ // Cancel order (only if pending or processing)
5989
6425
  async cancelOrder(ctx) {
5990
6426
  try {
5991
6427
  const user = ctx.state.user;
5992
6428
  if (!user) {
5993
6429
  return ctx.unauthorized("Authentication required");
5994
6430
  }
5995
- const hasPermission = await ensureEcommercePermission(ctx);
5996
- if (!hasPermission) {
5997
- return;
6431
+ try {
6432
+ const hasPermission = await ensureEcommercePermission(ctx);
6433
+ if (!hasPermission) {
6434
+ return;
6435
+ }
6436
+ } catch (permissionError) {
6437
+ strapi.log.error(`[cancelOrder] Error checking permission:`, permissionError);
6438
+ return ctx.badRequest("Permission check failed");
5998
6439
  }
5999
6440
  const { id } = ctx.params;
6000
6441
  if (!id) {
6001
6442
  return ctx.badRequest("Order ID is required");
6002
6443
  }
6003
- const order = await strapi.db.query("plugin::webbycommerce.order").findOne({
6004
- where: { id }
6005
- });
6444
+ const orderId = typeof id === "string" ? isNaN(id) ? id : parseInt(id, 10) : id;
6445
+ strapi.log.info(`[cancelOrder] Attempting to cancel order ${orderId} (original: ${id}, type: ${typeof id}) for user ${user.id}`);
6446
+ let order;
6447
+ try {
6448
+ order = await strapi.db.query("plugin::webbycommerce.order").findOne({
6449
+ where: {
6450
+ id: orderId,
6451
+ user: user.id
6452
+ // Filter by user ID directly for security
6453
+ },
6454
+ populate: ["items", "user"]
6455
+ });
6456
+ if (!order && orderId !== id) {
6457
+ strapi.log.info(`[cancelOrder] Order not found with normalized ID ${orderId}, trying original ID ${id}`);
6458
+ order = await strapi.db.query("plugin::webbycommerce.order").findOne({
6459
+ where: {
6460
+ id,
6461
+ user: user.id
6462
+ },
6463
+ populate: ["items", "user"]
6464
+ });
6465
+ }
6466
+ } catch (queryError) {
6467
+ strapi.log.error(`[cancelOrder] Error querying order ${orderId}:`, queryError);
6468
+ strapi.log.error(`[cancelOrder] Query error message:`, queryError.message);
6469
+ return ctx.badRequest(`Failed to retrieve order: ${queryError.message || "Database error"}`);
6470
+ }
6006
6471
  if (!order) {
6472
+ strapi.log.warn(`[cancelOrder] Order ${orderId} not found for user ${user.id}`);
6007
6473
  return ctx.notFound("Order not found");
6008
6474
  }
6009
- if (order.user !== user.id) {
6010
- return ctx.forbidden("You can only cancel your own orders");
6475
+ strapi.log.info(`[cancelOrder] Found order ${order.id} (order_number: ${order.order_number}) with status: ${order.status || "null/undefined"}`);
6476
+ const orderStatus = order.status || "";
6477
+ const cancellableStatuses = ["pending", "processing"];
6478
+ if (!orderStatus) {
6479
+ strapi.log.warn(`[cancelOrder] Order ${order.id} has no status`);
6480
+ return ctx.badRequest("Order status is invalid");
6011
6481
  }
6012
- if (order.status !== "pending") {
6013
- return ctx.badRequest("Only pending orders can be cancelled");
6482
+ if (!cancellableStatuses.includes(orderStatus)) {
6483
+ if (orderStatus === "cancelled") {
6484
+ return ctx.badRequest("Order is already cancelled");
6485
+ }
6486
+ if (orderStatus === "delivered") {
6487
+ return ctx.badRequest("Delivered orders cannot be cancelled");
6488
+ }
6489
+ if (orderStatus === "shipped") {
6490
+ return ctx.badRequest("Shipped orders cannot be cancelled. Please contact support for returns.");
6491
+ }
6492
+ return ctx.badRequest(`Order with status '${orderStatus}' cannot be cancelled`);
6014
6493
  }
6015
- const updatedOrder = await strapi.db.query("plugin::webbycommerce.order").update({
6016
- where: { id },
6017
- data: { status: "cancelled" }
6018
- });
6019
- for (const item of order.items) {
6020
- const product = await strapi.db.query("plugin::webbycommerce.product").findOne({
6021
- where: { id: item.product_id }
6494
+ let updatedOrder;
6495
+ try {
6496
+ updatedOrder = await strapi.db.query("plugin::webbycommerce.order").update({
6497
+ where: { id: order.id },
6498
+ data: { status: "cancelled" }
6022
6499
  });
6023
- if (product) {
6024
- const newStockQuantity = product.stock_quantity + item.quantity;
6025
- const newStockStatus = newStockQuantity > 0 ? "in_stock" : "out_of_stock";
6026
- await strapi.db.query("plugin::webbycommerce.product").update({
6027
- where: { id: item.product_id },
6028
- data: {
6029
- stock_quantity: newStockQuantity,
6030
- stock_status: newStockStatus
6500
+ if (!updatedOrder) {
6501
+ strapi.log.error(`[cancelOrder] Failed to update order ${order.id} status - update returned null`);
6502
+ return ctx.badRequest("Failed to update order status");
6503
+ }
6504
+ strapi.log.info(`[cancelOrder] Successfully updated order ${order.id} status to cancelled`);
6505
+ } catch (updateError) {
6506
+ strapi.log.error(`[cancelOrder] Error updating order ${order.id}:`, updateError);
6507
+ strapi.log.error(`[cancelOrder] Update error message:`, updateError.message);
6508
+ strapi.log.error(`[cancelOrder] Update error stack:`, updateError.stack);
6509
+ return ctx.badRequest(`Failed to update order status: ${updateError.message || "Unknown error"}`);
6510
+ }
6511
+ if (order.items && Array.isArray(order.items) && order.items.length > 0) {
6512
+ try {
6513
+ for (const item of order.items) {
6514
+ const productId = typeof item === "object" && item.id ? item.id : item;
6515
+ if (!productId) {
6516
+ strapi.log.warn(`[cancelOrder] Skipping item with invalid ID for order ${order.id}`);
6517
+ continue;
6031
6518
  }
6032
- });
6519
+ try {
6520
+ const product = await strapi.db.query("plugin::webbycommerce.product").findOne({
6521
+ where: { id: productId }
6522
+ });
6523
+ if (product) {
6524
+ const quantityToRestore = 1;
6525
+ const newStockQuantity = (product.stock_quantity || 0) + quantityToRestore;
6526
+ const newStockStatus = newStockQuantity > 0 ? "in_stock" : "out_of_stock";
6527
+ await strapi.db.query("plugin::webbycommerce.product").update({
6528
+ where: { id: productId },
6529
+ data: {
6530
+ stock_quantity: newStockQuantity,
6531
+ stock_status: newStockStatus
6532
+ }
6533
+ });
6534
+ strapi.log.info(`[cancelOrder] Restored ${quantityToRestore} unit(s) of product ${productId} for order ${order.id}`);
6535
+ } else {
6536
+ strapi.log.warn(`[cancelOrder] Product ${productId} not found for order ${order.id}`);
6537
+ }
6538
+ } catch (productError) {
6539
+ strapi.log.error(`[cancelOrder] Error processing product ${productId} for order ${order.id}:`, productError);
6540
+ }
6541
+ }
6542
+ } catch (stockError) {
6543
+ strapi.log.error(`[cancelOrder] Error restoring stock for order ${order.id}:`, stockError);
6033
6544
  }
6034
6545
  }
6035
- ctx.send({
6036
- data: {
6037
- id: updatedOrder.id,
6038
- order_number: updatedOrder.order_number,
6039
- status: updatedOrder.status
6040
- },
6041
- message: "Order cancelled successfully"
6042
- });
6546
+ try {
6547
+ return ctx.send({
6548
+ data: {
6549
+ id: updatedOrder.id,
6550
+ order_number: updatedOrder.order_number,
6551
+ status: updatedOrder.status
6552
+ },
6553
+ message: "Order cancelled successfully"
6554
+ });
6555
+ } catch (sendError) {
6556
+ strapi.log.error(`[cancelOrder] Error sending response for order ${order.id}:`, sendError);
6557
+ return ctx.send({
6558
+ data: {
6559
+ id: updatedOrder.id,
6560
+ order_number: updatedOrder.order_number,
6561
+ status: updatedOrder.status
6562
+ },
6563
+ message: "Order cancelled successfully"
6564
+ });
6565
+ }
6043
6566
  } catch (error) {
6044
- strapi.log.error("Cancel order error:", error);
6045
- ctx.badRequest("Failed to cancel order");
6567
+ strapi.log.error(`[cancelOrder] Unexpected error cancelling order:`, error);
6568
+ strapi.log.error(`[cancelOrder] Error name:`, error.name);
6569
+ strapi.log.error(`[cancelOrder] Error message:`, error.message);
6570
+ strapi.log.error(`[cancelOrder] Error stack:`, error.stack);
6571
+ const errorMessage = error.message || "Unknown error occurred";
6572
+ const errorDetails = error.details ? JSON.stringify(error.details) : "";
6573
+ strapi.log.error(`[cancelOrder] Full error details:`, {
6574
+ name: error.name,
6575
+ message: errorMessage,
6576
+ details: errorDetails,
6577
+ stack: error.stack
6578
+ });
6579
+ return ctx.badRequest(`Failed to cancel order: ${errorMessage}${errorDetails ? ` - ${errorDetails}` : ""}`);
6046
6580
  }
6047
6581
  },
6048
6582
  // Update order status (admin only)
@@ -6135,15 +6669,16 @@ var require_order2 = __commonJS({
6135
6669
  return ctx.badRequest("Order ID is required");
6136
6670
  }
6137
6671
  const order = await strapi.db.query("plugin::webbycommerce.order").findOne({
6138
- where: { id },
6139
- populate: ["shipping_address"]
6672
+ where: {
6673
+ id,
6674
+ user: user.id
6675
+ // Filter by user ID directly for security
6676
+ },
6677
+ populate: ["shipping_address", "user"]
6140
6678
  });
6141
6679
  if (!order) {
6142
6680
  return ctx.notFound("Order not found");
6143
6681
  }
6144
- if (order.user !== user.id) {
6145
- return ctx.forbidden("You can only view your own order tracking");
6146
- }
6147
6682
  const trackingTimeline = this.generateTrackingTimeline(order);
6148
6683
  ctx.send({
6149
6684
  data: {
@@ -6172,87 +6707,142 @@ var require_order2 = __commonJS({
6172
6707
  },
6173
6708
  // Send order confirmation email
6174
6709
  async sendOrderConfirmationEmail(user, order) {
6175
- const settings = await strapi.store({ type: "plugin", name: "webbycommerce" }).get({ key: "settings" });
6176
- const smtpSettings = settings?.smtp;
6177
- if (!smtpSettings) {
6178
- strapi.log.warn("SMTP settings not configured, skipping order confirmation email");
6179
- return;
6180
- }
6181
- const emailData = {
6182
- to: user.email,
6183
- subject: `Order Confirmation - ${order.order_number}`,
6184
- html: `
6185
- <h2>Order Confirmation</h2>
6186
- <p>Dear ${user.username || "Customer"},</p>
6187
- <p>Thank you for your order! Here are the details:</p>
6188
- <h3>Order #${order.order_number}</h3>
6189
- <p><strong>Total: $${order.total} ${order.currency}</strong></p>
6190
- <p><strong>Status:</strong> ${order.status}</p>
6191
- <p><strong>Items:</strong></p>
6192
- <ul>
6193
- ${order.items.map((item) => `<li>${item.product_name} (x${item.quantity}) - $${item.total_price}</li>`).join("")}
6194
- </ul>
6195
- <p>We will process your order shortly.</p>
6196
- <p>Best regards,<br>Your Ecommerce Team</p>
6197
- `
6198
- };
6199
- await sendEmail(emailData);
6710
+ try {
6711
+ const settings = await strapi.store({ type: "plugin", name: "webbycommerce" }).get({ key: "settings" });
6712
+ const smtpSettings = settings?.smtp;
6713
+ if (!smtpSettings) {
6714
+ strapi.log.warn("SMTP settings not configured, skipping order confirmation email");
6715
+ return;
6716
+ }
6717
+ let orderWithItems = order;
6718
+ if (!order.items || !Array.isArray(order.items) || order.items.length === 0) {
6719
+ orderWithItems = await strapi.db.query("plugin::webbycommerce.order").findOne({
6720
+ where: { id: order.id },
6721
+ populate: ["items"]
6722
+ });
6723
+ }
6724
+ let itemsHtml = "<li>No items found</li>";
6725
+ if (orderWithItems && orderWithItems.items && Array.isArray(orderWithItems.items) && orderWithItems.items.length > 0) {
6726
+ itemsHtml = orderWithItems.items.map((item) => {
6727
+ if (!item) return "";
6728
+ const productName = item.name || item.product_name || "Unknown Product";
6729
+ const productPrice = item.price || item.product_price || 0;
6730
+ return `<li>${productName} - $${parseFloat(productPrice).toFixed(2)}</li>`;
6731
+ }).filter((item) => item !== "").join("");
6732
+ if (!itemsHtml) {
6733
+ itemsHtml = "<li>No items found</li>";
6734
+ }
6735
+ }
6736
+ const emailData = {
6737
+ to: user.email,
6738
+ subject: `Order Confirmation - ${order.order_number || orderWithItems?.order_number || "N/A"}`,
6739
+ html: `
6740
+ <h2>Order Confirmation</h2>
6741
+ <p>Dear ${user.username || "Customer"},</p>
6742
+ <p>Thank you for your order! Here are the details:</p>
6743
+ <h3>Order #${order.order_number || orderWithItems?.order_number || "N/A"}</h3>
6744
+ <p><strong>Total: $${order.total || orderWithItems?.total || 0} ${order.currency || orderWithItems?.currency || "USD"}</strong></p>
6745
+ <p><strong>Status:</strong> ${order.status || orderWithItems?.status || "pending"}</p>
6746
+ <p><strong>Items:</strong></p>
6747
+ <ul>
6748
+ ${itemsHtml}
6749
+ </ul>
6750
+ <p>We will process your order shortly.</p>
6751
+ <p>Best regards,<br>Your Ecommerce Team</p>
6752
+ `
6753
+ };
6754
+ await sendEmail(emailData);
6755
+ strapi.log.info(`Order confirmation email sent successfully for order ${order.id || orderWithItems?.id}`);
6756
+ } catch (error) {
6757
+ strapi.log.error("Error sending order confirmation email:", error);
6758
+ strapi.log.error("Email error details:", {
6759
+ message: error.message,
6760
+ stack: error.stack,
6761
+ orderId: order?.id,
6762
+ orderNumber: order?.order_number
6763
+ });
6764
+ }
6200
6765
  },
6201
6766
  // Send order status update email
6202
6767
  async sendOrderStatusUpdateEmail(user, order, newStatus) {
6203
- const settings = await strapi.store({ type: "plugin", name: "webbycommerce" }).get({ key: "settings" });
6204
- const smtpSettings = settings?.smtp;
6205
- if (!smtpSettings) {
6206
- strapi.log.warn("SMTP settings not configured, skipping order status update email");
6207
- return;
6768
+ try {
6769
+ const settings = await strapi.store({ type: "plugin", name: "webbycommerce" }).get({ key: "settings" });
6770
+ const smtpSettings = settings?.smtp;
6771
+ if (!smtpSettings) {
6772
+ strapi.log.warn("SMTP settings not configured, skipping order status update email");
6773
+ return;
6774
+ }
6775
+ const statusMessages = {
6776
+ pending: "Your order is being prepared",
6777
+ processing: "Your order is now being processed",
6778
+ shipped: "Your order has been shipped",
6779
+ delivered: "Your order has been delivered successfully",
6780
+ cancelled: "Your order has been cancelled",
6781
+ refunded: "Your order has been refunded"
6782
+ };
6783
+ const emailData = {
6784
+ to: user.email,
6785
+ subject: `Order Status Update - ${order.order_number || "N/A"}`,
6786
+ html: `
6787
+ <h2>Order Status Update</h2>
6788
+ <p>Dear ${user.username || "Customer"},</p>
6789
+ <p>Your order status has been updated:</p>
6790
+ <h3>Order #${order.order_number || "N/A"}</h3>
6791
+ <p><strong>Status: ${newStatus ? newStatus.toUpperCase() : "UPDATED"}</strong></p>
6792
+ <p><strong>Message:</strong> ${statusMessages[newStatus] || "Status updated"}</p>
6793
+ ${order.tracking_number ? `<p><strong>Tracking Number:</strong> ${order.tracking_number}</p>` : ""}
6794
+ ${order.estimated_delivery ? `<p><strong>Estimated Delivery:</strong> ${new Date(order.estimated_delivery).toLocaleDateString()}</p>` : ""}
6795
+ <p>You can track your order at any time using our order tracking feature.</p>
6796
+ <p>Best regards,<br>Your Ecommerce Team</p>
6797
+ `
6798
+ };
6799
+ await sendEmail(emailData);
6800
+ strapi.log.info(`Order status update email sent successfully for order ${order.id}`);
6801
+ } catch (error) {
6802
+ strapi.log.error("Error sending order status update email:", error);
6803
+ strapi.log.error("Email error details:", {
6804
+ message: error.message,
6805
+ stack: error.stack,
6806
+ orderId: order?.id,
6807
+ orderNumber: order?.order_number,
6808
+ newStatus
6809
+ });
6208
6810
  }
6209
- const statusMessages = {
6210
- pending: "Your order is being prepared",
6211
- processing: "Your order is now being processed",
6212
- shipped: "Your order has been shipped",
6213
- delivered: "Your order has been delivered successfully",
6214
- cancelled: "Your order has been cancelled",
6215
- refunded: "Your order has been refunded"
6216
- };
6217
- const emailData = {
6218
- to: user.email,
6219
- subject: `Order Status Update - ${order.order_number}`,
6220
- html: `
6221
- <h2>Order Status Update</h2>
6222
- <p>Dear ${user.username || "Customer"},</p>
6223
- <p>Your order status has been updated:</p>
6224
- <h3>Order #${order.order_number}</h3>
6225
- <p><strong>Status: ${newStatus.toUpperCase()}</strong></p>
6226
- <p><strong>Message:</strong> ${statusMessages[newStatus] || "Status updated"}</p>
6227
- ${order.tracking_number ? `<p><strong>Tracking Number:</strong> ${order.tracking_number}</p>` : ""}
6228
- ${order.estimated_delivery ? `<p><strong>Estimated Delivery:</strong> ${new Date(order.estimated_delivery).toLocaleDateString()}</p>` : ""}
6229
- <p>You can track your order at any time using our order tracking feature.</p>
6230
- <p>Best regards,<br>Your Ecommerce Team</p>
6231
- `
6232
- };
6233
- await sendEmail(emailData);
6234
6811
  },
6235
6812
  // Restore stock when order is cancelled
6236
6813
  async restoreOrderStock(order) {
6237
6814
  try {
6815
+ if (!order.items || !Array.isArray(order.items) || order.items.length === 0) {
6816
+ strapi.log.warn(`[restoreOrderStock] No items found for order ${order.id}`);
6817
+ return;
6818
+ }
6238
6819
  for (const item of order.items) {
6820
+ const productId = typeof item === "object" && item.id ? item.id : item;
6821
+ if (!productId) {
6822
+ strapi.log.warn(`[restoreOrderStock] Skipping item with invalid ID for order ${order.id}`);
6823
+ continue;
6824
+ }
6239
6825
  const product = await strapi.db.query("plugin::webbycommerce.product").findOne({
6240
- where: { id: item.product_id }
6826
+ where: { id: productId }
6241
6827
  });
6242
6828
  if (product) {
6243
- const newStockQuantity = product.stock_quantity + item.quantity;
6829
+ const quantityToRestore = 1;
6830
+ const newStockQuantity = (product.stock_quantity || 0) + quantityToRestore;
6244
6831
  const newStockStatus = newStockQuantity > 0 ? "in_stock" : "out_of_stock";
6245
6832
  await strapi.db.query("plugin::webbycommerce.product").update({
6246
- where: { id: item.product_id },
6833
+ where: { id: productId },
6247
6834
  data: {
6248
6835
  stock_quantity: newStockQuantity,
6249
6836
  stock_status: newStockStatus
6250
6837
  }
6251
6838
  });
6839
+ strapi.log.info(`[restoreOrderStock] Restored ${quantityToRestore} unit(s) of product ${productId} for order ${order.id}`);
6840
+ } else {
6841
+ strapi.log.warn(`[restoreOrderStock] Product ${productId} not found for order ${order.id}`);
6252
6842
  }
6253
6843
  }
6254
6844
  } catch (error) {
6255
- strapi.log.error("Failed to restore order stock:", error);
6845
+ strapi.log.error(`[restoreOrderStock] Failed to restore order stock for order ${order.id}:`, error);
6256
6846
  }
6257
6847
  },
6258
6848
  // Generate tracking timeline based on order status
@@ -7744,7 +8334,7 @@ var require_cart2 = __commonJS({
7744
8334
  const guestId = user ? null : getGuestIdFromRequest(ctx) || randomUUID();
7745
8335
  const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
7746
8336
  const items = await cartService.getCartItems({ cartId: cart.id });
7747
- const totals = await cartService.getTotalsFromItems(items);
8337
+ const totals = await cartService.getTotalsFromItems(items, cart.coupon);
7748
8338
  ctx.send({
7749
8339
  data: {
7750
8340
  cart: {
@@ -7779,7 +8369,11 @@ var require_cart2 = __commonJS({
7779
8369
  const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
7780
8370
  await cartService.addOrUpdateItem({ cartId: cart.id, userId: user?.id, productId, quantity });
7781
8371
  const items = await cartService.getCartItems({ cartId: cart.id });
7782
- const totals = await cartService.getTotalsFromItems(items);
8372
+ const updatedCart = await strapi.db.query("plugin::webbycommerce.cart").findOne({
8373
+ where: { id: cart.id },
8374
+ populate: { coupon: true }
8375
+ });
8376
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
7783
8377
  ctx.send({
7784
8378
  data: {
7785
8379
  cart: {
@@ -7812,7 +8406,11 @@ var require_cart2 = __commonJS({
7812
8406
  const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
7813
8407
  await cartService.updateItemQuantity({ cartId: cart.id, userId: user?.id, cartItemId: id, quantity });
7814
8408
  const items = await cartService.getCartItems({ cartId: cart.id });
7815
- const totals = await cartService.getTotalsFromItems(items);
8409
+ const updatedCart = await strapi.db.query("plugin::webbycommerce.cart").findOne({
8410
+ where: { id: cart.id },
8411
+ populate: { coupon: true }
8412
+ });
8413
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
7816
8414
  ctx.send({
7817
8415
  data: {
7818
8416
  cart: {
@@ -7844,7 +8442,11 @@ var require_cart2 = __commonJS({
7844
8442
  const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
7845
8443
  await cartService.removeItem({ cartId: cart.id, cartItemId: id });
7846
8444
  const items = await cartService.getCartItems({ cartId: cart.id });
7847
- const totals = await cartService.getTotalsFromItems(items);
8445
+ const updatedCart = await strapi.db.query("plugin::webbycommerce.cart").findOne({
8446
+ where: { id: cart.id },
8447
+ populate: { coupon: true }
8448
+ });
8449
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
7848
8450
  ctx.send({
7849
8451
  data: {
7850
8452
  cart: {
@@ -7901,7 +8503,7 @@ var require_cart2 = __commonJS({
7901
8503
  if (!user && !guestId) return ctx.badRequest("guest_id is required for guest cart");
7902
8504
  const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
7903
8505
  const items = await cartService.getCartItems({ cartId: cart.id });
7904
- const totals = await cartService.getTotalsFromItems(items);
8506
+ const totals = await cartService.getTotalsFromItems(items, cart.coupon);
7905
8507
  ctx.send({
7906
8508
  data: {
7907
8509
  cart: {
@@ -7918,10 +8520,81 @@ var require_cart2 = __commonJS({
7918
8520
  }
7919
8521
  },
7920
8522
  async applyCoupon(ctx) {
7921
- ctx.badRequest("Coupon support is not implemented yet");
8523
+ try {
8524
+ const user = ctx.state.user;
8525
+ const hasPermission = await ensureEcommercePermission(ctx);
8526
+ if (!hasPermission) return;
8527
+ const { coupon_code } = ctx.request.body || {};
8528
+ if (!coupon_code) {
8529
+ return ctx.badRequest("Coupon code is required");
8530
+ }
8531
+ const cartService = strapi.plugin("webbycommerce").service("cart");
8532
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
8533
+ if (!user && !guestId) return ctx.badRequest("guest_id is required for guest cart");
8534
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
8535
+ const coupon = await cartService.validateAndApplyCoupon({
8536
+ cartId: cart.id,
8537
+ couponCode: coupon_code
8538
+ });
8539
+ const items = await cartService.getCartItems({ cartId: cart.id });
8540
+ const updatedCart = await strapi.db.query("plugin::webbycommerce.cart").findOne({
8541
+ where: { id: cart.id },
8542
+ populate: { coupon: true }
8543
+ });
8544
+ const totals = await cartService.getTotalsFromItems(items, updatedCart?.coupon);
8545
+ ctx.send({
8546
+ data: {
8547
+ cart: {
8548
+ id: cart.id,
8549
+ guest_id: cart.guest_id || guestId || null,
8550
+ currency: cart.currency || "USD"
8551
+ },
8552
+ coupon: {
8553
+ code: coupon.code,
8554
+ type: coupon.type,
8555
+ value: coupon.value,
8556
+ discount_amount: totals.discount
8557
+ },
8558
+ items,
8559
+ totals
8560
+ },
8561
+ message: "Coupon applied successfully"
8562
+ });
8563
+ } catch (error) {
8564
+ strapi.log.error("Error applying coupon:", error);
8565
+ const status = error?.status || 400;
8566
+ if (status === 404) return ctx.notFound(error.message);
8567
+ return ctx.badRequest(error.message || "Invalid coupon code");
8568
+ }
7922
8569
  },
7923
8570
  async removeCoupon(ctx) {
7924
- ctx.badRequest("Coupon support is not implemented yet");
8571
+ try {
8572
+ const user = ctx.state.user;
8573
+ const hasPermission = await ensureEcommercePermission(ctx);
8574
+ if (!hasPermission) return;
8575
+ const cartService = strapi.plugin("webbycommerce").service("cart");
8576
+ const guestId = user ? null : getGuestIdFromRequest(ctx);
8577
+ if (!user && !guestId) return ctx.badRequest("guest_id is required for guest cart");
8578
+ const cart = await cartService.getOrCreateCart({ userId: user?.id, guestId });
8579
+ await cartService.removeCoupon({ cartId: cart.id });
8580
+ const items = await cartService.getCartItems({ cartId: cart.id });
8581
+ const totals = await cartService.getTotalsFromItems(items, null);
8582
+ ctx.send({
8583
+ data: {
8584
+ cart: {
8585
+ id: cart.id,
8586
+ guest_id: cart.guest_id || guestId || null,
8587
+ currency: cart.currency || "USD"
8588
+ },
8589
+ items,
8590
+ totals
8591
+ },
8592
+ message: "Coupon removed successfully"
8593
+ });
8594
+ } catch (error) {
8595
+ strapi.log.error("Error removing coupon:", error);
8596
+ ctx.badRequest("Failed to remove coupon", { error: error.message });
8597
+ }
7925
8598
  },
7926
8599
  async checkout(ctx) {
7927
8600
  const orderController = strapi.plugin("webbycommerce").controller("order");
@@ -9659,24 +10332,29 @@ var require_compare3 = __commonJS({
9659
10332
  module2.exports = createCoreService("plugin::webbycommerce.compare", ({ strapi: strapi2 }) => ({
9660
10333
  async findUserCompare(userId) {
9661
10334
  try {
9662
- const compare = await strapi2.entityService.findMany("plugin::webbycommerce.compare", {
9663
- filters: {
9664
- userId
10335
+ const compares = await strapi2.db.query("plugin::webbycommerce.compare").findMany({
10336
+ where: {
10337
+ userId: String(userId)
9665
10338
  },
10339
+ orderBy: { createdAt: "desc" },
9666
10340
  populate: {
9667
10341
  products: {
9668
10342
  populate: {
9669
10343
  images: true,
9670
10344
  product_categories: true,
9671
10345
  tags: true,
9672
- variations: true
10346
+ variations: {
10347
+ populate: {
10348
+ attributes: true,
10349
+ attributeValues: true
10350
+ }
10351
+ }
9673
10352
  }
9674
10353
  },
9675
10354
  category: true
9676
- },
9677
- sort: { createdAt: "desc" }
10355
+ }
9678
10356
  });
9679
- return compare.length > 0 ? compare[0] : null;
10357
+ return compares.length > 0 ? compares[0] : null;
9680
10358
  } catch (error) {
9681
10359
  throw new Error(`Failed to find user compare: ${error.message}`);
9682
10360
  }
@@ -9692,10 +10370,10 @@ var require_compare3 = __commonJS({
9692
10370
  notes: data.notes || null,
9693
10371
  category: data.categoryId || null
9694
10372
  };
9695
- const compare = await strapi2.entityService.create("plugin::webbycommerce.compare", {
10373
+ await strapi2.entityService.create("plugin::webbycommerce.compare", {
9696
10374
  data: compareData
9697
10375
  });
9698
- return compare;
10376
+ return await this.findUserCompare(userId);
9699
10377
  } catch (error) {
9700
10378
  throw new Error(`Failed to create user compare: ${error.message}`);
9701
10379
  }
@@ -9712,7 +10390,10 @@ var require_compare3 = __commonJS({
9712
10390
  if (compare.products.length >= 4) {
9713
10391
  throw new Error("Compare list is full. Maximum 4 products allowed.");
9714
10392
  }
9715
- const productExists = compare.products.some((product2) => product2.id === parseInt(productId));
10393
+ const productExists = compare.products.some((product2) => {
10394
+ const productIdValue = typeof product2 === "object" && product2 !== null ? product2.id : product2;
10395
+ return productIdValue === parseInt(productId);
10396
+ });
9716
10397
  if (productExists) {
9717
10398
  throw new Error("Product already exists in compare list");
9718
10399
  }
@@ -9725,13 +10406,16 @@ var require_compare3 = __commonJS({
9725
10406
  if (compare.category && product.product_categories && product.product_categories.length > 0 && compare.category.id !== product.product_categories[0].id) {
9726
10407
  strapi2.log.warn(`Adding product from different category to compare list. Compare category: ${compare.category.name}, Product category: ${product.product_categories[0].name}`);
9727
10408
  }
9728
- const updatedCompare = await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
10409
+ const existingProductIds = compare.products.map((p) => {
10410
+ return typeof p === "object" && p !== null ? p.id : p;
10411
+ }).filter((id) => id != null);
10412
+ await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
9729
10413
  data: {
9730
- products: { set: [...compare.products.map((p) => p.id), parseInt(productId, 10)].map((id) => ({ id })) },
10414
+ products: { set: [...existingProductIds, parseInt(productId, 10)].map((id) => ({ id })) },
9731
10415
  category: compare.category || product.product_categories?.[0]?.id || null
9732
10416
  }
9733
10417
  });
9734
- return updatedCompare;
10418
+ return await this.findUserCompare(userId);
9735
10419
  } catch (error) {
9736
10420
  throw new Error(`Failed to add product to compare: ${error.message}`);
9737
10421
  }
@@ -9745,15 +10429,20 @@ var require_compare3 = __commonJS({
9745
10429
  if (!compare.products) {
9746
10430
  compare.products = [];
9747
10431
  }
9748
- const updatedProducts = compare.products.filter((product) => product.id !== parseInt(productId)).map((product) => product.id);
9749
- const updatedCompare = await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
10432
+ const updatedProducts = compare.products.filter((product) => {
10433
+ const productIdValue = typeof product === "object" && product !== null ? product.id : product;
10434
+ return productIdValue !== parseInt(productId);
10435
+ }).map((product) => {
10436
+ return typeof product === "object" && product !== null ? product.id : product;
10437
+ });
10438
+ await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
9750
10439
  data: {
9751
10440
  products: { set: updatedProducts.map((id) => ({ id })) },
9752
10441
  // Reset category if no products left
9753
10442
  category: updatedProducts.length === 0 ? null : compare.category
9754
10443
  }
9755
10444
  });
9756
- return updatedCompare;
10445
+ return await this.findUserCompare(userId);
9757
10446
  } catch (error) {
9758
10447
  throw new Error(`Failed to remove product from compare: ${error.message}`);
9759
10448
  }
@@ -9764,13 +10453,13 @@ var require_compare3 = __commonJS({
9764
10453
  if (!compare) {
9765
10454
  throw new Error("Compare list not found");
9766
10455
  }
9767
- const updatedCompare = await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
10456
+ await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
9768
10457
  data: {
9769
10458
  products: { set: [] },
9770
10459
  category: null
9771
10460
  }
9772
10461
  });
9773
- return updatedCompare;
10462
+ return await this.findUserCompare(userId);
9774
10463
  } catch (error) {
9775
10464
  throw new Error(`Failed to clear compare list: ${error.message}`);
9776
10465
  }
@@ -9781,14 +10470,14 @@ var require_compare3 = __commonJS({
9781
10470
  if (!compare) {
9782
10471
  throw new Error("Compare list not found");
9783
10472
  }
9784
- const updatedCompare = await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
10473
+ await strapi2.entityService.update("plugin::webbycommerce.compare", compare.id, {
9785
10474
  data: {
9786
10475
  name: data.name !== void 0 ? data.name : compare.name,
9787
10476
  notes: data.notes !== void 0 ? data.notes : compare.notes,
9788
10477
  isPublic: data.isPublic !== void 0 ? data.isPublic : compare.isPublic
9789
10478
  }
9790
10479
  });
9791
- return updatedCompare;
10480
+ return await this.findUserCompare(userId);
9792
10481
  } catch (error) {
9793
10482
  throw new Error(`Failed to update compare list: ${error.message}`);
9794
10483
  }
@@ -9872,6 +10561,7 @@ var require_cart3 = __commonJS({
9872
10561
  var CART_UID = "plugin::webbycommerce.cart";
9873
10562
  var CART_ITEM_UID = "plugin::webbycommerce.cart-item";
9874
10563
  var PRODUCT_UID = "plugin::webbycommerce.product";
10564
+ var COUPON_UID = "plugin::webbycommerce.coupon";
9875
10565
  var asInt = (value) => {
9876
10566
  const n = Number.parseInt(String(value), 10);
9877
10567
  return Number.isFinite(n) ? n : null;
@@ -9883,41 +10573,65 @@ var require_cart3 = __commonJS({
9883
10573
  };
9884
10574
  module2.exports = createCoreService(CART_ITEM_UID, ({ strapi: strapi2 }) => ({
9885
10575
  async getOrCreateCart({ userId, guestId }) {
10576
+ let cart;
9886
10577
  if (userId) {
9887
- const existing = await strapi2.db.query(CART_UID).findOne({
10578
+ cart = await strapi2.db.query(CART_UID).findOne({
9888
10579
  where: { user: userId },
9889
10580
  select: ["id", "guest_id", "currency"]
9890
10581
  });
9891
- if (existing?.id) return existing;
9892
- return await strapi2.db.query(CART_UID).create({
10582
+ if (cart?.id) {
10583
+ const cartWithCoupon = await strapi2.db.query(CART_UID).findOne({
10584
+ where: { id: cart.id },
10585
+ populate: { coupon: true }
10586
+ });
10587
+ return cartWithCoupon || cart;
10588
+ }
10589
+ cart = await strapi2.db.query(CART_UID).create({
9893
10590
  data: {
9894
10591
  user: userId,
9895
10592
  currency: "USD"
9896
10593
  },
9897
10594
  select: ["id", "guest_id", "currency"]
9898
10595
  });
10596
+ return await strapi2.db.query(CART_UID).findOne({
10597
+ where: { id: cart.id },
10598
+ populate: { coupon: true }
10599
+ });
9899
10600
  }
9900
10601
  if (guestId) {
9901
- const existing = await strapi2.db.query(CART_UID).findOne({
10602
+ cart = await strapi2.db.query(CART_UID).findOne({
9902
10603
  where: { guest_id: String(guestId) },
9903
10604
  select: ["id", "guest_id", "currency"]
9904
10605
  });
9905
- if (existing?.id) return existing;
9906
- return await strapi2.db.query(CART_UID).create({
10606
+ if (cart?.id) {
10607
+ const cartWithCoupon = await strapi2.db.query(CART_UID).findOne({
10608
+ where: { id: cart.id },
10609
+ populate: { coupon: true }
10610
+ });
10611
+ return cartWithCoupon || cart;
10612
+ }
10613
+ cart = await strapi2.db.query(CART_UID).create({
9907
10614
  data: {
9908
10615
  guest_id: String(guestId),
9909
10616
  currency: "USD"
9910
10617
  },
9911
10618
  select: ["id", "guest_id", "currency"]
9912
10619
  });
10620
+ return await strapi2.db.query(CART_UID).findOne({
10621
+ where: { id: cart.id },
10622
+ populate: { coupon: true }
10623
+ });
9913
10624
  }
9914
- const created = await strapi2.db.query(CART_UID).create({
10625
+ cart = await strapi2.db.query(CART_UID).create({
9915
10626
  data: {
9916
10627
  currency: "USD"
9917
10628
  },
9918
10629
  select: ["id", "guest_id", "currency"]
9919
10630
  });
9920
- return created;
10631
+ return await strapi2.db.query(CART_UID).findOne({
10632
+ where: { id: cart.id },
10633
+ populate: { coupon: true }
10634
+ });
9921
10635
  },
9922
10636
  async getCartItems({ cartId }) {
9923
10637
  return await strapi2.db.query(CART_ITEM_UID).findMany({
@@ -9931,13 +10645,23 @@ var require_cart3 = __commonJS({
9931
10645
  }
9932
10646
  });
9933
10647
  },
9934
- async getTotalsFromItems(items) {
10648
+ async getTotalsFromItems(items, coupon = null) {
9935
10649
  const safe = Array.isArray(items) ? items : [];
9936
10650
  const totalItems = safe.reduce((sum, it) => sum + (Number(it.quantity) || 0), 0);
9937
10651
  const subtotal = safe.reduce((sum, it) => sum + (Number(it.total_price) || 0), 0);
10652
+ let discount = 0;
10653
+ if (coupon) {
10654
+ if (coupon.type === "percentage") {
10655
+ discount = subtotal * Number(coupon.value || 0) / 100;
10656
+ } else if (coupon.type === "fixed") {
10657
+ discount = Number(coupon.value || 0);
10658
+ if (discount > subtotal) {
10659
+ discount = subtotal;
10660
+ }
10661
+ }
10662
+ }
9938
10663
  const tax = 0;
9939
10664
  const shipping = 0;
9940
- const discount = 0;
9941
10665
  const total = subtotal + tax + shipping - discount;
9942
10666
  return {
9943
10667
  totalItems,
@@ -10087,6 +10811,157 @@ var require_cart3 = __commonJS({
10087
10811
  where: { cart: cartId }
10088
10812
  });
10089
10813
  return true;
10814
+ },
10815
+ async validateAndApplyCoupon({ cartId, couponCode }) {
10816
+ if (!couponCode || typeof couponCode !== "string" || !couponCode.trim()) {
10817
+ const err = new Error("Coupon code is required");
10818
+ err.status = 400;
10819
+ throw err;
10820
+ }
10821
+ const normalizedCode = couponCode.trim();
10822
+ strapi2.log.info(`[webbycommerce] Looking for coupon code: "${normalizedCode}"`);
10823
+ let coupon = null;
10824
+ try {
10825
+ const coupons = await strapi2.entityService.findMany(COUPON_UID, {
10826
+ filters: {
10827
+ code: normalizedCode
10828
+ }
10829
+ });
10830
+ strapi2.log.debug(`[webbycommerce] entityService found ${coupons?.length || 0} coupons with exact match`);
10831
+ if (coupons && Array.isArray(coupons) && coupons.length > 0) {
10832
+ coupon = coupons[0];
10833
+ strapi2.log.debug(`[webbycommerce] Found via entityService: "${coupon.code}"`);
10834
+ }
10835
+ } catch (entityServiceError) {
10836
+ strapi2.log.warn(`[webbycommerce] entityService query failed:`, entityServiceError.message);
10837
+ }
10838
+ if (!coupon) {
10839
+ try {
10840
+ coupon = await strapi2.db.query(COUPON_UID).findOne({
10841
+ where: { code: normalizedCode }
10842
+ });
10843
+ if (coupon) {
10844
+ strapi2.log.debug(`[webbycommerce] Found via db.query: "${coupon.code}"`);
10845
+ } else {
10846
+ strapi2.log.debug(`[webbycommerce] db.query exact match returned null`);
10847
+ }
10848
+ } catch (dbError) {
10849
+ strapi2.log.warn(`[webbycommerce] db.query exact match failed:`, dbError.message);
10850
+ }
10851
+ }
10852
+ if (!coupon) {
10853
+ try {
10854
+ coupon = await strapi2.db.query(COUPON_UID).findOne({
10855
+ where: { code: normalizedCode.toUpperCase() }
10856
+ });
10857
+ if (coupon) {
10858
+ strapi2.log.debug(`[webbycommerce] Found via db.query (uppercase): "${coupon.code}"`);
10859
+ }
10860
+ } catch (dbError) {
10861
+ }
10862
+ }
10863
+ if (!coupon) {
10864
+ try {
10865
+ coupon = await strapi2.db.query(COUPON_UID).findOne({
10866
+ where: { code: normalizedCode.toLowerCase() }
10867
+ });
10868
+ if (coupon) {
10869
+ strapi2.log.debug(`[webbycommerce] Found via db.query (lowercase): "${coupon.code}"`);
10870
+ }
10871
+ } catch (dbError) {
10872
+ }
10873
+ }
10874
+ if (!coupon) {
10875
+ try {
10876
+ strapi2.log.debug(`[webbycommerce] Trying fallback: fetching all coupons for case-insensitive match`);
10877
+ const allCoupons = await strapi2.entityService.findMany(COUPON_UID, {
10878
+ filters: {}
10879
+ });
10880
+ strapi2.log.debug(`[webbycommerce] Fetched ${allCoupons?.length || 0} total coupons`);
10881
+ if (allCoupons && Array.isArray(allCoupons)) {
10882
+ const allCodes = allCoupons.map((c) => `"${c.code || "N/A"}"`).join(", ");
10883
+ strapi2.log.debug(`[webbycommerce] All coupon codes: ${allCodes}`);
10884
+ coupon = allCoupons.find(
10885
+ (c) => {
10886
+ const couponCode2 = c.code ? String(c.code).trim() : "";
10887
+ const searchCode = normalizedCode.toLowerCase();
10888
+ const match = couponCode2.toLowerCase() === searchCode;
10889
+ if (match) {
10890
+ strapi2.log.debug(`[webbycommerce] Case-insensitive match found: "${couponCode2}" === "${normalizedCode}"`);
10891
+ }
10892
+ return match;
10893
+ }
10894
+ );
10895
+ }
10896
+ } catch (fallbackError) {
10897
+ strapi2.log.error(`[webbycommerce] Fallback query failed:`, fallbackError.message, fallbackError.stack);
10898
+ }
10899
+ }
10900
+ if (!coupon) {
10901
+ strapi2.log.error(`[webbycommerce] Coupon not found: "${normalizedCode}"`);
10902
+ try {
10903
+ const availableCoupons = await strapi2.entityService.findMany(COUPON_UID, {
10904
+ filters: {}
10905
+ });
10906
+ if (availableCoupons && Array.isArray(availableCoupons)) {
10907
+ const codes = availableCoupons.slice(0, 20).map((c) => `"${c.code || "N/A"}"`).join(", ");
10908
+ strapi2.log.error(`[webbycommerce] Available coupons (${availableCoupons.length}): ${codes}`);
10909
+ } else {
10910
+ strapi2.log.error(`[webbycommerce] No coupons found in database or query returned invalid format`);
10911
+ }
10912
+ } catch (debugError) {
10913
+ strapi2.log.error(`[webbycommerce] Error fetching coupons for debug:`, debugError.message, debugError.stack);
10914
+ }
10915
+ const err = new Error("Invalid coupon code");
10916
+ err.status = 400;
10917
+ throw err;
10918
+ }
10919
+ strapi2.log.info(`[webbycommerce] Found coupon: "${coupon.code}" (ID: ${coupon.id}, Active: ${coupon.is_active})`);
10920
+ if (coupon.is_active === false) {
10921
+ const err = new Error("This coupon is not active");
10922
+ err.status = 400;
10923
+ throw err;
10924
+ }
10925
+ if (coupon.expires_at) {
10926
+ const now = /* @__PURE__ */ new Date();
10927
+ const expiresAt = new Date(coupon.expires_at);
10928
+ if (expiresAt < now) {
10929
+ const err = new Error("This coupon has expired");
10930
+ err.status = 400;
10931
+ throw err;
10932
+ }
10933
+ }
10934
+ if (coupon.usage_limit !== null && coupon.usage_limit !== void 0) {
10935
+ const usedCount = coupon.used_count || 0;
10936
+ if (usedCount >= coupon.usage_limit) {
10937
+ const err = new Error("This coupon has reached its usage limit");
10938
+ err.status = 400;
10939
+ throw err;
10940
+ }
10941
+ }
10942
+ const items = await this.getCartItems({ cartId });
10943
+ const subtotal = items.reduce((sum, it) => sum + (Number(it.total_price) || 0), 0);
10944
+ if (coupon.minimum_order_amount !== null && coupon.minimum_order_amount !== void 0) {
10945
+ if (subtotal < Number(coupon.minimum_order_amount)) {
10946
+ const err = new Error(
10947
+ `Minimum order amount of ${coupon.minimum_order_amount} required for this coupon`
10948
+ );
10949
+ err.status = 400;
10950
+ throw err;
10951
+ }
10952
+ }
10953
+ await strapi2.db.query(CART_UID).update({
10954
+ where: { id: cartId },
10955
+ data: { coupon: coupon.id }
10956
+ });
10957
+ return coupon;
10958
+ },
10959
+ async removeCoupon({ cartId }) {
10960
+ await strapi2.db.query(CART_UID).update({
10961
+ where: { id: cartId },
10962
+ data: { coupon: null }
10963
+ });
10964
+ return true;
10090
10965
  }
10091
10966
  }));
10092
10967
  }
@@ -10100,24 +10975,28 @@ var require_wishlist3 = __commonJS({
10100
10975
  module2.exports = createCoreService("plugin::webbycommerce.wishlist", ({ strapi: strapi2 }) => ({
10101
10976
  async findUserWishlist(userId) {
10102
10977
  try {
10103
- const wishlist = await strapi2.entityService.findMany("plugin::webbycommerce.wishlist", {
10104
- filters: {
10105
- // userId is stored as a string in this content-type; normalize to string for reliable matching
10978
+ const wishlists = await strapi2.db.query("plugin::webbycommerce.wishlist").findMany({
10979
+ where: {
10106
10980
  userId: String(userId)
10107
10981
  },
10982
+ orderBy: { createdAt: "desc" },
10108
10983
  populate: {
10109
10984
  products: {
10110
10985
  populate: {
10111
10986
  images: true,
10112
10987
  product_categories: true,
10113
10988
  tags: true,
10114
- variations: true
10989
+ variations: {
10990
+ populate: {
10991
+ attributes: true,
10992
+ attributeValues: true
10993
+ }
10994
+ }
10115
10995
  }
10116
10996
  }
10117
- },
10118
- sort: { createdAt: "desc" }
10997
+ }
10119
10998
  });
10120
- return wishlist.length > 0 ? wishlist[0] : null;
10999
+ return wishlists.length > 0 ? wishlists[0] : null;
10121
11000
  } catch (error) {
10122
11001
  throw new Error(`Failed to find user wishlist: ${error.message}`);
10123
11002
  }
@@ -10132,10 +11011,10 @@ var require_wishlist3 = __commonJS({
10132
11011
  name: data.name || null,
10133
11012
  description: data.description || null
10134
11013
  };
10135
- const wishlist = await strapi2.entityService.create("plugin::webbycommerce.wishlist", {
11014
+ await strapi2.entityService.create("plugin::webbycommerce.wishlist", {
10136
11015
  data: wishlistData
10137
11016
  });
10138
- return wishlist;
11017
+ return await this.findUserWishlist(userId);
10139
11018
  } catch (error) {
10140
11019
  throw new Error(`Failed to create user wishlist: ${error.message}`);
10141
11020
  }
@@ -10149,7 +11028,10 @@ var require_wishlist3 = __commonJS({
10149
11028
  if (!wishlist.products) {
10150
11029
  wishlist.products = [];
10151
11030
  }
10152
- const productExists = wishlist.products.some((product2) => product2.id === parseInt(productId));
11031
+ const productExists = wishlist.products.some((product2) => {
11032
+ const productIdValue = typeof product2 === "object" && product2 !== null ? product2.id : product2;
11033
+ return productIdValue === parseInt(productId);
11034
+ });
10153
11035
  if (productExists) {
10154
11036
  throw new Error("Product already exists in wishlist");
10155
11037
  }
@@ -10160,12 +11042,15 @@ var require_wishlist3 = __commonJS({
10160
11042
  if (!product) {
10161
11043
  throw new Error("Product not found");
10162
11044
  }
10163
- const updatedWishlist = await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
11045
+ const existingProductIds = wishlist.products.map((p) => {
11046
+ return typeof p === "object" && p !== null ? p.id : p;
11047
+ }).filter((id) => id != null);
11048
+ await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
10164
11049
  data: {
10165
- products: [...wishlist.products.map((p) => p.id), productId]
11050
+ products: { set: [...existingProductIds, parseInt(productId, 10)].map((id) => ({ id })) }
10166
11051
  }
10167
11052
  });
10168
- return updatedWishlist;
11053
+ return await this.findUserWishlist(userId);
10169
11054
  } catch (error) {
10170
11055
  throw new Error(`Failed to add product to wishlist: ${error.message}`);
10171
11056
  }
@@ -10179,13 +11064,18 @@ var require_wishlist3 = __commonJS({
10179
11064
  if (!wishlist.products) {
10180
11065
  wishlist.products = [];
10181
11066
  }
10182
- const updatedProducts = wishlist.products.filter((product) => product.id !== parseInt(productId)).map((product) => product.id);
10183
- const updatedWishlist = await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
11067
+ const updatedProducts = wishlist.products.filter((product) => {
11068
+ const productIdValue = typeof product === "object" && product !== null ? product.id : product;
11069
+ return productIdValue !== parseInt(productId);
11070
+ }).map((product) => {
11071
+ return typeof product === "object" && product !== null ? product.id : product;
11072
+ });
11073
+ await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
10184
11074
  data: {
10185
- products: updatedProducts
11075
+ products: { set: updatedProducts.map((id) => ({ id })) }
10186
11076
  }
10187
11077
  });
10188
- return updatedWishlist;
11078
+ return await this.findUserWishlist(userId);
10189
11079
  } catch (error) {
10190
11080
  throw new Error(`Failed to remove product from wishlist: ${error.message}`);
10191
11081
  }
@@ -10196,12 +11086,12 @@ var require_wishlist3 = __commonJS({
10196
11086
  if (!wishlist) {
10197
11087
  throw new Error("Wishlist not found");
10198
11088
  }
10199
- const updatedWishlist = await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
11089
+ await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
10200
11090
  data: {
10201
- products: []
11091
+ products: { set: [] }
10202
11092
  }
10203
11093
  });
10204
- return updatedWishlist;
11094
+ return await this.findUserWishlist(userId);
10205
11095
  } catch (error) {
10206
11096
  throw new Error(`Failed to clear wishlist: ${error.message}`);
10207
11097
  }
@@ -10212,14 +11102,14 @@ var require_wishlist3 = __commonJS({
10212
11102
  if (!wishlist) {
10213
11103
  throw new Error("Wishlist not found");
10214
11104
  }
10215
- const updatedWishlist = await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
11105
+ await strapi2.entityService.update("plugin::webbycommerce.wishlist", wishlist.id, {
10216
11106
  data: {
10217
11107
  name: data.name !== void 0 ? data.name : wishlist.name,
10218
11108
  description: data.description !== void 0 ? data.description : wishlist.description,
10219
11109
  isPublic: data.isPublic !== void 0 ? data.isPublic : wishlist.isPublic
10220
11110
  }
10221
11111
  });
10222
- return updatedWishlist;
11112
+ return await this.findUserWishlist(userId);
10223
11113
  } catch (error) {
10224
11114
  throw new Error(`Failed to update wishlist: ${error.message}`);
10225
11115
  }