@timardex/cluemart-server-shared 1.0.284 → 1.0.287

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.
@@ -9302,6 +9302,8 @@ export {
9302
9302
  EnumInviteStatus,
9303
9303
  EnumChatType,
9304
9304
  EnumResourceType,
9305
+ EnumNotificationResourceType,
9306
+ EnumNotificationType,
9305
9307
  EnumUserLicence,
9306
9308
  EnumEventDateStatus,
9307
9309
  dateFormat,
@@ -9350,4 +9352,4 @@ export {
9350
9352
  SchoolModel,
9351
9353
  AffiliateModel
9352
9354
  };
9353
- //# sourceMappingURL=chunk-4FWW5CDC.mjs.map
9355
+ //# sourceMappingURL=chunk-6EDO734D.mjs.map
package/dist/index.cjs CHANGED
@@ -64,6 +64,7 @@ __export(index_exports, {
64
64
  VerificationTokenModel: () => VerificationTokenModel,
65
65
  activeAffiliateCodeExists: () => activeAffiliateCodeExists,
66
66
  associatesSchema: () => associatesSchema,
67
+ awardAffiliateVendorSubscriptionRewards: () => awardAffiliateVendorSubscriptionRewards,
67
68
  baseResourceFields: () => baseResourceFields,
68
69
  connectToDatabase: () => connectToDatabase,
69
70
  convertObjectIdsToStrings: () => convertObjectIdsToStrings,
@@ -73,9 +74,11 @@ __export(index_exports, {
73
74
  express: () => import_express.default,
74
75
  findAffiliateByPromoCode: () => findAffiliateByPromoCode,
75
76
  findEventOrImportedMarketById: () => findEventOrImportedMarketById,
77
+ getSubscriptionRewardPeriodBounds: () => getSubscriptionRewardPeriodBounds,
76
78
  locationGeoSchema: () => locationGeoSchema,
77
79
  locationsSchema: () => locationsSchema,
78
- mongoose: () => import_mongoose36.default,
80
+ mapVendorLicenceToSubscriptionRewardType: () => mapVendorLicenceToSubscriptionRewardType,
81
+ mongoose: () => import_mongoose37.default,
79
82
  normalizeAffiliatePromoCodes: () => normalizeAffiliatePromoCodes,
80
83
  normalizePromoCode: () => normalizePromoCode,
81
84
  refundPolicySchema: () => refundPolicySchema,
@@ -3705,14 +3708,14 @@ var EnumGameType = /* @__PURE__ */ ((EnumGameType2) => {
3705
3708
  EnumGameType2["ODD_ONE_OUT"] = "oddOneOut";
3706
3709
  return EnumGameType2;
3707
3710
  })(EnumGameType || {});
3708
- var EnumAffiliateRewardType = /* @__PURE__ */ ((EnumAffiliateRewardType2) => {
3709
- EnumAffiliateRewardType2["ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS"] = "ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS";
3710
- EnumAffiliateRewardType2["ACTIVE_VENDOR_BONUS_REWARD"] = "ACTIVE_VENDOR_BONUS_REWARD";
3711
- EnumAffiliateRewardType2["ACTIVE_VENDOR_PRO_SUBSCRIPTION"] = "ACTIVE_VENDOR_PRO_SUBSCRIPTION";
3712
- EnumAffiliateRewardType2["ACTIVE_VENDOR_STANDARD_SUBSCRIPTION"] = "ACTIVE_VENDOR_STANDARD_SUBSCRIPTION";
3713
- EnumAffiliateRewardType2["NEW_EVENT_REGISTRATION"] = "NEW_EVENT_REGISTRATION";
3714
- EnumAffiliateRewardType2["NEW_VENDOR_REGISTRATION"] = "NEW_VENDOR_REGISTRATION";
3715
- return EnumAffiliateRewardType2;
3711
+ var EnumAffiliateRewardType = /* @__PURE__ */ ((EnumAffiliateRewardType22) => {
3712
+ EnumAffiliateRewardType22["ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS"] = "ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS";
3713
+ EnumAffiliateRewardType22["ACTIVE_VENDOR_BONUS_REWARD"] = "ACTIVE_VENDOR_BONUS_REWARD";
3714
+ EnumAffiliateRewardType22["ACTIVE_VENDOR_PRO_SUBSCRIPTION"] = "ACTIVE_VENDOR_PRO_SUBSCRIPTION";
3715
+ EnumAffiliateRewardType22["ACTIVE_VENDOR_STANDARD_SUBSCRIPTION"] = "ACTIVE_VENDOR_STANDARD_SUBSCRIPTION";
3716
+ EnumAffiliateRewardType22["NEW_EVENT_REGISTRATION"] = "NEW_EVENT_REGISTRATION";
3717
+ EnumAffiliateRewardType22["NEW_VENDOR_REGISTRATION"] = "NEW_VENDOR_REGISTRATION";
3718
+ return EnumAffiliateRewardType22;
3716
3719
  })(EnumAffiliateRewardType || {});
3717
3720
  var statusOptions = [
3718
3721
  ...Object.values(EnumInviteStatus).map((status) => ({
@@ -9410,89 +9413,12 @@ async function activeAffiliateCodeExists(affiliateCode) {
9410
9413
  return existing !== null;
9411
9414
  }
9412
9415
 
9413
- // src/service/affiliate/createAffiliateReward.ts
9414
- var AFFILIATE_REWARDS = {
9415
- [EnumAffiliateRewardType.NEW_EVENT_REGISTRATION]: {
9416
- description: "10 points for new event registration (one-time reward)",
9417
- value: 10
9418
- },
9419
- [EnumAffiliateRewardType.NEW_VENDOR_REGISTRATION]: {
9420
- description: "5 points for new vendor registration (one-time reward)",
9421
- value: 5
9422
- },
9423
- [EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION]: {
9424
- description: "3 points for active vendor pro subscription (monthly reward)",
9425
- value: 3
9426
- },
9427
- [EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION]: {
9428
- description: "1 point for active vendor standard subscription (monthly reward)",
9429
- value: 1
9430
- },
9431
- [EnumAffiliateRewardType.ACTIVE_VENDOR_BONUS_REWARD]: {
9432
- description: "5 bonus points for every 10th active vendor (one-time reward)",
9433
- value: 5
9434
- },
9435
- [EnumAffiliateRewardType.ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS]: {
9436
- description: "50 points for active event with vendor registrations (one-time reward)",
9437
- value: 50
9438
- }
9439
- };
9440
- function createAffiliateReward(rewardType, createdAt) {
9441
- const reward = AFFILIATE_REWARDS[rewardType];
9442
- return {
9443
- createdAt,
9444
- redeemedAt: null,
9445
- rewardDescription: reward.description,
9446
- rewardType,
9447
- rewardValue: reward.value
9448
- };
9449
- }
9450
-
9451
- // src/service/affiliate/findAffiliateByPromoCode.ts
9452
- async function findAffiliateByPromoCode(promoCode) {
9453
- return AffiliateModel.findOne({
9454
- affiliateCode: promoCode,
9455
- deletedAt: null
9456
- }).exec();
9457
- }
9458
-
9459
9416
  // src/service/promoCode/normalizePromoCode.ts
9460
9417
  function normalizePromoCode(decoratedPromoCode) {
9461
9418
  const normalized = decoratedPromoCode?.trim().toUpperCase();
9462
9419
  return normalized || null;
9463
9420
  }
9464
9421
 
9465
- // src/service/affiliate/normalizeAffiliatePromoCodes.ts
9466
- function normalizeAffiliatePromoCodes(promoCodes) {
9467
- const normalized = (promoCodes ?? []).map((code) => normalizePromoCode(code)).filter((code) => code !== null);
9468
- return [...new Set(normalized)];
9469
- }
9470
-
9471
- // src/service/database.ts
9472
- var import_mongoose28 = __toESM(require("mongoose"));
9473
- var connectToDatabase = async ({
9474
- appName,
9475
- dbName,
9476
- dbPassword,
9477
- dbUser,
9478
- mongodbUri
9479
- }) => {
9480
- try {
9481
- const mongoUri = mongodbUri ? mongodbUri : (
9482
- // Fallback to MongoDB Atlas connection string
9483
- `mongodb+srv://${dbUser}:${dbPassword}@${dbName}.mongodb.net/?retryWrites=true&w=majority&appName=${appName}`
9484
- );
9485
- await import_mongoose28.default.connect(mongoUri);
9486
- const connectionType = mongodbUri ? "Local MongoDB" : "MongoDB Atlas";
9487
- console.log(
9488
- `${connectionType} connected from server/src/service/database.ts`
9489
- );
9490
- } catch (err) {
9491
- console.error("Error connecting to MongoDB:", err);
9492
- throw err;
9493
- }
9494
- };
9495
-
9496
9422
  // src/service/saveNotificationsInDb.ts
9497
9423
  async function saveNotificationsInDb(payload) {
9498
9424
  const { data, message, title, type, userIds } = payload;
@@ -9626,6 +9552,285 @@ async function sendPushNotifications({
9626
9552
  }
9627
9553
  }
9628
9554
 
9555
+ // src/service/affiliate/createAffiliateReward.ts
9556
+ var AFFILIATE_REWARDS = {
9557
+ [EnumAffiliateRewardType.NEW_EVENT_REGISTRATION]: {
9558
+ description: "10 points for new event registration (one-time reward)",
9559
+ value: 10
9560
+ },
9561
+ [EnumAffiliateRewardType.NEW_VENDOR_REGISTRATION]: {
9562
+ description: "5 points for new vendor registration (one-time reward)",
9563
+ value: 5
9564
+ },
9565
+ [EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION]: {
9566
+ description: "3 points for active vendor pro subscription (monthly reward)",
9567
+ value: 3
9568
+ },
9569
+ [EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION]: {
9570
+ description: "1 point for active vendor standard subscription (monthly reward)",
9571
+ value: 1
9572
+ },
9573
+ [EnumAffiliateRewardType.ACTIVE_VENDOR_BONUS_REWARD]: {
9574
+ description: "5 bonus points for every 10th active vendor (one-time reward)",
9575
+ value: 5
9576
+ },
9577
+ [EnumAffiliateRewardType.ACTIVE_EVENT_WITH_VENDOR_REGISTRATIONS]: {
9578
+ description: "50 points for active event with vendor registrations (one-time reward)",
9579
+ value: 50
9580
+ }
9581
+ };
9582
+ function createAffiliateReward(rewardType, createdAt) {
9583
+ const reward = AFFILIATE_REWARDS[rewardType];
9584
+ return {
9585
+ createdAt,
9586
+ redeemedAt: null,
9587
+ rewardDescription: reward.description,
9588
+ rewardType,
9589
+ rewardValue: reward.value
9590
+ };
9591
+ }
9592
+
9593
+ // src/service/affiliate/vendorSubscriptionRewards.ts
9594
+ function mapVendorLicenceToSubscriptionRewardType(licences, now = /* @__PURE__ */ new Date()) {
9595
+ const validTypes = new Set(
9596
+ (licences ?? []).filter(
9597
+ (licence) => new Date(licence.expiryDate).getTime() > now.getTime()
9598
+ ).map((licence) => licence.licenceType)
9599
+ );
9600
+ if (validTypes.has(EnumUserLicence.PRO_VENDOR) || validTypes.has(EnumUserLicence.PRO_PLUS_VENDOR)) {
9601
+ return EnumAffiliateRewardType.ACTIVE_VENDOR_PRO_SUBSCRIPTION;
9602
+ }
9603
+ if (validTypes.has(EnumUserLicence.STANDARD_VENDOR)) {
9604
+ return EnumAffiliateRewardType.ACTIVE_VENDOR_STANDARD_SUBSCRIPTION;
9605
+ }
9606
+ return null;
9607
+ }
9608
+ function getSubscriptionRewardPeriodBounds(now = /* @__PURE__ */ new Date()) {
9609
+ const periodStart = new Date(
9610
+ Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)
9611
+ );
9612
+ const periodEnd = new Date(
9613
+ Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1)
9614
+ );
9615
+ return { periodEnd, periodStart };
9616
+ }
9617
+
9618
+ // src/service/affiliate/awardAffiliateVendorSubscriptionRewards.ts
9619
+ function isEligibleVendorResource(resource) {
9620
+ return resource.resourceType === EnumResourceType.VENDOR && resource.resourceActive === true && resource.resourceDeletedAt == null;
9621
+ }
9622
+ async function findAffiliatesWithActiveVendorReferrals() {
9623
+ return await AffiliateModel.find({
9624
+ affiliateResources: {
9625
+ $elemMatch: {
9626
+ resourceActive: true,
9627
+ resourceDeletedAt: null,
9628
+ resourceType: EnumResourceType.VENDOR
9629
+ }
9630
+ },
9631
+ deletedAt: null
9632
+ }).select("_id affiliateCode affiliateResources owner").lean().exec();
9633
+ }
9634
+ async function findEligibleReferredVendor(resourceId, affiliateCode) {
9635
+ return await VendorModel.findOne({
9636
+ _id: resourceId,
9637
+ active: true,
9638
+ deletedAt: null,
9639
+ promoCodes: affiliateCode
9640
+ }).select("_id name owner.userId").lean().exec();
9641
+ }
9642
+ async function resolveSubscriptionRewardTypeForVendorOwner(vendorOwnerUserId, now) {
9643
+ const vendorOwner = await UserModel.findById(vendorOwnerUserId).select("licences").lean().exec();
9644
+ return mapVendorLicenceToSubscriptionRewardType(vendorOwner?.licences, now);
9645
+ }
9646
+ async function notifyAffiliateOwnerOfSubscriptionReward({
9647
+ affiliateId,
9648
+ affiliateOwnerUserId,
9649
+ resourceName,
9650
+ rewardValue
9651
+ }) {
9652
+ const payload = {
9653
+ data: {
9654
+ resourceId: String(affiliateId),
9655
+ resourceName,
9656
+ resourceType: EnumNotificationResourceType.AFFILIATE_REWARD_RECEIVED
9657
+ },
9658
+ message: `You earned ${rewardValue} points! Your referred vendor "${resourceName}" has an active subscription.`,
9659
+ title: "Affiliate points earned",
9660
+ type: EnumNotificationType.SYSTEM,
9661
+ userIds: [affiliateOwnerUserId]
9662
+ };
9663
+ try {
9664
+ await saveNotificationsInDb(payload);
9665
+ await sendPushNotifications(payload);
9666
+ } catch (error) {
9667
+ console.error(
9668
+ `[affiliate] subscription reward notification failed for affiliate ${String(affiliateId)}:`,
9669
+ error instanceof Error ? error.message : error
9670
+ );
9671
+ }
9672
+ }
9673
+ async function tryAwardSubscriptionReward({
9674
+ affiliate,
9675
+ createdAt,
9676
+ period,
9677
+ resource,
9678
+ rewardType,
9679
+ vendor
9680
+ }) {
9681
+ const reward = createAffiliateReward(rewardType, createdAt);
9682
+ const updateResult = await AffiliateModel.updateOne(
9683
+ {
9684
+ _id: affiliate._id,
9685
+ affiliateResources: {
9686
+ $elemMatch: {
9687
+ resourceActive: true,
9688
+ resourceDeletedAt: null,
9689
+ resourceId: vendor._id,
9690
+ resourceType: EnumResourceType.VENDOR,
9691
+ rewards: {
9692
+ $not: {
9693
+ $elemMatch: {
9694
+ createdAt: {
9695
+ $gte: period.periodStart,
9696
+ $lt: period.periodEnd
9697
+ },
9698
+ rewardType
9699
+ }
9700
+ }
9701
+ }
9702
+ }
9703
+ },
9704
+ deletedAt: null
9705
+ },
9706
+ {
9707
+ $inc: { overallPoints: reward.rewardValue },
9708
+ $push: { "affiliateResources.$.rewards": reward }
9709
+ }
9710
+ ).exec();
9711
+ if (updateResult.modifiedCount === 0) {
9712
+ return "skipped";
9713
+ }
9714
+ const affiliateOwnerUserId = affiliate.owner?.userId;
9715
+ if (affiliateOwnerUserId) {
9716
+ await notifyAffiliateOwnerOfSubscriptionReward({
9717
+ affiliateId: affiliate._id,
9718
+ affiliateOwnerUserId,
9719
+ resourceName: resource.resourceName || vendor.name || "vendor",
9720
+ rewardValue: reward.rewardValue
9721
+ });
9722
+ }
9723
+ return "awarded";
9724
+ }
9725
+ async function processAffiliateVendorReferral({
9726
+ affiliate,
9727
+ affiliateCode,
9728
+ createdAt,
9729
+ now,
9730
+ period,
9731
+ resource
9732
+ }) {
9733
+ const vendor = await findEligibleReferredVendor(
9734
+ resource.resourceId,
9735
+ affiliateCode
9736
+ );
9737
+ if (!vendor?.owner?.userId) {
9738
+ return "skipped";
9739
+ }
9740
+ const rewardType = await resolveSubscriptionRewardTypeForVendorOwner(
9741
+ vendor.owner.userId,
9742
+ now
9743
+ );
9744
+ if (!rewardType) {
9745
+ return "skipped";
9746
+ }
9747
+ return tryAwardSubscriptionReward({
9748
+ affiliate,
9749
+ createdAt,
9750
+ period,
9751
+ resource,
9752
+ rewardType,
9753
+ vendor
9754
+ });
9755
+ }
9756
+ async function awardAffiliateVendorSubscriptionRewards(now = /* @__PURE__ */ new Date()) {
9757
+ const period = getSubscriptionRewardPeriodBounds(now);
9758
+ const createdAt = now;
9759
+ const affiliates = await findAffiliatesWithActiveVendorReferrals();
9760
+ let awarded = 0;
9761
+ let skipped = 0;
9762
+ const seenPairs = /* @__PURE__ */ new Set();
9763
+ for (const affiliate of affiliates) {
9764
+ const affiliateCode = normalizePromoCode(affiliate.affiliateCode);
9765
+ if (!affiliateCode) {
9766
+ continue;
9767
+ }
9768
+ for (const resource of affiliate.affiliateResources) {
9769
+ if (!isEligibleVendorResource(resource)) {
9770
+ continue;
9771
+ }
9772
+ const pairKey = `${String(affiliate._id)}:${String(resource.resourceId)}`;
9773
+ if (seenPairs.has(pairKey)) {
9774
+ continue;
9775
+ }
9776
+ seenPairs.add(pairKey);
9777
+ const outcome = await processAffiliateVendorReferral({
9778
+ affiliate,
9779
+ affiliateCode,
9780
+ createdAt,
9781
+ now,
9782
+ period,
9783
+ resource
9784
+ });
9785
+ if (outcome === "awarded") {
9786
+ awarded += 1;
9787
+ } else {
9788
+ skipped += 1;
9789
+ }
9790
+ }
9791
+ }
9792
+ return { awarded, skipped };
9793
+ }
9794
+
9795
+ // src/service/affiliate/findAffiliateByPromoCode.ts
9796
+ async function findAffiliateByPromoCode(promoCode) {
9797
+ return AffiliateModel.findOne({
9798
+ affiliateCode: promoCode,
9799
+ deletedAt: null
9800
+ }).exec();
9801
+ }
9802
+
9803
+ // src/service/affiliate/normalizeAffiliatePromoCodes.ts
9804
+ function normalizeAffiliatePromoCodes(promoCodes) {
9805
+ const normalized = (promoCodes ?? []).map((code) => normalizePromoCode(code)).filter((code) => code !== null);
9806
+ return [...new Set(normalized)];
9807
+ }
9808
+
9809
+ // src/service/database.ts
9810
+ var import_mongoose29 = __toESM(require("mongoose"));
9811
+ var connectToDatabase = async ({
9812
+ appName,
9813
+ dbName,
9814
+ dbPassword,
9815
+ dbUser,
9816
+ mongodbUri
9817
+ }) => {
9818
+ try {
9819
+ const mongoUri = mongodbUri ? mongodbUri : (
9820
+ // Fallback to MongoDB Atlas connection string
9821
+ `mongodb+srv://${dbUser}:${dbPassword}@${dbName}.mongodb.net/?retryWrites=true&w=majority&appName=${appName}`
9822
+ );
9823
+ await import_mongoose29.default.connect(mongoUri);
9824
+ const connectionType = mongodbUri ? "Local MongoDB" : "MongoDB Atlas";
9825
+ console.log(
9826
+ `${connectionType} connected from server/src/service/database.ts`
9827
+ );
9828
+ } catch (err) {
9829
+ console.error("Error connecting to MongoDB:", err);
9830
+ throw err;
9831
+ }
9832
+ };
9833
+
9629
9834
  // src/service/updateAdStatus.ts
9630
9835
  async function updateAdStatuses() {
9631
9836
  const now = /* @__PURE__ */ new Date();
@@ -9662,9 +9867,9 @@ async function updateAdStatuses() {
9662
9867
  }
9663
9868
 
9664
9869
  // src/service/associate.ts
9665
- var import_mongoose30 = __toESM(require("mongoose"));
9870
+ var import_mongoose31 = __toESM(require("mongoose"));
9666
9871
  function normalizeObjectId(id) {
9667
- return typeof id === "string" ? new import_mongoose30.default.Types.ObjectId(id) : id;
9872
+ return typeof id === "string" ? new import_mongoose31.default.Types.ObjectId(id) : id;
9668
9873
  }
9669
9874
  async function getAssociateEmailsForResource({
9670
9875
  normalizedResourceId,
@@ -9816,12 +10021,12 @@ async function updateVendorBasedOnUserLicense(userId, licenceType) {
9816
10021
  }
9817
10022
 
9818
10023
  // src/service/objectIdToString.ts
9819
- var import_mongoose33 = __toESM(require("mongoose"));
10024
+ var import_mongoose34 = __toESM(require("mongoose"));
9820
10025
  function convertObjectIdsToStrings(obj) {
9821
10026
  if (obj === null || obj === void 0) {
9822
10027
  return obj;
9823
10028
  }
9824
- if (obj instanceof import_mongoose33.default.Types.ObjectId) {
10029
+ if (obj instanceof import_mongoose34.default.Types.ObjectId) {
9825
10030
  return obj.toString();
9826
10031
  }
9827
10032
  if (obj instanceof Date) {
@@ -10005,7 +10210,7 @@ function updateRelationDatesToUnavailable(relationDates, eventDateTime) {
10005
10210
 
10006
10211
  // src/types/index.ts
10007
10212
  var import_express = __toESM(require("express"));
10008
- var import_mongoose36 = __toESM(require("mongoose"));
10213
+ var import_mongoose37 = __toESM(require("mongoose"));
10009
10214
  var EnumPubSubEvents = /* @__PURE__ */ ((EnumPubSubEvents2) => {
10010
10215
  EnumPubSubEvents2["GET_CHAT_MESSAGE"] = "GET_CHAT_MESSAGE";
10011
10216
  EnumPubSubEvents2["GET_NOTIFICATIONS"] = "GET_NOTIFICATIONS";
@@ -10049,6 +10254,7 @@ var EnumPubSubEvents = /* @__PURE__ */ ((EnumPubSubEvents2) => {
10049
10254
  VerificationTokenModel,
10050
10255
  activeAffiliateCodeExists,
10051
10256
  associatesSchema,
10257
+ awardAffiliateVendorSubscriptionRewards,
10052
10258
  baseResourceFields,
10053
10259
  connectToDatabase,
10054
10260
  convertObjectIdsToStrings,
@@ -10058,8 +10264,10 @@ var EnumPubSubEvents = /* @__PURE__ */ ((EnumPubSubEvents2) => {
10058
10264
  express,
10059
10265
  findAffiliateByPromoCode,
10060
10266
  findEventOrImportedMarketById,
10267
+ getSubscriptionRewardPeriodBounds,
10061
10268
  locationGeoSchema,
10062
10269
  locationsSchema,
10270
+ mapVendorLicenceToSubscriptionRewardType,
10063
10271
  mongoose,
10064
10272
  normalizeAffiliatePromoCodes,
10065
10273
  normalizePromoCode,