@goweekdays/core 2.11.10 → 2.11.11

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,11 @@
1
1
  # @goweekdays/core
2
2
 
3
+ ## 2.11.11
4
+
5
+ ### Patch Changes
6
+
7
+ - ae3339b: Refactor and simplify subscription fee computation logic
8
+
3
9
  ## 2.11.10
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -930,7 +930,7 @@ declare function useSubscriptionService(): {
930
930
  promoCode?: string;
931
931
  org: string;
932
932
  plan: string;
933
- }, skipProration?: boolean) => Promise<{
933
+ }) => Promise<{
934
934
  monthlyAmount: number;
935
935
  proratedAmount: number;
936
936
  subscriptionAmount: number;
package/dist/index.js CHANGED
@@ -8417,147 +8417,85 @@ function useSubscriptionService() {
8417
8417
  }
8418
8418
  return total;
8419
8419
  }
8420
- async function computeFee(value, skipProration) {
8420
+ function computeMonthlyAmount(seats, planPrice, promo) {
8421
+ if (!promo)
8422
+ return planPrice * seats;
8423
+ const limit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8424
+ switch (promo.type) {
8425
+ case "fixed":
8426
+ return Math.min(seats, limit) * (promo.fixedRate ?? 0) + Math.max(seats - limit, 0) * planPrice;
8427
+ case "flat":
8428
+ return (promo.flatRate ?? 0) + Math.max(seats - limit, 0) * planPrice;
8429
+ case "volume":
8430
+ return promo.tiers?.length ? calculateVolumeTierAmount(promo.tiers, 1, seats, planPrice) : planPrice * seats;
8431
+ default:
8432
+ return planPrice * seats;
8433
+ }
8434
+ }
8435
+ function computeProration(subscription, seats, plan, promo) {
8436
+ const additionalSeats = Math.max(0, seats - subscription.paidSeats);
8437
+ if (additionalSeats === 0)
8438
+ return 0;
8439
+ const today = /* @__PURE__ */ new Date();
8440
+ const nextBilling = new Date(subscription.nextBillingDate);
8441
+ const daysRemaining = Math.max(
8442
+ 0,
8443
+ (nextBilling.getTime() - today.getTime()) / 864e5
8444
+ );
8445
+ const cycleDays = plan.billingCycle === "yearly" ? 365 : 30;
8446
+ if (daysRemaining === 0)
8447
+ return 0;
8448
+ const additionalAmount = computeMonthlyAmount(
8449
+ additionalSeats,
8450
+ plan.price,
8451
+ promo
8452
+ );
8453
+ return additionalAmount / cycleDays * daysRemaining;
8454
+ }
8455
+ async function computeFee(value) {
8421
8456
  const { error } = schemaSubscriptionCompute.validate(value);
8422
8457
  if (error) {
8423
8458
  throw new import_utils44.BadRequestError(`Invalid subscription data: ${error.message}`);
8424
8459
  }
8425
8460
  const existingSubscription = await getByOrg(value.org);
8426
8461
  const plan = await getPlanById(value.plan);
8427
- if (!plan) {
8462
+ if (!plan)
8428
8463
  throw new import_utils44.BadRequestError("Plan not found.");
8464
+ const isNew = !existingSubscription;
8465
+ const isPromoChange = !!existingSubscription && value.promoCode !== void 0 && value.promoCode !== existingSubscription.promoCode;
8466
+ const isSeatIncrease = !!existingSubscription && value.seats > existingSubscription.seats;
8467
+ const isSeatDecrease = !!existingSubscription && value.seats < existingSubscription.seats;
8468
+ if (isPromoChange && isSeatIncrease) {
8469
+ throw new import_utils44.BadRequestError(
8470
+ "Cannot change promo code while increasing seats. Perform actions separately."
8471
+ );
8429
8472
  }
8430
- const org = await getOrgById(value.org);
8431
- if (!org) {
8432
- throw new import_utils44.BadRequestError("Organization not found.");
8433
- }
8434
- let promo = null;
8435
- const promoCode = value.promoCode;
8436
- if (promoCode) {
8437
- promo = await getPromoByCode(promoCode);
8438
- if (!promo) {
8439
- throw new import_utils44.BadRequestError("Promo code not found.");
8440
- }
8441
- if (promo.usage && promo.usage > 0) {
8442
- const currentUsageCount = await countByPromoId(
8443
- promo._id?.toString() ?? ""
8444
- );
8445
- const isAlreadyUsingPromo = existingSubscription?.promoCode === promoCode;
8446
- if (!isAlreadyUsingPromo && currentUsageCount >= promo.usage) {
8447
- throw new import_utils44.BadRequestError(
8448
- "Promo code has reached its maximum usage limit."
8449
- );
8450
- }
8451
- }
8452
- }
8453
- let monthlyAmount = plan.price * value.seats;
8454
- if (promo) {
8455
- const promoSeatLimit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8456
- switch (promo.type) {
8457
- case "fixed": {
8458
- const promoSeats = Math.min(value.seats, promoSeatLimit);
8459
- const standardSeats = Math.max(value.seats - promoSeatLimit, 0);
8460
- monthlyAmount = Math.max(promo.fixedRate ?? 0, 0) * promoSeats + plan.price * standardSeats;
8461
- break;
8462
- }
8463
- case "flat": {
8464
- const standardSeats = Math.max(value.seats - promoSeatLimit, 0);
8465
- monthlyAmount = (promo.flatRate ?? 0) + plan.price * standardSeats;
8466
- break;
8467
- }
8468
- case "volume": {
8469
- if (promo.tiers && promo.tiers.length > 0) {
8470
- monthlyAmount = calculateVolumeTierAmount(
8471
- promo.tiers,
8472
- 1,
8473
- value.seats,
8474
- plan.price
8475
- );
8476
- }
8477
- break;
8478
- }
8479
- default:
8480
- monthlyAmount = plan.price * value.seats;
8481
- }
8482
- }
8473
+ const effectivePromoCode = isPromoChange ? value.promoCode : existingSubscription?.promoCode;
8474
+ const promo = effectivePromoCode ? await getPromoByCode(effectivePromoCode) : null;
8475
+ const monthlyAmount = computeMonthlyAmount(value.seats, plan.price, promo);
8483
8476
  let proratedAmount = 0;
8484
- let daysRemaining = 0;
8485
- let totalDaysInCycle = 30;
8486
- if (existingSubscription) {
8487
- const today = /* @__PURE__ */ new Date();
8488
- const nextBillingDate = new Date(existingSubscription.nextBillingDate);
8489
- const timeDiff = nextBillingDate.getTime() - today.getTime();
8490
- daysRemaining = Math.max(0, Math.ceil(timeDiff / (1e3 * 60 * 60 * 24)));
8491
- if (plan.billingCycle === "yearly") {
8492
- totalDaysInCycle = 365;
8493
- }
8494
- daysRemaining = Math.min(daysRemaining, totalDaysInCycle);
8495
- const daysElapsed = totalDaysInCycle - daysRemaining;
8496
- const additionalSeats = Math.max(
8497
- 0,
8498
- value.seats - existingSubscription.paidSeats
8477
+ if (existingSubscription && isSeatIncrease && !isPromoChange) {
8478
+ proratedAmount = computeProration(
8479
+ existingSubscription,
8480
+ value.seats,
8481
+ plan,
8482
+ promo
8499
8483
  );
8500
- if (additionalSeats > 0 && daysRemaining > 0 && daysElapsed > 0 && !skipProration) {
8501
- let additionalSeatsAmount = 0;
8502
- if (promo?.type === "volume" && promo.tiers && promo.tiers.length > 0) {
8503
- additionalSeatsAmount = calculateVolumeTierAmount(
8504
- promo.tiers,
8505
- existingSubscription.paidSeats + 1,
8506
- additionalSeats,
8507
- plan.price
8508
- );
8509
- } else if (promo?.type === "fixed") {
8510
- const promoSeatLimit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8511
- const startSeat = existingSubscription.paidSeats + 1;
8512
- const endSeat = value.seats;
8513
- const promoSeatsInRange = Math.max(
8514
- 0,
8515
- Math.min(promoSeatLimit, endSeat) - startSeat + 1
8516
- );
8517
- const standardSeatsInRange = Math.max(
8518
- 0,
8519
- additionalSeats - promoSeatsInRange
8520
- );
8521
- additionalSeatsAmount = (promo.fixedRate ?? 0) * promoSeatsInRange + plan.price * standardSeatsInRange;
8522
- } else if (promo?.type === "flat") {
8523
- const promoSeatLimit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8524
- const startSeat = existingSubscription.paidSeats + 1;
8525
- const endSeat = value.seats;
8526
- if (startSeat > promoSeatLimit) {
8527
- additionalSeatsAmount = plan.price * additionalSeats;
8528
- } else {
8529
- const seatsStillCoveredByFlat = Math.max(
8530
- 0,
8531
- Math.min(promoSeatLimit, endSeat) - startSeat + 1
8532
- );
8533
- const seatsAtPlanPrice = additionalSeats - seatsStillCoveredByFlat;
8534
- additionalSeatsAmount = plan.price * seatsAtPlanPrice;
8535
- }
8536
- } else {
8537
- additionalSeatsAmount = plan.price * additionalSeats;
8538
- }
8539
- const dailyRate = additionalSeatsAmount / totalDaysInCycle;
8540
- proratedAmount = dailyRate * daysRemaining;
8541
- }
8542
8484
  }
8543
- const isIncrease = existingSubscription && value.seats > existingSubscription.paidSeats;
8544
8485
  let subscriptionAmount;
8545
- if (!existingSubscription) {
8546
- subscriptionAmount = Math.round(monthlyAmount * 100) / 100;
8547
- } else if (proratedAmount > 0) {
8548
- subscriptionAmount = Math.round((existingSubscription.amount + proratedAmount) * 100) / 100;
8549
- } else if (isIncrease) {
8550
- subscriptionAmount = Math.round(monthlyAmount * 100) / 100;
8486
+ if (isNew) {
8487
+ subscriptionAmount = monthlyAmount;
8488
+ } else if (isPromoChange) {
8489
+ subscriptionAmount = monthlyAmount;
8490
+ } else if (isSeatIncrease) {
8491
+ subscriptionAmount = existingSubscription.amount + proratedAmount;
8551
8492
  } else {
8552
- subscriptionAmount = Math.round(existingSubscription.amount * 100) / 100;
8493
+ subscriptionAmount = existingSubscription.amount;
8553
8494
  }
8554
8495
  return {
8555
- // The monthly fee for the next billing cycle
8556
8496
  monthlyAmount: Math.round(monthlyAmount * 100) / 100,
8557
- // The prorated amount to charge now for mid-cycle seat additions
8558
8497
  proratedAmount: Math.round(proratedAmount * 100) / 100,
8559
- // The new subscription.amount for updates
8560
- subscriptionAmount,
8498
+ subscriptionAmount: Math.round(subscriptionAmount * 100) / 100,
8561
8499
  currency: plan.currency
8562
8500
  };
8563
8501
  }
@@ -8699,20 +8637,23 @@ function useSubscriptionService() {
8699
8637
  const { subscriptionAmount, proratedAmount, currency } = await computeFee(
8700
8638
  {
8701
8639
  seats: value.seats,
8702
- promoCode: value.promoCode ?? "",
8703
8640
  plan: value.plan ?? "",
8704
8641
  org: value.org
8705
8642
  }
8706
8643
  );
8707
8644
  const seatIncreased = value.seats > subscription.paidSeats;
8708
- const additionalSeats = value.seats - subscription.paidSeats;
8709
- const paidSeats = seatIncreased ? value.seats : subscription.paidSeats;
8645
+ const additionalSeats = seatIncreased ? value.seats - subscription.paidSeats : 0;
8646
+ const paidSeats = seatIncreased ? subscription.paidSeats + additionalSeats : subscription.paidSeats;
8710
8647
  await updateById(
8711
8648
  subscription._id?.toString() ?? "",
8712
- { seats: value.seats, amount: subscriptionAmount, paidSeats },
8649
+ {
8650
+ seats: value.seats,
8651
+ amount: subscriptionAmount,
8652
+ paidSeats
8653
+ },
8713
8654
  session
8714
8655
  );
8715
- if (seatIncreased) {
8656
+ if (seatIncreased && proratedAmount > 0) {
8716
8657
  await addTransaction(
8717
8658
  {
8718
8659
  type: "add-seat",
@@ -8778,15 +8719,12 @@ function useSubscriptionService() {
8778
8719
  if (!plan) {
8779
8720
  throw new import_utils44.BadRequestError("Plan not found.");
8780
8721
  }
8781
- const { subscriptionAmount, currency } = await computeFee(
8782
- {
8783
- seats: subscription.seats,
8784
- promoCode: value.promoCode ?? "",
8785
- plan: plan._id?.toString() ?? "",
8786
- org: value.org
8787
- },
8788
- true
8789
- );
8722
+ const { subscriptionAmount, currency } = await computeFee({
8723
+ seats: subscription.seats,
8724
+ promoCode: value.promoCode ?? "",
8725
+ plan: plan._id?.toString() ?? "",
8726
+ org: value.org
8727
+ });
8790
8728
  await updateById(
8791
8729
  subscription._id?.toString() ?? "",
8792
8730
  { promoCode: value.promoCode ?? "", amount: subscriptionAmount },