@goweekdays/core 2.11.11 → 2.11.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @goweekdays/core
2
2
 
3
+ ## 2.11.13
4
+
5
+ ### Patch Changes
6
+
7
+ - 0458614: Add support for next promo code and billing period start
8
+
9
+ ## 2.11.12
10
+
11
+ ### Patch Changes
12
+
13
+ - ab6a66f: Update dependencies
14
+
3
15
  ## 2.11.11
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -866,8 +866,10 @@ type TSubscription = {
866
866
  currency: string;
867
867
  billingCycle: "monthly" | "yearly";
868
868
  promoCode?: string;
869
+ nextPromoCode?: string;
869
870
  retry?: number;
870
871
  status?: string;
872
+ billingPeriodStart: Date | string;
871
873
  nextBillingDate: Date | string;
872
874
  createdAt?: Date | string;
873
875
  updatedAt?: Date | string;
@@ -904,7 +906,9 @@ declare function useSubscriptionRepo(): {
904
906
  paidSeats?: number;
905
907
  amount?: number;
906
908
  promoCode?: string;
909
+ nextPromoCode?: string;
907
910
  status?: string;
911
+ billingPeriodStart?: Date;
908
912
  nextBillingDate?: Date;
909
913
  }, session?: ClientSession) => Promise<string>;
910
914
  getByStatus: (status?: string, limit?: number, retry?: number) => Promise<TSubscription[]>;
@@ -949,12 +953,23 @@ declare function useSubscriptionService(): {
949
953
  processPaidInvoice: (invoiceId: string) => Promise<string>;
950
954
  };
951
955
 
956
+ type TSubscriptionTransactionMetadata = {
957
+ additionalSeats?: number;
958
+ seats?: number;
959
+ paidSeats?: number;
960
+ plan?: string;
961
+ promoCode?: string;
962
+ nextPromoCode?: string;
963
+ billingPeriodStart?: Date | string;
964
+ nextBillingDate?: Date | string;
965
+ };
952
966
  type TSubscriptionTransaction = {
953
967
  _id?: ObjectId;
954
968
  subscription: string | ObjectId;
955
969
  amount: number;
956
970
  currency: string;
957
971
  type: "initiate" | "add-seat" | "remove-seat" | "renewal" | "promo-applied" | "promo-removed" | "promo-expired" | "promo-updated";
972
+ metadata?: TSubscriptionTransactionMetadata;
958
973
  description?: string;
959
974
  createdBy: string | ObjectId;
960
975
  createdByName?: string;
package/dist/index.js CHANGED
@@ -5486,6 +5486,7 @@ var schemaSubscriptionCompute = import_joi20.default.object({
5486
5486
  seats: import_joi20.default.number().integer().min(1).required(),
5487
5487
  plan: import_joi20.default.string().hex().length(24).required(),
5488
5488
  promoCode: import_joi20.default.string().optional().allow("", null),
5489
+ nextPromoCode: import_joi20.default.string().optional().allow("", null),
5489
5490
  org: import_joi20.default.string().hex().length(24).required()
5490
5491
  });
5491
5492
  var schemaSubscribe = import_joi20.default.object({
@@ -5504,6 +5505,7 @@ var schema2 = {
5504
5505
  };
5505
5506
  var schemaSubscription = import_joi20.default.object({
5506
5507
  ...schema2,
5508
+ billingPeriodStart: import_joi20.default.date().optional().allow("", null),
5507
5509
  org: import_joi20.default.string().hex().length(24).required(),
5508
5510
  orgName: import_joi20.default.string().optional().allow("", null),
5509
5511
  currency: import_joi20.default.string().length(3).required(),
@@ -5550,9 +5552,11 @@ function modelSubscription(data) {
5550
5552
  amount: data.amount,
5551
5553
  currency: data.currency,
5552
5554
  billingCycle: data.billingCycle,
5553
- promoCode: data.promoCode,
5555
+ promoCode: data.promoCode ?? "",
5556
+ nextPromoCode: data.nextPromoCode ?? "",
5554
5557
  status: data.status ?? "active",
5555
5558
  retry: data.retry ?? 0,
5559
+ billingPeriodStart: data.billingPeriodStart,
5556
5560
  nextBillingDate: data.nextBillingDate,
5557
5561
  createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
5558
5562
  updatedAt: data.updatedAt ?? ""
@@ -5864,7 +5868,9 @@ function useSubscriptionRepo() {
5864
5868
  paidSeats: import_joi21.default.number().integer().min(0).optional(),
5865
5869
  amount: import_joi21.default.number().positive().optional().allow(0),
5866
5870
  promoCode: import_joi21.default.string().max(50).optional().allow("", null),
5871
+ nextPromoCode: import_joi21.default.string().max(50).optional().allow("", null),
5867
5872
  status: import_joi21.default.string().valid("active", "due", "overdue", "suspended").optional().allow("", null),
5873
+ billingPeriodStart: import_joi21.default.date().optional().allow("", null),
5868
5874
  nextBillingDate: import_joi21.default.date().optional().allow("", null)
5869
5875
  });
5870
5876
  const { error } = validation.validate(options);
@@ -6020,7 +6026,17 @@ var schemaSubscriptionTransaction = import_joi22.default.object({
6020
6026
  "promo-expired",
6021
6027
  "promo-updated"
6022
6028
  ).required(),
6023
- description: import_joi22.default.string().max(255).optional().allow("", null),
6029
+ metadata: import_joi22.default.object({
6030
+ additionalSeats: import_joi22.default.number().integer().min(1).optional(),
6031
+ seats: import_joi22.default.number().integer().min(0).optional(),
6032
+ paidSeats: import_joi22.default.number().integer().min(0).optional(),
6033
+ plan: import_joi22.default.string().hex().length(24).optional(),
6034
+ promoCode: import_joi22.default.string().optional().allow("", null),
6035
+ nextPromoCode: import_joi22.default.string().optional().allow("", null),
6036
+ billingPeriodStart: import_joi22.default.date().optional().allow("", null),
6037
+ nextBillingDate: import_joi22.default.date().optional().allow("", null)
6038
+ }).optional(),
6039
+ description: import_joi22.default.string().optional().allow("", null),
6024
6040
  createdBy: import_joi22.default.string().hex().length(24).required(),
6025
6041
  createdByName: import_joi22.default.string().optional().allow("", null)
6026
6042
  });
@@ -6058,6 +6074,7 @@ function modelSubscriptionTransaction(data) {
6058
6074
  amount: data.amount,
6059
6075
  currency: data.currency,
6060
6076
  type: data.type,
6077
+ metadata: data.metadata ?? {},
6061
6078
  description: data.description ?? "",
6062
6079
  createdBy: data.createdBy,
6063
6080
  createdByName: data.createdByName,
@@ -8425,13 +8442,42 @@ function useSubscriptionService() {
8425
8442
  case "fixed":
8426
8443
  return Math.min(seats, limit) * (promo.fixedRate ?? 0) + Math.max(seats - limit, 0) * planPrice;
8427
8444
  case "flat":
8428
- return (promo.flatRate ?? 0) + Math.max(seats - limit, 0) * planPrice;
8445
+ return (promo.flatRate ?? 0) + Math.max(0, seats - limit) * planPrice;
8429
8446
  case "volume":
8430
8447
  return promo.tiers?.length ? calculateVolumeTierAmount(promo.tiers, 1, seats, planPrice) : planPrice * seats;
8431
8448
  default:
8432
8449
  return planPrice * seats;
8433
8450
  }
8434
8451
  }
8452
+ function computeAdditionalSeatAmount(additionalSeats, planPrice, promo, paidSeats) {
8453
+ if (additionalSeats <= 0)
8454
+ return 0;
8455
+ if (!promo) {
8456
+ return additionalSeats * planPrice;
8457
+ }
8458
+ const limit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8459
+ switch (promo.type) {
8460
+ case "flat": {
8461
+ const chargeableSeats = Math.max(0, paidSeats + additionalSeats - limit) - Math.max(0, paidSeats - limit);
8462
+ return chargeableSeats * planPrice;
8463
+ }
8464
+ case "fixed": {
8465
+ const promoSeatsLeft = Math.max(0, limit - paidSeats);
8466
+ const promoSeats = Math.min(additionalSeats, promoSeatsLeft);
8467
+ const normalSeats = additionalSeats - promoSeats;
8468
+ return promoSeats * (promo.fixedRate ?? 0) + normalSeats * planPrice;
8469
+ }
8470
+ case "volume":
8471
+ return promo.tiers?.length ? calculateVolumeTierAmount(
8472
+ promo.tiers,
8473
+ paidSeats + 1,
8474
+ additionalSeats,
8475
+ planPrice
8476
+ ) : additionalSeats * planPrice;
8477
+ default:
8478
+ return additionalSeats * planPrice;
8479
+ }
8480
+ }
8435
8481
  function computeProration(subscription, seats, plan, promo) {
8436
8482
  const additionalSeats = Math.max(0, seats - subscription.paidSeats);
8437
8483
  if (additionalSeats === 0)
@@ -8445,10 +8491,11 @@ function useSubscriptionService() {
8445
8491
  const cycleDays = plan.billingCycle === "yearly" ? 365 : 30;
8446
8492
  if (daysRemaining === 0)
8447
8493
  return 0;
8448
- const additionalAmount = computeMonthlyAmount(
8494
+ const additionalAmount = computeAdditionalSeatAmount(
8449
8495
  additionalSeats,
8450
8496
  plan.price,
8451
- promo
8497
+ promo,
8498
+ subscription.paidSeats
8452
8499
  );
8453
8500
  return additionalAmount / cycleDays * daysRemaining;
8454
8501
  }
@@ -8464,7 +8511,6 @@ function useSubscriptionService() {
8464
8511
  const isNew = !existingSubscription;
8465
8512
  const isPromoChange = !!existingSubscription && value.promoCode !== void 0 && value.promoCode !== existingSubscription.promoCode;
8466
8513
  const isSeatIncrease = !!existingSubscription && value.seats > existingSubscription.seats;
8467
- const isSeatDecrease = !!existingSubscription && value.seats < existingSubscription.seats;
8468
8514
  if (isPromoChange && isSeatIncrease) {
8469
8515
  throw new import_utils44.BadRequestError(
8470
8516
  "Cannot change promo code while increasing seats. Perform actions separately."
@@ -8534,7 +8580,8 @@ function useSubscriptionService() {
8534
8580
  if (!membership) {
8535
8581
  throw new import_utils44.BadRequestError("User is not a member of the organization.");
8536
8582
  }
8537
- const nextBillingDate = /* @__PURE__ */ new Date();
8583
+ const billingPeriodStart = /* @__PURE__ */ new Date();
8584
+ const nextBillingDate = new Date(billingPeriodStart);
8538
8585
  nextBillingDate.setDate(nextBillingDate.getDate() + 30);
8539
8586
  const { subscriptionAmount, currency } = await computeFee({
8540
8587
  seats: value.seats,
@@ -8559,6 +8606,7 @@ function useSubscriptionService() {
8559
8606
  amount: subscriptionAmount,
8560
8607
  currency,
8561
8608
  billingCycle: plan.billingCycle,
8609
+ billingPeriodStart,
8562
8610
  nextBillingDate,
8563
8611
  promoCode: value.promoCode
8564
8612
  },
@@ -8662,7 +8710,17 @@ function useSubscriptionService() {
8662
8710
  currency,
8663
8711
  subscription: subscription._id?.toString() ?? "",
8664
8712
  createdBy: value.user,
8665
- createdByName: `${userData.firstName} ${userData.lastName}`
8713
+ createdByName: `${userData.firstName} ${userData.lastName}`,
8714
+ metadata: {
8715
+ additionalSeats,
8716
+ seats: value.seats,
8717
+ paidSeats,
8718
+ plan: value.plan ?? "",
8719
+ promoCode: subscription.promoCode ?? "",
8720
+ nextPromoCode: subscription.nextPromoCode ?? "",
8721
+ billingPeriodStart: subscription.billingPeriodStart,
8722
+ nextBillingDate: subscription.nextBillingDate
8723
+ }
8666
8724
  },
8667
8725
  session
8668
8726
  );
@@ -8727,7 +8785,7 @@ function useSubscriptionService() {
8727
8785
  });
8728
8786
  await updateById(
8729
8787
  subscription._id?.toString() ?? "",
8730
- { promoCode: value.promoCode ?? "", amount: subscriptionAmount },
8788
+ { nextPromoCode: value.promoCode ?? "" },
8731
8789
  session
8732
8790
  );
8733
8791
  if (subscription.promoCode) {
@@ -8738,7 +8796,7 @@ function useSubscriptionService() {
8738
8796
  {
8739
8797
  promo: promo._id,
8740
8798
  org: value.org,
8741
- usedBy: userData.email
8799
+ usedBy: value.user
8742
8800
  },
8743
8801
  session
8744
8802
  );
@@ -8746,12 +8804,19 @@ function useSubscriptionService() {
8746
8804
  await addTransaction(
8747
8805
  {
8748
8806
  type: "promo-updated",
8749
- description: `Updated promo code to ${value.promoCode || "none"}.`,
8807
+ description: `Promo code ${value.promoCode || "removed"} scheduled to take effect on the next billing cycle.`,
8750
8808
  amount: 0,
8751
- currency,
8809
+ currency: subscription.currency,
8752
8810
  subscription: subscription._id?.toString() ?? "",
8753
8811
  createdBy: value.user,
8754
- createdByName: `${userData.firstName} ${userData.lastName}`
8812
+ metadata: {
8813
+ seats: subscription.seats,
8814
+ paidSeats: subscription.paidSeats,
8815
+ promoCode: subscription.promoCode,
8816
+ nextPromoCode: value.promoCode ?? "",
8817
+ billingPeriodStart: subscription.billingPeriodStart,
8818
+ nextBillingDate: subscription.nextBillingDate
8819
+ }
8755
8820
  },
8756
8821
  session
8757
8822
  );
@@ -8911,39 +8976,43 @@ function useSubscriptionService() {
8911
8976
  }
8912
8977
  const ledgerBill = await getByInvoice(invoiceId);
8913
8978
  if (!ledgerBill) {
8914
- throw new import_utils44.BadRequestError(
8915
- "Ledger bill not found for the given invoice ID."
8916
- );
8979
+ throw new import_utils44.BadRequestError("Ledger bill not found.");
8917
8980
  }
8918
8981
  const orgId = String(ledgerBill.org);
8919
- const org = await getOrgById(orgId);
8920
- if (!org) {
8921
- throw new import_utils44.BadRequestError("Organization not found for the ledger bill.");
8922
- }
8923
8982
  const subscription = await getByOrg(orgId);
8924
8983
  if (!subscription) {
8925
- throw new import_utils44.BadRequestError("Subscription not found for the organization.");
8984
+ throw new import_utils44.BadRequestError("Subscription not found.");
8926
8985
  }
8927
8986
  const plan = await getDefaultPlan();
8928
8987
  if (!plan) {
8929
- throw new import_utils44.BadRequestError("Default plan not found.");
8988
+ throw new import_utils44.BadRequestError("Plan not found.");
8930
8989
  }
8931
8990
  const session = import_utils44.useAtlas.getClient()?.startSession();
8932
8991
  if (!session) {
8933
- throw new Error("Unable to start database session.");
8992
+ throw new import_utils44.InternalServerError("Unable to start database session.");
8934
8993
  }
8935
8994
  try {
8936
8995
  session.startTransaction();
8937
- const subscriptionId = String(subscription._id);
8938
- const nextBillingDate = /* @__PURE__ */ new Date();
8996
+ const billingPeriodStart = /* @__PURE__ */ new Date();
8997
+ const nextBillingDate = new Date(billingPeriodStart);
8939
8998
  nextBillingDate.setMonth(nextBillingDate.getMonth() + 1);
8999
+ const effectivePromoCode = subscription.nextPromoCode || subscription.promoCode;
9000
+ const promo = effectivePromoCode ? await getPromoByCode(effectivePromoCode) : null;
9001
+ const monthlyAmount = computeMonthlyAmount(
9002
+ subscription.seats,
9003
+ plan.price,
9004
+ promo
9005
+ );
8940
9006
  await updateById(
8941
- subscriptionId,
9007
+ subscription._id?.toString() ?? "",
8942
9008
  {
8943
9009
  status: "active",
9010
+ billingPeriodStart,
8944
9011
  nextBillingDate,
8945
9012
  paidSeats: subscription.seats,
8946
- amount: plan.price * subscription.seats
9013
+ amount: Math.round(monthlyAmount * 100) / 100,
9014
+ promoCode: effectivePromoCode,
9015
+ nextPromoCode: ""
8947
9016
  },
8948
9017
  session
8949
9018
  );
@@ -8954,14 +9023,7 @@ function useSubscriptionService() {
8954
9023
  return "Successfully processed paid invoice.";
8955
9024
  } catch (error2) {
8956
9025
  await session.abortTransaction();
8957
- import_utils44.logger.log({
8958
- level: "error",
8959
- message: `Failed to process paid invoice ${invoiceId}: ${error2 instanceof Error ? error2.message : String(error2)}`
8960
- });
8961
- if (error2 instanceof import_utils44.AppError) {
8962
- throw error2;
8963
- }
8964
- throw new import_utils44.InternalServerError("Failed to process paid invoice.");
9026
+ throw error2;
8965
9027
  } finally {
8966
9028
  session.endSession();
8967
9029
  }
@@ -9497,6 +9559,7 @@ function useOrgService() {
9497
9559
  paidSeats: value.seats,
9498
9560
  currency: plan.currency,
9499
9561
  billingCycle: plan.billingCycle,
9562
+ billingPeriodStart: currentDate,
9500
9563
  nextBillingDate
9501
9564
  },
9502
9565
  session